Repository: panyiho/NineGridView Branch: master Commit: b9603957d85d Files: 73 Total size: 223.6 KB Directory structure: gitextract_9vah1spq/ ├── .gitignore ├── .idea/ │ ├── .name │ ├── compiler.xml │ ├── copyright/ │ │ └── profiles_settings.xml │ ├── encodings.xml │ ├── gradle.xml │ ├── misc.xml │ ├── modules.xml │ ├── scopes/ │ │ └── scope_settings.xml │ └── vcs.xml ├── README.md ├── WeixinNineGridLayout.iml ├── app/ │ ├── .gitignore │ ├── app.iml │ ├── build.gradle │ ├── libs/ │ │ └── picasso-2.3.4.jar │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── example/ │ │ └── administrator/ │ │ └── weixinninegridlayout/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── weixinninegridlayout/ │ │ ├── CustomImageView.java │ │ ├── Image.java │ │ ├── L.java │ │ ├── MainActivity.java │ │ ├── MainAdapter.java │ │ ├── NineGridlayout.java │ │ └── ScreenTools.java │ └── res/ │ ├── layout/ │ │ ├── activity_main.xml │ │ └── item_ninegridlayout.xml │ ├── menu/ │ │ └── menu_main.xml │ ├── values/ │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── values-w820dp/ │ └── dimens.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── picasso/ │ ├── Action.java │ ├── AssetRequestHandler.java │ ├── BitmapHunter.java │ ├── Cache.java │ ├── Callback.java │ ├── ContactsPhotoRequestHandler.java │ ├── ContentStreamRequestHandler.java │ ├── DeferredRequestCreator.java │ ├── Dispatcher.java │ ├── Downloader.java │ ├── FetchAction.java │ ├── FileRequestHandler.java │ ├── GetAction.java │ ├── ImageViewAction.java │ ├── LruCache.java │ ├── MarkableInputStream.java │ ├── MediaStoreRequestHandler.java │ ├── NetworkRequestHandler.java │ ├── Picasso.java │ ├── PicassoDrawable.java │ ├── PicassoExecutorService.java │ ├── RemoteViewsAction.java │ ├── Request.java │ ├── RequestCreator.java │ ├── RequestHandler.java │ ├── ResourceRequestHandler.java │ ├── Stats.java │ ├── StatsSnapshot.java │ ├── Target.java │ ├── TargetAction.java │ ├── Transformation.java │ ├── UrlConnectionDownloader.java │ └── Utils.java └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build ================================================ FILE: .idea/.name ================================================ WeixinNineGridLayout ================================================ FILE: .idea/compiler.xml ================================================ ================================================ FILE: .idea/copyright/profiles_settings.xml ================================================ ================================================ FILE: .idea/encodings.xml ================================================ ================================================ FILE: .idea/gradle.xml ================================================ ================================================ FILE: .idea/misc.xml ================================================ localhost 5050 ================================================ FILE: .idea/modules.xml ================================================ ================================================ FILE: .idea/scopes/scope_settings.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: README.md ================================================ NineGridLayout -------------- 1. 简介 这是一个用于实现像微信朋友圈和微博的类似的九宫格图片展示控件,通过自定义viewgroup实现,是用方便,只需要很简单的就可嵌入到项目中去。 ---------- 2. 使用方法 在项目的layout文件中添加如下xml即可加入到布局文件 因为大部分这类控件都是在listview里面用的,所以针对Listview的复用有针对性的优化,只需要在项目中调用NineGridlayout.setImagesData就可以实现图片的加载和显示。同时为了解耦合和,重写了一个CustomImageView嵌入了picasso图片加载库来加载图片。 ---------- 3. 效果展示 由于我用markdown嵌入图片发现大小不能调整,所以如果想要看跟多详情,可以去我的CSDN博客,地址:[自定义九宫格控件NineGridLayout ,实现微信朋友圈图片九宫格显示][1] ![效果1][2] ![效果2][3] [1]: http://blog.csdn.net/u012650948/article/details/43638427 [3]: http://img.blog.csdn.net/20150208195246244?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjY1MDk0OA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center ---------- 4. 协议 > /* * Copyright (C) 2015 panyihong * * 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. */ ================================================ FILE: WeixinNineGridLayout.iml ================================================ ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/app.iml ================================================ ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 21 buildToolsVersion "21.1.0" defaultConfig { applicationId "com.weixinninegridlayout" minSdkVersion 9 targetSdkVersion 21 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:21.0.0' compile files('libs/picasso-2.3.4.jar') } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in D:\android statio\sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: app/src/androidTest/java/com/example/administrator/weixinninegridlayout/ApplicationTest.java ================================================ package com.example.administrator.weixinninegridlayout; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/weixinninegridlayout/CustomImageView.java ================================================ package com.weixinninegridlayout; import android.content.Context; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.AttributeSet; import android.view.MotionEvent; import android.widget.ImageView; import com.squareup.picasso.Picasso; /** * Created by Pan_ on 2015/2/2. */ public class CustomImageView extends ImageView { private String url; private boolean isAttachedToWindow; public CustomImageView(Context context, AttributeSet attrs) { super(context, attrs); } public CustomImageView(Context context) { super(context); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Drawable drawable=getDrawable(); if(drawable!=null) { drawable.mutate().setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY); } break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: Drawable drawableUp=getDrawable(); if(drawableUp!=null) { drawableUp.mutate().clearColorFilter(); } break; } return super.onTouchEvent(event); } @Override public void onAttachedToWindow() { isAttachedToWindow = true; setImageUrl(url); super.onAttachedToWindow(); } @Override public void onDetachedFromWindow() { Picasso.with(getContext()).cancelRequest(this); isAttachedToWindow = false; setImageBitmap(null); super.onDetachedFromWindow(); } public void setImageUrl(String url) { if (!TextUtils.isEmpty(url)) { this.url = url; if (isAttachedToWindow) { Picasso.with(getContext()).load(url).placeholder(new ColorDrawable(Color.parseColor("#f5f5f5"))).into(this); } } } } ================================================ FILE: app/src/main/java/com/weixinninegridlayout/Image.java ================================================ package com.weixinninegridlayout; /** * Created by Pan_ on 2015/2/3. */ public class Image { private String url; private int width; private int height; public Image(String url, int width, int height) { this.url = url; this.width = width; this.height = height; L.i(toString()); } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } @Override public String toString() { return "image---->>url="+url+"width="+width+"height"+height; } } ================================================ FILE: app/src/main/java/com/weixinninegridlayout/L.java ================================================ package com.weixinninegridlayout; import android.util.Log; public final class L { private L() { } public static void v(Throwable t) { log(Log.VERBOSE, t, null); } public static void v(Object format, Object... args) { log(Log.VERBOSE, null, format, args); } public static void v(Throwable t, Object format, Object... args) { log(Log.VERBOSE, t, format, args); } public static void d(Throwable t) { log(Log.DEBUG, t, null); } public static void d(Object format, Object... args) { log(Log.DEBUG, null, format, args); } public static void d(Throwable t, Object format, Object... args) { log(Log.DEBUG, t, format, args); } public static void i(Throwable t) { log(Log.INFO, t, null); } public static void i(Object format, Object... args) { log(Log.INFO, null, format, args); } public static void i(Throwable t, Object format, Object... args) { log(Log.INFO, t, format, args); } public static void w(Throwable t) { log(Log.WARN, t, null); } public static void w(Object format, Object... args) { log(Log.WARN, null, format, args); } public static void w(Throwable t, Object format, Object... args) { log(Log.WARN, t, format, args); } public static void e(Throwable t) { log(Log.ERROR, t, null); } public static void e(Object format, Object... args) { log(Log.ERROR, null, format, args); } public static void e(Throwable t, Object format, Object... args) { log(Log.ERROR, t, format, args); } private static void log(final int pType, final Throwable t, final Object format, final Object... args) { final StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[4]; final String fullClassName = stackTraceElement.getClassName(); final String className = fullClassName.substring(fullClassName.lastIndexOf(".") + 1); final int lineNumber = stackTraceElement.getLineNumber(); final String method = stackTraceElement.getMethodName(); final String tag = new StringBuilder("[").append(Thread.currentThread().getId()).append("]") .append(className).append("<").append(lineNumber).append(">").toString(); final StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(method); stringBuilder.append("(): "); if (format != null) { try { final String message = (args == null) ? format.toString() : String.format( (String) format, args); stringBuilder.append(message); } catch (Exception e) { stringBuilder.append("日志格式化异常:"); stringBuilder.append(format.toString()); e.printStackTrace(); } } switch (pType) { case Log.VERBOSE: if (t != null) { Log.v(tag, stringBuilder.toString(), t); } else { Log.v(tag, stringBuilder.toString()); } break; case Log.DEBUG: if (t != null) { Log.d(tag, stringBuilder.toString(), t); } else { Log.d(tag, stringBuilder.toString()); } break; case Log.INFO: if (t != null) { Log.i(tag, stringBuilder.toString(), t); } else { Log.i(tag, stringBuilder.toString()); } break; case Log.WARN: if (t != null) { Log.w(tag, stringBuilder.toString(), t); } else { Log.w(tag, stringBuilder.toString()); } break; case Log.ERROR: if (t != null) { Log.e(tag, stringBuilder.toString(), t); } else { Log.e(tag, stringBuilder.toString()); } break; } // } } } ================================================ FILE: app/src/main/java/com/weixinninegridlayout/MainActivity.java ================================================ package com.weixinninegridlayout; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.widget.ListView; import java.util.ArrayList; import java.util.List; public class MainActivity extends ActionBarActivity { private ListView listView; private List> imagesList; private String[][] images=new String[][]{{ "http://img4.duitang.com/uploads/item/201209/25/20120925201555_eUHEU.jpeg","640","960"} ,{"file:///android_asset/img2.jpg","250","250"} ,{"file:///android_asset/img3.jpg","250","250"} ,{"file:///android_asset/img4.jpg","250","250"} ,{"file:///android_asset/img5.jpg","250","250"} ,{"file:///android_asset/img6.jpg","250","250"} ,{"file:///android_asset/img7.jpg","250","250"} ,{"file:///android_asset/img8.jpg","250","250"} ,{"http://img3.douban.com/view/photo/raw/public/p1708880537.jpg","1280","800"} }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView= (ListView) findViewById(R.id.lv_main); initData(); listView.setAdapter(new MainAdapter(MainActivity.this,imagesList)); } private void initData() { imagesList=new ArrayList<>(); //这里单独添加一条单条的测试数据,用来测试单张的时候横竖图片的效果 ArrayList singleList=new ArrayList<>(); singleList.add(new Image(images[8][0],Integer.parseInt(images[8][1]),Integer.parseInt(images[8][2]))); imagesList.add(singleList); //从一到9生成9条朋友圈内容,分别是1~9张图片 for(int i=0;i<9;i++){ ArrayList itemList=new ArrayList<>(); for(int j=0;j<=i;j++){ itemList.add(new Image(images[j][0],Integer.parseInt(images[j][1]),Integer.parseInt(images[j][2]))); } imagesList.add(itemList); } } } ================================================ FILE: app/src/main/java/com/weixinninegridlayout/MainAdapter.java ================================================ package com.weixinninegridlayout; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import java.util.List; /** * Created by Pan_ on 2015/2/3. */ public class MainAdapter extends BaseAdapter { private Context context; private List> datalist; public MainAdapter(Context context, List> datalist) { this.context = context; this.datalist = datalist; } @Override public int getCount() { return datalist.size(); } @Override public Object getItem(int position) { return datalist.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; List itemList = datalist.get(position); if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.item_ninegridlayout, parent, false); viewHolder = new ViewHolder(); viewHolder.ivMore = (NineGridlayout) convertView.findViewById(R.id.iv_ngrid_layout); viewHolder.ivOne = (CustomImageView) convertView.findViewById(R.id.iv_oneimage); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } if (itemList.isEmpty() || itemList.isEmpty()) { viewHolder.ivMore.setVisibility(View.GONE); viewHolder.ivOne.setVisibility(View.GONE); } else if (itemList.size() == 1) { viewHolder.ivMore.setVisibility(View.GONE); viewHolder.ivOne.setVisibility(View.VISIBLE); handlerOneImage(viewHolder, itemList.get(0)); } else { viewHolder.ivMore.setVisibility(View.VISIBLE); viewHolder.ivOne.setVisibility(View.GONE); viewHolder.ivMore.setImagesData(itemList); } return convertView; } private void handlerOneImage(ViewHolder viewHolder, Image image) { int totalWidth; int imageWidth; int imageHeight; ScreenTools screentools = ScreenTools.instance(context); totalWidth = screentools.getScreenWidth() - screentools.dip2px(80); imageWidth = screentools.dip2px(image.getWidth()); imageHeight = screentools.dip2px(image.getHeight()); if (image.getWidth() <= image.getHeight()) { if (imageHeight > totalWidth) { imageHeight = totalWidth; imageWidth = (imageHeight * image.getWidth()) / image.getHeight(); } } else { if (imageWidth > totalWidth) { imageWidth = totalWidth; imageHeight = (imageWidth * image.getHeight()) / image.getWidth(); } } ViewGroup.LayoutParams layoutparams = viewHolder.ivOne.getLayoutParams(); layoutparams.height = imageHeight; layoutparams.width = imageWidth; viewHolder.ivOne.setLayoutParams(layoutparams); viewHolder.ivOne.setClickable(true); viewHolder.ivOne.setScaleType(android.widget.ImageView.ScaleType.FIT_XY); viewHolder.ivOne.setImageUrl(image.getUrl()); } class ViewHolder { public NineGridlayout ivMore; public CustomImageView ivOne; } } ================================================ FILE: app/src/main/java/com/weixinninegridlayout/NineGridlayout.java ================================================ package com.weixinninegridlayout; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import java.util.List; /** * Created by Pan_ on 2015/2/2. */ public class NineGridlayout extends ViewGroup { /** * 图片之间的间隔 */ private int gap = 5; private int columns;// private int rows;// private List listData; private int totalWidth; public NineGridlayout(Context context) { super(context); } public NineGridlayout(Context context, AttributeSet attrs) { super(context, attrs); ScreenTools screenTools=ScreenTools.instance(getContext()); totalWidth=screenTools.getScreenWidth()-screenTools.dip2px(80); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { } private void layoutChildrenView(){ int childrenCount = listData.size(); int singleWidth = (totalWidth - gap * (3 - 1)) / 3; int singleHeight = singleWidth; //根据子view数量确定高度 ViewGroup.LayoutParams params = getLayoutParams(); params.height = singleHeight * rows + gap * (rows - 1); setLayoutParams(params); for (int i = 0; i < childrenCount; i++) { CustomImageView childrenView = (CustomImageView) getChildAt(i); childrenView.setImageUrl(((Image) listData.get(i)).getUrl()); int[] position = findPosition(i); int left = (singleWidth + gap) * position[1]; int top = (singleHeight + gap) * position[0]; int right = left + singleWidth; int bottom = top + singleHeight; childrenView.layout(left, top, right, bottom); } } private int[] findPosition(int childNum) { int[] position = new int[2]; for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { if ((i * columns + j) == childNum) { position[0] = i;//行 position[1] = j;//列 break; } } } return position; } public int getGap() { return gap; } public void setGap(int gap) { this.gap = gap; } public void setImagesData(List lists) { if (lists == null || lists.isEmpty()) { return; } //初始化布局 generateChildrenLayout(lists.size()); //这里做一个重用view的处理 if (listData == null) { int i = 0; while (i < lists.size()) { CustomImageView iv = generateImageView(); addView(iv,generateDefaultLayoutParams()); i++; } } else { int oldViewCount = listData.size(); int newViewCount = lists.size(); if (oldViewCount > newViewCount) { removeViews(newViewCount - 1, oldViewCount - newViewCount); } else if (oldViewCount < newViewCount) { for (int i = 0; i < newViewCount - oldViewCount; i++) { CustomImageView iv = generateImageView(); addView(iv,generateDefaultLayoutParams()); } } } listData = lists; layoutChildrenView(); } /** * 根据图片个数确定行列数量 * 对应关系如下 * num row column * 1 1 1 * 2 1 2 * 3 1 3 * 4 2 2 * 5 2 3 * 6 2 3 * 7 3 3 * 8 3 3 * 9 3 3 * * @param length */ private void generateChildrenLayout(int length) { if (length <= 3) { rows = 1; columns = length; } else if (length <= 6) { rows = 2; columns = 3; if (length == 4) { columns = 2; } } else { rows = 3; columns = 3; } } private CustomImageView generateImageView() { CustomImageView iv = new CustomImageView(getContext()); iv.setScaleType(ImageView.ScaleType.CENTER_CROP); iv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { } }); iv.setBackgroundColor(Color.parseColor("#f5f5f5")); return iv; } } ================================================ FILE: app/src/main/java/com/weixinninegridlayout/ScreenTools.java ================================================ package com.weixinninegridlayout; import android.content.Context; public class ScreenTools { private static ScreenTools mScreenTools; private Context context; private ScreenTools(Context context) { this.context = context.getApplicationContext(); } public static ScreenTools instance(Context context) { if (mScreenTools == null) mScreenTools = new ScreenTools(context); return mScreenTools; } public int dip2px(float f) { return (int) (0.5D + (double) (f * getDensity(context))); } public int dip2px(int i) { return (int) (0.5D + (double) (getDensity(context) * (float) i)); } public int get480Height(int i) { return (i * getScreenWidth()) / 480; } public float getDensity(Context context) { return context.getResources().getDisplayMetrics().density; } public int getScal() { return (100 * getScreenWidth()) / 480; } public int getScreenDensityDpi() { return context.getResources().getDisplayMetrics().densityDpi; } public int getScreenHeight() { return context.getResources().getDisplayMetrics().heightPixels; } public int getScreenWidth() { return context.getResources().getDisplayMetrics().widthPixels; } public float getXdpi() { return context.getResources().getDisplayMetrics().xdpi; } public float getYdpi() { return context.getResources().getDisplayMetrics().ydpi; } public int px2dip(float f) { float f1 = getDensity(context); return (int) (((double) f - 0.5D) / (double) f1); } public int px2dip(int i) { float f = getDensity(context); return (int) (((double) i - 0.5D) / (double) f); } } ================================================ FILE: app/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: app/src/main/res/layout/item_ninegridlayout.xml ================================================ ================================================ FILE: app/src/main/res/menu/menu_main.xml ================================================ ================================================ FILE: app/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: app/src/main/res/values/strings.xml ================================================ WeixinNineGridLayout Hello world! Settings ================================================ FILE: app/src/main/res/values/styles.xml ================================================ ================================================ FILE: app/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.0.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() } } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Wed Apr 10 15:27:10 PDT 2013 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip ================================================ FILE: gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # For Cygwin, ensure paths are in UNIX format before anything is touched. if $cygwin ; then [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` fi # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >&- APP_HOME="`pwd -P`" cd "$SAVED" >&- CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: picasso/Action.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; abstract class Action { static class RequestWeakReference extends WeakReference { final Action action; public RequestWeakReference(Action action, T referent, ReferenceQueue q) { super(referent, q); this.action = action; } } final Picasso picasso; final Request request; final WeakReference target; final boolean skipCache; final boolean noFade; final int errorResId; final Drawable errorDrawable; final String key; boolean willReplay; boolean cancelled; Action(Picasso picasso, T target, Request request, boolean skipCache, boolean noFade, int errorResId, Drawable errorDrawable, String key) { this.picasso = picasso; this.request = request; this.target = new RequestWeakReference(this, target, picasso.referenceQueue); this.skipCache = skipCache; this.noFade = noFade; this.errorResId = errorResId; this.errorDrawable = errorDrawable; this.key = key; } abstract void complete(Bitmap result, Picasso.LoadedFrom from); abstract void error(); void cancel() { cancelled = true; } Request getRequest() { return request; } T getTarget() { return target.get(); } String getKey() { return key; } boolean isCancelled() { return cancelled; } boolean willReplay() { return willReplay; } Picasso getPicasso() { return picasso; } } ================================================ FILE: picasso/AssetRequestHandler.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.content.Context; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import java.io.IOException; import java.io.InputStream; import static android.content.ContentResolver.SCHEME_FILE; import static com.squareup.picasso.Picasso.LoadedFrom.DISK; class AssetRequestHandler extends RequestHandler { protected static final String ANDROID_ASSET = "android_asset"; private static final int ASSET_PREFIX_LENGTH = (SCHEME_FILE + ":///" + ANDROID_ASSET + "/").length(); private final AssetManager assetManager; public AssetRequestHandler(Context context) { assetManager = context.getAssets(); } @Override public boolean canHandleRequest(Request data) { Uri uri = data.uri; return (SCHEME_FILE.equals(uri.getScheme()) && !uri.getPathSegments().isEmpty() && ANDROID_ASSET.equals(uri.getPathSegments().get(0))); } @Override public Result load(Request data) throws IOException { String filePath = data.uri.toString().substring(ASSET_PREFIX_LENGTH); return new Result(decodeAsset(data, filePath), DISK); } Bitmap decodeAsset(Request data, String filePath) throws IOException { final BitmapFactory.Options options = createBitmapOptions(data); if (requiresInSampleSize(options)) { InputStream is = null; try { is = assetManager.open(filePath); BitmapFactory.decodeStream(is, null, options); } finally { Utils.closeQuietly(is); } calculateInSampleSize(data.targetWidth, data.targetHeight, options); } InputStream is = assetManager.open(filePath); try { return BitmapFactory.decodeStream(is, null, options); } finally { Utils.closeQuietly(is); } } } ================================================ FILE: picasso/BitmapHunter.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.graphics.Bitmap; import android.graphics.Matrix; import android.net.NetworkInfo; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Future; import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY; import static com.squareup.picasso.Utils.OWNER_HUNTER; import static com.squareup.picasso.Utils.VERB_DECODED; import static com.squareup.picasso.Utils.VERB_EXECUTING; import static com.squareup.picasso.Utils.VERB_JOINED; import static com.squareup.picasso.Utils.VERB_REMOVED; import static com.squareup.picasso.Utils.VERB_TRANSFORMED; import static com.squareup.picasso.Utils.getLogIdsForHunter; import static com.squareup.picasso.Utils.log; class BitmapHunter implements Runnable { /** * Global lock for bitmap decoding to ensure that we are only are decoding one at a time. Since * this will only ever happen in background threads we help avoid excessive memory thrashing as * well as potential OOMs. Shamelessly stolen from Volley. */ private static final Object DECODE_LOCK = new Object(); private static final ThreadLocal NAME_BUILDER = new ThreadLocal() { @Override protected StringBuilder initialValue() { return new StringBuilder(Utils.THREAD_PREFIX); } }; final Picasso picasso; final Dispatcher dispatcher; final Cache cache; final Stats stats; final String key; final Request data; final boolean skipMemoryCache; final RequestHandler requestHandler; Action action; List actions; Bitmap result; Future future; Picasso.LoadedFrom loadedFrom; Exception exception; int exifRotation; // Determined during decoding of original resource. int retryCount; BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action, RequestHandler requestHandler) { this.picasso = picasso; this.dispatcher = dispatcher; this.cache = cache; this.stats = stats; this.key = action.getKey(); this.data = action.getRequest(); this.skipMemoryCache = action.skipCache; this.requestHandler = requestHandler; this.retryCount = requestHandler.getRetryCount(); this.action = action; } @Override public void run() { try { updateThreadName(data); if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this)); } result = hunt(); if (result == null) { dispatcher.dispatchFailed(this); } else { dispatcher.dispatchComplete(this); } } catch (Downloader.ResponseException e) { exception = e; dispatcher.dispatchFailed(this); } catch (IOException e) { exception = e; dispatcher.dispatchRetry(this); } catch (OutOfMemoryError e) { StringWriter writer = new StringWriter(); stats.createSnapshot().dump(new PrintWriter(writer)); exception = new RuntimeException(writer.toString(), e); dispatcher.dispatchFailed(this); } catch (Exception e) { exception = e; dispatcher.dispatchFailed(this); } finally { Thread.currentThread().setName(Utils.THREAD_IDLE_NAME); } } Bitmap hunt() throws IOException { Bitmap bitmap = null; if (!skipMemoryCache) { bitmap = cache.get(key); if (bitmap != null) { stats.dispatchCacheHit(); loadedFrom = MEMORY; if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache"); } return bitmap; } } data.loadFromLocalCacheOnly = (retryCount == 0); RequestHandler.Result result = requestHandler.load(data); if (result != null) { bitmap = result.getBitmap(); loadedFrom = result.getLoadedFrom(); exifRotation = result.getExifOrientation(); } if (bitmap != null) { if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_DECODED, data.logId()); } stats.dispatchBitmapDecoded(bitmap); if (data.needsTransformation() || exifRotation != 0) { synchronized (DECODE_LOCK) { if (data.needsMatrixTransform() || exifRotation != 0) { bitmap = transformResult(data, bitmap, exifRotation); if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId()); } } if (data.hasCustomTransformations()) { bitmap = applyCustomTransformations(data.transformations, bitmap); if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations"); } } } if (bitmap != null) { stats.dispatchBitmapTransformed(bitmap); } } } return bitmap; } void attach(Action action) { boolean loggingEnabled = picasso.loggingEnabled; Request request = action.request; if (this.action == null) { this.action = action; if (loggingEnabled) { if (actions == null || actions.isEmpty()) { log(OWNER_HUNTER, VERB_JOINED, request.logId(), "to empty hunter"); } else { log(OWNER_HUNTER, VERB_JOINED, request.logId(), getLogIdsForHunter(this, "to ")); } } return; } if (actions == null) { actions = new ArrayList(3); } actions.add(action); if (loggingEnabled) { log(OWNER_HUNTER, VERB_JOINED, request.logId(), getLogIdsForHunter(this, "to ")); } } void detach(Action action) { if (this.action == action) { this.action = null; } else if (actions != null) { actions.remove(action); } if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_REMOVED, action.request.logId(), getLogIdsForHunter(this, "from ")); } } boolean cancel() { return action == null && (actions == null || actions.isEmpty()) && future != null && future.cancel(false); } boolean isCancelled() { return future != null && future.isCancelled(); } boolean shouldSkipMemoryCache() { return skipMemoryCache; } boolean shouldRetry(boolean airplaneMode, NetworkInfo info) { boolean hasRetries = retryCount > 0; if (!hasRetries) { return false; } retryCount--; return requestHandler.shouldRetry(airplaneMode, info); } boolean supportsReplay() { return requestHandler.supportsReplay(); } Bitmap getResult() { return result; } String getKey() { return key; } Request getData() { return data; } Action getAction() { return action; } Picasso getPicasso() { return picasso; } List getActions() { return actions; } Exception getException() { return exception; } Picasso.LoadedFrom getLoadedFrom() { return loadedFrom; } static void updateThreadName(Request data) { String name = data.getName(); StringBuilder builder = NAME_BUILDER.get(); builder.ensureCapacity(Utils.THREAD_PREFIX.length() + name.length()); builder.replace(Utils.THREAD_PREFIX.length(), builder.length(), name); Thread.currentThread().setName(builder.toString()); } static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) { Request request = action.getRequest(); List requestHandlers = picasso.getRequestHandlers(); final int count = requestHandlers.size(); for (int i = 0; i < count; i++) { RequestHandler requestHandler = requestHandlers.get(i); if (requestHandler.canHandleRequest(request)) { return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler); } } throw new IllegalStateException("Unrecognized type of request: " + request); } static Bitmap applyCustomTransformations(List transformations, Bitmap result) { for (int i = 0, count = transformations.size(); i < count; i++) { final Transformation transformation = transformations.get(i); Bitmap newResult = transformation.transform(result); if (newResult == null) { final StringBuilder builder = new StringBuilder() // .append("Transformation ") .append(transformation.key()) .append(" returned null after ") .append(i) .append(" previous transformation(s).\n\nTransformation list:\n"); for (Transformation t : transformations) { builder.append(t.key()).append('\n'); } Picasso.HANDLER.post(new Runnable() { @Override public void run() { throw new NullPointerException(builder.toString()); } }); return null; } if (newResult == result && result.isRecycled()) { Picasso.HANDLER.post(new Runnable() { @Override public void run() { throw new IllegalStateException("Transformation " + transformation.key() + " returned input Bitmap but recycled it."); } }); return null; } // If the transformation returned a new bitmap ensure they recycled the original. if (newResult != result && !result.isRecycled()) { Picasso.HANDLER.post(new Runnable() { @Override public void run() { throw new IllegalStateException("Transformation " + transformation.key() + " mutated input Bitmap but failed to recycle the original."); } }); return null; } result = newResult; } return result; } static Bitmap transformResult(Request data, Bitmap result, int exifRotation) { int inWidth = result.getWidth(); int inHeight = result.getHeight(); int drawX = 0; int drawY = 0; int drawWidth = inWidth; int drawHeight = inHeight; Matrix matrix = new Matrix(); if (data.needsMatrixTransform()) { int targetWidth = data.targetWidth; int targetHeight = data.targetHeight; float targetRotation = data.rotationDegrees; if (targetRotation != 0) { if (data.hasRotationPivot) { matrix.setRotate(targetRotation, data.rotationPivotX, data.rotationPivotY); } else { matrix.setRotate(targetRotation); } } if (data.centerCrop) { float widthRatio = targetWidth / (float) inWidth; float heightRatio = targetHeight / (float) inHeight; float scale; if (widthRatio > heightRatio) { scale = widthRatio; int newSize = (int) Math.ceil(inHeight * (heightRatio / widthRatio)); drawY = (inHeight - newSize) / 2; drawHeight = newSize; } else { scale = heightRatio; int newSize = (int) Math.ceil(inWidth * (widthRatio / heightRatio)); drawX = (inWidth - newSize) / 2; drawWidth = newSize; } matrix.preScale(scale, scale); } else if (data.centerInside) { float widthRatio = targetWidth / (float) inWidth; float heightRatio = targetHeight / (float) inHeight; float scale = widthRatio < heightRatio ? widthRatio : heightRatio; matrix.preScale(scale, scale); } else if (targetWidth != 0 && targetHeight != 0 // && (targetWidth != inWidth || targetHeight != inHeight)) { // If an explicit target size has been specified and they do not match the results bounds, // pre-scale the existing matrix appropriately. float sx = targetWidth / (float) inWidth; float sy = targetHeight / (float) inHeight; matrix.preScale(sx, sy); } } if (exifRotation != 0) { matrix.preRotate(exifRotation); } Bitmap newResult = Bitmap.createBitmap(result, drawX, drawY, drawWidth, drawHeight, matrix, true); if (newResult != result) { result.recycle(); result = newResult; } return result; } } ================================================ FILE: picasso/Cache.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.graphics.Bitmap; /** * A memory cache for storing the most recently used images. *

* Note: The {@link com.squareup.picasso.Cache} is accessed by multiple threads. You must ensure * your {@link com.squareup.picasso.Cache} implementation is thread safe when {@link com.squareup.picasso.Cache#get(String)} or {@link * com.squareup.picasso.Cache#set(String, android.graphics.Bitmap)} is called. */ public interface Cache { /** Retrieve an image for the specified {@code key} or {@code null}. */ Bitmap get(String key); /** Store an image in the cache for the specified {@code key}. */ void set(String key, Bitmap bitmap); /** Returns the current size of the cache in bytes. */ int size(); /** Returns the maximum size in bytes that the cache can hold. */ int maxSize(); /** Clears the cache. */ void clear(); /** A cache which does not store any values. */ Cache NONE = new Cache() { @Override public Bitmap get(String key) { return null; } @Override public void set(String key, Bitmap bitmap) { // Ignore. } @Override public int size() { return 0; } @Override public int maxSize() { return 0; } @Override public void clear() { } }; } ================================================ FILE: picasso/Callback.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; public interface Callback { void onSuccess(); void onError(); public static class EmptyCallback implements Callback { @Override public void onSuccess() { } @Override public void onError() { } } } ================================================ FILE: picasso/ContactsPhotoRequestHandler.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.Context; import android.content.UriMatcher; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.provider.ContactsContract; import java.io.IOException; import java.io.InputStream; import static android.content.ContentResolver.SCHEME_CONTENT; import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH; import static android.provider.ContactsContract.Contacts.openContactPhotoInputStream; import static com.squareup.picasso.Picasso.LoadedFrom.DISK; class ContactsPhotoRequestHandler extends RequestHandler { /** A lookup uri (e.g. content://com.android.contacts/contacts/lookup/3570i61d948d30808e537) */ private static final int ID_LOOKUP = 1; /** A contact thumbnail uri (e.g. content://com.android.contacts/contacts/38/photo) */ private static final int ID_THUMBNAIL = 2; /** A contact uri (e.g. content://com.android.contacts/contacts/38) */ private static final int ID_CONTACT = 3; /** * A contact display photo (high resolution) uri * (e.g. content://com.android.contacts/display_photo/5) */ private static final int ID_DISPLAY_PHOTO = 4; private static final UriMatcher matcher; static { matcher = new UriMatcher(UriMatcher.NO_MATCH); matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#", ID_LOOKUP); matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*", ID_LOOKUP); matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/photo", ID_THUMBNAIL); matcher.addURI(ContactsContract.AUTHORITY, "contacts/#", ID_CONTACT); matcher.addURI(ContactsContract.AUTHORITY, "display_photo/#", ID_DISPLAY_PHOTO); } final Context context; ContactsPhotoRequestHandler(Context context) { this.context = context; } @Override public boolean canHandleRequest(Request data) { final Uri uri = data.uri; return (SCHEME_CONTENT.equals(uri.getScheme()) && ContactsContract.Contacts.CONTENT_URI.getHost().equals(uri.getHost()) && !uri.getPathSegments().contains(ContactsContract.Contacts.Photo.CONTENT_DIRECTORY)); } @Override public Result load(Request data) throws IOException { InputStream is = null; try { is = getInputStream(data); return new Result(decodeStream(is, data), DISK); } finally { Utils.closeQuietly(is); } } private InputStream getInputStream(Request data) throws IOException { ContentResolver contentResolver = context.getContentResolver(); Uri uri = data.uri; switch (matcher.match(uri)) { case ID_LOOKUP: uri = ContactsContract.Contacts.lookupContact(contentResolver, uri); if (uri == null) { return null; } // Resolved the uri to a contact uri, intentionally fall through to process the resolved uri case ID_CONTACT: if (SDK_INT < ICE_CREAM_SANDWICH) { return openContactPhotoInputStream(contentResolver, uri); } else { return ContactPhotoStreamIcs.get(contentResolver, uri); } case ID_THUMBNAIL: case ID_DISPLAY_PHOTO: return contentResolver.openInputStream(uri); default: throw new IllegalStateException("Invalid uri: " + uri); } } private Bitmap decodeStream(InputStream stream, Request data) throws IOException { if (stream == null) { return null; } final BitmapFactory.Options options = createBitmapOptions(data); if (requiresInSampleSize(options)) { InputStream is = getInputStream(data); try { BitmapFactory.decodeStream(is, null, options); } finally { Utils.closeQuietly(is); } calculateInSampleSize(data.targetWidth, data.targetHeight, options); } return BitmapFactory.decodeStream(stream, null, options); } @TargetApi(ICE_CREAM_SANDWICH) private static class ContactPhotoStreamIcs { static InputStream get(ContentResolver contentResolver, Uri uri) { return openContactPhotoInputStream(contentResolver, uri, true); } } } ================================================ FILE: picasso/ContentStreamRequestHandler.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.content.ContentResolver; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import java.io.IOException; import java.io.InputStream; import static android.content.ContentResolver.SCHEME_CONTENT; import static com.squareup.picasso.Picasso.LoadedFrom.DISK; class ContentStreamRequestHandler extends RequestHandler { final Context context; ContentStreamRequestHandler(Context context) { this.context = context; } @Override public boolean canHandleRequest(Request data) { return SCHEME_CONTENT.equals(data.uri.getScheme()); } @Override public Result load(Request data) throws IOException { return new Result(decodeContentStream(data), DISK); } protected Bitmap decodeContentStream(Request data) throws IOException { ContentResolver contentResolver = context.getContentResolver(); final BitmapFactory.Options options = createBitmapOptions(data); if (requiresInSampleSize(options)) { InputStream is = null; try { is = contentResolver.openInputStream(data.uri); BitmapFactory.decodeStream(is, null, options); } finally { Utils.closeQuietly(is); } calculateInSampleSize(data.targetWidth, data.targetHeight, options); } InputStream is = contentResolver.openInputStream(data.uri); try { return BitmapFactory.decodeStream(is, null, options); } finally { Utils.closeQuietly(is); } } } ================================================ FILE: picasso/DeferredRequestCreator.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.view.ViewTreeObserver; import android.widget.ImageView; import java.lang.ref.WeakReference; class DeferredRequestCreator implements ViewTreeObserver.OnPreDrawListener { final RequestCreator creator; final WeakReference target; Callback callback; DeferredRequestCreator(RequestCreator creator, ImageView target) { this(creator, target, null); } DeferredRequestCreator(RequestCreator creator, ImageView target, Callback callback) { this.creator = creator; this.target = new WeakReference(target); this.callback = callback; target.getViewTreeObserver().addOnPreDrawListener(this); } @Override public boolean onPreDraw() { ImageView target = this.target.get(); if (target == null) { return true; } ViewTreeObserver vto = target.getViewTreeObserver(); if (!vto.isAlive()) { return true; } int width = target.getWidth(); int height = target.getHeight(); if (width <= 0 || height <= 0) { return true; } vto.removeOnPreDrawListener(this); this.creator.unfit().resize(width, height).into(target, callback); return true; } void cancel() { callback = null; ImageView target = this.target.get(); if (target == null) { return; } ViewTreeObserver vto = target.getViewTreeObserver(); if (!vto.isAlive()) { return; } vto.removeOnPreDrawListener(this); } } ================================================ FILE: picasso/Dispatcher.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.Manifest; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.ExecutorService; import static android.content.Context.CONNECTIVITY_SERVICE; import static android.content.Intent.ACTION_AIRPLANE_MODE_CHANGED; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static com.squareup.picasso.BitmapHunter.forRequest; import static com.squareup.picasso.Utils.OWNER_DISPATCHER; import static com.squareup.picasso.Utils.VERB_BATCHED; import static com.squareup.picasso.Utils.VERB_CANCELED; import static com.squareup.picasso.Utils.VERB_DELIVERED; import static com.squareup.picasso.Utils.VERB_ENQUEUED; import static com.squareup.picasso.Utils.VERB_IGNORED; import static com.squareup.picasso.Utils.VERB_REPLAYING; import static com.squareup.picasso.Utils.VERB_RETRYING; import static com.squareup.picasso.Utils.getLogIdsForHunter; import static com.squareup.picasso.Utils.getService; import static com.squareup.picasso.Utils.hasPermission; import static com.squareup.picasso.Utils.log; class Dispatcher { private static final int RETRY_DELAY = 500; private static final int AIRPLANE_MODE_ON = 1; private static final int AIRPLANE_MODE_OFF = 0; static final int REQUEST_SUBMIT = 1; static final int REQUEST_CANCEL = 2; static final int REQUEST_GCED = 3; static final int HUNTER_COMPLETE = 4; static final int HUNTER_RETRY = 5; static final int HUNTER_DECODE_FAILED = 6; static final int HUNTER_DELAY_NEXT_BATCH = 7; static final int HUNTER_BATCH_COMPLETE = 8; static final int NETWORK_STATE_CHANGE = 9; static final int AIRPLANE_MODE_CHANGE = 10; private static final String DISPATCHER_THREAD_NAME = "Dispatcher"; private static final int BATCH_DELAY = 200; // ms final DispatcherThread dispatcherThread; final Context context; final ExecutorService service; final Downloader downloader; final Map hunterMap; final Map failedActions; final Handler handler; final Handler mainThreadHandler; final Cache cache; final Stats stats; final List batch; final NetworkBroadcastReceiver receiver; final boolean scansNetworkChanges; boolean airplaneMode; Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler, Downloader downloader, Cache cache, Stats stats) { this.dispatcherThread = new DispatcherThread(); this.dispatcherThread.start(); this.context = context; this.service = service; this.hunterMap = new LinkedHashMap(); this.failedActions = new WeakHashMap(); this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this); this.downloader = downloader; this.mainThreadHandler = mainThreadHandler; this.cache = cache; this.stats = stats; this.batch = new ArrayList(4); this.airplaneMode = Utils.isAirplaneModeOn(this.context); this.scansNetworkChanges = hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE); this.receiver = new NetworkBroadcastReceiver(this); receiver.register(); } void shutdown() { service.shutdown(); dispatcherThread.quit(); receiver.unregister(); } void dispatchSubmit(Action action) { handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action)); } void dispatchCancel(Action action) { handler.sendMessage(handler.obtainMessage(REQUEST_CANCEL, action)); } void dispatchComplete(BitmapHunter hunter) { handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter)); } void dispatchRetry(BitmapHunter hunter) { handler.sendMessageDelayed(handler.obtainMessage(HUNTER_RETRY, hunter), RETRY_DELAY); } void dispatchFailed(BitmapHunter hunter) { handler.sendMessage(handler.obtainMessage(HUNTER_DECODE_FAILED, hunter)); } void dispatchNetworkStateChange(NetworkInfo info) { handler.sendMessage(handler.obtainMessage(NETWORK_STATE_CHANGE, info)); } void dispatchAirplaneModeChange(boolean airplaneMode) { handler.sendMessage(handler.obtainMessage(AIRPLANE_MODE_CHANGE, airplaneMode ? AIRPLANE_MODE_ON : AIRPLANE_MODE_OFF, 0)); } void performSubmit(Action action) { BitmapHunter hunter = hunterMap.get(action.getKey()); if (hunter != null) { hunter.attach(action); return; } if (service.isShutdown()) { if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down"); } return; } hunter = forRequest(action.getPicasso(), this, cache, stats, action); hunter.future = service.submit(hunter); hunterMap.put(action.getKey(), hunter); failedActions.remove(action.getTarget()); if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId()); } } void performCancel(Action action) { String key = action.getKey(); BitmapHunter hunter = hunterMap.get(key); if (hunter != null) { hunter.detach(action); if (hunter.cancel()) { hunterMap.remove(key); if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_CANCELED, action.getRequest().logId()); } } } Action remove = failedActions.remove(action.getTarget()); if (remove != null && remove.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_CANCELED, remove.getRequest().logId(), "from replaying"); } } void performRetry(BitmapHunter hunter) { if (hunter.isCancelled()) return; if (service.isShutdown()) { performError(hunter, false); return; } NetworkInfo networkInfo = null; if (scansNetworkChanges) { ConnectivityManager connectivityManager = getService(context, CONNECTIVITY_SERVICE); networkInfo = connectivityManager.getActiveNetworkInfo(); } boolean hasConnectivity = networkInfo != null && networkInfo.isConnected(); boolean shouldRetryHunter = hunter.shouldRetry(airplaneMode, networkInfo); boolean supportsReplay = hunter.supportsReplay(); if (!shouldRetryHunter) { // Mark for replay only if we observe network info changes and support replay. boolean willReplay = scansNetworkChanges && supportsReplay; performError(hunter, willReplay); if (willReplay) { markForReplay(hunter); } return; } // If we don't scan for network changes (missing permission) or if we have connectivity, retry. if (!scansNetworkChanges || hasConnectivity) { if (hunter.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_RETRYING, getLogIdsForHunter(hunter)); } hunter.future = service.submit(hunter); return; } performError(hunter, supportsReplay); if (supportsReplay) { markForReplay(hunter); } } void performComplete(BitmapHunter hunter) { if (!hunter.shouldSkipMemoryCache()) { cache.set(hunter.getKey(), hunter.getResult()); } hunterMap.remove(hunter.getKey()); batch(hunter); if (hunter.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion"); } } void performBatchComplete() { List copy = new ArrayList(batch); batch.clear(); mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy)); logBatch(copy); } void performError(BitmapHunter hunter, boolean willReplay) { if (hunter.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for error" + (willReplay ? " (will replay)" : "")); } hunterMap.remove(hunter.getKey()); batch(hunter); } void performAirplaneModeChange(boolean airplaneMode) { this.airplaneMode = airplaneMode; } void performNetworkStateChange(NetworkInfo info) { if (service instanceof PicassoExecutorService) { ((PicassoExecutorService) service).adjustThreadCount(info); } // Intentionally check only if isConnected() here before we flush out failed actions. if (info != null && info.isConnected()) { flushFailedActions(); } } private void flushFailedActions() { if (!failedActions.isEmpty()) { Iterator iterator = failedActions.values().iterator(); while (iterator.hasNext()) { Action action = iterator.next(); iterator.remove(); if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_REPLAYING, action.getRequest().logId()); } performSubmit(action); } } } private void markForReplay(BitmapHunter hunter) { Action action = hunter.getAction(); if (action != null) { markForReplay(action); } List joined = hunter.getActions(); if (joined != null) { //noinspection ForLoopReplaceableByForEach for (int i = 0, n = joined.size(); i < n; i++) { Action join = joined.get(i); markForReplay(join); } } } private void markForReplay(Action action) { Object target = action.getTarget(); if (target != null) { action.willReplay = true; failedActions.put(target, action); } } private void batch(BitmapHunter hunter) { if (hunter.isCancelled()) { return; } batch.add(hunter); if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) { handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY); } } private void logBatch(List copy) { if (copy == null || copy.isEmpty()) return; BitmapHunter hunter = copy.get(0); Picasso picasso = hunter.getPicasso(); if (picasso.loggingEnabled) { StringBuilder builder = new StringBuilder(); for (BitmapHunter bitmapHunter : copy) { if (builder.length() > 0) builder.append(", "); builder.append(Utils.getLogIdsForHunter(bitmapHunter)); } log(OWNER_DISPATCHER, VERB_DELIVERED, builder.toString()); } } private static class DispatcherHandler extends Handler { private final Dispatcher dispatcher; public DispatcherHandler(Looper looper, Dispatcher dispatcher) { super(looper); this.dispatcher = dispatcher; } @Override public void handleMessage(final Message msg) { switch (msg.what) { case REQUEST_SUBMIT: { Action action = (Action) msg.obj; dispatcher.performSubmit(action); break; } case REQUEST_CANCEL: { Action action = (Action) msg.obj; dispatcher.performCancel(action); break; } case HUNTER_COMPLETE: { BitmapHunter hunter = (BitmapHunter) msg.obj; dispatcher.performComplete(hunter); break; } case HUNTER_RETRY: { BitmapHunter hunter = (BitmapHunter) msg.obj; dispatcher.performRetry(hunter); break; } case HUNTER_DECODE_FAILED: { BitmapHunter hunter = (BitmapHunter) msg.obj; dispatcher.performError(hunter, false); break; } case HUNTER_DELAY_NEXT_BATCH: { dispatcher.performBatchComplete(); break; } case NETWORK_STATE_CHANGE: { NetworkInfo info = (NetworkInfo) msg.obj; dispatcher.performNetworkStateChange(info); break; } case AIRPLANE_MODE_CHANGE: { dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON); break; } default: Picasso.HANDLER.post(new Runnable() { @Override public void run() { throw new AssertionError("Unknown handler message received: " + msg.what); } }); } } } static class DispatcherThread extends HandlerThread { DispatcherThread() { super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND); } } static class NetworkBroadcastReceiver extends BroadcastReceiver { static final String EXTRA_AIRPLANE_STATE = "state"; private final Dispatcher dispatcher; NetworkBroadcastReceiver(Dispatcher dispatcher) { this.dispatcher = dispatcher; } void register() { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_AIRPLANE_MODE_CHANGED); if (dispatcher.scansNetworkChanges) { filter.addAction(CONNECTIVITY_ACTION); } dispatcher.context.registerReceiver(this, filter); } void unregister() { dispatcher.context.unregisterReceiver(this); } @Override public void onReceive(Context context, Intent intent) { // On some versions of Android this may be called with a null Intent, // also without extras (getExtras() == null), in such case we use defaults. if (intent == null) { return; } final String action = intent.getAction(); if (ACTION_AIRPLANE_MODE_CHANGED.equals(action)) { if (!intent.hasExtra(EXTRA_AIRPLANE_STATE)) { return; // No airplane state, ignore it. Should we query Utils.isAirplaneModeOn? } dispatcher.dispatchAirplaneModeChange(intent.getBooleanExtra(EXTRA_AIRPLANE_STATE, false)); } else if (CONNECTIVITY_ACTION.equals(action)) { ConnectivityManager connectivityManager = getService(context, CONNECTIVITY_SERVICE); dispatcher.dispatchNetworkStateChange(connectivityManager.getActiveNetworkInfo()); } } } } ================================================ FILE: picasso/Downloader.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.graphics.Bitmap; import android.net.Uri; import java.io.IOException; import java.io.InputStream; /** A mechanism to load images from external resources such as a disk cache and/or the internet. */ public interface Downloader { /** * Download the specified image {@code url} from the internet. * * @param uri Remote image URL. * @param localCacheOnly If {@code true} the URL should only be loaded if available in a local * disk cache. * @return {@link com.squareup.picasso.Downloader.Response} containing either a {@link android.graphics.Bitmap} representation of the request or an * {@link java.io.InputStream} for the image data. {@code null} can be returned to indicate a problem * loading the bitmap. * @throws java.io.IOException if the requested URL cannot successfully be loaded. */ Response load(Uri uri, boolean localCacheOnly) throws IOException; /** Thrown for non-2XX responses. */ class ResponseException extends IOException { public ResponseException(String message) { super(message); } } /** Response stream or bitmap and info. */ class Response { final InputStream stream; final Bitmap bitmap; final boolean cached; final long contentLength; /** * Response image and info. * * @param bitmap Image. * @param loadedFromCache {@code true} if the source of the image is from a local disk cache. * @deprecated Use {@link com.squareup.picasso.Downloader.Response#Response(android.graphics.Bitmap, boolean, long)} instead. */ @Deprecated @SuppressWarnings("UnusedDeclaration") public Response(Bitmap bitmap, boolean loadedFromCache) { this(bitmap, loadedFromCache, -1); } /** * Response stream and info. * * @param stream Image data stream. * @param loadedFromCache {@code true} if the source of the stream is from a local disk cache. * @deprecated Use {@link com.squareup.picasso.Downloader.Response#Response(java.io.InputStream, boolean, long)} instead. */ @Deprecated @SuppressWarnings("UnusedDeclaration") public Response(InputStream stream, boolean loadedFromCache) { this(stream, loadedFromCache, -1); } /** * Response image and info. * * @param bitmap Image. * @param loadedFromCache {@code true} if the source of the image is from a local disk cache. * @param contentLength The content length of the response, typically derived by the * {@code Content-Length} HTTP header. */ public Response(Bitmap bitmap, boolean loadedFromCache, long contentLength) { if (bitmap == null) { throw new IllegalArgumentException("Bitmap may not be null."); } this.stream = null; this.bitmap = bitmap; this.cached = loadedFromCache; this.contentLength = contentLength; } /** * Response stream and info. * * @param stream Image data stream. * @param loadedFromCache {@code true} if the source of the stream is from a local disk cache. * @param contentLength The content length of the response, typically derived by the * {@code Content-Length} HTTP header. */ public Response(InputStream stream, boolean loadedFromCache, long contentLength) { if (stream == null) { throw new IllegalArgumentException("Stream may not be null."); } this.stream = stream; this.bitmap = null; this.cached = loadedFromCache; this.contentLength = contentLength; } /** * Input stream containing image data. *

* If this returns {@code null}, image data will be available via {@link #getBitmap()}. */ public InputStream getInputStream() { return stream; } /** * Bitmap representing the image. *

* If this returns {@code null}, image data will be available via {@link #getInputStream()}. */ public Bitmap getBitmap() { return bitmap; } /** Content length of the response. */ public long getContentLength() { return contentLength; } } } ================================================ FILE: picasso/FetchAction.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.graphics.Bitmap; class FetchAction extends Action { FetchAction(Picasso picasso, Request data, boolean skipCache, String key) { super(picasso, null, data, skipCache, false, 0, null, key); } @Override void complete(Bitmap result, Picasso.LoadedFrom from) { } @Override public void error() { } } ================================================ FILE: picasso/FileRequestHandler.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.content.Context; import android.media.ExifInterface; import android.net.Uri; import java.io.IOException; import static android.content.ContentResolver.SCHEME_FILE; import static android.media.ExifInterface.ORIENTATION_NORMAL; import static android.media.ExifInterface.ORIENTATION_ROTATE_180; import static android.media.ExifInterface.ORIENTATION_ROTATE_270; import static android.media.ExifInterface.ORIENTATION_ROTATE_90; import static android.media.ExifInterface.TAG_ORIENTATION; import static com.squareup.picasso.Picasso.LoadedFrom.DISK; class FileRequestHandler extends ContentStreamRequestHandler { FileRequestHandler(Context context) { super(context); } @Override public boolean canHandleRequest(Request data) { return SCHEME_FILE.equals(data.uri.getScheme()); } @Override public Result load(Request data) throws IOException { return new Result(decodeContentStream(data), DISK, getFileExifRotation(data.uri)); } static int getFileExifRotation(Uri uri) throws IOException { ExifInterface exifInterface = new ExifInterface(uri.getPath()); int orientation = exifInterface.getAttributeInt(TAG_ORIENTATION, ORIENTATION_NORMAL); switch (orientation) { case ORIENTATION_ROTATE_90: return 90; case ORIENTATION_ROTATE_180: return 180; case ORIENTATION_ROTATE_270: return 270; default: return 0; } } } ================================================ FILE: picasso/GetAction.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.graphics.Bitmap; class GetAction extends Action { GetAction(Picasso picasso, Request data, boolean skipCache, String key) { super(picasso, null, data, skipCache, false, 0, null, key); } @Override void complete(Bitmap result, Picasso.LoadedFrom from) { } @Override public void error() { } } ================================================ FILE: picasso/ImageViewAction.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.widget.ImageView; class ImageViewAction extends Action { Callback callback; ImageViewAction(Picasso picasso, ImageView imageView, Request data, boolean skipCache, boolean noFade, int errorResId, Drawable errorDrawable, String key, Callback callback) { super(picasso, imageView, data, skipCache, noFade, errorResId, errorDrawable, key); this.callback = callback; } @Override public void complete(Bitmap result, Picasso.LoadedFrom from) { if (result == null) { throw new AssertionError( String.format("Attempted to complete action with no result!\n%s", this)); } ImageView target = this.target.get(); if (target == null) { return; } Context context = picasso.context; boolean indicatorsEnabled = picasso.indicatorsEnabled; PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled); if (callback != null) { callback.onSuccess(); } } @Override public void error() { ImageView target = this.target.get(); if (target == null) { return; } if (errorResId != 0) { target.setImageResource(errorResId); } else if (errorDrawable != null) { target.setImageDrawable(errorDrawable); } if (callback != null) { callback.onError(); } } @Override void cancel() { super.cancel(); if (callback != null) { callback = null; } } } ================================================ FILE: picasso/LruCache.java ================================================ /* * Copyright (C) 2011 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 com.squareup.picasso; import android.content.Context; import android.graphics.Bitmap; import java.util.LinkedHashMap; import java.util.Map; /** A memory cache which uses a least-recently used eviction policy. */ public class LruCache implements Cache { final LinkedHashMap map; private final int maxSize; private int size; private int putCount; private int evictionCount; private int hitCount; private int missCount; /** Create a cache using an appropriate portion of the available RAM as the maximum size. */ public LruCache(Context context) { this(Utils.calculateMemoryCacheSize(context)); } /** Create a cache with a given maximum size in bytes. */ public LruCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("Max size must be positive."); } this.maxSize = maxSize; this.map = new LinkedHashMap(0, 0.75f, true); } @Override public Bitmap get(String key) { if (key == null) { throw new NullPointerException("key == null"); } Bitmap mapValue; synchronized (this) { mapValue = map.get(key); if (mapValue != null) { hitCount++; return mapValue; } missCount++; } return null; } @Override public void set(String key, Bitmap bitmap) { if (key == null || bitmap == null) { throw new NullPointerException("key == null || bitmap == null"); } Bitmap previous; synchronized (this) { putCount++; size += Utils.getBitmapBytes(bitmap); previous = map.put(key, bitmap); if (previous != null) { size -= Utils.getBitmapBytes(previous); } } trimToSize(maxSize); } private void trimToSize(int maxSize) { while (true) { String key; Bitmap value; synchronized (this) { if (size < 0 || (map.isEmpty() && size != 0)) { throw new IllegalStateException( getClass().getName() + ".sizeOf() is reporting inconsistent results!"); } if (size <= maxSize || map.isEmpty()) { break; } Map.Entry toEvict = map.entrySet().iterator().next(); key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= Utils.getBitmapBytes(value); evictionCount++; } } } /** Clear the cache. */ public final void evictAll() { trimToSize(-1); // -1 will evict 0-sized elements } /** Returns the sum of the sizes of the entries in this cache. */ public final synchronized int size() { return size; } /** Returns the maximum sum of the sizes of the entries in this cache. */ public final synchronized int maxSize() { return maxSize; } public final synchronized void clear() { evictAll(); } /** Returns the number of times {@link #get} returned a value. */ public final synchronized int hitCount() { return hitCount; } /** Returns the number of times {@link #get} returned {@code null}. */ public final synchronized int missCount() { return missCount; } /** Returns the number of times {@link #set(String, android.graphics.Bitmap)} was called. */ public final synchronized int putCount() { return putCount; } /** Returns the number of values that have been evicted. */ public final synchronized int evictionCount() { return evictionCount; } } ================================================ FILE: picasso/MarkableInputStream.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; /** * An input stream wrapper that supports unlimited independent cursors for * marking and resetting. Each cursor is a token, and it's the caller's * responsibility to keep track of these. */ final class MarkableInputStream extends InputStream { private static final int DEFAULT_BUFFER_SIZE = 4096; private final InputStream in; private long offset; private long reset; private long limit; private long defaultMark = -1; public MarkableInputStream(InputStream in) { this(in, DEFAULT_BUFFER_SIZE); } public MarkableInputStream(InputStream in, int size) { if (!in.markSupported()) { in = new BufferedInputStream(in, size); } this.in = in; } /** Marks this place in the stream so we can reset back to it later. */ @Override public void mark(int readLimit) { defaultMark = savePosition(readLimit); } /** * Returns an opaque token representing the current position in the stream. * Call {@link #reset(long)} to return to this position in the stream later. * It is an error to call {@link #reset(long)} after consuming more than * {@code readLimit} bytes from this stream. */ public long savePosition(int readLimit) { long offsetLimit = offset + readLimit; if (limit < offsetLimit) { setLimit(offsetLimit); } return offset; } /** * Makes sure that the underlying stream can backtrack the full range from * {@code reset} thru {@code limit}. Since we can't call {@code mark()} * without also adjusting the reset-to-position on the underlying stream this * method resets first and then marks the union of the two byte ranges. On * buffered streams this additional cursor motion shouldn't result in any * additional I/O. */ private void setLimit(long limit) { try { if (reset < offset && offset <= this.limit) { in.reset(); in.mark((int) (limit - reset)); skip(reset, offset); } else { reset = offset; in.mark((int) (limit - offset)); } this.limit = limit; } catch (IOException e) { throw new IllegalStateException("Unable to mark: " + e); } } /** Resets the stream to the most recent {@link #mark mark}. */ @Override public void reset() throws IOException { reset(defaultMark); } /** Resets the stream to the position recorded by {@code token}. */ public void reset(long token) throws IOException { if (offset > limit || token < reset) { throw new IOException("Cannot reset"); } in.reset(); skip(reset, token); offset = token; } /** Skips {@code target - current} bytes and returns. */ private void skip(long current, long target) throws IOException { while (current < target) { long skipped = in.skip(target - current); if (skipped == 0) { if (read() == -1) { break; // EOF } else { skipped = 1; } } current += skipped; } } @Override public int read() throws IOException { int result = in.read(); if (result != -1) { offset++; } return result; } @Override public int read(byte[] buffer) throws IOException { int count = in.read(buffer); if (count != -1) { offset += count; } return count; } @Override public int read(byte[] buffer, int offset, int length) throws IOException { int count = in.read(buffer, offset, length); if (count != -1) { this.offset += count; } return count; } @Override public long skip(long byteCount) throws IOException { long skipped = in.skip(byteCount); offset += skipped; return skipped; } @Override public int available() throws IOException { return in.available(); } @Override public void close() throws IOException { in.close(); } @Override public boolean markSupported() { return in.markSupported(); } } ================================================ FILE: picasso/MediaStoreRequestHandler.java ================================================ /* * Copyright (C) 2014 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.provider.MediaStore; import java.io.IOException; import static android.content.ContentResolver.SCHEME_CONTENT; import static android.content.ContentUris.parseId; import static android.provider.MediaStore.Images; import static android.provider.MediaStore.Video; import static android.provider.MediaStore.Images.Thumbnails.FULL_SCREEN_KIND; import static android.provider.MediaStore.Images.Thumbnails.MICRO_KIND; import static android.provider.MediaStore.Images.Thumbnails.MINI_KIND; import static com.squareup.picasso.MediaStoreRequestHandler.PicassoKind.FULL; import static com.squareup.picasso.MediaStoreRequestHandler.PicassoKind.MICRO; import static com.squareup.picasso.MediaStoreRequestHandler.PicassoKind.MINI; import static com.squareup.picasso.Picasso.LoadedFrom.DISK; class MediaStoreRequestHandler extends ContentStreamRequestHandler { private static final String[] CONTENT_ORIENTATION = new String[] { Images.ImageColumns.ORIENTATION }; MediaStoreRequestHandler(Context context) { super(context); } @Override public boolean canHandleRequest(Request data) { final Uri uri = data.uri; return (SCHEME_CONTENT.equals(uri.getScheme()) && MediaStore.AUTHORITY.equals(uri.getAuthority())); } @Override public Result load(Request data) throws IOException { ContentResolver contentResolver = context.getContentResolver(); int exifOrientation = getExifOrientation(contentResolver, data.uri); String mimeType = contentResolver.getType(data.uri); boolean isVideo = mimeType != null && mimeType.startsWith("video/"); if (data.hasSize()) { PicassoKind picassoKind = getPicassoKind(data.targetWidth, data.targetHeight); if (!isVideo && picassoKind == FULL) { return new Result(decodeContentStream(data), DISK, exifOrientation); } long id = parseId(data.uri); BitmapFactory.Options options = createBitmapOptions(data); options.inJustDecodeBounds = true; calculateInSampleSize(data.targetWidth, data.targetHeight, picassoKind.width, picassoKind.height, options); Bitmap bitmap; if (isVideo) { // Since MediaStore doesn't provide the full screen kind thumbnail, we use the mini kind // instead which is the largest thumbnail size can be fetched from MediaStore. int kind = (picassoKind == FULL) ? Video.Thumbnails.MINI_KIND : picassoKind.androidKind; bitmap = Video.Thumbnails.getThumbnail(contentResolver, id, kind, options); } else { bitmap = Images.Thumbnails.getThumbnail(contentResolver, id, picassoKind.androidKind, options); } if (bitmap != null) { return new Result(bitmap, DISK, exifOrientation); } } return new Result(decodeContentStream(data), DISK, exifOrientation); } static PicassoKind getPicassoKind(int targetWidth, int targetHeight) { if (targetWidth <= MICRO.width && targetHeight <= MICRO.height) { return MICRO; } else if (targetWidth <= MINI.width && targetHeight <= MINI.height) { return MINI; } return FULL; } static int getExifOrientation(ContentResolver contentResolver, Uri uri) { Cursor cursor = null; try { cursor = contentResolver.query(uri, CONTENT_ORIENTATION, null, null, null); if (cursor == null || !cursor.moveToFirst()) { return 0; } return cursor.getInt(0); } catch (RuntimeException ignored) { // If the orientation column doesn't exist, assume no rotation. return 0; } finally { if (cursor != null) { cursor.close(); } } } enum PicassoKind { MICRO(MICRO_KIND, 96, 96), MINI(MINI_KIND, 512, 384), FULL(FULL_SCREEN_KIND, -1, -1); final int androidKind; final int width; final int height; PicassoKind(int androidKind, int width, int height) { this.androidKind = androidKind; this.width = width; this.height = height; } } } ================================================ FILE: picasso/NetworkRequestHandler.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.NetworkInfo; import java.io.IOException; import java.io.InputStream; import static com.squareup.picasso.Downloader.Response; import static com.squareup.picasso.Picasso.LoadedFrom.DISK; import static com.squareup.picasso.Picasso.LoadedFrom.NETWORK; class NetworkRequestHandler extends RequestHandler { static final int RETRY_COUNT = 2; private static final int MARKER = 65536; private static final String SCHEME_HTTP = "http"; private static final String SCHEME_HTTPS = "https"; private final Downloader downloader; private final Stats stats; public NetworkRequestHandler(Downloader downloader, Stats stats) { this.downloader = downloader; this.stats = stats; } @Override public boolean canHandleRequest(Request data) { String scheme = data.uri.getScheme(); return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme)); } @Override public Result load(Request data) throws IOException { Response response = downloader.load(data.uri, data.loadFromLocalCacheOnly); if (response == null) { return null; } Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK; Bitmap bitmap = response.getBitmap(); if (bitmap != null) { return new Result(bitmap, loadedFrom); } InputStream is = response.getInputStream(); if (is == null) { return null; } // Sometimes response content length is zero when requests are being replayed. Haven't found // root cause to this but retrying the request seems safe to do so. if (response.getContentLength() == 0) { Utils.closeQuietly(is); throw new IOException("Received response with 0 content-length header."); } if (loadedFrom == NETWORK && response.getContentLength() > 0) { stats.dispatchDownloadFinished(response.getContentLength()); } try { return new Result(decodeStream(is, data), loadedFrom); } finally { Utils.closeQuietly(is); } } @Override int getRetryCount() { return RETRY_COUNT; } @Override boolean shouldRetry(boolean airplaneMode, NetworkInfo info) { return info == null || info.isConnected(); } @Override boolean supportsReplay() { return true; } private Bitmap decodeStream(InputStream stream, Request data) throws IOException { MarkableInputStream markStream = new MarkableInputStream(stream); stream = markStream; long mark = markStream.savePosition(MARKER); final BitmapFactory.Options options = createBitmapOptions(data); final boolean calculateSize = requiresInSampleSize(options); boolean isWebPFile = Utils.isWebPFile(stream); markStream.reset(mark); // When decode WebP network stream, BitmapFactory throw JNI Exception and make app crash. // Decode byte array instead if (isWebPFile) { byte[] bytes = Utils.toByteArray(stream); if (calculateSize) { BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); calculateInSampleSize(data.targetWidth, data.targetHeight, options); } return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); } else { if (calculateSize) { BitmapFactory.decodeStream(stream, null, options); calculateInSampleSize(data.targetWidth, data.targetHeight, options); markStream.reset(mark); } Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options); if (bitmap == null) { // Treat null as an IO exception, we will eventually retry. throw new IOException("Failed to decode stream."); } return bitmap; } } } ================================================ FILE: picasso/Picasso.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Color; import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Process; import android.widget.ImageView; import android.widget.RemoteViews; import java.io.File; import java.lang.ref.ReferenceQueue; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.ExecutorService; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static com.squareup.picasso.Action.RequestWeakReference; import static com.squareup.picasso.Dispatcher.HUNTER_BATCH_COMPLETE; import static com.squareup.picasso.Dispatcher.REQUEST_GCED; import static com.squareup.picasso.Utils.OWNER_MAIN; import static com.squareup.picasso.Utils.THREAD_PREFIX; import static com.squareup.picasso.Utils.VERB_COMPLETED; import static com.squareup.picasso.Utils.VERB_ERRORED; import static com.squareup.picasso.Utils.checkMain; import static com.squareup.picasso.Utils.log; /** * Image downloading, transformation, and caching manager. *

* Use {@link #with(android.content.Context)} for the global singleton instance or construct your * own instance with {@link com.squareup.picasso.Picasso.Builder}. */ public class Picasso { /** Callbacks for Picasso events. */ public interface Listener { /** * Invoked when an image has failed to load. This is useful for reporting image failures to a * remote analytics service, for example. */ void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception); } /** * A transformer that is called immediately before every request is submitted. This can be used to * modify any information about a request. *

* For example, if you use a CDN you can change the hostname for the image based on the current * location of the user in order to get faster download speeds. *

* NOTE: This is a beta feature. The API is subject to change in a backwards incompatible * way at any time. */ public interface RequestTransformer { /** * Transform a request before it is submitted to be processed. * * @return The original request or a new request to replace it. Must not be null. */ Request transformRequest(Request request); /** A {@link com.squareup.picasso.Picasso.RequestTransformer} which returns the original request. */ RequestTransformer IDENTITY = new RequestTransformer() { @Override public Request transformRequest(Request request) { return request; } }; } static final String TAG = "Picasso"; static final Handler HANDLER = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case HUNTER_BATCH_COMPLETE: { @SuppressWarnings("unchecked") List batch = (List) msg.obj; //noinspection ForLoopReplaceableByForEach for (int i = 0, n = batch.size(); i < n; i++) { BitmapHunter hunter = batch.get(i); hunter.picasso.complete(hunter); } break; } case REQUEST_GCED: { Action action = (Action) msg.obj; action.picasso.cancelExistingRequest(action.getTarget()); break; } default: throw new AssertionError("Unknown handler message received: " + msg.what); } } }; static Picasso singleton = null; private final Listener listener; private final RequestTransformer requestTransformer; private final CleanupThread cleanupThread; private final List requestHandlers; final Context context; final Dispatcher dispatcher; final Cache cache; final Stats stats; final Map targetToAction; final Map targetToDeferredRequestCreator; final ReferenceQueue referenceQueue; boolean indicatorsEnabled; volatile boolean loggingEnabled; boolean shutdown; Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener, RequestTransformer requestTransformer, List extraRequestHandlers, Stats stats, boolean indicatorsEnabled, boolean loggingEnabled) { this.context = context; this.dispatcher = dispatcher; this.cache = cache; this.listener = listener; this.requestTransformer = requestTransformer; final int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0); final List allRequestHandlers = new ArrayList(7 + extraCount); // ResourceRequestHandler needs to be the first in the list to avoid // forcing other RequestHandlers to perform null checks on request.uri // to cover the (request.resourceId != 0) case. allRequestHandlers.add(new ResourceRequestHandler(context)); if (extraRequestHandlers != null) { allRequestHandlers.addAll(extraRequestHandlers); } allRequestHandlers.add(new ContactsPhotoRequestHandler(context)); allRequestHandlers.add(new MediaStoreRequestHandler(context)); allRequestHandlers.add(new ContentStreamRequestHandler(context)); allRequestHandlers.add(new AssetRequestHandler(context)); allRequestHandlers.add(new FileRequestHandler(context)); allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats)); requestHandlers = Collections.unmodifiableList(allRequestHandlers); this.stats = stats; this.targetToAction = new WeakHashMap(); this.targetToDeferredRequestCreator = new WeakHashMap(); this.indicatorsEnabled = indicatorsEnabled; this.loggingEnabled = loggingEnabled; this.referenceQueue = new ReferenceQueue(); this.cleanupThread = new CleanupThread(referenceQueue, HANDLER); this.cleanupThread.start(); } /** Cancel any existing requests for the specified target {@link android.widget.ImageView}. */ public void cancelRequest(ImageView view) { cancelExistingRequest(view); } /** Cancel any existing requests for the specified {@link com.squareup.picasso.Target} instance. */ public void cancelRequest(Target target) { cancelExistingRequest(target); } /** * Cancel any existing requests for the specified {@link android.widget.RemoteViews} target with the given {@code * viewId}. */ public void cancelRequest(RemoteViews remoteViews, int viewId) { cancelExistingRequest(new RemoteViewsAction.RemoteViewsTarget(remoteViews, viewId)); } /** * Start an image request using the specified URI. *

* Passing {@code null} as a {@code uri} will not trigger any request but will set a placeholder, * if one is specified. * * @see #load(java.io.File) * @see #load(String) * @see #load(int) */ public RequestCreator load(Uri uri) { return new RequestCreator(this, uri, 0); } /** * Start an image request using the specified path. This is a convenience method for calling * {@link #load(android.net.Uri)}. *

* This path may be a remote URL, file resource (prefixed with {@code file:}), content resource * (prefixed with {@code content:}), or android resource (prefixed with {@code * android.resource:}. *

* Passing {@code null} as a {@code path} will not trigger any request but will set a * placeholder, if one is specified. * * @see #load(android.net.Uri) * @see #load(java.io.File) * @see #load(int) * @throws IllegalArgumentException if {@code path} is empty or blank string. */ public RequestCreator load(String path) { if (path == null) { return new RequestCreator(this, null, 0); } if (path.trim().length() == 0) { throw new IllegalArgumentException("Path must not be empty."); } return load(Uri.parse(path)); } /** * Start an image request using the specified image file. This is a convenience method for * calling {@link #load(android.net.Uri)}. *

* Passing {@code null} as a {@code file} will not trigger any request but will set a * placeholder, if one is specified. *

* Equivalent to calling {@link #load(android.net.Uri) load(Uri.fromFile(file))}. * * @see #load(android.net.Uri) * @see #load(String) * @see #load(int) */ public RequestCreator load(File file) { if (file == null) { return new RequestCreator(this, null, 0); } return load(Uri.fromFile(file)); } /** * Start an image request using the specified drawable resource ID. * * @see #load(android.net.Uri) * @see #load(String) * @see #load(java.io.File) */ public RequestCreator load(int resourceId) { if (resourceId == 0) { throw new IllegalArgumentException("Resource ID must not be zero."); } return new RequestCreator(this, null, resourceId); } /** * {@code true} if debug display, logging, and statistics are enabled. *

* @deprecated Use {@link #areIndicatorsEnabled()} and {@link #isLoggingEnabled()} instead. */ @SuppressWarnings("UnusedDeclaration") @Deprecated public boolean isDebugging() { return areIndicatorsEnabled() && isLoggingEnabled(); } /** * Toggle whether debug display, logging, and statistics are enabled. *

* @deprecated Use {@link #setIndicatorsEnabled(boolean)} and {@link #setLoggingEnabled(boolean)} * instead. */ @SuppressWarnings("UnusedDeclaration") @Deprecated public void setDebugging(boolean debugging) { setIndicatorsEnabled(debugging); } /** Toggle whether to display debug indicators on images. */ @SuppressWarnings("UnusedDeclaration") public void setIndicatorsEnabled(boolean enabled) { indicatorsEnabled = enabled; } /** {@code true} if debug indicators should are displayed on images. */ @SuppressWarnings("UnusedDeclaration") public boolean areIndicatorsEnabled() { return indicatorsEnabled; } /** * Toggle whether debug logging is enabled. *

* WARNING: Enabling this will result in excessive object allocation. This should be only * be used for debugging Picasso behavior. Do NOT pass {@code BuildConfig.DEBUG}. */ public void setLoggingEnabled(boolean enabled) { loggingEnabled = enabled; } /** {@code true} if debug logging is enabled. */ public boolean isLoggingEnabled() { return loggingEnabled; } /** * Creates a {@link com.squareup.picasso.StatsSnapshot} of the current stats for this instance. *

* NOTE: The snapshot may not always be completely up-to-date if requests are still in * progress. */ @SuppressWarnings("UnusedDeclaration") public StatsSnapshot getSnapshot() { return stats.createSnapshot(); } /** Stops this instance from accepting further requests. */ public void shutdown() { if (this == singleton) { throw new UnsupportedOperationException("Default singleton instance cannot be shutdown."); } if (shutdown) { return; } cache.clear(); cleanupThread.shutdown(); stats.shutdown(); dispatcher.shutdown(); for (DeferredRequestCreator deferredRequestCreator : targetToDeferredRequestCreator.values()) { deferredRequestCreator.cancel(); } targetToDeferredRequestCreator.clear(); shutdown = true; } List getRequestHandlers() { return requestHandlers; } Request transformRequest(Request request) { Request transformed = requestTransformer.transformRequest(request); if (transformed == null) { throw new IllegalStateException("Request transformer " + requestTransformer.getClass().getCanonicalName() + " returned null for " + request); } return transformed; } void defer(ImageView view, DeferredRequestCreator request) { targetToDeferredRequestCreator.put(view, request); } void enqueueAndSubmit(Action action) { Object target = action.getTarget(); if (target != null) { // This will also check we are on the main thread. cancelExistingRequest(target); targetToAction.put(target, action); } submit(action); } void submit(Action action) { dispatcher.dispatchSubmit(action); } Bitmap quickMemoryCacheCheck(String key) { Bitmap cached = cache.get(key); if (cached != null) { stats.dispatchCacheHit(); } else { stats.dispatchCacheMiss(); } return cached; } void complete(BitmapHunter hunter) { Action single = hunter.getAction(); List joined = hunter.getActions(); boolean hasMultiple = joined != null && !joined.isEmpty(); boolean shouldDeliver = single != null || hasMultiple; if (!shouldDeliver) { return; } Uri uri = hunter.getData().uri; Exception exception = hunter.getException(); Bitmap result = hunter.getResult(); LoadedFrom from = hunter.getLoadedFrom(); if (single != null) { deliverAction(result, from, single); } if (hasMultiple) { //noinspection ForLoopReplaceableByForEach for (int i = 0, n = joined.size(); i < n; i++) { Action join = joined.get(i); deliverAction(result, from, join); } } if (listener != null && exception != null) { listener.onImageLoadFailed(this, uri, exception); } } private void deliverAction(Bitmap result, LoadedFrom from, Action action) { if (action.isCancelled()) { return; } if (!action.willReplay()) { targetToAction.remove(action.getTarget()); } if (result != null) { if (from == null) { throw new AssertionError("LoadedFrom cannot be null."); } action.complete(result, from); if (loggingEnabled) { log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + from); } } else { action.error(); if (loggingEnabled) { log(OWNER_MAIN, VERB_ERRORED, action.request.logId()); } } } private void cancelExistingRequest(Object target) { checkMain(); Action action = targetToAction.remove(target); if (action != null) { action.cancel(); dispatcher.dispatchCancel(action); } if (target instanceof ImageView) { ImageView targetImageView = (ImageView) target; DeferredRequestCreator deferredRequestCreator = targetToDeferredRequestCreator.remove(targetImageView); if (deferredRequestCreator != null) { deferredRequestCreator.cancel(); } } } private static class CleanupThread extends Thread { private final ReferenceQueue referenceQueue; private final Handler handler; CleanupThread(ReferenceQueue referenceQueue, Handler handler) { this.referenceQueue = referenceQueue; this.handler = handler; setDaemon(true); setName(THREAD_PREFIX + "refQueue"); } @Override public void run() { Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND); while (true) { try { RequestWeakReference remove = (RequestWeakReference) referenceQueue.remove(); handler.sendMessage(handler.obtainMessage(REQUEST_GCED, remove.action)); } catch (InterruptedException e) { break; } catch (final Exception e) { handler.post(new Runnable() { @Override public void run() { throw new RuntimeException(e); } }); break; } } } void shutdown() { interrupt(); } } /** * The global default {@link com.squareup.picasso.Picasso} instance. *

* This instance is automatically initialized with defaults that are suitable to most * implementations. *

    *
  • LRU memory cache of 15% the available application RAM
  • *
  • Disk cache of 2% storage space up to 50MB but no less than 5MB. (Note: this is only * available on API 14+ or if you are using a standalone library that provides a disk * cache on all API levels like OkHttp)
  • *
  • Three download threads for disk and network access.
  • *
*

* If these settings do not meet the requirements of your application you can construct your own * instance with full control over the configuration by using {@link com.squareup.picasso.Picasso.Builder}. */ public static Picasso with(Context context) { if (singleton == null) { synchronized (Picasso.class) { if (singleton == null) { singleton = new Builder(context).build(); } } } return singleton; } /** Fluent API for creating {@link com.squareup.picasso.Picasso} instances. */ @SuppressWarnings("UnusedDeclaration") // Public API. public static class Builder { private final Context context; private Downloader downloader; private ExecutorService service; private Cache cache; private Listener listener; private RequestTransformer transformer; private List requestHandlers; private boolean indicatorsEnabled; private boolean loggingEnabled; /** Start building a new {@link com.squareup.picasso.Picasso} instance. */ public Builder(Context context) { if (context == null) { throw new IllegalArgumentException("Context must not be null."); } this.context = context.getApplicationContext(); } /** Specify the {@link com.squareup.picasso.Downloader} that will be used for downloading images. */ public Builder downloader(Downloader downloader) { if (downloader == null) { throw new IllegalArgumentException("Downloader must not be null."); } if (this.downloader != null) { throw new IllegalStateException("Downloader already set."); } this.downloader = downloader; return this; } /** Specify the executor service for loading images in the background. */ public Builder executor(ExecutorService executorService) { if (executorService == null) { throw new IllegalArgumentException("Executor service must not be null."); } if (this.service != null) { throw new IllegalStateException("Executor service already set."); } this.service = executorService; return this; } /** Specify the memory cache used for the most recent images. */ public Builder memoryCache(Cache memoryCache) { if (memoryCache == null) { throw new IllegalArgumentException("Memory cache must not be null."); } if (this.cache != null) { throw new IllegalStateException("Memory cache already set."); } this.cache = memoryCache; return this; } /** Specify a listener for interesting events. */ public Builder listener(Listener listener) { if (listener == null) { throw new IllegalArgumentException("Listener must not be null."); } if (this.listener != null) { throw new IllegalStateException("Listener already set."); } this.listener = listener; return this; } /** * Specify a transformer for all incoming requests. *

* NOTE: This is a beta feature. The API is subject to change in a backwards incompatible * way at any time. */ public Builder requestTransformer(RequestTransformer transformer) { if (transformer == null) { throw new IllegalArgumentException("Transformer must not be null."); } if (this.transformer != null) { throw new IllegalStateException("Transformer already set."); } this.transformer = transformer; return this; } /** Register a {@link RequestHandler}. */ public Builder addRequestHandler(RequestHandler requestHandler) { if (requestHandler == null) { throw new IllegalArgumentException("RequestHandler must not be null."); } if (requestHandlers == null) { requestHandlers = new ArrayList(); } if (requestHandlers.contains(requestHandler)) { throw new IllegalStateException("RequestHandler already registered."); } requestHandlers.add(requestHandler); return this; } /** * @deprecated Use {@link #indicatorsEnabled(boolean)} instead. * Whether debugging is enabled or not. */ @Deprecated public Builder debugging(boolean debugging) { return indicatorsEnabled(debugging); } /** Toggle whether to display debug indicators on images. */ public Builder indicatorsEnabled(boolean enabled) { this.indicatorsEnabled = enabled; return this; } /** * Toggle whether debug logging is enabled. *

* WARNING: Enabling this will result in excessive object allocation. This should be only * be used for debugging purposes. Do NOT pass {@code BuildConfig.DEBUG}. */ public Builder loggingEnabled(boolean enabled) { this.loggingEnabled = enabled; return this; } /** Create the {@link com.squareup.picasso.Picasso} instance. */ public Picasso build() { Context context = this.context; if (downloader == null) { downloader = Utils.createDefaultDownloader(context); } if (cache == null) { cache = new LruCache(context); } if (service == null) { service = new PicassoExecutorService(); } if (transformer == null) { transformer = RequestTransformer.IDENTITY; } Stats stats = new Stats(cache); Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats); return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats, indicatorsEnabled, loggingEnabled); } } /** Describes where the image was loaded from. */ public enum LoadedFrom { MEMORY(Color.GREEN), DISK(Color.YELLOW), NETWORK(Color.RED); final int debugColor; private LoadedFrom(int debugColor) { this.debugColor = debugColor; } } } ================================================ FILE: picasso/PicassoDrawable.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.SystemClock; import android.widget.ImageView; import static android.graphics.Color.WHITE; import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY; final class PicassoDrawable extends BitmapDrawable { // Only accessed from main thread. private static final Paint DEBUG_PAINT = new Paint(); private static final float FADE_DURATION = 200f; //ms /** * Create or update the drawable on the target {@link android.widget.ImageView} to display the supplied bitmap * image. */ static void setBitmap(ImageView target, Context context, Bitmap bitmap, Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) { Drawable placeholder = target.getDrawable(); if (placeholder instanceof AnimationDrawable) { ((AnimationDrawable) placeholder).stop(); } PicassoDrawable drawable = new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging); target.setImageDrawable(drawable); } /** * Create or update the drawable on the target {@link android.widget.ImageView} to display the supplied * placeholder image. */ static void setPlaceholder(ImageView target, int placeholderResId, Drawable placeholderDrawable) { if (placeholderResId != 0) { target.setImageResource(placeholderResId); } else { target.setImageDrawable(placeholderDrawable); } if (target.getDrawable() instanceof AnimationDrawable) { ((AnimationDrawable) target.getDrawable()).start(); } } private final boolean debugging; private final float density; private final Picasso.LoadedFrom loadedFrom; Drawable placeholder; long startTimeMillis; boolean animating; int alpha = 0xFF; PicassoDrawable(Context context, Bitmap bitmap, Drawable placeholder, Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) { super(context.getResources(), bitmap); this.debugging = debugging; this.density = context.getResources().getDisplayMetrics().density; this.loadedFrom = loadedFrom; boolean fade = loadedFrom != MEMORY && !noFade; if (fade) { this.placeholder = placeholder; animating = true; startTimeMillis = SystemClock.uptimeMillis(); } } @Override public void draw(Canvas canvas) { if (!animating) { super.draw(canvas); } else { float normalized = (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION; if (normalized >= 1f) { animating = false; placeholder = null; super.draw(canvas); } else { if (placeholder != null) { placeholder.draw(canvas); } int partialAlpha = (int) (alpha * normalized); super.setAlpha(partialAlpha); super.draw(canvas); super.setAlpha(alpha); if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { invalidateSelf(); } } } if (debugging) { drawDebugIndicator(canvas); } } @Override public void setAlpha(int alpha) { this.alpha = alpha; if (placeholder != null) { placeholder.setAlpha(alpha); } super.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { if (placeholder != null) { placeholder.setColorFilter(cf); } super.setColorFilter(cf); } @Override protected void onBoundsChange(Rect bounds) { if (placeholder != null) { placeholder.setBounds(bounds); } super.onBoundsChange(bounds); } private void drawDebugIndicator(Canvas canvas) { DEBUG_PAINT.setColor(WHITE); Path path = getTrianglePath(new Point(0, 0), (int) (16 * density)); canvas.drawPath(path, DEBUG_PAINT); DEBUG_PAINT.setColor(loadedFrom.debugColor); path = getTrianglePath(new Point(0, 0), (int) (15 * density)); canvas.drawPath(path, DEBUG_PAINT); } private static Path getTrianglePath(Point p1, int width) { Point p2 = new Point(p1.x + width, p1.y); Point p3 = new Point(p1.x, p1.y + width); Path path = new Path(); path.moveTo(p1.x, p1.y); path.lineTo(p2.x, p2.y); path.lineTo(p3.x, p3.y); return path; } } ================================================ FILE: picasso/PicassoExecutorService.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.telephony.TelephonyManager; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * The default {@link java.util.concurrent.ExecutorService} used for new {@link com.squareup.picasso.Picasso} instances. *

* Exists as a custom type so that we can differentiate the use of defaults versus a user-supplied * instance. */ class PicassoExecutorService extends ThreadPoolExecutor { private static final int DEFAULT_THREAD_COUNT = 3; PicassoExecutorService() { super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new Utils.PicassoThreadFactory()); } void adjustThreadCount(NetworkInfo info) { if (info == null || !info.isConnectedOrConnecting()) { setThreadCount(DEFAULT_THREAD_COUNT); return; } switch (info.getType()) { case ConnectivityManager.TYPE_WIFI: case ConnectivityManager.TYPE_WIMAX: case ConnectivityManager.TYPE_ETHERNET: setThreadCount(4); break; case ConnectivityManager.TYPE_MOBILE: switch (info.getSubtype()) { case TelephonyManager.NETWORK_TYPE_LTE: // 4G case TelephonyManager.NETWORK_TYPE_HSPAP: case TelephonyManager.NETWORK_TYPE_EHRPD: setThreadCount(3); break; case TelephonyManager.NETWORK_TYPE_UMTS: // 3G case TelephonyManager.NETWORK_TYPE_CDMA: case TelephonyManager.NETWORK_TYPE_EVDO_0: case TelephonyManager.NETWORK_TYPE_EVDO_A: case TelephonyManager.NETWORK_TYPE_EVDO_B: setThreadCount(2); break; case TelephonyManager.NETWORK_TYPE_GPRS: // 2G case TelephonyManager.NETWORK_TYPE_EDGE: setThreadCount(1); break; default: setThreadCount(DEFAULT_THREAD_COUNT); } break; default: setThreadCount(DEFAULT_THREAD_COUNT); } } private void setThreadCount(int threadCount) { setCorePoolSize(threadCount); setMaximumPoolSize(threadCount); } } ================================================ FILE: picasso/RemoteViewsAction.java ================================================ /* * Copyright (C) 2014 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.app.Notification; import android.app.NotificationManager; import android.appwidget.AppWidgetManager; import android.graphics.Bitmap; import android.widget.RemoteViews; import static android.content.Context.NOTIFICATION_SERVICE; import static com.squareup.picasso.Utils.getService; abstract class RemoteViewsAction extends Action { final RemoteViews remoteViews; final int viewId; RemoteViewsAction(Picasso picasso, Request data, RemoteViews remoteViews, int viewId, int errorResId, boolean skipCache, String key) { super(picasso, new RemoteViewsTarget(remoteViews, viewId), data, skipCache, false, errorResId, null, key); this.remoteViews = remoteViews; this.viewId = viewId; } @Override void complete(Bitmap result, Picasso.LoadedFrom from) { remoteViews.setImageViewBitmap(viewId, result); update(); } @Override public void error() { if (errorResId != 0) { setImageResource(errorResId); } } void setImageResource(int resId) { remoteViews.setImageViewResource(viewId, resId); update(); } abstract void update(); static class RemoteViewsTarget { final RemoteViews remoteViews; final int viewId; RemoteViewsTarget(RemoteViews remoteViews, int viewId) { this.remoteViews = remoteViews; this.viewId = viewId; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; RemoteViewsTarget remoteViewsTarget = (RemoteViewsTarget) o; return viewId == remoteViewsTarget.viewId && remoteViews.equals( remoteViewsTarget.remoteViews); } @Override public int hashCode() { return 31 * remoteViews.hashCode() + viewId; } } static class AppWidgetAction extends RemoteViewsAction { private final int[] appWidgetIds; AppWidgetAction(Picasso picasso, Request data, RemoteViews remoteViews, int viewId, int[] appWidgetIds, boolean skipCache, int errorResId, String key) { super(picasso, data, remoteViews, viewId, errorResId, skipCache, key); this.appWidgetIds = appWidgetIds; } @Override void update() { AppWidgetManager manager = AppWidgetManager.getInstance(picasso.context); manager.updateAppWidget(appWidgetIds, remoteViews); } } static class NotificationAction extends RemoteViewsAction { private final int notificationId; private final Notification notification; NotificationAction(Picasso picasso, Request data, RemoteViews remoteViews, int viewId, int notificationId, Notification notification, boolean skipCache, int errorResId, String key) { super(picasso, data, remoteViews, viewId, errorResId, skipCache, key); this.notificationId = notificationId; this.notification = notification; } @Override void update() { NotificationManager manager = getService(picasso.context, NOTIFICATION_SERVICE); manager.notify(notificationId, notification); } } } ================================================ FILE: picasso/Request.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.graphics.Bitmap; import android.net.Uri; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import static java.util.Collections.unmodifiableList; /** Immutable data about an image and the transformations that will be applied to it. */ public final class Request { private static final long TOO_LONG_LOG = TimeUnit.SECONDS.toNanos(5); /** A unique ID for the request. */ int id; /** The time that the request was first submitted (in nanos). */ long started; /** Whether or not this request should only load from local cache. */ boolean loadFromLocalCacheOnly; /** * The image URI. *

* This is mutually exclusive with {@link #resourceId}. */ public final Uri uri; /** * The image resource ID. *

* This is mutually exclusive with {@link #uri}. */ public final int resourceId; /** List of custom transformations to be applied after the built-in transformations. */ public final List transformations; /** Target image width for resizing. */ public final int targetWidth; /** Target image height for resizing. */ public final int targetHeight; /** * True if the final image should use the 'centerCrop' scale technique. *

* This is mutually exclusive with {@link #centerInside}. */ public final boolean centerCrop; /** * True if the final image should use the 'centerInside' scale technique. *

* This is mutually exclusive with {@link #centerCrop}. */ public final boolean centerInside; /** Amount to rotate the image in degrees. */ public final float rotationDegrees; /** Rotation pivot on the X axis. */ public final float rotationPivotX; /** Rotation pivot on the Y axis. */ public final float rotationPivotY; /** Whether or not {@link #rotationPivotX} and {@link #rotationPivotY} are set. */ public final boolean hasRotationPivot; /** Target image config for decoding. */ public final Bitmap.Config config; private Request(Uri uri, int resourceId, List transformations, int targetWidth, int targetHeight, boolean centerCrop, boolean centerInside, float rotationDegrees, float rotationPivotX, float rotationPivotY, boolean hasRotationPivot, Bitmap.Config config) { this.uri = uri; this.resourceId = resourceId; if (transformations == null) { this.transformations = null; } else { this.transformations = unmodifiableList(transformations); } this.targetWidth = targetWidth; this.targetHeight = targetHeight; this.centerCrop = centerCrop; this.centerInside = centerInside; this.rotationDegrees = rotationDegrees; this.rotationPivotX = rotationPivotX; this.rotationPivotY = rotationPivotY; this.hasRotationPivot = hasRotationPivot; this.config = config; } @Override public String toString() { final StringBuilder sb = new StringBuilder("Request{"); if (resourceId > 0) { sb.append(resourceId); } else { sb.append(uri); } if (transformations != null && !transformations.isEmpty()) { for (Transformation transformation : transformations) { sb.append(' ').append(transformation.key()); } } if (targetWidth > 0) { sb.append(" resize(").append(targetWidth).append(',').append(targetHeight).append(')'); } if (centerCrop) { sb.append(" centerCrop"); } if (centerInside) { sb.append(" centerInside"); } if (rotationDegrees != 0) { sb.append(" rotation(").append(rotationDegrees); if (hasRotationPivot) { sb.append(" @ ").append(rotationPivotX).append(',').append(rotationPivotY); } sb.append(')'); } if (config != null) { sb.append(' ').append(config); } sb.append('}'); return sb.toString(); } String logId() { long delta = System.nanoTime() - started; if (delta > TOO_LONG_LOG) { return plainId() + '+' + TimeUnit.NANOSECONDS.toSeconds(delta) + 's'; } return plainId() + '+' + TimeUnit.NANOSECONDS.toMillis(delta) + "ms"; } String plainId() { return "[R" + id + ']'; } String getName() { if (uri != null) { return String.valueOf(uri.getPath()); } return Integer.toHexString(resourceId); } public boolean hasSize() { return targetWidth != 0; } boolean needsTransformation() { return needsMatrixTransform() || hasCustomTransformations(); } boolean needsMatrixTransform() { return targetWidth != 0 || rotationDegrees != 0; } boolean hasCustomTransformations() { return transformations != null; } public Builder buildUpon() { return new Builder(this); } /** Builder for creating {@link com.squareup.picasso.Request} instances. */ public static final class Builder { private Uri uri; private int resourceId; private int targetWidth; private int targetHeight; private boolean centerCrop; private boolean centerInside; private float rotationDegrees; private float rotationPivotX; private float rotationPivotY; private boolean hasRotationPivot; private List transformations; private Bitmap.Config config; /** Start building a request using the specified {@link android.net.Uri}. */ public Builder(Uri uri) { setUri(uri); } /** Start building a request using the specified resource ID. */ public Builder(int resourceId) { setResourceId(resourceId); } Builder(Uri uri, int resourceId) { this.uri = uri; this.resourceId = resourceId; } private Builder(Request request) { uri = request.uri; resourceId = request.resourceId; targetWidth = request.targetWidth; targetHeight = request.targetHeight; centerCrop = request.centerCrop; centerInside = request.centerInside; rotationDegrees = request.rotationDegrees; rotationPivotX = request.rotationPivotX; rotationPivotY = request.rotationPivotY; hasRotationPivot = request.hasRotationPivot; if (request.transformations != null) { transformations = new ArrayList(request.transformations); } config = request.config; } boolean hasImage() { return uri != null || resourceId != 0; } boolean hasSize() { return targetWidth != 0; } /** * Set the target image Uri. *

* This will clear an image resource ID if one is set. */ public Builder setUri(Uri uri) { if (uri == null) { throw new IllegalArgumentException("Image URI may not be null."); } this.uri = uri; this.resourceId = 0; return this; } /** * Set the target image resource ID. *

* This will clear an image Uri if one is set. */ public Builder setResourceId(int resourceId) { if (resourceId == 0) { throw new IllegalArgumentException("Image resource ID may not be 0."); } this.resourceId = resourceId; this.uri = null; return this; } /** Resize the image to the specified size in pixels. */ public Builder resize(int targetWidth, int targetHeight) { if (targetWidth <= 0) { throw new IllegalArgumentException("Width must be positive number."); } if (targetHeight <= 0) { throw new IllegalArgumentException("Height must be positive number."); } this.targetWidth = targetWidth; this.targetHeight = targetHeight; return this; } /** Clear the resize transformation, if any. This will also clear center crop/inside if set. */ public Builder clearResize() { targetWidth = 0; targetHeight = 0; centerCrop = false; centerInside = false; return this; } /** * Crops an image inside of the bounds specified by {@link #resize(int, int)} rather than * distorting the aspect ratio. This cropping technique scales the image so that it fills the * requested bounds and then crops the extra. */ public Builder centerCrop() { if (centerInside) { throw new IllegalStateException("Center crop can not be used after calling centerInside"); } centerCrop = true; return this; } /** Clear the center crop transformation flag, if set. */ public Builder clearCenterCrop() { centerCrop = false; return this; } /** * Centers an image inside of the bounds specified by {@link #resize(int, int)}. This scales * the image so that both dimensions are equal to or less than the requested bounds. */ public Builder centerInside() { if (centerCrop) { throw new IllegalStateException("Center inside can not be used after calling centerCrop"); } centerInside = true; return this; } /** Clear the center inside transformation flag, if set. */ public Builder clearCenterInside() { centerInside = false; return this; } /** Rotate the image by the specified degrees. */ public Builder rotate(float degrees) { rotationDegrees = degrees; return this; } /** Rotate the image by the specified degrees around a pivot point. */ public Builder rotate(float degrees, float pivotX, float pivotY) { rotationDegrees = degrees; rotationPivotX = pivotX; rotationPivotY = pivotY; hasRotationPivot = true; return this; } /** Clear the rotation transformation, if any. */ public Builder clearRotation() { rotationDegrees = 0; rotationPivotX = 0; rotationPivotY = 0; hasRotationPivot = false; return this; } /** Decode the image using the specified config. */ public Builder config(Bitmap.Config config) { this.config = config; return this; } /** * Add a custom transformation to be applied to the image. *

* Custom transformations will always be run after the built-in transformations. */ public Builder transform(Transformation transformation) { if (transformation == null) { throw new IllegalArgumentException("Transformation must not be null."); } if (transformations == null) { transformations = new ArrayList(2); } transformations.add(transformation); return this; } /** Create the immutable {@link com.squareup.picasso.Request} object. */ public Request build() { if (centerInside && centerCrop) { throw new IllegalStateException("Center crop and center inside can not be used together."); } if (centerCrop && targetWidth == 0) { throw new IllegalStateException("Center crop requires calling resize."); } if (centerInside && targetWidth == 0) { throw new IllegalStateException("Center inside requires calling resize."); } return new Request(uri, resourceId, transformations, targetWidth, targetHeight, centerCrop, centerInside, rotationDegrees, rotationPivotX, rotationPivotY, hasRotationPivot, config); } } } ================================================ FILE: picasso/RequestCreator.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.app.Notification; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.net.Uri; import android.widget.ImageView; import android.widget.RemoteViews; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import static com.squareup.picasso.BitmapHunter.forRequest; import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY; import static com.squareup.picasso.PicassoDrawable.setBitmap; import static com.squareup.picasso.PicassoDrawable.setPlaceholder; import static com.squareup.picasso.RemoteViewsAction.AppWidgetAction; import static com.squareup.picasso.RemoteViewsAction.NotificationAction; import static com.squareup.picasso.Utils.OWNER_MAIN; import static com.squareup.picasso.Utils.VERB_CHANGED; import static com.squareup.picasso.Utils.VERB_COMPLETED; import static com.squareup.picasso.Utils.VERB_CREATED; import static com.squareup.picasso.Utils.checkMain; import static com.squareup.picasso.Utils.checkNotMain; import static com.squareup.picasso.Utils.createKey; import static com.squareup.picasso.Utils.isMain; import static com.squareup.picasso.Utils.log; /** Fluent API for building an image download request. */ @SuppressWarnings("UnusedDeclaration") // Public API. public class RequestCreator { private static int nextId = 0; private static int getRequestId() { if (isMain()) { return nextId++; } final CountDownLatch latch = new CountDownLatch(1); final AtomicInteger id = new AtomicInteger(); Picasso.HANDLER.post(new Runnable() { @Override public void run() { id.set(getRequestId()); latch.countDown(); } }); try { latch.await(); } catch (final InterruptedException e) { Picasso.HANDLER.post(new Runnable() { @Override public void run() { throw new RuntimeException(e); } }); } return id.get(); } private final Picasso picasso; private final Request.Builder data; private boolean skipMemoryCache; private boolean noFade; private boolean deferred; private int placeholderResId; private int errorResId; private Drawable placeholderDrawable; private Drawable errorDrawable; RequestCreator(Picasso picasso, Uri uri, int resourceId) { if (picasso.shutdown) { throw new IllegalStateException( "Picasso instance already shut down. Cannot submit new requests."); } this.picasso = picasso; this.data = new Request.Builder(uri, resourceId); } RequestCreator() { this.picasso = null; this.data = new Request.Builder(null, 0); } /** * A placeholder drawable to be used while the image is being loaded. If the requested image is * not immediately available in the memory cache then this resource will be set on the target * {@link android.widget.ImageView}. */ public RequestCreator placeholder(int placeholderResId) { if (placeholderResId == 0) { throw new IllegalArgumentException("Placeholder image resource invalid."); } if (placeholderDrawable != null) { throw new IllegalStateException("Placeholder image already set."); } this.placeholderResId = placeholderResId; return this; } /** * A placeholder drawable to be used while the image is being loaded. If the requested image is * not immediately available in the memory cache then this resource will be set on the target * {@link android.widget.ImageView}. *

* If you are not using a placeholder image but want to clear an existing image (such as when * used in an {@link android.widget.Adapter adapter}), pass in {@code null}. */ public RequestCreator placeholder(Drawable placeholderDrawable) { if (placeholderResId != 0) { throw new IllegalStateException("Placeholder image already set."); } this.placeholderDrawable = placeholderDrawable; return this; } /** An error drawable to be used if the request image could not be loaded. */ public RequestCreator error(int errorResId) { if (errorResId == 0) { throw new IllegalArgumentException("Error image resource invalid."); } if (errorDrawable != null) { throw new IllegalStateException("Error image already set."); } this.errorResId = errorResId; return this; } /** An error drawable to be used if the request image could not be loaded. */ public RequestCreator error(Drawable errorDrawable) { if (errorDrawable == null) { throw new IllegalArgumentException("Error image may not be null."); } if (errorResId != 0) { throw new IllegalStateException("Error image already set."); } this.errorDrawable = errorDrawable; return this; } /** * Attempt to resize the image to fit exactly into the target {@link android.widget.ImageView}'s bounds. This * will result in delayed execution of the request until the {@link android.widget.ImageView} has been laid out. *

* Note: This method works only when your target is an {@link android.widget.ImageView}. */ public RequestCreator fit() { deferred = true; return this; } /** Internal use only. Used by {@link com.squareup.picasso.DeferredRequestCreator}. */ RequestCreator unfit() { deferred = false; return this; } /** Resize the image to the specified dimension size. */ public RequestCreator resizeDimen(int targetWidthResId, int targetHeightResId) { Resources resources = picasso.context.getResources(); int targetWidth = resources.getDimensionPixelSize(targetWidthResId); int targetHeight = resources.getDimensionPixelSize(targetHeightResId); return resize(targetWidth, targetHeight); } /** Resize the image to the specified size in pixels. */ public RequestCreator resize(int targetWidth, int targetHeight) { data.resize(targetWidth, targetHeight); return this; } /** * Crops an image inside of the bounds specified by {@link #resize(int, int)} rather than * distorting the aspect ratio. This cropping technique scales the image so that it fills the * requested bounds and then crops the extra. */ public RequestCreator centerCrop() { data.centerCrop(); return this; } /** * Centers an image inside of the bounds specified by {@link #resize(int, int)}. This scales * the image so that both dimensions are equal to or less than the requested bounds. */ public RequestCreator centerInside() { data.centerInside(); return this; } /** Rotate the image by the specified degrees. */ public RequestCreator rotate(float degrees) { data.rotate(degrees); return this; } /** Rotate the image by the specified degrees around a pivot point. */ public RequestCreator rotate(float degrees, float pivotX, float pivotY) { data.rotate(degrees, pivotX, pivotY); return this; } /** * Attempt to decode the image using the specified config. *

* Note: This value may be ignored by {@link android.graphics.BitmapFactory}. See * {@link android.graphics.BitmapFactory.Options#inPreferredConfig its documentation} for more details. */ public RequestCreator config(Bitmap.Config config) { data.config(config); return this; } /** * Add a custom transformation to be applied to the image. *

* Custom transformations will always be run after the built-in transformations. */ // TODO show example of calling resize after a transform in the javadoc public RequestCreator transform(Transformation transformation) { data.transform(transformation); return this; } /** * Indicate that this action should not use the memory cache for attempting to load or save the * image. This can be useful when you know an image will only ever be used once (e.g., loading * an image from the filesystem and uploading to a remote server). */ public RequestCreator skipMemoryCache() { skipMemoryCache = true; return this; } /** Disable brief fade in of images loaded from the disk cache or network. */ public RequestCreator noFade() { noFade = true; return this; } /** * Synchronously fulfill this request. Must not be called from the main thread. *

* Note: The result of this operation is not cached in memory because the underlying * {@link com.squareup.picasso.Cache} implementation is not guaranteed to be thread-safe. */ public Bitmap get() throws IOException { long started = System.nanoTime(); checkNotMain(); if (deferred) { throw new IllegalStateException("Fit cannot be used with get."); } if (!data.hasImage()) { return null; } Request finalData = createRequest(started); String key = createKey(finalData, new StringBuilder()); Action action = new GetAction(picasso, finalData, skipMemoryCache, key); return forRequest(picasso, picasso.dispatcher, picasso.cache, picasso.stats, action).hunt(); } /** * Asynchronously fulfills the request without a {@link android.widget.ImageView} or {@link com.squareup.picasso.Target}. This is * useful when you want to warm up the cache with an image. *

* Note: It is safe to invoke this method from any thread. */ public void fetch() { long started = System.nanoTime(); if (deferred) { throw new IllegalStateException("Fit cannot be used with fetch."); } if (data.hasImage()) { Request request = createRequest(started); String key = createKey(request, new StringBuilder()); Action action = new FetchAction(picasso, request, skipMemoryCache, key); picasso.submit(action); } } /** * Asynchronously fulfills the request into the specified {@link com.squareup.picasso.Target}. In most cases, you * should use this when you are dealing with a custom {@link android.view.View View} or view * holder which should implement the {@link com.squareup.picasso.Target} interface. *

* Implementing on a {@link android.view.View View}: *

   * public class ProfileView extends FrameLayout implements Target {
   *   {@literal @}Override public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) {
   *     setBackgroundDrawable(new BitmapDrawable(bitmap));
   *   }
   *
   *   {@literal @}Override public void onBitmapFailed() {
   *     setBackgroundResource(R.drawable.profile_error);
   *   }
   *
   *   {@literal @}Override public void onPrepareLoad(Drawable placeHolderDrawable) {
   *     frame.setBackgroundDrawable(placeHolderDrawable);
   *   }
   * }
   * 
* Implementing on a view holder object for use inside of an adapter: *
   * public class ViewHolder implements Target {
   *   public FrameLayout frame;
   *   public TextView name;
   *
   *   {@literal @}Override public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) {
   *     frame.setBackgroundDrawable(new BitmapDrawable(bitmap));
   *   }
   *
   *   {@literal @}Override public void onBitmapFailed() {
   *     frame.setBackgroundResource(R.drawable.profile_error);
   *   }
   *
   *   {@literal @}Override public void onPrepareLoad(Drawable placeHolderDrawable) {
   *     frame.setBackgroundDrawable(placeHolderDrawable);
   *   }
   * }
   * 
*

* Note: This method keeps a weak reference to the {@link com.squareup.picasso.Target} instance and will be * garbage collected if you do not keep a strong reference to it. To receive callbacks when an * image is loaded use {@link #into(android.widget.ImageView, com.squareup.picasso.Callback)}. */ public void into(Target target) { long started = System.nanoTime(); checkMain(); if (target == null) { throw new IllegalArgumentException("Target must not be null."); } if (deferred) { throw new IllegalStateException("Fit cannot be used with a Target."); } Drawable drawable = placeholderResId != 0 ? picasso.context.getResources().getDrawable(placeholderResId) : placeholderDrawable; if (!data.hasImage()) { picasso.cancelRequest(target); target.onPrepareLoad(drawable); return; } Request request = createRequest(started); String requestKey = createKey(request); if (!skipMemoryCache) { Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey); if (bitmap != null) { picasso.cancelRequest(target); target.onBitmapLoaded(bitmap, MEMORY); return; } } target.onPrepareLoad(drawable); Action action = new TargetAction(picasso, target, request, skipMemoryCache, errorResId, errorDrawable, requestKey); picasso.enqueueAndSubmit(action); } /** * Asynchronously fulfills the request into the specified {@link android.widget.RemoteViews} object with the * given {@code viewId}. This is used for loading bitmaps into a {@link android.app.Notification}. */ public void into(RemoteViews remoteViews, int viewId, int notificationId, Notification notification) { long started = System.nanoTime(); checkMain(); if (remoteViews == null) { throw new IllegalArgumentException("RemoteViews must not be null."); } if (notification == null) { throw new IllegalArgumentException("Notification must not be null."); } if (deferred) { throw new IllegalStateException("Fit cannot be used with RemoteViews."); } if (placeholderDrawable != null || errorDrawable != null) { throw new IllegalArgumentException( "Cannot use placeholder or error drawables with remote views."); } Request request = createRequest(started); String key = createKey(request); RemoteViewsAction action = new NotificationAction(picasso, request, remoteViews, viewId, notificationId, notification, skipMemoryCache, errorResId, key); performRemoteViewInto(action); } /** * Asynchronously fulfills the request into the specified {@link android.widget.RemoteViews} object with the * given {@code viewId}. This is used for loading bitmaps into all instances of a widget. */ public void into(RemoteViews remoteViews, int viewId, int[] appWidgetIds) { long started = System.nanoTime(); checkMain(); if (remoteViews == null) { throw new IllegalArgumentException("remoteViews must not be null."); } if (appWidgetIds == null) { throw new IllegalArgumentException("appWidgetIds must not be null."); } if (deferred) { throw new IllegalStateException("Fit cannot be used with remote views."); } if (placeholderDrawable != null || errorDrawable != null) { throw new IllegalArgumentException( "Cannot use placeholder or error drawables with remote views."); } Request request = createRequest(started); String key = createKey(request); RemoteViewsAction action = new AppWidgetAction(picasso, request, remoteViews, viewId, appWidgetIds, skipMemoryCache, errorResId, key); performRemoteViewInto(action); } /** * Asynchronously fulfills the request into the specified {@link android.widget.ImageView}. *

* Note: This method keeps a weak reference to the {@link android.widget.ImageView} instance and will * automatically support object recycling. */ public void into(ImageView target) { into(target, null); } /** * Asynchronously fulfills the request into the specified {@link android.widget.ImageView} and invokes the * target {@link com.squareup.picasso.Callback} if it's not {@code null}. *

* Note: The {@link com.squareup.picasso.Callback} param is a strong reference and will prevent your * {@link android.app.Activity} or {@link android.app.Fragment} from being garbage collected. If * you use this method, it is strongly recommended you invoke an adjacent * {@link com.squareup.picasso.Picasso#cancelRequest(android.widget.ImageView)} call to prevent temporary leaking. */ public void into(ImageView target, Callback callback) { long started = System.nanoTime(); checkMain(); if (target == null) { throw new IllegalArgumentException("Target must not be null."); } if (!data.hasImage()) { picasso.cancelRequest(target); setPlaceholder(target, placeholderResId, placeholderDrawable); return; } if (deferred) { if (data.hasSize()) { throw new IllegalStateException("Fit cannot be used with resize."); } int width = target.getWidth(); int height = target.getHeight(); if (width == 0 || height == 0) { setPlaceholder(target, placeholderResId, placeholderDrawable); picasso.defer(target, new DeferredRequestCreator(this, target, callback)); return; } data.resize(width, height); } Request request = createRequest(started); String requestKey = createKey(request); if (!skipMemoryCache) { Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey); if (bitmap != null) { picasso.cancelRequest(target); setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled); if (picasso.loggingEnabled) { log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY); } if (callback != null) { callback.onSuccess(); } return; } } setPlaceholder(target, placeholderResId, placeholderDrawable); Action action = new ImageViewAction(picasso, target, request, skipMemoryCache, noFade, errorResId, errorDrawable, requestKey, callback); picasso.enqueueAndSubmit(action); } /** Create the request optionally passing it through the request transformer. */ private Request createRequest(long started) { int id = getRequestId(); Request request = data.build(); request.id = id; request.started = started; boolean loggingEnabled = picasso.loggingEnabled; if (loggingEnabled) { log(OWNER_MAIN, VERB_CREATED, request.plainId(), request.toString()); } Request transformed = picasso.transformRequest(request); if (transformed != request) { // If the request was changed, copy over the id and timestamp from the original. transformed.id = id; transformed.started = started; if (loggingEnabled) { log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed); } } return transformed; } private void performRemoteViewInto(RemoteViewsAction action) { if (!skipMemoryCache) { Bitmap bitmap = picasso.quickMemoryCacheCheck(action.getKey()); if (bitmap != null) { action.complete(bitmap, MEMORY); return; } } if (placeholderResId != 0) { action.setImageResource(placeholderResId); } picasso.enqueueAndSubmit(action); } } ================================================ FILE: picasso/RequestHandler.java ================================================ /* * Copyright (C) 2014 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.NetworkInfo; import java.io.IOException; /** * {@link com.squareup.picasso.RequestHandler} allows you to extend Picasso to load images * in ways that are not supported by default in the library. *

*

Usage

*

{@link com.squareup.picasso.RequestHandler} must be subclassed to be used. You will have to * override two methods ({@link #canHandleRequest(com.squareup.picasso.Request)} and * {@link #load(com.squareup.picasso.Request)}) with your custom logic to load images.

* *

You should then register your {@link com.squareup.picasso.RequestHandler} using * {@link com.squareup.picasso.Picasso.Builder#addRequestHandler(com.squareup.picasso.RequestHandler)}

* * NOTE: This is a beta feature. The API is subject to change in a backwards * incompatible way at any time. * * @see com.squareup.picasso.Picasso.Builder#addRequestHandler(com.squareup.picasso.RequestHandler) */ public abstract class RequestHandler { /** * {@link com.squareup.picasso.RequestHandler.Result} represents the result of a {@link #load(com.squareup.picasso.Request)} call in a * {@link com.squareup.picasso.RequestHandler}. * * @see com.squareup.picasso.RequestHandler * @see #load(com.squareup.picasso.Request) */ public static final class Result { private final Picasso.LoadedFrom loadedFrom; private final Bitmap bitmap; private final int exifOrientation; public Result(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) { this(bitmap, loadedFrom, 0); } Result(Bitmap bitmap, Picasso.LoadedFrom loadedFrom, int exifOrientation) { this.bitmap = bitmap; this.loadedFrom = loadedFrom; this.exifOrientation = exifOrientation; } /** * Returns the resulting {@link android.graphics.Bitmap} generated * from a {@link #load(com.squareup.picasso.Request)} call. */ public Bitmap getBitmap() { return bitmap; } /** * Returns the resulting {@link com.squareup.picasso.Picasso.LoadedFrom} generated * from a {@link #load(com.squareup.picasso.Request)} call. */ public Picasso.LoadedFrom getLoadedFrom() { return loadedFrom; } /** * Returns the resulting EXIF orientation generated * from a {@link #load(com.squareup.picasso.Request)} call. This is only accessible * to built-in RequestHandlers. */ int getExifOrientation() { return exifOrientation; } } /** * Whether or not this {@link com.squareup.picasso.RequestHandler} can handle a request with the * given {@link com.squareup.picasso.Request}. */ public abstract boolean canHandleRequest(Request data); /** * Loads an image for the given {@link com.squareup.picasso.Request}. * * @param data the {@link android.net.Uri} to load the image from. * @return A {@link com.squareup.picasso.RequestHandler.Result} instance representing the result. */ public abstract Result load(Request data) throws IOException; int getRetryCount() { return 0; } boolean shouldRetry(boolean airplaneMode, NetworkInfo info) { return false; } boolean supportsReplay() { return false; } /** * Lazily create {@link android.graphics.BitmapFactory.Options} based in given * {@link com.squareup.picasso.Request}, only instantiating them if needed. */ static BitmapFactory.Options createBitmapOptions(Request data) { final boolean justBounds = data.hasSize(); final boolean hasConfig = data.config != null; BitmapFactory.Options options = null; if (justBounds || hasConfig) { options = new BitmapFactory.Options(); options.inJustDecodeBounds = justBounds; if (hasConfig) { options.inPreferredConfig = data.config; } } return options; } static boolean requiresInSampleSize(BitmapFactory.Options options) { return options != null && options.inJustDecodeBounds; } static void calculateInSampleSize(int reqWidth, int reqHeight, BitmapFactory.Options options) { calculateInSampleSize(reqWidth, reqHeight, options.outWidth, options.outHeight, options); } static void calculateInSampleSize(int reqWidth, int reqHeight, int width, int height, BitmapFactory.Options options) { int sampleSize = 1; if (height > reqHeight || width > reqWidth) { final int heightRatio = (int) Math.floor((float) height / (float) reqHeight); final int widthRatio = (int) Math.floor((float) width / (float) reqWidth); sampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } options.inSampleSize = sampleSize; options.inJustDecodeBounds = false; } } ================================================ FILE: picasso/ResourceRequestHandler.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import java.io.IOException; import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE; import static com.squareup.picasso.Picasso.LoadedFrom.DISK; class ResourceRequestHandler extends RequestHandler { private final Context context; ResourceRequestHandler(Context context) { this.context = context; } @Override public boolean canHandleRequest(Request data) { if (data.resourceId != 0) { return true; } return SCHEME_ANDROID_RESOURCE.equals(data.uri.getScheme()); } @Override public Result load(Request data) throws IOException { Resources res = Utils.getResources(context, data); int id = Utils.getResourceId(res, data); return new Result(decodeResource(res, id, data), DISK); } private static Bitmap decodeResource(Resources resources, int id, Request data) { final BitmapFactory.Options options = createBitmapOptions(data); if (requiresInSampleSize(options)) { BitmapFactory.decodeResource(resources, id, options); calculateInSampleSize(data.targetWidth, data.targetHeight, options); } return BitmapFactory.decodeResource(resources, id, options); } } ================================================ FILE: picasso/Stats.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.graphics.Bitmap; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; class Stats { private static final int CACHE_HIT = 0; private static final int CACHE_MISS = 1; private static final int BITMAP_DECODE_FINISHED = 2; private static final int BITMAP_TRANSFORMED_FINISHED = 3; private static final int DOWNLOAD_FINISHED = 4; private static final String STATS_THREAD_NAME = Utils.THREAD_PREFIX + "Stats"; final HandlerThread statsThread; final Cache cache; final Handler handler; long cacheHits; long cacheMisses; long totalDownloadSize; long totalOriginalBitmapSize; long totalTransformedBitmapSize; long averageDownloadSize; long averageOriginalBitmapSize; long averageTransformedBitmapSize; int downloadCount; int originalBitmapCount; int transformedBitmapCount; Stats(Cache cache) { this.cache = cache; this.statsThread = new HandlerThread(STATS_THREAD_NAME, THREAD_PRIORITY_BACKGROUND); this.statsThread.start(); this.handler = new StatsHandler(statsThread.getLooper(), this); } void dispatchBitmapDecoded(Bitmap bitmap) { processBitmap(bitmap, BITMAP_DECODE_FINISHED); } void dispatchBitmapTransformed(Bitmap bitmap) { processBitmap(bitmap, BITMAP_TRANSFORMED_FINISHED); } void dispatchDownloadFinished(long size) { handler.sendMessage(handler.obtainMessage(DOWNLOAD_FINISHED, size)); } void dispatchCacheHit() { handler.sendEmptyMessage(CACHE_HIT); } void dispatchCacheMiss() { handler.sendEmptyMessage(CACHE_MISS); } void shutdown() { statsThread.quit(); } void performCacheHit() { cacheHits++; } void performCacheMiss() { cacheMisses++; } void performDownloadFinished(Long size) { downloadCount++; totalDownloadSize += size; averageDownloadSize = getAverage(downloadCount, totalDownloadSize); } void performBitmapDecoded(long size) { originalBitmapCount++; totalOriginalBitmapSize += size; averageOriginalBitmapSize = getAverage(originalBitmapCount, totalOriginalBitmapSize); } void performBitmapTransformed(long size) { transformedBitmapCount++; totalTransformedBitmapSize += size; averageTransformedBitmapSize = getAverage(originalBitmapCount, totalTransformedBitmapSize); } StatsSnapshot createSnapshot() { return new StatsSnapshot(cache.maxSize(), cache.size(), cacheHits, cacheMisses, totalDownloadSize, totalOriginalBitmapSize, totalTransformedBitmapSize, averageDownloadSize, averageOriginalBitmapSize, averageTransformedBitmapSize, downloadCount, originalBitmapCount, transformedBitmapCount, System.currentTimeMillis()); } private void processBitmap(Bitmap bitmap, int what) { // Never send bitmaps to the handler as they could be recycled before we process them. int bitmapSize = Utils.getBitmapBytes(bitmap); handler.sendMessage(handler.obtainMessage(what, bitmapSize, 0)); } private static long getAverage(int count, long totalSize) { return totalSize / count; } private static class StatsHandler extends Handler { private final Stats stats; public StatsHandler(Looper looper, Stats stats) { super(looper); this.stats = stats; } @Override public void handleMessage(final Message msg) { switch (msg.what) { case CACHE_HIT: stats.performCacheHit(); break; case CACHE_MISS: stats.performCacheMiss(); break; case BITMAP_DECODE_FINISHED: stats.performBitmapDecoded(msg.arg1); break; case BITMAP_TRANSFORMED_FINISHED: stats.performBitmapTransformed(msg.arg1); break; case DOWNLOAD_FINISHED: stats.performDownloadFinished((Long) msg.obj); break; default: Picasso.HANDLER.post(new Runnable() { @Override public void run() { throw new AssertionError("Unhandled stats message." + msg.what); } }); } } } } ================================================ FILE: picasso/StatsSnapshot.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.util.Log; import java.io.PrintWriter; import java.io.StringWriter; import static com.squareup.picasso.Picasso.TAG; /** Represents all stats for a {@link com.squareup.picasso.Picasso} instance at a single point in time. */ public class StatsSnapshot { public final int maxSize; public final int size; public final long cacheHits; public final long cacheMisses; public final long totalDownloadSize; public final long totalOriginalBitmapSize; public final long totalTransformedBitmapSize; public final long averageDownloadSize; public final long averageOriginalBitmapSize; public final long averageTransformedBitmapSize; public final int downloadCount; public final int originalBitmapCount; public final int transformedBitmapCount; public final long timeStamp; public StatsSnapshot(int maxSize, int size, long cacheHits, long cacheMisses, long totalDownloadSize, long totalOriginalBitmapSize, long totalTransformedBitmapSize, long averageDownloadSize, long averageOriginalBitmapSize, long averageTransformedBitmapSize, int downloadCount, int originalBitmapCount, int transformedBitmapCount, long timeStamp) { this.maxSize = maxSize; this.size = size; this.cacheHits = cacheHits; this.cacheMisses = cacheMisses; this.totalDownloadSize = totalDownloadSize; this.totalOriginalBitmapSize = totalOriginalBitmapSize; this.totalTransformedBitmapSize = totalTransformedBitmapSize; this.averageDownloadSize = averageDownloadSize; this.averageOriginalBitmapSize = averageOriginalBitmapSize; this.averageTransformedBitmapSize = averageTransformedBitmapSize; this.downloadCount = downloadCount; this.originalBitmapCount = originalBitmapCount; this.transformedBitmapCount = transformedBitmapCount; this.timeStamp = timeStamp; } /** Prints out this {@link com.squareup.picasso.StatsSnapshot} into log. */ @SuppressWarnings("UnusedDeclaration") public void dump() { StringWriter logWriter = new StringWriter(); dump(new PrintWriter(logWriter)); Log.i(TAG, logWriter.toString()); } /** Prints out this {@link com.squareup.picasso.StatsSnapshot} with the the provided {@link java.io.PrintWriter}. */ public void dump(PrintWriter writer) { writer.println("===============BEGIN PICASSO STATS ==============="); writer.println("Memory Cache Stats"); writer.print(" Max Cache Size: "); writer.println(maxSize); writer.print(" Cache Size: "); writer.println(size); writer.print(" Cache % Full: "); writer.println((int) Math.ceil((float) size / maxSize * 100)); writer.print(" Cache Hits: "); writer.println(cacheHits); writer.print(" Cache Misses: "); writer.println(cacheMisses); writer.println("Network Stats"); writer.print(" Download Count: "); writer.println(downloadCount); writer.print(" Total Download Size: "); writer.println(totalDownloadSize); writer.print(" Average Download Size: "); writer.println(averageDownloadSize); writer.println("Bitmap Stats"); writer.print(" Total Bitmaps Decoded: "); writer.println(originalBitmapCount); writer.print(" Total Bitmap Size: "); writer.println(totalOriginalBitmapSize); writer.print(" Total Transformed Bitmaps: "); writer.println(transformedBitmapCount); writer.print(" Total Transformed Bitmap Size: "); writer.println(totalTransformedBitmapSize); writer.print(" Average Bitmap Size: "); writer.println(averageOriginalBitmapSize); writer.print(" Average Transformed Bitmap Size: "); writer.println(averageTransformedBitmapSize); writer.println("===============END PICASSO STATS ==============="); writer.flush(); } @Override public String toString() { return "StatsSnapshot{" + "maxSize=" + maxSize + ", size=" + size + ", cacheHits=" + cacheHits + ", cacheMisses=" + cacheMisses + ", downloadCount=" + downloadCount + ", totalDownloadSize=" + totalDownloadSize + ", averageDownloadSize=" + averageDownloadSize + ", totalOriginalBitmapSize=" + totalOriginalBitmapSize + ", totalTransformedBitmapSize=" + totalTransformedBitmapSize + ", averageOriginalBitmapSize=" + averageOriginalBitmapSize + ", averageTransformedBitmapSize=" + averageTransformedBitmapSize + ", originalBitmapCount=" + originalBitmapCount + ", transformedBitmapCount=" + transformedBitmapCount + ", timeStamp=" + timeStamp + '}'; } } ================================================ FILE: picasso/Target.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import static com.squareup.picasso.Picasso.LoadedFrom; /** * Represents an arbitrary listener for image loading. *

* Objects implementing this class must have a working implementation of * {@link Object#equals(Object)} and {@link Object#hashCode()} for proper storage internally. * Instances of this interface will also be compared to determine if view recycling is occurring. * It is recommended that you add this interface directly on to a custom view type when using in an * adapter to ensure correct recycling behavior. */ public interface Target { /** * Callback when an image has been successfully loaded. *

* Note: You must not recycle the bitmap. */ void onBitmapLoaded(Bitmap bitmap, LoadedFrom from); /** * Callback indicating the image could not be successfully loaded. *

* Note: The passed {@link android.graphics.drawable.Drawable} may be {@code null} if none has been * specified via {@link com.squareup.picasso.RequestCreator#error(android.graphics.drawable.Drawable)} * or {@link com.squareup.picasso.RequestCreator#error(int)}. */ void onBitmapFailed(Drawable errorDrawable); /** * Callback invoked right before your request is submitted. *

* Note: The passed {@link android.graphics.drawable.Drawable} may be {@code null} if none has been * specified via {@link com.squareup.picasso.RequestCreator#placeholder(android.graphics.drawable.Drawable)} * or {@link com.squareup.picasso.RequestCreator#placeholder(int)}. */ void onPrepareLoad(Drawable placeHolderDrawable); } ================================================ FILE: picasso/TargetAction.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; final class TargetAction extends Action { TargetAction(Picasso picasso, Target target, Request data, boolean skipCache, int errorResId, Drawable errorDrawable, String key) { super(picasso, target, data, skipCache, false, errorResId, errorDrawable, key); } @Override void complete(Bitmap result, Picasso.LoadedFrom from) { if (result == null) { throw new AssertionError( String.format("Attempted to complete action with no result!\n%s", this)); } Target target = getTarget(); if (target != null) { target.onBitmapLoaded(result, from); if (result.isRecycled()) { throw new IllegalStateException("Target callback must not recycle bitmap!"); } } } @Override void error() { Target target = getTarget(); if (target != null) { if (errorResId != 0) { target.onBitmapFailed(picasso.context.getResources().getDrawable(errorResId)); } else { target.onBitmapFailed(errorDrawable); } } } } ================================================ FILE: picasso/Transformation.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.graphics.Bitmap; /** Image transformation. */ public interface Transformation { /** * Transform the source bitmap into a new bitmap. If you create a new bitmap instance, you must * call {@link android.graphics.Bitmap#recycle()} on {@code source}. You may return the original * if no transformation is required. */ Bitmap transform(Bitmap source); /** * Returns a unique key for the transformation, used for caching purposes. If the transformation * has parameters (e.g. size, scale factor, etc) then these should be part of the key. */ String key(); } ================================================ FILE: picasso/UrlConnectionDownloader.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.content.Context; import android.net.Uri; import android.net.http.HttpResponseCache; import android.os.Build; import java.io.File; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import static com.squareup.picasso.Utils.parseResponseSourceHeader; /** * A {@link com.squareup.picasso.Downloader} which uses {@link java.net.HttpURLConnection} to download images. A disk cache of 2% * of the total available space will be used (capped at 50MB) will automatically be installed in the * application's cache directory, when available. */ public class UrlConnectionDownloader implements Downloader { static final String RESPONSE_SOURCE = "X-Android-Response-Source"; private static final Object lock = new Object(); static volatile Object cache; private final Context context; public UrlConnectionDownloader(Context context) { this.context = context.getApplicationContext(); } protected HttpURLConnection openConnection(Uri path) throws IOException { HttpURLConnection connection = (HttpURLConnection) new URL(path.toString()).openConnection(); connection.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT); connection.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT); return connection; } @Override public Response load(Uri uri, boolean localCacheOnly) throws IOException { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { installCacheIfNeeded(context); } HttpURLConnection connection = openConnection(uri); connection.setUseCaches(true); if (localCacheOnly) { connection.setRequestProperty("Cache-Control", "only-if-cached,max-age=" + Integer.MAX_VALUE); } int responseCode = connection.getResponseCode(); if (responseCode >= 300) { connection.disconnect(); throw new ResponseException(responseCode + " " + connection.getResponseMessage()); } long contentLength = connection.getHeaderFieldInt("Content-Length", -1); boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE)); return new Response(connection.getInputStream(), fromCache, contentLength); } private static void installCacheIfNeeded(Context context) { // DCL + volatile should be safe after Java 5. if (cache == null) { try { synchronized (lock) { if (cache == null) { cache = ResponseCacheIcs.install(context); } } } catch (IOException ignored) { } } } private static class ResponseCacheIcs { static Object install(Context context) throws IOException { File cacheDir = Utils.createDefaultCacheDir(context); HttpResponseCache cache = HttpResponseCache.getInstalled(); if (cache == null) { long maxSize = Utils.calculateDiskCacheSize(cacheDir); cache = HttpResponseCache.install(cacheDir, maxSize); } return cache; } } } ================================================ FILE: picasso/Utils.java ================================================ /* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.picasso; import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.os.Looper; import android.os.Process; import android.os.StatFs; import android.provider.Settings; import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.concurrent.ThreadFactory; import static android.content.Context.ACTIVITY_SERVICE; import static android.content.pm.ApplicationInfo.FLAG_LARGE_HEAP; import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION_CODES.HONEYCOMB; import static android.os.Build.VERSION_CODES.HONEYCOMB_MR1; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static android.provider.Settings.System.AIRPLANE_MODE_ON; import static com.squareup.picasso.Picasso.TAG; import static java.lang.String.format; final class Utils { static final String THREAD_PREFIX = "Picasso-"; static final String THREAD_IDLE_NAME = THREAD_PREFIX + "Idle"; static final int DEFAULT_READ_TIMEOUT = 20 * 1000; // 20s static final int DEFAULT_CONNECT_TIMEOUT = 15 * 1000; // 15s private static final String PICASSO_CACHE = "picasso-cache"; private static final int KEY_PADDING = 50; // Determined by exact science. private static final int MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024; // 5MB private static final int MAX_DISK_CACHE_SIZE = 50 * 1024 * 1024; // 50MB /** Thread confined to main thread for key creation. */ static final StringBuilder MAIN_THREAD_KEY_BUILDER = new StringBuilder(); /** Logging */ static final String OWNER_MAIN = "Main"; static final String OWNER_DISPATCHER = "Dispatcher"; static final String OWNER_HUNTER = "Hunter"; static final String VERB_CREATED = "created"; static final String VERB_CHANGED = "changed"; static final String VERB_IGNORED = "ignored"; static final String VERB_ENQUEUED = "enqueued"; static final String VERB_CANCELED = "canceled"; static final String VERB_BATCHED = "batched"; static final String VERB_RETRYING = "retrying"; static final String VERB_EXECUTING = "executing"; static final String VERB_DECODED = "decoded"; static final String VERB_TRANSFORMED = "transformed"; static final String VERB_JOINED = "joined"; static final String VERB_REMOVED = "removed"; static final String VERB_DELIVERED = "delivered"; static final String VERB_REPLAYING = "replaying"; static final String VERB_COMPLETED = "completed"; static final String VERB_ERRORED = "errored"; /* WebP file header 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 'R' | 'I' | 'F' | 'F' | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | File Size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 'W' | 'E' | 'B' | 'P' | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ private static final int WEBP_FILE_HEADER_SIZE = 12; private static final String WEBP_FILE_HEADER_RIFF = "RIFF"; private static final String WEBP_FILE_HEADER_WEBP = "WEBP"; private Utils() { // No instances. } static int getBitmapBytes(Bitmap bitmap) { int result; if (SDK_INT >= HONEYCOMB_MR1) { result = BitmapHoneycombMR1.getByteCount(bitmap); } else { result = bitmap.getRowBytes() * bitmap.getHeight(); } if (result < 0) { throw new IllegalStateException("Negative size: " + bitmap); } return result; } static void checkNotMain() { if (isMain()) { throw new IllegalStateException("Method call should not happen from the main thread."); } } static void checkMain() { if (!isMain()) { throw new IllegalStateException("Method call should happen from the main thread."); } } static boolean isMain() { return Looper.getMainLooper().getThread() == Thread.currentThread(); } static String getLogIdsForHunter(BitmapHunter hunter) { return getLogIdsForHunter(hunter, ""); } static String getLogIdsForHunter(BitmapHunter hunter, String prefix) { StringBuilder builder = new StringBuilder(prefix); Action action = hunter.getAction(); if (action != null) { builder.append(action.request.logId()); } List actions = hunter.getActions(); if (actions != null) { for (int i = 0, count = actions.size(); i < count; i++) { if (i > 0 || action != null) builder.append(", "); builder.append(actions.get(i).request.logId()); } } return builder.toString(); } static void log(String owner, String verb, String logId) { log(owner, verb, logId, ""); } static void log(String owner, String verb, String logId, String extras) { Log.d(TAG, format("%1$-11s %2$-12s %3$s %4$s", owner, verb, logId, extras)); } static String createKey(Request data) { String result = createKey(data, MAIN_THREAD_KEY_BUILDER); MAIN_THREAD_KEY_BUILDER.setLength(0); return result; } static String createKey(Request data, StringBuilder builder) { if (data.uri != null) { String path = data.uri.toString(); builder.ensureCapacity(path.length() + KEY_PADDING); builder.append(path); } else { builder.ensureCapacity(KEY_PADDING); builder.append(data.resourceId); } builder.append('\n'); if (data.rotationDegrees != 0) { builder.append("rotation:").append(data.rotationDegrees); if (data.hasRotationPivot) { builder.append('@').append(data.rotationPivotX).append('x').append(data.rotationPivotY); } builder.append('\n'); } if (data.targetWidth != 0) { builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight); builder.append('\n'); } if (data.centerCrop) { builder.append("centerCrop\n"); } else if (data.centerInside) { builder.append("centerInside\n"); } if (data.transformations != null) { //noinspection ForLoopReplaceableByForEach for (int i = 0, count = data.transformations.size(); i < count; i++) { builder.append(data.transformations.get(i).key()); builder.append('\n'); } } return builder.toString(); } static void closeQuietly(InputStream is) { if (is == null) return; try { is.close(); } catch (IOException ignored) { } } /** Returns {@code true} if header indicates the response body was loaded from the disk cache. */ static boolean parseResponseSourceHeader(String header) { if (header == null) { return false; } String[] parts = header.split(" ", 2); if ("CACHE".equals(parts[0])) { return true; } if (parts.length == 1) { return false; } try { return "CONDITIONAL_CACHE".equals(parts[0]) && Integer.parseInt(parts[1]) == 304; } catch (NumberFormatException e) { return false; } } static Downloader createDefaultDownloader(Context context) { boolean okUrlFactory = false; try { Class.forName("com.squareup.okhttp.OkUrlFactory"); okUrlFactory = true; } catch (ClassNotFoundException ignored) { } boolean okHttpClient = false; try { Class.forName("com.squareup.okhttp.OkHttpClient"); okHttpClient = true; } catch (ClassNotFoundException ignored) { } if (okHttpClient != okUrlFactory) { throw new RuntimeException("" + "Picasso detected an unsupported OkHttp on the classpath.\n" + "To use OkHttp with this version of Picasso, you'll need:\n" + "1. com.squareup.okhttp:okhttp:1.6.0 (or newer)\n" + "2. com.squareup.okhttp:okhttp-urlconnection:1.6.0 (or newer)\n" + "Note that OkHttp 2.0.0+ is supported!"); } return okHttpClient ? OkHttpLoaderCreator.create(context) : new UrlConnectionDownloader(context); } static File createDefaultCacheDir(Context context) { File cache = new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE); if (!cache.exists()) { //noinspection ResultOfMethodCallIgnored cache.mkdirs(); } return cache; } static long calculateDiskCacheSize(File dir) { long size = MIN_DISK_CACHE_SIZE; try { StatFs statFs = new StatFs(dir.getAbsolutePath()); long available = ((long) statFs.getBlockCount()) * statFs.getBlockSize(); // Target 2% of the total space. size = available / 50; } catch (IllegalArgumentException ignored) { } // Bound inside min/max size for disk cache. return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE); } static int calculateMemoryCacheSize(Context context) { ActivityManager am = getService(context, ACTIVITY_SERVICE); boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0; int memoryClass = am.getMemoryClass(); if (largeHeap && SDK_INT >= HONEYCOMB) { memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am); } // Target ~15% of the available heap. return 1024 * 1024 * memoryClass / 7; } static boolean isAirplaneModeOn(Context context) { ContentResolver contentResolver = context.getContentResolver(); return Settings.System.getInt(contentResolver, AIRPLANE_MODE_ON, 0) != 0; } @SuppressWarnings("unchecked") static T getService(Context context, String service) { return (T) context.getSystemService(service); } static boolean hasPermission(Context context, String permission) { return context.checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; } static byte[] toByteArray(InputStream input) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024 * 4]; int n; while (-1 != (n = input.read(buffer))) { byteArrayOutputStream.write(buffer, 0, n); } return byteArrayOutputStream.toByteArray(); } static boolean isWebPFile(InputStream stream) throws IOException { byte[] fileHeaderBytes = new byte[WEBP_FILE_HEADER_SIZE]; boolean isWebPFile = false; if (stream.read(fileHeaderBytes, 0, WEBP_FILE_HEADER_SIZE) == WEBP_FILE_HEADER_SIZE) { // If a file's header starts with RIFF and end with WEBP, the file is a WebP file isWebPFile = WEBP_FILE_HEADER_RIFF.equals(new String(fileHeaderBytes, 0, 4, "US-ASCII")) && WEBP_FILE_HEADER_WEBP.equals(new String(fileHeaderBytes, 8, 4, "US-ASCII")); } return isWebPFile; } static int getResourceId(Resources resources, Request data) throws FileNotFoundException { if (data.resourceId != 0 || data.uri == null) { return data.resourceId; } String pkg = data.uri.getAuthority(); if (pkg == null) throw new FileNotFoundException("No package provided: " + data.uri); int id; List segments = data.uri.getPathSegments(); if (segments == null || segments.isEmpty()) { throw new FileNotFoundException("No path segments: " + data.uri); } else if (segments.size() == 1) { try { id = Integer.parseInt(segments.get(0)); } catch (NumberFormatException e) { throw new FileNotFoundException("Last path segment is not a resource ID: " + data.uri); } } else if (segments.size() == 2) { String type = segments.get(0); String name = segments.get(1); id = resources.getIdentifier(name, type, pkg); } else { throw new FileNotFoundException("More than two path segments: " + data.uri); } return id; } static Resources getResources(Context context, Request data) throws FileNotFoundException { if (data.resourceId != 0 || data.uri == null) { return context.getResources(); } String pkg = data.uri.getAuthority(); if (pkg == null) throw new FileNotFoundException("No package provided: " + data.uri); try { PackageManager pm = context.getPackageManager(); return pm.getResourcesForApplication(pkg); } catch (PackageManager.NameNotFoundException e) { throw new FileNotFoundException("Unable to obtain resources for package: " + data.uri); } } @TargetApi(HONEYCOMB) private static class ActivityManagerHoneycomb { static int getLargeMemoryClass(ActivityManager activityManager) { return activityManager.getLargeMemoryClass(); } } static class PicassoThreadFactory implements ThreadFactory { @SuppressWarnings("NullableProblems") public Thread newThread(Runnable r) { return new PicassoThread(r); } } private static class PicassoThread extends Thread { public PicassoThread(Runnable r) { super(r); } @Override public void run() { Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND); super.run(); } } @TargetApi(HONEYCOMB_MR1) private static class BitmapHoneycombMR1 { static int getByteCount(Bitmap bitmap) { return bitmap.getByteCount(); } } private static class OkHttpLoaderCreator { static Downloader create(Context context) { return new OkHttpDownloader(context); } } } ================================================ FILE: settings.gradle ================================================ include ':app'