├── .classpath ├── .project ├── .settings └── org.eclipse.core.resources.prefs ├── AndroidManifest.xml ├── README.md ├── assets └── test_gif.gif ├── gen └── com │ └── ftxgame │ └── ftxcn │ ├── BuildConfig.java │ └── R.java ├── ic_launcher-web.png ├── libs ├── android-support-v4.jar ├── disklrucache-2.0.2.jar └── okhttp-1.1.1.jar ├── lint.xml ├── proguard-project.txt ├── project.properties ├── res ├── drawable-hdpi │ ├── empty_newsthumb.png │ ├── ic_launcher.png │ ├── loader_0.png │ ├── loader_1.png │ ├── loader_2.png │ ├── loader_3.png │ ├── loader_4.png │ ├── loader_5.png │ ├── loader_6.png │ └── loader_7.png ├── drawable-ldpi │ └── ic_launcher.png ├── drawable-mdpi │ ├── ic_launcher.png │ ├── test.jpg │ └── test_gif.gif ├── drawable-xhdpi │ └── ic_launcher.png ├── drawable │ └── loader.xml ├── layout │ └── activity_main.xml ├── menu │ └── activity_main.xml ├── values-v11 │ └── styles.xml ├── values-v14 │ └── styles.xml └── values │ ├── attribs.xml │ ├── strings.xml │ └── styles.xml └── src ├── android └── support │ └── util │ ├── Base64.java │ └── LruCache.java ├── com ├── foxykeep │ └── datadroid │ │ ├── exception │ │ ├── ConnectionException.java │ │ ├── CustomRequestException.java │ │ └── DataException.java │ │ ├── internal │ │ └── network │ │ │ └── NetworkConnectionImpl.java │ │ ├── network │ │ ├── NetworkConnection.java │ │ └── UserAgentUtils.java │ │ ├── requestmanager │ │ ├── Request.java │ │ └── RequestManager.java │ │ ├── service │ │ ├── MultiThreadedIntentService.java │ │ └── RequestService.java │ │ └── util │ │ ├── DataDroidLog.java │ │ └── ObjectUtils.java └── ftxgame │ └── ftxcn │ └── MainActivity.java └── net └── frakbot ├── cache └── CacheHelper.java ├── imageviewex ├── Converters.java ├── ImageAlign.java ├── ImageViewEx.java ├── ImageViewNext.java ├── broadcastreceiver │ └── ConnectivityChangeBroadcastReceiver.java ├── listener │ └── ImageViewExRequestListener.java ├── operation │ ├── ImageDiskCacheOperation.java │ ├── ImageDownloadOperation.java │ └── ImageMemCacheOperation.java ├── requestmanager │ ├── ImageViewExRequestFactory.java │ └── ImageViewExRequestManager.java └── service │ └── ImageViewExService.java └── remote └── RemoteHelper.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | GifImageViewEx 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/project.properties=UTF-8 3 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ------- 2 | 项目说明 3 | 4 | android解析本地或者URL的gif图片或者jpg、png等图片,基于ImageViewEx。 5 | 6 | 简单介绍 7 | 8 | 1、本项目是采用了ImageViewEx插件 9 | [https://github.com/frapontillo/ImageViewEx](https://github.com/frapontillo/ImageViewEx "ImageViewEx") 10 | 2、为了保证项目的可运行性,特将ImageViewEx插件所需要的第三方jar包和android library都加入到本项目的源码中 11 | 项目包括以下几点 12 | 13 | 1、本地gif读取播放 14 | 2、本地jpg/png图片读取 15 | 3、URL gif图片读取播放 16 | 4、URL jpg/png图片读取 17 | 18 | -------------------------------------------------------------------------------- /assets/test_gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/assets/test_gif.gif -------------------------------------------------------------------------------- /gen/com/ftxgame/ftxcn/BuildConfig.java: -------------------------------------------------------------------------------- 1 | /** Automatically generated file. DO NOT MODIFY */ 2 | package com.ftxgame.ftxcn; 3 | 4 | public final class BuildConfig { 5 | public final static boolean DEBUG = true; 6 | } -------------------------------------------------------------------------------- /gen/com/ftxgame/ftxcn/R.java: -------------------------------------------------------------------------------- 1 | /* AUTO-GENERATED FILE. DO NOT MODIFY. 2 | * 3 | * This class was automatically generated by the 4 | * aapt tool from the resource data it found. It 5 | * should not be modified by hand. 6 | */ 7 | 8 | package com.ftxgame.ftxcn; 9 | 10 | public final class R { 11 | public static final class attr { 12 | /**

Must be a boolean value, either "true" or "false". 13 |

This may also be a reference to a resource (in the form 14 | "@[package:]type:name") or 15 | theme attribute (in the form 16 | "?[package:][type:]name") 17 | containing a value of this type. 18 | */ 19 | public static final int adjustViewBounds=0x7f010000; 20 | /**

May be a reference to another resource, in the form "@[+][package:]type:name" 21 | or to a theme attribute in the form "?[package:][type:]name". 22 |

May be a color value, in the form of "#rgb", "#argb", 23 | "#rrggbb", or "#aarrggbb". 24 | */ 25 | public static final int emptyDrawable=0x7f010001; 26 | /**

Must be one of the following constant values.

27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
ConstantValueDescription
none0
horizontal1
vertical2
36 | */ 37 | public static final int fillDirection=0x7f010002; 38 | } 39 | public static final class drawable { 40 | public static final int empty_newsthumb=0x7f020000; 41 | public static final int ic_launcher=0x7f020001; 42 | public static final int loader=0x7f020002; 43 | public static final int loader_0=0x7f020003; 44 | public static final int loader_1=0x7f020004; 45 | public static final int loader_2=0x7f020005; 46 | public static final int loader_3=0x7f020006; 47 | public static final int loader_4=0x7f020007; 48 | public static final int loader_5=0x7f020008; 49 | public static final int loader_6=0x7f020009; 50 | public static final int loader_7=0x7f02000a; 51 | public static final int test=0x7f02000b; 52 | public static final int test_gif=0x7f02000c; 53 | } 54 | public static final class id { 55 | public static final int horizontal=0x7f040001; 56 | public static final int imageViewEx1=0x7f040003; 57 | public static final int imageViewEx2=0x7f040004; 58 | public static final int imageViewNext1=0x7f040005; 59 | public static final int imageViewNext2=0x7f040006; 60 | public static final int menu_settings=0x7f040007; 61 | public static final int none=0x7f040000; 62 | public static final int vertical=0x7f040002; 63 | } 64 | public static final class layout { 65 | public static final int activity_main=0x7f030000; 66 | } 67 | public static final class menu { 68 | public static final int activity_main=0x7f070000; 69 | } 70 | public static final class string { 71 | public static final int app_name=0x7f050000; 72 | public static final int hello_world=0x7f050001; 73 | public static final int menu_settings=0x7f050002; 74 | } 75 | public static final class style { 76 | /** 77 | Base application theme, dependent on API level. This theme is replaced 78 | by AppBaseTheme from res/values-vXX/styles.xml on newer devices. 79 | 80 | 81 | Theme customizations available in newer API levels can go in 82 | res/values-vXX/styles.xml, while customizations related to 83 | backward-compatibility can go here. 84 | 85 | 86 | Base application theme for API 11+. This theme completely replaces 87 | AppBaseTheme from res/values/styles.xml on API 11+ devices. 88 | 89 | API 11 theme customizations can go here. 90 | 91 | Base application theme for API 14+. This theme completely replaces 92 | AppBaseTheme from BOTH res/values/styles.xml and 93 | res/values-v11/styles.xml on API 14+ devices. 94 | 95 | API 14 theme customizations can go here. 96 | */ 97 | public static final int AppBaseTheme=0x7f060000; 98 | /** Application theme. 99 | All customizations that are NOT specific to a particular API-level can go here. 100 | */ 101 | public static final int AppTheme=0x7f060001; 102 | } 103 | public static final class styleable { 104 | /** Attributes that can be used with a ImageViewEx. 105 |

Includes the following attributes:

106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
AttributeDescription
{@link #ImageViewEx_adjustViewBounds com.ftxgame.ftxcn:adjustViewBounds}
{@link #ImageViewEx_emptyDrawable com.ftxgame.ftxcn:emptyDrawable}
{@link #ImageViewEx_fillDirection com.ftxgame.ftxcn:fillDirection}
114 | @see #ImageViewEx_adjustViewBounds 115 | @see #ImageViewEx_emptyDrawable 116 | @see #ImageViewEx_fillDirection 117 | */ 118 | public static final int[] ImageViewEx = { 119 | 0x7f010000, 0x7f010001, 0x7f010002 120 | }; 121 | /** 122 |

This symbol is the offset where the {@link com.ftxgame.ftxcn.R.attr#adjustViewBounds} 123 | attribute's value can be found in the {@link #ImageViewEx} array. 124 | 125 | 126 |

Must be a boolean value, either "true" or "false". 127 |

This may also be a reference to a resource (in the form 128 | "@[package:]type:name") or 129 | theme attribute (in the form 130 | "?[package:][type:]name") 131 | containing a value of this type. 132 | @attr name android:adjustViewBounds 133 | */ 134 | public static final int ImageViewEx_adjustViewBounds = 0; 135 | /** 136 |

This symbol is the offset where the {@link com.ftxgame.ftxcn.R.attr#emptyDrawable} 137 | attribute's value can be found in the {@link #ImageViewEx} array. 138 | 139 | 140 |

May be a reference to another resource, in the form "@[+][package:]type:name" 141 | or to a theme attribute in the form "?[package:][type:]name". 142 |

May be a color value, in the form of "#rgb", "#argb", 143 | "#rrggbb", or "#aarrggbb". 144 | @attr name android:emptyDrawable 145 | */ 146 | public static final int ImageViewEx_emptyDrawable = 1; 147 | /** 148 |

This symbol is the offset where the {@link com.ftxgame.ftxcn.R.attr#fillDirection} 149 | attribute's value can be found in the {@link #ImageViewEx} array. 150 | 151 | 152 |

Must be one of the following constant values.

153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 |
ConstantValueDescription
none0
horizontal1
vertical2
162 | @attr name android:fillDirection 163 | */ 164 | public static final int ImageViewEx_fillDirection = 2; 165 | }; 166 | } 167 | -------------------------------------------------------------------------------- /ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/ic_launcher-web.png -------------------------------------------------------------------------------- /libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/libs/android-support-v4.jar -------------------------------------------------------------------------------- /libs/disklrucache-2.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/libs/disklrucache-2.0.2.jar -------------------------------------------------------------------------------- /libs/okhttp-1.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/libs/okhttp-1.1.1.jar -------------------------------------------------------------------------------- /lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-17 15 | -------------------------------------------------------------------------------- /res/drawable-hdpi/empty_newsthumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-hdpi/empty_newsthumb.png -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-hdpi/loader_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-hdpi/loader_0.png -------------------------------------------------------------------------------- /res/drawable-hdpi/loader_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-hdpi/loader_1.png -------------------------------------------------------------------------------- /res/drawable-hdpi/loader_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-hdpi/loader_2.png -------------------------------------------------------------------------------- /res/drawable-hdpi/loader_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-hdpi/loader_3.png -------------------------------------------------------------------------------- /res/drawable-hdpi/loader_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-hdpi/loader_4.png -------------------------------------------------------------------------------- /res/drawable-hdpi/loader_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-hdpi/loader_5.png -------------------------------------------------------------------------------- /res/drawable-hdpi/loader_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-hdpi/loader_6.png -------------------------------------------------------------------------------- /res/drawable-hdpi/loader_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-hdpi/loader_7.png -------------------------------------------------------------------------------- /res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-mdpi/test.jpg -------------------------------------------------------------------------------- /res/drawable-mdpi/test_gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-mdpi/test_gif.gif -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangwenmai/GifImageViewEx/de37b1947a90917e01e5cfa465838a7f5b48630a/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable/loader.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 18 | 19 | 25 | 26 | 33 | 34 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /res/menu/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /res/values/attribs.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.ftxgame.ftxcn 5 | Hello world! 6 | Settings 7 | 8 | -------------------------------------------------------------------------------- /res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /src/android/support/util/Base64.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package android.support.util; 18 | 19 | import android.util.Base64OutputStream; 20 | 21 | import java.io.UnsupportedEncodingException; 22 | 23 | /** 24 | * Utilities for encoding and decoding the Base64 representation of binary data. See RFCs 2045 and 3548. 27 | *

28 | * Implementation copied from the Froyo implementation of {@link Base64} 29 | */ 30 | public final class Base64 { 31 | /** 32 | * Default values for encoder/decoder flags. 33 | */ 34 | public static final int DEFAULT = 0; 35 | 36 | /** 37 | * Encoder flag bit to omit the padding '=' characters at the end of the output (if any). 38 | */ 39 | public static final int NO_PADDING = 1; 40 | 41 | /** 42 | * Encoder flag bit to omit all line terminators (i.e., the output will be on one long line). 43 | */ 44 | public static final int NO_WRAP = 2; 45 | 46 | /** 47 | * Encoder flag bit to indicate lines should be terminated with a CRLF pair instead of just an 48 | * LF. Has no effect if {@code NO_WRAP} is specified as well. 49 | */ 50 | public static final int CRLF = 4; 51 | 52 | /** 53 | * Encoder/decoder flag bit to indicate using the "URL and filename safe" variant of Base64 (see 54 | * RFC 3548 section 4) where {@code -} and {@code _} are used in place of {@code +} and 55 | * {@code /}. 56 | */ 57 | public static final int URL_SAFE = 8; 58 | 59 | /** 60 | * Flag to pass to {@link Base64OutputStream} to indicate that it should not close the output 61 | * stream it is wrapping when it itself is closed. 62 | */ 63 | public static final int NO_CLOSE = 16; 64 | 65 | // -------------------------------------------------------- 66 | // shared code 67 | // -------------------------------------------------------- 68 | 69 | /* package */static abstract class Coder { 70 | public byte[] output; 71 | public int op; 72 | 73 | /** 74 | * Encode/decode another block of input data. this.output is provided by the caller, and 75 | * must be big enough to hold all the coded data. On exit, this.opwill be set to the length 76 | * of the coded data. 77 | * 78 | * @param finish true if this is the final call to process for this object. Will finalize 79 | * the coder state and include any final bytes in the output. 80 | * @return true if the input so far is good; false if some error has been detected in the 81 | * input stream.. 82 | */ 83 | public abstract boolean process(byte[] input, int offset, int len, boolean finish); 84 | 85 | /** 86 | * @return the maximum number of bytes a call to process() could produce for the given 87 | * number of input bytes. This may be an overestimate. 88 | */ 89 | public abstract int maxOutputSize(int len); 90 | } 91 | 92 | // -------------------------------------------------------- 93 | // decoding 94 | // -------------------------------------------------------- 95 | 96 | /** 97 | * Decode the Base64-encoded data in input and return the data in a new byte array. 98 | *

99 | * The padding '=' characters at the end are considered optional, but if any are present, there 100 | * must be the correct number of them. 101 | * 102 | * @param str the input String to decode, which is converted to bytes using the default charset 103 | * @param flags controls certain features of the decoded output. Pass {@code DEFAULT} to decode 104 | * standard Base64. 105 | * @throws IllegalArgumentException if the input contains incorrect padding 106 | */ 107 | public static byte[] decode(String str, int flags) { 108 | return decode(str.getBytes(), flags); 109 | } 110 | 111 | /** 112 | * Decode the Base64-encoded data in input and return the data in a new byte array. 113 | *

114 | * The padding '=' characters at the end are considered optional, but if any are present, there 115 | * must be the correct number of them. 116 | * 117 | * @param input the input array to decode 118 | * @param flags controls certain features of the decoded output. Pass {@code DEFAULT} to decode 119 | * standard Base64. 120 | * @throws IllegalArgumentException if the input contains incorrect padding 121 | */ 122 | public static byte[] decode(byte[] input, int flags) { 123 | return decode(input, 0, input.length, flags); 124 | } 125 | 126 | /** 127 | * Decode the Base64-encoded data in input and return the data in a new byte array. 128 | *

129 | * The padding '=' characters at the end are considered optional, but if any are present, there 130 | * must be the correct number of them. 131 | * 132 | * @param input the data to decode 133 | * @param offset the position within the input array at which to start 134 | * @param len the number of bytes of input to decode 135 | * @param flags controls certain features of the decoded output. Pass {@code DEFAULT} to decode 136 | * standard Base64. 137 | * @throws IllegalArgumentException if the input contains incorrect padding 138 | */ 139 | public static byte[] decode(byte[] input, int offset, int len, int flags) { 140 | // Allocate space for the most data the input could represent. 141 | // (It could contain less if it contains whitespace, etc.) 142 | Decoder decoder = new Decoder(flags, new byte[len * 3 / 4]); 143 | 144 | if (!decoder.process(input, offset, len, true)) { 145 | throw new IllegalArgumentException("bad base-64"); 146 | } 147 | 148 | // Maybe we got lucky and allocated exactly enough output space. 149 | if (decoder.op == decoder.output.length) { 150 | return decoder.output; 151 | } 152 | 153 | // Need to shorten the array, so allocate a new one of the 154 | // right size and copy. 155 | byte[] temp = new byte[decoder.op]; 156 | System.arraycopy(decoder.output, 0, temp, 0, decoder.op); 157 | return temp; 158 | } 159 | 160 | /* package */static class Decoder extends Coder { 161 | /** 162 | * Lookup table for turning bytes into their position in the Base64 alphabet. 163 | */ 164 | private static final int DECODE[] = { 165 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 166 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 167 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 168 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, 169 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 170 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 171 | -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 172 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, 173 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 174 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 175 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 176 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 177 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 178 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 179 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 180 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 181 | }; 182 | 183 | /** 184 | * Decode lookup table for the "web safe" variant (RFC 3548 sec. 4) where - and _ replace + 185 | * and /. 186 | */ 187 | private static final int DECODE_WEBSAFE[] = { 188 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 189 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 190 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, 191 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, 192 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 193 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, 194 | -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 195 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, 196 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 197 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 198 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 199 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 200 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 201 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 202 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 203 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 204 | }; 205 | 206 | /** Non-data values in the DECODE arrays. */ 207 | private static final int SKIP = -1; 208 | private static final int EQUALS = -2; 209 | 210 | /** 211 | * States 0-3 are reading through the next input tuple. State 4 is having read one '=' and 212 | * expecting exactly one more. State 5 is expecting no more data or padding characters in 213 | * the input. State 6 is the error state; an error has been detected in the input and no 214 | * future input can "fix" it. 215 | */ 216 | private int state; // state number (0 to 6) 217 | private int value; 218 | 219 | final private int[] alphabet; 220 | 221 | public Decoder(int flags, byte[] output) { 222 | this.output = output; 223 | 224 | alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; 225 | state = 0; 226 | value = 0; 227 | } 228 | 229 | /** 230 | * @return an overestimate for the number of bytes {@code len} bytes could decode to. 231 | */ 232 | @Override 233 | public int maxOutputSize(int len) { 234 | return len * 3 / 4 + 10; 235 | } 236 | 237 | /** 238 | * Decode another block of input data. 239 | * 240 | * @return true if the state machine is still healthy. false if bad base-64 data has been 241 | * detected in the input stream. 242 | */ 243 | @Override 244 | public boolean process(byte[] input, int offset, int len, boolean finish) { 245 | if (this.state == 6) { 246 | return false; 247 | } 248 | 249 | int p = offset; 250 | len += offset; 251 | 252 | // Using local variables makes the decoder about 12% 253 | // faster than if we manipulate the member variables in 254 | // the loop. (Even alphabet makes a measurable 255 | // difference, which is somewhat surprising to me since 256 | // the member variable is final.) 257 | int state = this.state; 258 | int value = this.value; 259 | int op = 0; 260 | final byte[] output = this.output; 261 | final int[] alphabet = this.alphabet; 262 | 263 | while (p < len) { 264 | // Try the fast path: we're starting a new tuple and the 265 | // next four bytes of the input stream are all data 266 | // bytes. This corresponds to going through states 267 | // 0-1-2-3-0. We expect to use this method for most of 268 | // the data. 269 | // 270 | // If any of the next four bytes of input are non-data 271 | // (whitespace, etc.), value will end up negative. (All 272 | // the non-data values in decode are small negative 273 | // numbers, so shifting any of them up and or'ing them 274 | // together will result in a value with its top bit set.) 275 | // 276 | // You can remove this whole block and the output should 277 | // be the same, just slower. 278 | if (state == 0) { 279 | while (p + 4 <= len && 280 | (value = ((alphabet[input[p] & 0xff] << 18) | 281 | (alphabet[input[p + 1] & 0xff] << 12) | 282 | (alphabet[input[p + 2] & 0xff] << 6) | 283 | (alphabet[input[p + 3] & 0xff]))) >= 0) { 284 | output[op + 2] = (byte) value; 285 | output[op + 1] = (byte) (value >> 8); 286 | output[op] = (byte) (value >> 16); 287 | op += 3; 288 | p += 4; 289 | } 290 | if (p >= len) { 291 | break; 292 | } 293 | } 294 | 295 | // The fast path isn't available -- either we've read a 296 | // partial tuple, or the next four input bytes aren't all 297 | // data, or whatever. Fall back to the slower state 298 | // machine implementation. 299 | 300 | int d = alphabet[input[p++] & 0xff]; 301 | 302 | switch (state) { 303 | case 0: 304 | if (d >= 0) { 305 | value = d; 306 | ++state; 307 | } else if (d != SKIP) { 308 | this.state = 6; 309 | return false; 310 | } 311 | break; 312 | 313 | case 1: 314 | if (d >= 0) { 315 | value = (value << 6) | d; 316 | ++state; 317 | } else if (d != SKIP) { 318 | this.state = 6; 319 | return false; 320 | } 321 | break; 322 | 323 | case 2: 324 | if (d >= 0) { 325 | value = (value << 6) | d; 326 | ++state; 327 | } else if (d == EQUALS) { 328 | // Emit the last (partial) output tuple; 329 | // expect exactly one more padding character. 330 | output[op++] = (byte) (value >> 4); 331 | state = 4; 332 | } else if (d != SKIP) { 333 | this.state = 6; 334 | return false; 335 | } 336 | break; 337 | 338 | case 3: 339 | if (d >= 0) { 340 | // Emit the output triple and return to state 0. 341 | value = (value << 6) | d; 342 | output[op + 2] = (byte) value; 343 | output[op + 1] = (byte) (value >> 8); 344 | output[op] = (byte) (value >> 16); 345 | op += 3; 346 | state = 0; 347 | } else if (d == EQUALS) { 348 | // Emit the last (partial) output tuple; 349 | // expect no further data or padding characters. 350 | output[op + 1] = (byte) (value >> 2); 351 | output[op] = (byte) (value >> 10); 352 | op += 2; 353 | state = 5; 354 | } else if (d != SKIP) { 355 | this.state = 6; 356 | return false; 357 | } 358 | break; 359 | 360 | case 4: 361 | if (d == EQUALS) { 362 | ++state; 363 | } else if (d != SKIP) { 364 | this.state = 6; 365 | return false; 366 | } 367 | break; 368 | 369 | case 5: 370 | if (d != SKIP) { 371 | this.state = 6; 372 | return false; 373 | } 374 | break; 375 | } 376 | } 377 | 378 | if (!finish) { 379 | // We're out of input, but a future call could provide 380 | // more. 381 | this.state = state; 382 | this.value = value; 383 | this.op = op; 384 | return true; 385 | } 386 | 387 | // Done reading input. Now figure out where we are left in 388 | // the state machine and finish up. 389 | 390 | switch (state) { 391 | case 0: 392 | // Output length is a multiple of three. Fine. 393 | break; 394 | case 1: 395 | // Read one extra input byte, which isn't enough to 396 | // make another output byte. Illegal. 397 | this.state = 6; 398 | return false; 399 | case 2: 400 | // Read two extra input bytes, enough to emit 1 more 401 | // output byte. Fine. 402 | output[op++] = (byte) (value >> 4); 403 | break; 404 | case 3: 405 | // Read three extra input bytes, enough to emit 2 more 406 | // output bytes. Fine. 407 | output[op++] = (byte) (value >> 10); 408 | output[op++] = (byte) (value >> 2); 409 | break; 410 | case 4: 411 | // Read one padding '=' when we expected 2. Illegal. 412 | this.state = 6; 413 | return false; 414 | case 5: 415 | // Read all the padding '='s we expected and no more. 416 | // Fine. 417 | break; 418 | } 419 | 420 | this.state = state; 421 | this.op = op; 422 | return true; 423 | } 424 | } 425 | 426 | // -------------------------------------------------------- 427 | // encoding 428 | // -------------------------------------------------------- 429 | 430 | /** 431 | * Base64-encode the given data and return a newly allocated String with the result. 432 | * 433 | * @param input the data to encode 434 | * @param flags controls certain features of the encoded output. Passing {@code DEFAULT} results 435 | * in output that adheres to RFC 2045. 436 | */ 437 | public static String encodeToString(byte[] input, int flags) { 438 | try { 439 | return new String(encode(input, flags), "US-ASCII"); 440 | } catch (UnsupportedEncodingException e) { 441 | // US-ASCII is guaranteed to be available. 442 | throw new AssertionError(e); 443 | } 444 | } 445 | 446 | /** 447 | * Base64-encode the given data and return a newly allocated String with the result. 448 | * 449 | * @param input the data to encode 450 | * @param offset the position within the input array at which to start 451 | * @param len the number of bytes of input to encode 452 | * @param flags controls certain features of the encoded output. Passing {@code DEFAULT} results 453 | * in output that adheres to RFC 2045. 454 | */ 455 | public static String encodeToString(byte[] input, int offset, int len, int flags) { 456 | try { 457 | return new String(encode(input, offset, len, flags), "US-ASCII"); 458 | } catch (UnsupportedEncodingException e) { 459 | // US-ASCII is guaranteed to be available. 460 | throw new AssertionError(e); 461 | } 462 | } 463 | 464 | /** 465 | * Base64-encode the given data and return a newly allocated byte[] with the result. 466 | * 467 | * @param input the data to encode 468 | * @param flags controls certain features of the encoded output. Passing {@code DEFAULT} results 469 | * in output that adheres to RFC 2045. 470 | */ 471 | public static byte[] encode(byte[] input, int flags) { 472 | return encode(input, 0, input.length, flags); 473 | } 474 | 475 | /** 476 | * Base64-encode the given data and return a newly allocated byte[] with the result. 477 | * 478 | * @param input the data to encode 479 | * @param offset the position within the input array at which to start 480 | * @param len the number of bytes of input to encode 481 | * @param flags controls certain features of the encoded output. Passing {@code DEFAULT} results 482 | * in output that adheres to RFC 2045. 483 | */ 484 | public static byte[] encode(byte[] input, int offset, int len, int flags) { 485 | Encoder encoder = new Encoder(flags, null); 486 | 487 | // Compute the exact length of the array we will produce. 488 | int output_len = len / 3 * 4; 489 | 490 | // Account for the tail of the data and the padding bytes, if any. 491 | if (encoder.do_padding) { 492 | if (len % 3 > 0) { 493 | output_len += 4; 494 | } 495 | } else { 496 | switch (len % 3) { 497 | case 0: 498 | break; 499 | case 1: 500 | output_len += 2; 501 | break; 502 | case 2: 503 | output_len += 3; 504 | break; 505 | } 506 | } 507 | 508 | // Account for the newlines, if any. 509 | if (encoder.do_newline && len > 0) { 510 | output_len += (((len - 1) / (3 * Encoder.LINE_GROUPS)) + 1) * 511 | (encoder.do_cr ? 2 : 1); 512 | } 513 | 514 | encoder.output = new byte[output_len]; 515 | encoder.process(input, offset, len, true); 516 | 517 | assert encoder.op == output_len; 518 | 519 | return encoder.output; 520 | } 521 | 522 | /* package */static class Encoder extends Coder { 523 | /** 524 | * Emit a new line every this many output tuples. Corresponds to a 76-character line length 525 | * (the maximum allowable according to RFC 526 | * 2045). 527 | */ 528 | public static final int LINE_GROUPS = 19; 529 | 530 | /** 531 | * Lookup table for turning Base64 alphabet positions (6 bits) into output bytes. 532 | */ 533 | private static final byte ENCODE[] = { 534 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 535 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 536 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 537 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', 538 | }; 539 | 540 | /** 541 | * Lookup table for turning Base64 alphabet positions (6 bits) into output bytes. 542 | */ 543 | private static final byte ENCODE_WEBSAFE[] = { 544 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 545 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 546 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 547 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', 548 | }; 549 | 550 | final private byte[] tail; 551 | /* package */int tailLen; 552 | private int count; 553 | 554 | final public boolean do_padding; 555 | final public boolean do_newline; 556 | final public boolean do_cr; 557 | final private byte[] alphabet; 558 | 559 | public Encoder(int flags, byte[] output) { 560 | this.output = output; 561 | 562 | do_padding = (flags & NO_PADDING) == 0; 563 | do_newline = (flags & NO_WRAP) == 0; 564 | do_cr = (flags & CRLF) != 0; 565 | alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; 566 | 567 | tail = new byte[2]; 568 | tailLen = 0; 569 | 570 | count = do_newline ? LINE_GROUPS : -1; 571 | } 572 | 573 | /** 574 | * @return an overestimate for the number of bytes {@code len} bytes could encode to. 575 | */ 576 | @Override 577 | public int maxOutputSize(int len) { 578 | return len * 8 / 5 + 10; 579 | } 580 | 581 | @Override 582 | public boolean process(byte[] input, int offset, int len, boolean finish) { 583 | // Using local variables makes the encoder about 9% faster. 584 | final byte[] alphabet = this.alphabet; 585 | final byte[] output = this.output; 586 | int op = 0; 587 | int count = this.count; 588 | 589 | int p = offset; 590 | len += offset; 591 | int v = -1; 592 | 593 | // First we need to concatenate the tail of the previous call 594 | // with any input bytes available now and see if we can empty 595 | // the tail. 596 | 597 | switch (tailLen) { 598 | case 0: 599 | // There was no tail. 600 | break; 601 | 602 | case 1: 603 | if (p + 2 <= len) { 604 | // A 1-byte tail with at least 2 bytes of 605 | // input available now. 606 | v = ((tail[0] & 0xff) << 16) | 607 | ((input[p++] & 0xff) << 8) | 608 | (input[p++] & 0xff); 609 | tailLen = 0; 610 | } 611 | break; 612 | 613 | case 2: 614 | if (p + 1 <= len) { 615 | // A 2-byte tail with at least 1 byte of input. 616 | v = ((tail[0] & 0xff) << 16) | 617 | ((tail[1] & 0xff) << 8) | 618 | (input[p++] & 0xff); 619 | tailLen = 0; 620 | } 621 | break; 622 | } 623 | 624 | if (v != -1) { 625 | output[op++] = alphabet[(v >> 18) & 0x3f]; 626 | output[op++] = alphabet[(v >> 12) & 0x3f]; 627 | output[op++] = alphabet[(v >> 6) & 0x3f]; 628 | output[op++] = alphabet[v & 0x3f]; 629 | if (--count == 0) { 630 | if (do_cr) { 631 | output[op++] = '\r'; 632 | } 633 | output[op++] = '\n'; 634 | count = LINE_GROUPS; 635 | } 636 | } 637 | 638 | // At this point either there is no tail, or there are fewer 639 | // than 3 bytes of input available. 640 | 641 | // The main loop, turning 3 input bytes into 4 output bytes on 642 | // each iteration. 643 | while (p + 3 <= len) { 644 | v = ((input[p] & 0xff) << 16) | 645 | ((input[p + 1] & 0xff) << 8) | 646 | (input[p + 2] & 0xff); 647 | output[op] = alphabet[(v >> 18) & 0x3f]; 648 | output[op + 1] = alphabet[(v >> 12) & 0x3f]; 649 | output[op + 2] = alphabet[(v >> 6) & 0x3f]; 650 | output[op + 3] = alphabet[v & 0x3f]; 651 | p += 3; 652 | op += 4; 653 | if (--count == 0) { 654 | if (do_cr) { 655 | output[op++] = '\r'; 656 | } 657 | output[op++] = '\n'; 658 | count = LINE_GROUPS; 659 | } 660 | } 661 | 662 | if (finish) { 663 | // Finish up the tail of the input. Note that we need to 664 | // consume any bytes in tail before any bytes 665 | // remaining in input; there should be at most two bytes 666 | // total. 667 | 668 | if (p - tailLen == len - 1) { 669 | int t = 0; 670 | v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; 671 | tailLen -= t; 672 | output[op++] = alphabet[(v >> 6) & 0x3f]; 673 | output[op++] = alphabet[v & 0x3f]; 674 | if (do_padding) { 675 | output[op++] = '='; 676 | output[op++] = '='; 677 | } 678 | if (do_newline) { 679 | if (do_cr) { 680 | output[op++] = '\r'; 681 | } 682 | output[op++] = '\n'; 683 | } 684 | } else if (p - tailLen == len - 2) { 685 | int t = 0; 686 | v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | 687 | (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); 688 | tailLen -= t; 689 | output[op++] = alphabet[(v >> 12) & 0x3f]; 690 | output[op++] = alphabet[(v >> 6) & 0x3f]; 691 | output[op++] = alphabet[v & 0x3f]; 692 | if (do_padding) { 693 | output[op++] = '='; 694 | } 695 | if (do_newline) { 696 | if (do_cr) { 697 | output[op++] = '\r'; 698 | } 699 | output[op++] = '\n'; 700 | } 701 | } else if (do_newline && op > 0 && count != LINE_GROUPS) { 702 | if (do_cr) { 703 | output[op++] = '\r'; 704 | } 705 | output[op++] = '\n'; 706 | } 707 | 708 | assert tailLen == 0; 709 | assert p == len; 710 | } else { 711 | // Save the leftovers in tail to be consumed on the next 712 | // call to encodeInternal. 713 | 714 | if (p == len - 1) { 715 | tail[tailLen++] = input[p]; 716 | } else if (p == len - 2) { 717 | tail[tailLen++] = input[p]; 718 | tail[tailLen++] = input[p + 1]; 719 | } 720 | } 721 | 722 | this.op = op; 723 | this.count = count; 724 | 725 | return true; 726 | } 727 | } 728 | 729 | private Base64() { 730 | } // don't instantiate 731 | } 732 | -------------------------------------------------------------------------------- /src/android/support/util/LruCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package android.support.util; 18 | 19 | import java.util.LinkedHashMap; 20 | import java.util.Locale; 21 | import java.util.Map; 22 | 23 | /** 24 | * Static library version of {@link android.util.LruCache}. Used to write apps 25 | * that run on API levels prior to 12. When running on API level 12 or above, 26 | * this implementation is still used; it does not try to switch to the 27 | * framework's implementation. See the framework SDK documentation for a class 28 | * overview. 29 | */ 30 | public class LruCache { 31 | private final LinkedHashMap map; 32 | 33 | /** Size of this cache in units. Not necessarily the number of elements. */ 34 | private int size; 35 | private int maxSize; 36 | 37 | private int putCount; 38 | private int createCount; 39 | private int evictionCount; 40 | private int hitCount; 41 | private int missCount; 42 | 43 | /** 44 | * @param maxSize for caches that do not override {@link #sizeOf}, this is 45 | * the maximum number of entries in the cache. For all other caches, 46 | * this is the maximum sum of the sizes of the entries in this cache. 47 | */ 48 | public LruCache(int maxSize) { 49 | if (maxSize <= 0) { 50 | throw new IllegalArgumentException("maxSize <= 0"); 51 | } 52 | this.maxSize = maxSize; 53 | this.map = new LinkedHashMap(0, 0.75f, true); 54 | } 55 | 56 | /** 57 | * Returns the value for {@code key} if it exists in the cache or can be 58 | * created by {@code #create}. If a value was returned, it is moved to the 59 | * head of the queue. This returns null if a value is not cached and cannot 60 | * be created. 61 | */ 62 | public final V get(K key) { 63 | if (key == null) { 64 | throw new NullPointerException("key == null"); 65 | } 66 | 67 | V mapValue; 68 | synchronized (this) { 69 | mapValue = map.get(key); 70 | if (mapValue != null) { 71 | hitCount++; 72 | return mapValue; 73 | } 74 | missCount++; 75 | } 76 | 77 | /* 78 | * Attempt to create a value. This may take a long time, and the map 79 | * may be different when create() returns. If a conflicting value was 80 | * added to the map while create() was working, we leave that value in 81 | * the map and release the created value. 82 | */ 83 | 84 | V createdValue = create(key); 85 | if (createdValue == null) { 86 | return null; 87 | } 88 | 89 | synchronized (this) { 90 | createCount++; 91 | mapValue = map.put(key, createdValue); 92 | 93 | if (mapValue != null) { 94 | // There was a conflict so undo that last put 95 | map.put(key, mapValue); 96 | } else { 97 | size += safeSizeOf(key, createdValue); 98 | } 99 | } 100 | 101 | if (mapValue != null) { 102 | entryRemoved(false, key, createdValue, mapValue); 103 | return mapValue; 104 | } else { 105 | trimToSize(maxSize); 106 | return createdValue; 107 | } 108 | } 109 | 110 | /** 111 | * Caches {@code value} for {@code key}. The value is moved to the head of 112 | * the queue. 113 | * 114 | * @return the previous value mapped by {@code key}. 115 | */ 116 | public final V put(K key, V value) { 117 | if (key == null || value == null) { 118 | throw new NullPointerException("key == null || value == null"); 119 | } 120 | 121 | V previous; 122 | synchronized (this) { 123 | putCount++; 124 | size += safeSizeOf(key, value); 125 | previous = map.put(key, value); 126 | if (previous != null) { 127 | size -= safeSizeOf(key, previous); 128 | } 129 | } 130 | 131 | if (previous != null) { 132 | entryRemoved(false, key, previous, value); 133 | } 134 | 135 | trimToSize(maxSize); 136 | return previous; 137 | } 138 | 139 | /** 140 | * Remove the eldest entries until the total of remaining entries is at or 141 | * below the requested size. 142 | * 143 | * @param maxSize the maximum size of the cache before returning. May be -1 144 | * to evict even 0-sized elements. 145 | */ 146 | public void trimToSize(int maxSize) { 147 | while (true) { 148 | K key; 149 | V value; 150 | synchronized (this) { 151 | if (size < 0 || (map.isEmpty() && size != 0)) { 152 | throw new IllegalStateException(getClass().getName() 153 | + ".sizeOf() is reporting inconsistent results!"); 154 | } 155 | 156 | if (size <= maxSize || map.isEmpty()) { 157 | break; 158 | } 159 | 160 | Map.Entry toEvict = map.entrySet().iterator().next(); 161 | key = toEvict.getKey(); 162 | value = toEvict.getValue(); 163 | map.remove(key); 164 | size -= safeSizeOf(key, value); 165 | evictionCount++; 166 | } 167 | 168 | entryRemoved(true, key, value, null); 169 | } 170 | } 171 | 172 | /** 173 | * Removes the entry for {@code key} if it exists. 174 | * 175 | * @return the previous value mapped by {@code key}. 176 | */ 177 | public final V remove(K key) { 178 | if (key == null) { 179 | throw new NullPointerException("key == null"); 180 | } 181 | 182 | V previous; 183 | synchronized (this) { 184 | previous = map.remove(key); 185 | if (previous != null) { 186 | size -= safeSizeOf(key, previous); 187 | } 188 | } 189 | 190 | if (previous != null) { 191 | entryRemoved(false, key, previous, null); 192 | } 193 | 194 | return previous; 195 | } 196 | 197 | /** 198 | * Called for entries that have been evicted or removed. This method is 199 | * invoked when a value is evicted to make space, removed by a call to 200 | * {@link #remove}, or replaced by a call to {@link #put}. The default 201 | * implementation does nothing. 202 | * 203 | *

The method is called without synchronization: other threads may 204 | * access the cache while this method is executing. 205 | * 206 | * @param evicted true if the entry is being removed to make space, false 207 | * if the removal was caused by a {@link #put} or {@link #remove}. 208 | * @param newValue the new value for {@code key}, if it exists. If non-null, 209 | * this removal was caused by a {@link #put}. Otherwise it was caused by 210 | * an eviction or a {@link #remove}. 211 | */ 212 | protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {} 213 | 214 | /** 215 | * Called after a cache miss to compute a value for the corresponding key. 216 | * Returns the computed value or null if no value can be computed. The 217 | * default implementation returns null. 218 | * 219 | *

The method is called without synchronization: other threads may 220 | * access the cache while this method is executing. 221 | * 222 | *

If a value for {@code key} exists in the cache when this method 223 | * returns, the created value will be released with {@link #entryRemoved} 224 | * and discarded. This can occur when multiple threads request the same key 225 | * at the same time (causing multiple values to be created), or when one 226 | * thread calls {@link #put} while another is creating a value for the same 227 | * key. 228 | */ 229 | protected V create(K key) { 230 | return null; 231 | } 232 | 233 | private int safeSizeOf(K key, V value) { 234 | int result = sizeOf(key, value); 235 | if (result < 0) { 236 | throw new IllegalStateException("Negative size: " + key + "=" + value); 237 | } 238 | return result; 239 | } 240 | 241 | /** 242 | * Returns the size of the entry for {@code key} and {@code value} in 243 | * user-defined units. The default implementation returns 1 so that size 244 | * is the number of entries and max size is the maximum number of entries. 245 | * 246 | *

An entry's size must not change while it is in the cache. 247 | */ 248 | protected int sizeOf(K key, V value) { 249 | return 1; 250 | } 251 | 252 | /** 253 | * Clear the cache, calling {@link #entryRemoved} on each removed entry. 254 | */ 255 | public final void evictAll() { 256 | trimToSize(-1); // -1 will evict 0-sized elements 257 | } 258 | 259 | /** 260 | * For caches that do not override {@link #sizeOf}, this returns the number 261 | * of entries in the cache. For all other caches, this returns the sum of 262 | * the sizes of the entries in this cache. 263 | */ 264 | public synchronized final int size() { 265 | return size; 266 | } 267 | 268 | /** 269 | * For caches that do not override {@link #sizeOf}, this returns the maximum 270 | * number of entries in the cache. For all other caches, this returns the 271 | * maximum sum of the sizes of the entries in this cache. 272 | */ 273 | public synchronized final int maxSize() { 274 | return maxSize; 275 | } 276 | 277 | /** 278 | * Returns the number of times {@link #get} returned a value. 279 | */ 280 | public synchronized final int hitCount() { 281 | return hitCount; 282 | } 283 | 284 | /** 285 | * Returns the number of times {@link #get} returned null or required a new 286 | * value to be created. 287 | */ 288 | public synchronized final int missCount() { 289 | return missCount; 290 | } 291 | 292 | /** 293 | * Returns the number of times {@link #create(Object)} returned a value. 294 | */ 295 | public synchronized final int createCount() { 296 | return createCount; 297 | } 298 | 299 | /** 300 | * Returns the number of times {@link #put} was called. 301 | */ 302 | public synchronized final int putCount() { 303 | return putCount; 304 | } 305 | 306 | /** 307 | * Returns the number of values that have been evicted. 308 | */ 309 | public synchronized final int evictionCount() { 310 | return evictionCount; 311 | } 312 | 313 | /** 314 | * Returns a copy of the current contents of the cache, ordered from least 315 | * recently accessed to most recently accessed. 316 | */ 317 | public synchronized final Map snapshot() { 318 | return new LinkedHashMap(map); 319 | } 320 | 321 | @Override public synchronized final String toString() { 322 | int accesses = hitCount + missCount; 323 | int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; 324 | return String.format(Locale.US, "LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", 325 | maxSize, hitCount, missCount, hitPercent); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /src/com/foxykeep/datadroid/exception/ConnectionException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2011 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package com.foxykeep.datadroid.exception; 10 | 11 | /** 12 | * Thrown to indicate that a connection error occurred. 13 | * 14 | * @author Foxykeep 15 | */ 16 | public final class ConnectionException extends Exception { 17 | 18 | private static final long serialVersionUID = 4658308128254827562L; 19 | 20 | private String mRedirectionUrl; 21 | private int mStatusCode = -1; 22 | 23 | /** 24 | * Constructs a new {@link ConnectionException} that includes the current stack trace. 25 | */ 26 | public ConnectionException() { 27 | super(); 28 | } 29 | 30 | /** 31 | * Constructs a new {@link ConnectionException} that includes the current stack trace, the 32 | * specified detail message and the specified cause. 33 | * 34 | * @param detailMessage The detail message for this exception. 35 | * @param throwable The cause of this exception. 36 | */ 37 | public ConnectionException(final String detailMessage, final Throwable throwable) { 38 | super(detailMessage, throwable); 39 | } 40 | 41 | /** 42 | * Constructs a new {@link ConnectionException} that includes the current stack trace and the 43 | * specified detail message. 44 | * 45 | * @param detailMessage The detail message for this exception. 46 | */ 47 | public ConnectionException(final String detailMessage) { 48 | super(detailMessage); 49 | } 50 | 51 | /** 52 | * Constructs a new {@link ConnectionException} that includes the current stack trace and the 53 | * specified detail message. 54 | * 55 | * @param detailMessage The detail message for this exception. 56 | * @param redirectionUrl The redirection URL. 57 | */ 58 | public ConnectionException(final String detailMessage, final String redirectionUrl) { 59 | super(detailMessage); 60 | mRedirectionUrl = redirectionUrl; 61 | mStatusCode = 301; 62 | } 63 | 64 | /** 65 | * Constructs a new {@link ConnectionException} that includes the current stack trace and the 66 | * specified detail message and the error status code 67 | * 68 | * @param detailMessage The detail message for this exception. 69 | * @param statusCode The HTTP status code 70 | */ 71 | public ConnectionException(final String detailMessage, final int statusCode) { 72 | super(detailMessage); 73 | mStatusCode = statusCode; 74 | } 75 | 76 | /** 77 | * Constructs a new {@link ConnectionException} that includes the current stack trace and the 78 | * specified cause. 79 | * 80 | * @param throwable The cause of this exception. 81 | */ 82 | public ConnectionException(final Throwable throwable) { 83 | super(throwable); 84 | } 85 | 86 | public String getRedirectionUrl() { 87 | return mRedirectionUrl; 88 | } 89 | 90 | public int getStatusCode() { 91 | return mStatusCode; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/com/foxykeep/datadroid/exception/CustomRequestException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2011 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package com.foxykeep.datadroid.exception; 10 | 11 | /** 12 | * Thrown to indicate that a custom exception occurred. 13 | *

14 | * Subclass this class to create special exceptions for your needs. 15 | * 16 | * @author Foxykeep 17 | */ 18 | public abstract class CustomRequestException extends Exception { 19 | 20 | private static final long serialVersionUID = 4658308128254827562L; 21 | 22 | /** 23 | * Constructs a new {@link CustomRequestException} that includes the current stack trace. 24 | */ 25 | public CustomRequestException() { 26 | super(); 27 | } 28 | 29 | /** 30 | * Constructs a new {@link CustomRequestException} that includes the current stack trace, the 31 | * specified detail message and the specified cause. 32 | * 33 | * @param detailMessage The detail message for this exception. 34 | * @param throwable The cause of this exception. 35 | */ 36 | public CustomRequestException(final String detailMessage, final Throwable throwable) { 37 | super(detailMessage, throwable); 38 | } 39 | 40 | /** 41 | * Constructs a new {@link CustomRequestException} that includes the current stack trace and the 42 | * specified detail message. 43 | * 44 | * @param detailMessage The detail message for this exception. 45 | */ 46 | public CustomRequestException(final String detailMessage) { 47 | super(detailMessage); 48 | } 49 | 50 | /** 51 | * Constructs a new {@link CustomRequestException} that includes the current stack trace and the 52 | * specified cause. 53 | * 54 | * @param throwable The cause of this exception. 55 | */ 56 | public CustomRequestException(final Throwable throwable) { 57 | super(throwable); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/com/foxykeep/datadroid/exception/DataException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2011 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package com.foxykeep.datadroid.exception; 10 | 11 | /** 12 | * Thrown to indicate that a compulsory parameter is missing. 13 | * 14 | * @author Foxykeep 15 | */ 16 | public final class DataException extends Exception { 17 | 18 | private static final long serialVersionUID = -6031863210486494461L; 19 | 20 | /** 21 | * Constructs a new {@link DataException} that includes the current stack trace. 22 | */ 23 | public DataException() { 24 | super(); 25 | } 26 | 27 | /** 28 | * Constructs a new {@link DataException} that includes the current stack trace, the 29 | * specified detail message and the specified cause. 30 | * 31 | * @param detailMessage The detail message for this exception. 32 | * @param throwable The cause of this exception. 33 | */ 34 | public DataException(final String detailMessage, final Throwable throwable) { 35 | super(detailMessage, throwable); 36 | } 37 | 38 | /** 39 | * Constructs a new {@link DataException} that includes the current stack trace and the 40 | * specified detail message. 41 | * 42 | * @param detailMessage The detail message for this exception. 43 | */ 44 | public DataException(final String detailMessage) { 45 | super(detailMessage); 46 | } 47 | 48 | /** 49 | * Constructs a new {@link DataException} that includes the current stack trace and the 50 | * specified cause. 51 | * 52 | * @param throwable The cause of this exception. 53 | */ 54 | public DataException(final Throwable throwable) { 55 | super(throwable); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/com/foxykeep/datadroid/internal/network/NetworkConnectionImpl.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2011 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package com.foxykeep.datadroid.internal.network; 10 | 11 | import com.foxykeep.datadroid.exception.ConnectionException; 12 | import com.foxykeep.datadroid.network.NetworkConnection.ConnectionResult; 13 | import com.foxykeep.datadroid.network.NetworkConnection.Method; 14 | import com.foxykeep.datadroid.network.UserAgentUtils; 15 | import com.foxykeep.datadroid.util.DataDroidLog; 16 | 17 | import android.content.Context; 18 | import android.support.util.Base64; 19 | import android.text.TextUtils; 20 | import android.util.Log; 21 | 22 | import org.apache.http.HttpStatus; 23 | import org.apache.http.auth.UsernamePasswordCredentials; 24 | import org.apache.http.message.BasicNameValuePair; 25 | import org.apache.http.protocol.HTTP; 26 | 27 | import java.io.BufferedReader; 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | import java.io.InputStreamReader; 31 | import java.io.OutputStream; 32 | import java.net.HttpURLConnection; 33 | import java.net.URL; 34 | import java.net.URLEncoder; 35 | import java.security.KeyManagementException; 36 | import java.security.NoSuchAlgorithmException; 37 | import java.security.cert.X509Certificate; 38 | import java.util.ArrayList; 39 | import java.util.HashMap; 40 | import java.util.Map.Entry; 41 | import java.util.zip.GZIPInputStream; 42 | import javax.net.ssl.HostnameVerifier; 43 | import javax.net.ssl.HttpsURLConnection; 44 | import javax.net.ssl.SSLContext; 45 | import javax.net.ssl.SSLSession; 46 | import javax.net.ssl.SSLSocketFactory; 47 | import javax.net.ssl.TrustManager; 48 | import javax.net.ssl.X509TrustManager; 49 | 50 | /** 51 | * Implementation of the network connection. 52 | * 53 | * @author Foxykeep 54 | */ 55 | public final class NetworkConnectionImpl { 56 | 57 | private static final String TAG = NetworkConnectionImpl.class.getSimpleName(); 58 | 59 | private static final String ACCEPT_CHARSET_HEADER = "Accept-Charset"; 60 | private static final String ACCEPT_ENCODING_HEADER = "Accept-Encoding"; 61 | private static final String AUTHORIZATION_HEADER = "Authorization"; 62 | private static final String LOCATION_HEADER = "Location"; 63 | 64 | private static final String UTF8_CHARSET = "UTF-8"; 65 | 66 | // Default connection and socket timeout of 60 seconds. Tweak to taste. 67 | private static final int OPERATION_TIMEOUT = 60 * 1000; 68 | 69 | private NetworkConnectionImpl() { 70 | // No public constructor 71 | } 72 | 73 | /** 74 | * Call the webservice using the given parameters to construct the request and return the 75 | * result. 76 | * 77 | * @param context The context to use for this operation. Used to generate the user agent if 78 | * needed. 79 | * @param urlValue The webservice URL. 80 | * @param method The request method to use. 81 | * @param parameterList The parameters to add to the request. 82 | * @param headerMap The headers to add to the request. 83 | * @param isGzipEnabled Whether the request will use gzip compression if available on the 84 | * server. 85 | * @param userAgent The user agent to set in the request. If null, a default Android one will be 86 | * created. 87 | * @param postText The POSTDATA text to add in the request. 88 | * @param credentials The credentials to use for authentication. 89 | * @param isSslValidationEnabled Whether the request will validate the SSL certificates. 90 | * @return The result of the webservice call. 91 | */ 92 | public static ConnectionResult execute(Context context, String urlValue, Method method, 93 | ArrayList parameterList, HashMap headerMap, 94 | boolean isGzipEnabled, String userAgent, String postText, 95 | UsernamePasswordCredentials credentials, boolean isSslValidationEnabled) throws 96 | ConnectionException { 97 | HttpURLConnection connection = null; 98 | try { 99 | // Prepare the request information 100 | if (userAgent == null) { 101 | userAgent = UserAgentUtils.get(context); 102 | } 103 | if (headerMap == null) { 104 | headerMap = new HashMap(); 105 | } 106 | headerMap.put(HTTP.USER_AGENT, userAgent); 107 | if (isGzipEnabled) { 108 | headerMap.put(ACCEPT_ENCODING_HEADER, "gzip"); 109 | } 110 | headerMap.put(ACCEPT_CHARSET_HEADER, UTF8_CHARSET); 111 | if (credentials != null) { 112 | headerMap.put(AUTHORIZATION_HEADER, createAuthenticationHeader(credentials)); 113 | } 114 | 115 | StringBuilder paramBuilder = new StringBuilder(); 116 | if (parameterList != null && !parameterList.isEmpty()) { 117 | for (int i = 0, size = parameterList.size(); i < size; i++) { 118 | BasicNameValuePair parameter = parameterList.get(i); 119 | String name = parameter.getName(); 120 | String value = parameter.getValue(); 121 | if (TextUtils.isEmpty(name)) { 122 | // Empty parameter name. Check the next one. 123 | continue; 124 | } 125 | if (value == null) { 126 | value = ""; 127 | } 128 | paramBuilder.append(URLEncoder.encode(name, UTF8_CHARSET)); 129 | paramBuilder.append("="); 130 | paramBuilder.append(URLEncoder.encode(value, UTF8_CHARSET)); 131 | paramBuilder.append("&"); 132 | } 133 | } 134 | 135 | // Log the request 136 | if (DataDroidLog.canLog(Log.DEBUG)) { 137 | DataDroidLog.d(TAG, "Request url: " + urlValue); 138 | DataDroidLog.d(TAG, "Method: " + method.toString()); 139 | 140 | if (parameterList != null && !parameterList.isEmpty()) { 141 | DataDroidLog.d(TAG, "Parameters:"); 142 | for (int i = 0, size = parameterList.size(); i < size; i++) { 143 | BasicNameValuePair parameter = parameterList.get(i); 144 | String message = "- \"" + parameter.getName() + "\" = \"" 145 | + parameter.getValue() + "\""; 146 | DataDroidLog.d(TAG, message); 147 | } 148 | 149 | DataDroidLog.d(TAG, "Parameters String: \"" + paramBuilder.toString() + "\""); 150 | } 151 | 152 | if (postText != null) { 153 | DataDroidLog.d(TAG, "Post data: " + postText); 154 | } 155 | 156 | if (headerMap != null && !headerMap.isEmpty()) { 157 | DataDroidLog.d(TAG, "Headers:"); 158 | for (Entry header : headerMap.entrySet()) { 159 | DataDroidLog.d(TAG, "- " + header.getKey() + " = " + header.getValue()); 160 | } 161 | } 162 | } 163 | 164 | // Create the connection object 165 | URL url = null; 166 | String outputText = null; 167 | switch (method) { 168 | case GET: 169 | case DELETE: 170 | String fullUrlValue = urlValue; 171 | if (paramBuilder.length() > 0) { 172 | fullUrlValue += "?" + paramBuilder.toString(); 173 | } 174 | url = new URL(fullUrlValue); 175 | connection = (HttpURLConnection) url.openConnection(); 176 | break; 177 | case PUT: 178 | case POST: 179 | url = new URL(urlValue); 180 | connection = (HttpURLConnection) url.openConnection(); 181 | connection.setDoOutput(true); 182 | 183 | if (paramBuilder.length() > 0) { 184 | outputText = paramBuilder.toString(); 185 | headerMap.put(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded"); 186 | headerMap.put(HTTP.CONTENT_LEN, 187 | String.valueOf(outputText.getBytes().length)); 188 | } else if (postText != null) { 189 | outputText = postText; 190 | } 191 | break; 192 | } 193 | 194 | // Set the request method 195 | connection.setRequestMethod(method.toString()); 196 | 197 | // If it's an HTTPS request and the SSL Validation is disabled 198 | if (url.getProtocol().equals("https") 199 | && !isSslValidationEnabled) { 200 | HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; 201 | httpsConnection.setSSLSocketFactory(getAllHostsValidSocketFactory()); 202 | httpsConnection.setHostnameVerifier(getAllHostsValidVerifier()); 203 | } 204 | 205 | // Add the headers 206 | if (!headerMap.isEmpty()) { 207 | for (Entry header : headerMap.entrySet()) { 208 | connection.addRequestProperty(header.getKey(), header.getValue()); 209 | } 210 | } 211 | 212 | // Set the connection and read timeout 213 | connection.setConnectTimeout(OPERATION_TIMEOUT); 214 | connection.setReadTimeout(OPERATION_TIMEOUT); 215 | 216 | // Set the outputStream content for POST and PUT requests 217 | if ((method == Method.POST || method == Method.PUT) && outputText != null) { 218 | OutputStream output = null; 219 | try { 220 | output = connection.getOutputStream(); 221 | output.write(outputText.getBytes()); 222 | } finally { 223 | if (output != null) { 224 | try { 225 | output.close(); 226 | } catch (IOException e) { 227 | // Already catching the first IOException so nothing to do here. 228 | } 229 | } 230 | } 231 | } 232 | 233 | String contentEncoding = connection.getHeaderField(HTTP.CONTENT_ENCODING); 234 | 235 | int responseCode = connection.getResponseCode(); 236 | boolean isGzip = contentEncoding != null 237 | && contentEncoding.equalsIgnoreCase("gzip"); 238 | DataDroidLog.d(TAG, "Response code: " + responseCode); 239 | 240 | if (responseCode == HttpStatus.SC_MOVED_PERMANENTLY) { 241 | String redirectionUrl = connection.getHeaderField(LOCATION_HEADER); 242 | throw new ConnectionException("New location : " + redirectionUrl, 243 | redirectionUrl); 244 | } 245 | 246 | InputStream errorStream = connection.getErrorStream(); 247 | if (errorStream != null) { 248 | String error = convertStreamToString(errorStream, isGzip); 249 | throw new ConnectionException(error, responseCode); 250 | } 251 | 252 | String body = convertStreamToString(connection.getInputStream(), 253 | isGzip); 254 | 255 | if (DataDroidLog.canLog(Log.VERBOSE)) { 256 | DataDroidLog.v(TAG, "Response body: "); 257 | 258 | int pos = 0; 259 | int bodyLength = body.length(); 260 | while (pos < bodyLength) { 261 | DataDroidLog.v(TAG, body.substring(pos, Math.min(bodyLength - 1, pos + 200))); 262 | pos = pos + 200; 263 | } 264 | } 265 | 266 | return new ConnectionResult(connection.getHeaderFields(), body); 267 | } catch (IOException e) { 268 | DataDroidLog.e(TAG, "IOException", e); 269 | throw new ConnectionException(e); 270 | } catch (KeyManagementException e) { 271 | DataDroidLog.e(TAG, "KeyManagementException", e); 272 | throw new ConnectionException(e); 273 | } catch (NoSuchAlgorithmException e) { 274 | DataDroidLog.e(TAG, "NoSuchAlgorithmException", e); 275 | throw new ConnectionException(e); 276 | } finally { 277 | if (connection != null) { 278 | connection.disconnect(); 279 | } 280 | } 281 | } 282 | 283 | private static String createAuthenticationHeader(UsernamePasswordCredentials credentials) { 284 | StringBuilder sb = new StringBuilder(); 285 | sb.append(credentials.getUserName()).append(":").append(credentials.getPassword()); 286 | return "Basic " + Base64.encodeToString(sb.toString().getBytes(), Base64.NO_WRAP); 287 | } 288 | 289 | private static SSLSocketFactory sAllHostsValidSocketFactory; 290 | 291 | private static SSLSocketFactory getAllHostsValidSocketFactory() 292 | throws NoSuchAlgorithmException, KeyManagementException { 293 | if (sAllHostsValidSocketFactory == null) { 294 | TrustManager[] trustAllCerts = new TrustManager[] { 295 | new X509TrustManager() { 296 | public java.security.cert.X509Certificate[] getAcceptedIssuers() { 297 | return null; 298 | } 299 | 300 | public void checkClientTrusted(X509Certificate[] certs, String authType) {} 301 | 302 | public void checkServerTrusted(X509Certificate[] certs, String authType) {} 303 | } 304 | }; 305 | 306 | SSLContext sc = SSLContext.getInstance("SSL"); 307 | sc.init(null, trustAllCerts, new java.security.SecureRandom()); 308 | sAllHostsValidSocketFactory = sc.getSocketFactory(); 309 | } 310 | 311 | return sAllHostsValidSocketFactory; 312 | } 313 | 314 | private static HostnameVerifier sAllHostsValidVerifier; 315 | 316 | private static HostnameVerifier getAllHostsValidVerifier() { 317 | if (sAllHostsValidVerifier == null) { 318 | sAllHostsValidVerifier = new HostnameVerifier() { 319 | public boolean verify(String hostname, SSLSession session) { 320 | return true; 321 | } 322 | }; 323 | } 324 | 325 | return sAllHostsValidVerifier; 326 | } 327 | 328 | private static String convertStreamToString(InputStream is, boolean isGzipEnabled) 329 | throws IOException { 330 | InputStream cleanedIs = is; 331 | if (isGzipEnabled) { 332 | cleanedIs = new GZIPInputStream(is); 333 | } 334 | 335 | BufferedReader reader = null; 336 | try { 337 | reader = new BufferedReader(new InputStreamReader(cleanedIs, UTF8_CHARSET)); 338 | StringBuilder sb = new StringBuilder(); 339 | for (String line; (line = reader.readLine()) != null;) { 340 | sb.append(line); 341 | sb.append("\n"); 342 | } 343 | 344 | return sb.toString(); 345 | } finally { 346 | if (reader != null) { 347 | reader.close(); 348 | } 349 | 350 | cleanedIs.close(); 351 | 352 | if (isGzipEnabled) { 353 | is.close(); 354 | } 355 | } 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /src/com/foxykeep/datadroid/network/NetworkConnection.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2012 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package com.foxykeep.datadroid.network; 10 | 11 | import com.foxykeep.datadroid.exception.ConnectionException; 12 | import com.foxykeep.datadroid.internal.network.NetworkConnectionImpl; 13 | import com.foxykeep.datadroid.util.DataDroidLog; 14 | 15 | import android.content.Context; 16 | 17 | import org.apache.http.auth.UsernamePasswordCredentials; 18 | import org.apache.http.message.BasicNameValuePair; 19 | 20 | import java.util.ArrayList; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | /** 26 | * This class gives the user an API to easily call a webservice and return the received response. 27 | * 28 | * @author Foxykeep 29 | */ 30 | public final class NetworkConnection { 31 | 32 | private static final String LOG_TAG = NetworkConnection.class.getSimpleName(); 33 | 34 | public static enum Method { 35 | GET, POST, PUT, DELETE 36 | } 37 | 38 | /** 39 | * The result of a webservice call. 40 | *

41 | * Contains the headers and the body of the response as an unparsed String. 42 | * 43 | * @author Foxykeep 44 | */ 45 | public static final class ConnectionResult { 46 | 47 | public final Map> headerMap; 48 | public final String body; 49 | 50 | public ConnectionResult(Map> headerMap, String body) { 51 | this.headerMap = headerMap; 52 | this.body = body; 53 | } 54 | } 55 | 56 | private final Context mContext; 57 | private final String mUrl; 58 | private Method mMethod = Method.GET; 59 | private ArrayList mParameterList = null; 60 | private HashMap mHeaderMap = null; 61 | private boolean mIsGzipEnabled = true; 62 | private String mUserAgent = null; 63 | private String mPostText = null; 64 | private UsernamePasswordCredentials mCredentials = null; 65 | private boolean mIsSslValidationEnabled = true; 66 | 67 | /** 68 | * Create a {@link NetworkConnection}. 69 | *

70 | * The Method to use is {@link Method#GET} by default. 71 | * 72 | * @param context The context used by the {@link NetworkConnection}. Used to create the 73 | * User-Agent. 74 | * @param url The URL to call. 75 | */ 76 | public NetworkConnection(Context context, String url) { 77 | if (url == null) { 78 | DataDroidLog.e(LOG_TAG, "NetworkConnection.NetworkConnection - request URL cannot be null."); 79 | throw new NullPointerException("Request URL has not been set."); 80 | } 81 | mContext = context; 82 | mUrl = url; 83 | } 84 | 85 | /** 86 | * Set the method to use. Default is {@link Method#GET}. 87 | *

88 | * If set to another value than {@link Method#POST}, the POSTDATA text will be reset as it can 89 | * only be used with a POST request. 90 | * 91 | * @param method The method to use. 92 | * @return The networkConnection. 93 | */ 94 | public NetworkConnection setMethod(Method method) { 95 | mMethod = method; 96 | if (method != Method.POST) { 97 | mPostText = null; 98 | } 99 | return this; 100 | } 101 | 102 | /** 103 | * Set the parameters to add to the request. This is meant to be a "key" => "value" Map. 104 | *

105 | * The POSTDATA text will be reset as they cannot be used at the same time. 106 | * 107 | * @see #setPostText(String) 108 | * @see #setParameters(ArrayList) 109 | * @param parameterMap The parameters to add to the request. 110 | * @return The networkConnection. 111 | */ 112 | public NetworkConnection setParameters(HashMap parameterMap) { 113 | ArrayList parameterList = new ArrayList(); 114 | for (Map.Entry entry : parameterMap.entrySet()) { 115 | parameterList.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); 116 | } 117 | 118 | return setParameters(parameterList); 119 | } 120 | 121 | 122 | /** 123 | * Set the parameters to add to the request. This is meant to be a "key" => "value" Map. 124 | *

125 | * The POSTDATA text will be reset as they cannot be used at the same time. 126 | *

127 | * This method allows you to have multiple values for a single key in contrary to the HashMap 128 | * version of the method ({@link #setParameters(HashMap)}) 129 | * 130 | * @see #setPostText(String) 131 | * @see #setParameters(HashMap) 132 | * @param parameterList The parameters to add to the request. 133 | * @return The networkConnection. 134 | */ 135 | public NetworkConnection setParameters(ArrayList parameterList) { 136 | mParameterList = parameterList; 137 | mPostText = null; 138 | return this; 139 | } 140 | 141 | /** 142 | * Set the headers to add to the request. 143 | * 144 | * @param headerMap The headers to add to the request. 145 | * @return The networkConnection. 146 | */ 147 | public NetworkConnection setHeaderList(HashMap headerMap) { 148 | mHeaderMap = headerMap; 149 | return this; 150 | } 151 | 152 | /** 153 | * Set whether the request will use gzip compression if available on the server. Default is 154 | * true. 155 | * 156 | * @param isGzipEnabled Whether the request will user gzip compression if available on the 157 | * server. 158 | * @return The networkConnection. 159 | */ 160 | public NetworkConnection setGzipEnabled(boolean isGzipEnabled) { 161 | mIsGzipEnabled = isGzipEnabled; 162 | return this; 163 | } 164 | 165 | /** 166 | * Set the user agent to set in the request. Otherwise a default Android one will be used. 167 | * 168 | * @param userAgent The user agent. 169 | * @return The networkConnection. 170 | */ 171 | public NetworkConnection setUserAgent(String userAgent) { 172 | mUserAgent = userAgent; 173 | return this; 174 | } 175 | 176 | /** 177 | * Set the POSTDATA text that will be added in the request. Also automatically set the 178 | * {@link Method} to {@link Method#POST} to be able to use it. 179 | *

180 | * The parameters will be reset as they cannot be used at the same time. 181 | * 182 | * @param postText The POSTDATA text that will be added in the request. 183 | * @return The networkConnection. 184 | * @see #setParameters(HashMap) 185 | * @see #setPostText(String) 186 | */ 187 | public NetworkConnection setPostText(String postText) { 188 | return setPostText(postText, Method.POST); 189 | } 190 | 191 | /** 192 | * Set the POSTDATA text that will be added in the request and also set the {@link Method} 193 | * to use. The Method can only be {@link Method#POST} or {@link Method#PUT}. 194 | *

195 | * The parameters will be reset as they cannot be used at the same time. 196 | * 197 | * @param postText The POSTDATA text that will be added in the request. 198 | * @param method The method to use. 199 | * @return The networkConnection. 200 | * @see #setParameters(HashMap) 201 | * @see #setPostText(String) 202 | */ 203 | public NetworkConnection setPostText(String postText, Method method) { 204 | if (method != Method.POST && method != Method.PUT) { 205 | throw new IllegalArgumentException("Method must be POST or PUT"); 206 | } 207 | mPostText = postText; 208 | mMethod = method; 209 | mParameterList = null; 210 | return this; 211 | } 212 | 213 | /** 214 | * Set the credentials to use for authentication. 215 | * 216 | * @param credentials The credentials to use for authentication. 217 | * @return The networkConnection. 218 | */ 219 | public NetworkConnection setCredentials(UsernamePasswordCredentials credentials) { 220 | mCredentials = credentials; 221 | return this; 222 | } 223 | 224 | /** 225 | * Set whether the SSL certificates validation are enabled. Default is true. 226 | * 227 | * @param enabled Whether the SSL certificates validation are enabled. 228 | * @return The networkConnection. 229 | */ 230 | public NetworkConnection setSslValidationEnabled(boolean enabled) { 231 | mIsSslValidationEnabled = enabled; 232 | return this; 233 | } 234 | 235 | /** 236 | * Execute the webservice call and return the {@link ConnectionResult}. 237 | * 238 | * @return The result of the webservice call. 239 | */ 240 | public ConnectionResult execute() throws ConnectionException { 241 | return NetworkConnectionImpl.execute(mContext, mUrl, mMethod, mParameterList, 242 | mHeaderMap, mIsGzipEnabled, mUserAgent, mPostText, mCredentials, 243 | mIsSslValidationEnabled); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/com/foxykeep/datadroid/network/UserAgentUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2012 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package com.foxykeep.datadroid.network; 10 | 11 | import android.content.Context; 12 | import android.content.pm.PackageInfo; 13 | import android.content.pm.PackageManager; 14 | import android.content.pm.PackageManager.NameNotFoundException; 15 | import android.os.Build; 16 | import android.text.TextUtils; 17 | 18 | import java.util.Locale; 19 | 20 | /** 21 | * @author Foxykeep 22 | * 23 | * Utility class to generate and cache a User-Agent header for HTTP requests. 24 | */ 25 | public final class UserAgentUtils { 26 | 27 | private static String sUserAgent; 28 | 29 | private UserAgentUtils() { 30 | // No public constructor 31 | } 32 | 33 | /** 34 | * Get the User-Agent with the following syntax: 35 | *

36 | * Mozilla/5.0 (Linux; U; Android {Build.VERSION.RELEASE}; {locale.toString()}[; {Build.MODEL}] 37 | * [; Build/{Build.ID}]) {getPackageName()}/{getVersionCode()} 38 | * 39 | * @param context The context to use to generate the User-Agent. 40 | * @return The User-Agent. 41 | */ 42 | public synchronized static String get(Context context) { 43 | if (context == null) { 44 | throw new NullPointerException("Context cannot be null"); 45 | } 46 | if (sUserAgent == null) { 47 | sUserAgent = generate(context); 48 | } 49 | return sUserAgent; 50 | } 51 | 52 | private static String generate(Context context) { 53 | StringBuilder sb = new StringBuilder(); 54 | 55 | sb.append("Mozilla/5.0 (Linux; U; Android"); 56 | sb.append(Build.VERSION.RELEASE); 57 | sb.append("; "); 58 | sb.append(Locale.getDefault().toString()); 59 | 60 | String model = Build.MODEL; 61 | if (!TextUtils.isEmpty(model)) { 62 | sb.append("; "); 63 | sb.append(model); 64 | } 65 | 66 | String buildId = Build.ID; 67 | if (!TextUtils.isEmpty(buildId)) { 68 | sb.append("; Build/"); 69 | sb.append(buildId); 70 | } 71 | 72 | sb.append(") "); 73 | 74 | int versionCode = 0; 75 | String packageName = context.getPackageName(); 76 | try { 77 | PackageManager manager = context.getPackageManager(); 78 | PackageInfo packageInfo = manager.getPackageInfo(packageName, 0); 79 | versionCode = packageInfo.versionCode; 80 | } catch (NameNotFoundException e) { 81 | // Keep the versionCode 0 as default. 82 | } 83 | 84 | sb.append(packageName); 85 | sb.append("/"); 86 | sb.append(versionCode); 87 | 88 | return sb.toString(); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/com/foxykeep/datadroid/requestmanager/Request.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2012 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package com.foxykeep.datadroid.requestmanager; 10 | 11 | import com.foxykeep.datadroid.service.RequestService; 12 | import com.foxykeep.datadroid.util.ObjectUtils; 13 | 14 | import android.os.Bundle; 15 | import android.os.Parcel; 16 | import android.os.Parcelable; 17 | 18 | import java.util.ArrayList; 19 | 20 | /** 21 | * Class used to store your request information : request type as well as parameters. 22 | * 23 | * @author Foxykeep 24 | */ 25 | public final class Request implements Parcelable { 26 | 27 | private static final int TYPE_BOOLEAN = 1; 28 | private static final int TYPE_BYTE = 2; 29 | private static final int TYPE_CHAR = 3; 30 | private static final int TYPE_SHORT = 4; 31 | private static final int TYPE_INT = 5; 32 | private static final int TYPE_LONG = 6; 33 | private static final int TYPE_FLOAT = 7; 34 | private static final int TYPE_DOUBLE = 8; 35 | private static final int TYPE_STRING = 9; 36 | private static final int TYPE_CHARSEQUENCE = 10; 37 | private static final int TYPE_PARCELABLE = 11; 38 | 39 | private int mRequestType = -1; 40 | private boolean mMemoryCacheDataEnabled = false; 41 | private final ArrayList mParamList = new ArrayList(); 42 | private final ArrayList mTypeList = new ArrayList(); 43 | private Bundle mBundle = new Bundle(); 44 | 45 | /** 46 | * Constructor 47 | * 48 | * @param requestType The request type. 49 | */ 50 | public Request(int requestType) { 51 | mRequestType = requestType; 52 | } 53 | 54 | /** 55 | * Return the request type. 56 | * 57 | * @return The request type. 58 | */ 59 | public int getRequestType() { 60 | return mRequestType; 61 | } 62 | 63 | /** 64 | * Set whether the data returned from the {@link RequestService} must be cached in memory or 65 | * not. 66 | * 67 | * @param enabled Whether the data returned from the {@link RequestService} must be cached in 68 | * memory or not. 69 | */ 70 | public void setMemoryCacheEnabled(boolean enabled) { 71 | mMemoryCacheDataEnabled = enabled; 72 | } 73 | 74 | /** 75 | * Return whether the data returned from the {@link RequestService} must be cached in memory or 76 | * not. 77 | * 78 | * @return Whether the data returned from the {@link RequestService} must be cached in memory or 79 | * not. 80 | */ 81 | public boolean isMemoryCacheEnabled() { 82 | return mMemoryCacheDataEnabled; 83 | } 84 | 85 | /** 86 | * Add a boolean parameter to the request, replacing any existing value for the given name. 87 | * 88 | * @param name The parameter name. 89 | * @param value The parameter value. 90 | * @return This RequestData. 91 | */ 92 | public Request put(String name, boolean value) { 93 | removeFromRequestData(name); 94 | mParamList.add(name); 95 | mTypeList.add(TYPE_BOOLEAN); 96 | mBundle.putBoolean(name, value); 97 | return this; 98 | } 99 | 100 | /** 101 | * Add a byte parameter to the request, replacing any existing value for the given name. 102 | * 103 | * @param name The parameter name. 104 | * @param value The parameter value. 105 | * @return This RequestData. 106 | */ 107 | public Request put(String name, byte value) { 108 | removeFromRequestData(name); 109 | mParamList.add(name); 110 | mTypeList.add(TYPE_BYTE); 111 | mBundle.putByte(name, value); 112 | return this; 113 | } 114 | 115 | /** 116 | * Add a char parameter to the request, replacing any existing value for the given name. 117 | * 118 | * @param name The parameter name. 119 | * @param value The parameter value. 120 | * @return This RequestData. 121 | */ 122 | public Request put(String name, char value) { 123 | removeFromRequestData(name); 124 | mParamList.add(name); 125 | mTypeList.add(TYPE_CHAR); 126 | mBundle.putChar(name, value); 127 | return this; 128 | } 129 | 130 | /** 131 | * Add a short parameter to the request, replacing any existing value for the given name. 132 | * 133 | * @param name The parameter name. 134 | * @param value The parameter value. 135 | * @return This RequestData. 136 | */ 137 | public Request put(String name, short value) { 138 | removeFromRequestData(name); 139 | mParamList.add(name); 140 | mTypeList.add(TYPE_SHORT); 141 | mBundle.putShort(name, value); 142 | return this; 143 | } 144 | 145 | /** 146 | * Add a int parameter to the request, replacing any existing value for the given name. 147 | * 148 | * @param name The parameter name. 149 | * @param value The parameter value. 150 | * @return This RequestData. 151 | */ 152 | public Request put(String name, int value) { 153 | removeFromRequestData(name); 154 | mParamList.add(name); 155 | mTypeList.add(TYPE_INT); 156 | mBundle.putInt(name, value); 157 | return this; 158 | } 159 | 160 | /** 161 | * Add a long parameter to the request, replacing any existing value for the given name. 162 | * 163 | * @param name The parameter name. 164 | * @param value The parameter value. 165 | * @return This RequestData. 166 | */ 167 | public Request put(String name, long value) { 168 | removeFromRequestData(name); 169 | mParamList.add(name); 170 | mTypeList.add(TYPE_LONG); 171 | mBundle.putLong(name, value); 172 | return this; 173 | } 174 | 175 | /** 176 | * Add a float parameter to the request, replacing any existing value for the given name. 177 | * 178 | * @param name The parameter name. 179 | * @param value The parameter value. 180 | * @return This RequestData. 181 | */ 182 | public Request put(String name, float value) { 183 | removeFromRequestData(name); 184 | mParamList.add(name); 185 | mTypeList.add(TYPE_FLOAT); 186 | mBundle.putFloat(name, value); 187 | return this; 188 | } 189 | 190 | /** 191 | * Add a double parameter to the request, replacing any existing value for the given name. 192 | * 193 | * @param name The parameter name. 194 | * @param value The parameter value. 195 | * @return This RequestData. 196 | */ 197 | public Request put(String name, double value) { 198 | removeFromRequestData(name); 199 | mParamList.add(name); 200 | mTypeList.add(TYPE_DOUBLE); 201 | mBundle.putDouble(name, value); 202 | return this; 203 | } 204 | 205 | /** 206 | * Add a String parameter to the request, replacing any existing value for the given name. 207 | * 208 | * @param name The parameter name. 209 | * @param value The parameter value. 210 | * @return This RequestData. 211 | */ 212 | public Request put(String name, String value) { 213 | removeFromRequestData(name); 214 | mParamList.add(name); 215 | mTypeList.add(TYPE_STRING); 216 | mBundle.putString(name, value); 217 | return this; 218 | } 219 | 220 | /** 221 | * Add a CharSequence parameter to the request, replacing any existing value for the given name. 222 | * 223 | * @param name The parameter name. 224 | * @param value The parameter value. 225 | * @return This RequestData. 226 | */ 227 | public Request put(String name, CharSequence value) { 228 | removeFromRequestData(name); 229 | mParamList.add(name); 230 | mTypeList.add(TYPE_CHARSEQUENCE); 231 | mBundle.putCharSequence(name, value); 232 | return this; 233 | } 234 | 235 | /** 236 | * Add a Parcelable parameter to the request, replacing any existing value for the given name. 237 | * 238 | * @param name The parameter name. 239 | * @param value The parameter value. 240 | * @return This RequestData. 241 | */ 242 | public Request put(String name, Parcelable value) { 243 | removeFromRequestData(name); 244 | mParamList.add(name); 245 | mTypeList.add(TYPE_PARCELABLE); 246 | mBundle.putParcelable(name, value); 247 | return this; 248 | } 249 | 250 | private void removeFromRequestData(String name) { 251 | if (mParamList.contains(name)) { 252 | final int index = mParamList.indexOf(name); 253 | mParamList.remove(index); 254 | mTypeList.remove(index); 255 | mBundle.remove(name); 256 | } 257 | } 258 | 259 | /** 260 | * Check whether the request has an existing value for the given name. 261 | * 262 | * @param name The parameter name. 263 | * @return Whether the request has an existing value for the given name. 264 | */ 265 | public boolean contains(String name) { 266 | return mParamList.contains(name); 267 | } 268 | 269 | /** 270 | * Returns the value associated with the given name, or false if no mapping of the desired type 271 | * exists for the given name. 272 | * 273 | * @param name The parameter name. 274 | * @return A boolean value. 275 | */ 276 | public boolean getBoolean(String name) { 277 | return mBundle.getBoolean(name); 278 | } 279 | 280 | /** 281 | * Returns the value associated with the given name transformed as "1" if true or "0" if false. 282 | * If no mapping of the desired type exists for the given name, "0" is returned. 283 | * 284 | * @param name The parameter name. 285 | * @return The int String representation of the boolean value. 286 | */ 287 | public String getBooleanAsIntString(String name) { 288 | boolean value = getBoolean(name); 289 | return value ? "1" : "0"; 290 | } 291 | 292 | /** 293 | * Returns the value associated with the given name transformed as a String (using 294 | * {@link String#valueOf(boolean)}), or "false" if no mapping of the desired type exists for the 295 | * given name. 296 | * 297 | * @param name The parameter name. 298 | * @return The String representation of the boolean value. 299 | */ 300 | public String getBooleanAsString(String name) { 301 | boolean value = getBoolean(name); 302 | return String.valueOf(value); 303 | } 304 | 305 | /** 306 | * Returns the value associated with the given name, or (byte) 0 if no mapping of the desired 307 | * type exists for the given name. 308 | * 309 | * @param name The parameter name. 310 | * @return A byte value. 311 | */ 312 | public byte getByte(String name) { 313 | return mBundle.getByte(name); 314 | } 315 | 316 | /** 317 | * Returns the value associated with the given name, or (char) 0 if no mapping of the desired 318 | * type exists for the given name. 319 | * 320 | * @param name The parameter name. 321 | * @return A char value. 322 | */ 323 | public char getChar(String name) { 324 | return mBundle.getChar(name); 325 | } 326 | 327 | /** 328 | * Returns the value associated with the given name transformed as a String (using 329 | * {@link String#valueOf(char)}), or "0" if no mapping of the desired type exists for the given 330 | * name. 331 | * 332 | * @param name The parameter name. 333 | * @return The String representation of the boolean value. 334 | */ 335 | public String getCharAsString(String name) { 336 | char value = getChar(name); 337 | return String.valueOf(value); 338 | } 339 | 340 | /** 341 | * Returns the value associated with the given name, or (short) 0 if no mapping of the desired 342 | * type exists for the given name. 343 | * 344 | * @param name The parameter name. 345 | * @return A short value. 346 | */ 347 | public short getShort(String name) { 348 | return mBundle.getShort(name); 349 | } 350 | 351 | /** 352 | * Returns the value associated with the given name transformed as a String (using 353 | * {@link String#valueOf(int)}), or "0" if no mapping of the desired type exists for the given 354 | * name. 355 | * 356 | * @param name The parameter name. 357 | * @return The String representation of the boolean value. 358 | */ 359 | public String getShortAsString(String name) { 360 | short value = getShort(name); 361 | return String.valueOf(value); 362 | } 363 | 364 | /** 365 | * Returns the value associated with the given name, or 0 if no mapping of the desired type 366 | * exists for the given name. 367 | * 368 | * @param name The parameter name. 369 | * @return An int value. 370 | */ 371 | public int getInt(String name) { 372 | return mBundle.getInt(name); 373 | } 374 | 375 | /** 376 | * Returns the value associated with the given name transformed as a String (using 377 | * {@link String#valueOf(int)}), or "0" if no mapping of the desired type exists for the given 378 | * name. 379 | * 380 | * @param name The parameter name. 381 | * @return The String representation of the boolean value. 382 | */ 383 | public String getIntAsString(String name) { 384 | int value = getInt(name); 385 | return String.valueOf(value); 386 | } 387 | 388 | /** 389 | * Returns the value associated with the given name, or 0L if no mapping of the desired type 390 | * exists for the given name. 391 | * 392 | * @param name The parameter name. 393 | * @return A long value. 394 | */ 395 | public long getLong(String name) { 396 | return mBundle.getLong(name); 397 | } 398 | 399 | /** 400 | * Returns the value associated with the given name transformed as a String (using 401 | * {@link String#valueOf(long)}), or "0" if no mapping of the desired type exists for the given 402 | * name. 403 | * 404 | * @param name The parameter name. 405 | * @return The String representation of the boolean value. 406 | */ 407 | public String getLongAsString(String name) { 408 | long value = getLong(name); 409 | return String.valueOf(value); 410 | } 411 | 412 | /** 413 | * Returns the value associated with the given name, or 0.0f if no mapping of the desired type 414 | * exists for the given name. 415 | * 416 | * @param name The parameter name. 417 | * @return A float value. 418 | */ 419 | public float getFloat(String name) { 420 | return mBundle.getFloat(name); 421 | } 422 | 423 | /** 424 | * Returns the value associated with the given name transformed as a String (using 425 | * {@link String#valueOf(float)}), or "0" if no mapping of the desired type exists for the given 426 | * name. 427 | * 428 | * @param name The parameter name. 429 | * @return The String representation of the boolean value. 430 | */ 431 | public String getFloatAsString(String name) { 432 | float value = getFloat(name); 433 | return String.valueOf(value); 434 | } 435 | 436 | /** 437 | * Returns the value associated with the given name, or 0.0 if no mapping of the desired type 438 | * exists for the given name. 439 | * 440 | * @param name The parameter name. 441 | * @return A double value. 442 | */ 443 | public double getDouble(String name) { 444 | return mBundle.getDouble(name); 445 | } 446 | 447 | /** 448 | * Returns the value associated with the given name transformed as a String (using 449 | * {@link String#valueOf(double)}), or "0" if no mapping of the desired type exists for the 450 | * given name. 451 | * 452 | * @param name The parameter name. 453 | * @return The String representation of the boolean value. 454 | */ 455 | public String getDoubleAsString(String name) { 456 | double value = getDouble(name); 457 | return String.valueOf(value); 458 | } 459 | 460 | /** 461 | * Returns the value associated with the given name, or null if no mapping of the desired type 462 | * exists for the given name. 463 | * 464 | * @param name The parameter name. 465 | * @return A String value. 466 | */ 467 | public String getString(String name) { 468 | return mBundle.getString(name); 469 | } 470 | 471 | /** 472 | * Returns the value associated with the given name, or null if no mapping of the desired type 473 | * exists for the given name. 474 | * 475 | * @param name The parameter name. 476 | * @return A CharSequence value. 477 | */ 478 | public CharSequence getCharSequence(String name) { 479 | return mBundle.getCharSequence(name); 480 | } 481 | 482 | /** 483 | * Returns the value associated with the given name, or null if no mapping of the desired type 484 | * exists for the given name. 485 | * 486 | * @param name The parameter name. 487 | * @return A Parcelable value. 488 | */ 489 | public Parcelable getParcelable(String name) { 490 | return mBundle.getParcelable(name); 491 | } 492 | 493 | /** 494 | * Sets the ClassLoader to use by the underlying Bundle when getting Parcelable objects. 495 | * 496 | * @param classLoader The ClassLoader to use by the underlying Bundle when getting Parcelable 497 | * objects. 498 | */ 499 | public void setClassLoader(ClassLoader classLoader) { 500 | mBundle.setClassLoader(classLoader); 501 | } 502 | 503 | @Override 504 | public boolean equals(Object o) { 505 | if (o instanceof Request) { 506 | Request oParams = (Request) o; 507 | if (mRequestType != oParams.mRequestType) { 508 | return false; 509 | } 510 | 511 | if (mParamList.size() != oParams.mParamList.size()) { 512 | return false; 513 | } 514 | 515 | for (int i = 0, length = mParamList.size(); i < length; i++) { 516 | String param = mParamList.get(i); 517 | if (!oParams.mParamList.contains(param)) { 518 | return false; 519 | } 520 | 521 | int type = mTypeList.get(i); 522 | if (oParams.mTypeList.get(i) != type) { 523 | return false; 524 | } 525 | 526 | switch (mTypeList.get(i)) { 527 | case TYPE_BOOLEAN: 528 | if (mBundle.getBoolean(param) != oParams.mBundle.getBoolean(param)) { 529 | return false; 530 | } 531 | break; 532 | case TYPE_BYTE: 533 | if (mBundle.getByte(param) != oParams.mBundle.getByte(param)) { 534 | return false; 535 | } 536 | break; 537 | case TYPE_CHAR: 538 | if (mBundle.getChar(param) != oParams.mBundle.getChar(param)) { 539 | return false; 540 | } 541 | break; 542 | case TYPE_SHORT: 543 | if (mBundle.getShort(param) != oParams.mBundle.getShort(param)) { 544 | return false; 545 | } 546 | break; 547 | case TYPE_INT: 548 | if (mBundle.getInt(param) != oParams.mBundle.getInt(param)) { 549 | return false; 550 | } 551 | break; 552 | case TYPE_LONG: 553 | if (mBundle.getLong(param) != oParams.mBundle.getLong(param)) { 554 | return false; 555 | } 556 | break; 557 | case TYPE_FLOAT: 558 | if (mBundle.getFloat(param) != oParams.mBundle.getFloat(param)) { 559 | return false; 560 | } 561 | break; 562 | case TYPE_DOUBLE: 563 | if (mBundle.getDouble(param) != oParams.mBundle.getDouble(param)) { 564 | return false; 565 | } 566 | break; 567 | case TYPE_STRING: 568 | if (!ObjectUtils.safeEquals(mBundle.getString(param), 569 | oParams.mBundle.getString(param))) { 570 | return false; 571 | } 572 | break; 573 | case TYPE_CHARSEQUENCE: 574 | if (!ObjectUtils.safeEquals(mBundle.getCharSequence(param), 575 | oParams.mBundle.getCharSequence(param))) { 576 | return false; 577 | } 578 | break; 579 | case TYPE_PARCELABLE: 580 | if (!ObjectUtils.safeEquals(mBundle.getParcelable(param), 581 | oParams.mBundle.getParcelable(param))) { 582 | return false; 583 | } 584 | break; 585 | default: 586 | // We should never arrive here normally. 587 | throw new IllegalArgumentException( 588 | "The type of the field is not a valid one"); 589 | } 590 | } 591 | return true; 592 | } 593 | return false; 594 | } 595 | 596 | @Override 597 | public int hashCode() { 598 | ArrayList objectList = new ArrayList(); 599 | objectList.add(mRequestType); 600 | for (int i = 0, length = mParamList.size(); i < length; i++) { 601 | objectList.add(mBundle.get(mParamList.get(i))); 602 | } 603 | return objectList.hashCode(); 604 | } 605 | 606 | // Parcelable management 607 | private Request(final Parcel in) { 608 | mRequestType = in.readInt(); 609 | mMemoryCacheDataEnabled = in.readInt() == 1; 610 | in.readStringList(mParamList); 611 | for (int i = 0, n = in.readInt(); i < n; i++) { 612 | mTypeList.add(in.readInt()); 613 | } 614 | mBundle = in.readBundle(); 615 | } 616 | 617 | @Override 618 | public int describeContents() { 619 | return 0; 620 | } 621 | 622 | @Override 623 | public void writeToParcel(Parcel dest, int flags) { 624 | dest.writeInt(mRequestType); 625 | dest.writeInt(mMemoryCacheDataEnabled ? 1 : 0); 626 | dest.writeStringList(mParamList); 627 | dest.writeInt(mTypeList.size()); 628 | for (int i = 0, length = mTypeList.size(); i < length; i++) { 629 | dest.writeInt(mTypeList.get(i)); 630 | } 631 | dest.writeBundle(mBundle); 632 | } 633 | 634 | public static final Creator CREATOR = new Creator() { 635 | public Request createFromParcel(final Parcel in) { 636 | return new Request(in); 637 | } 638 | 639 | public Request[] newArray(final int size) { 640 | return new Request[size]; 641 | } 642 | }; 643 | } 644 | -------------------------------------------------------------------------------- /src/com/foxykeep/datadroid/requestmanager/RequestManager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2011 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package com.foxykeep.datadroid.requestmanager; 10 | 11 | import com.foxykeep.datadroid.service.RequestService; 12 | import com.foxykeep.datadroid.util.DataDroidLog; 13 | 14 | import android.app.Activity; 15 | import android.app.Fragment; 16 | import android.content.Context; 17 | import android.content.Intent; 18 | import android.os.Bundle; 19 | import android.os.Handler; 20 | import android.os.Looper; 21 | import android.os.ResultReceiver; 22 | import android.support.util.LruCache; 23 | 24 | import org.apache.http.HttpStatus; 25 | 26 | import java.lang.ref.WeakReference; 27 | import java.util.Collections; 28 | import java.util.EventListener; 29 | import java.util.HashMap; 30 | import java.util.HashSet; 31 | import java.util.Set; 32 | 33 | /** 34 | * This class allows to send requests through a {@link RequestService}. 35 | *

36 | * This class needs to be subclassed in your project. 37 | *

38 | * You can check the following page to see a tutorial on how to implement a webservice call using 39 | * the {@link RequestManager} : http://www.datadroidlib.com/installation. 41 | * 42 | * @author Foxykeep 43 | */ 44 | public abstract class RequestManager { 45 | 46 | private static final String TAG = RequestManager.class.getSimpleName(); 47 | 48 | /** 49 | * Clients may implements this interface to be notified when a request is finished. 50 | * 51 | * @author Foxykeep 52 | */ 53 | public static interface RequestListener extends EventListener { 54 | 55 | /** 56 | * Event fired when a request is finished. 57 | * 58 | * @param request The {@link Request} defining the request. 59 | * @param resultData The result of the service execution. 60 | */ 61 | public void onRequestFinished(Request request, Bundle resultData); 62 | 63 | /** 64 | * Event fired when a request encountered a connection error. 65 | * 66 | * @param request The {@link Request} defining the request. 67 | * @param statusCode The HTTP status code returned by the server (if the request succeeded 68 | * by the HTTP status code was not {@link HttpStatus#SC_OK}) or -1 if it was a 69 | * connection problem 70 | */ 71 | public void onRequestConnectionError(Request request, int statusCode); 72 | 73 | /** 74 | * Event fired when a request encountered a data error. 75 | * 76 | * @param request The {@link Request} defining the request. 77 | */ 78 | public void onRequestDataError(Request request); 79 | 80 | /** 81 | * Event fired when a request encountered a custom error. 82 | * 83 | * @param request The {@link Request} defining the request. 84 | * @param resultData The result of the service execution. 85 | */ 86 | public void onRequestCustomError(Request request, Bundle resultData); 87 | } 88 | 89 | public static final String RECEIVER_EXTRA_ERROR_TYPE = "com.foxykeep.datadroid.extra.error"; 90 | public static final String RECEIVER_EXTRA_CONNECTION_ERROR_STATUS_CODE = 91 | "com.foxykeep.datadroid.extra.connectionErrorStatusCode"; 92 | public static final int ERROR_TYPE_CONNEXION = 1; 93 | public static final int ERROR_TYPE_DATA = 2; 94 | public static final int ERROR_TYPE_CUSTOM = 3; 95 | 96 | private final Context mContext; 97 | 98 | private final Class mRequestService; 99 | private final HashMap mRequestReceiverMap; 100 | private final LruCache mMemoryCache; 101 | 102 | protected RequestManager(Context context, Class requestService) { 103 | mContext = context.getApplicationContext(); 104 | 105 | mRequestService = requestService; 106 | mRequestReceiverMap = new HashMap(); 107 | mMemoryCache = new LruCache(30); 108 | } 109 | 110 | /** 111 | * Add a {@link RequestListener} to this {@link RequestManager} to a specific {@link Request}. 112 | * Clients may use it in order to be notified when the corresponding request is completed. 113 | *

114 | * The listener is automatically removed when the request is completed and they are notified. 115 | *

116 | * Warning !! If it's an {@link Activity} or a {@link Fragment} that is used as a 117 | * listener, it must be detached when {@link Activity#onPause} is called in an {@link Activity}. 118 | * 119 | * @param listener The listener called when the Request is completed. 120 | * @param request The {@link Request} to listen to. 121 | */ 122 | public final void addRequestListener(RequestListener listener, Request request) { 123 | if (listener == null) { 124 | return; 125 | } 126 | if (request == null) { 127 | throw new IllegalArgumentException("Request cannot be null."); 128 | } 129 | RequestReceiver requestReceiver = mRequestReceiverMap.get(request); 130 | if (requestReceiver == null) { 131 | DataDroidLog.w(TAG, "You tried to add a listener to a non-existing request."); 132 | return; 133 | } 134 | 135 | requestReceiver.addListenerHolder(new ListenerHolder(listener)); 136 | } 137 | 138 | /** 139 | * Remove a {@link RequestListener} to this {@link RequestManager} from every {@link Request}s 140 | * which it is listening to. 141 | * 142 | * @param listener The listener to remove. 143 | */ 144 | public final void removeRequestListener(RequestListener listener) { 145 | removeRequestListener(listener, null); 146 | } 147 | 148 | /** 149 | * Remove a {@link RequestListener} to this {@link RequestManager} from a specific 150 | * {@link Request}. 151 | * 152 | * @param listener The listener to remove. 153 | * @param request The {@link Request} associated with this listener. If null, the listener will 154 | * be removed from every request it is currently associated with. 155 | */ 156 | public final void removeRequestListener(RequestListener listener, Request request) { 157 | if (listener == null) { 158 | return; 159 | } 160 | ListenerHolder holder = new ListenerHolder(listener); 161 | if (request != null) { 162 | RequestReceiver requestReceiver = mRequestReceiverMap.get(request); 163 | if (requestReceiver != null) { 164 | requestReceiver.removeListenerHolder(holder); 165 | } 166 | } else { 167 | for (RequestReceiver requestReceiver : mRequestReceiverMap.values()) { 168 | requestReceiver.removeListenerHolder(holder); 169 | } 170 | } 171 | } 172 | 173 | /** 174 | * Return whether a {@link Request} is still in progress or not. 175 | * 176 | * @param request The request. 177 | * @return Whether the request is still in progress or not. 178 | */ 179 | public final boolean isRequestInProgress(Request request) { 180 | return mRequestReceiverMap.containsKey(request); 181 | } 182 | 183 | /** 184 | * Call the given listener synchronously with the memory cached data corresponding to the 185 | * request. 186 | *

187 | * The method called in the listener will be 188 | * {@link RequestListener#onRequestFinished(Request, Bundle)}. 189 | *

190 | * If no cached data is found, {@link RequestListener#onRequestConnectionError(Request, int)} 191 | * will be called instead 192 | * 193 | * @param listener The listener to call with the data if any. 194 | * @param request The request associated with the memory cached data. 195 | */ 196 | public final void callListenerWithCachedData(RequestListener listener, Request request) { 197 | if (request == null) { 198 | throw new IllegalArgumentException("Request cannot be null."); 199 | } 200 | if (listener == null) { 201 | return; 202 | } 203 | 204 | if (request.isMemoryCacheEnabled()) { 205 | Bundle bundle = mMemoryCache.get(request); 206 | if (bundle != null) { 207 | listener.onRequestFinished(request, bundle); 208 | } else { 209 | listener.onRequestConnectionError(request, -1); 210 | } 211 | } 212 | } 213 | 214 | /** 215 | * Execute the {@link Request}. 216 | * 217 | * @param request The request to execute. 218 | * @param listener The listener called when the Request is completed. 219 | */ 220 | public final void execute(Request request, RequestListener listener) { 221 | if (request == null) { 222 | throw new IllegalArgumentException("Request cannot be null."); 223 | } 224 | if (mRequestReceiverMap.containsKey(request)) { 225 | DataDroidLog.d(TAG, 226 | "This request is already in progress. Adding the new listener to it."); 227 | 228 | // This exact request is already in progress. Adding the new listener. 229 | addRequestListener(listener, request); 230 | // Just check if the new request has the memory cache enabled. 231 | if (request.isMemoryCacheEnabled()) { 232 | // If true, enable it in the RequestReceiver (if it's not the case already) 233 | mRequestReceiverMap.get(request).enableMemoryCache(); 234 | } 235 | return; 236 | } 237 | DataDroidLog.d(TAG, "Creating a new request and adding the listener to it."); 238 | 239 | RequestReceiver requestReceiver = new RequestReceiver(request); 240 | mRequestReceiverMap.put(request, requestReceiver); 241 | 242 | addRequestListener(listener, request); 243 | 244 | Intent intent = new Intent(mContext, mRequestService); 245 | intent.putExtra(RequestService.INTENT_EXTRA_RECEIVER, requestReceiver); 246 | intent.putExtra(RequestService.INTENT_EXTRA_REQUEST, request); 247 | mContext.startService(intent); 248 | } 249 | 250 | private final class RequestReceiver extends ResultReceiver { 251 | 252 | private final Request mRequest; 253 | private final Set mListenerHolderSet; 254 | private boolean mMemoryCacheEnabled; 255 | 256 | /* package */ RequestReceiver(Request request) { 257 | super(new Handler(Looper.getMainLooper())); 258 | 259 | mRequest = request; 260 | mListenerHolderSet = Collections.synchronizedSet(new HashSet()); 261 | mMemoryCacheEnabled = request.isMemoryCacheEnabled(); 262 | 263 | // Clear the old memory cache if any 264 | mMemoryCache.remove(request); 265 | } 266 | 267 | /* package */ void enableMemoryCache() { 268 | mMemoryCacheEnabled = true; 269 | } 270 | 271 | /* package */ void addListenerHolder(ListenerHolder listenerHolder) { 272 | synchronized (mListenerHolderSet) { 273 | mListenerHolderSet.add(listenerHolder); 274 | } 275 | } 276 | 277 | /* package */ void removeListenerHolder(ListenerHolder listenerHolder) { 278 | synchronized (mListenerHolderSet) { 279 | mListenerHolderSet.remove(listenerHolder); 280 | } 281 | } 282 | 283 | @Override 284 | public void onReceiveResult(int resultCode, Bundle resultData) { 285 | if (mMemoryCacheEnabled) { 286 | mMemoryCache.put(mRequest, resultData); 287 | } 288 | 289 | mRequestReceiverMap.remove(mRequest); 290 | 291 | // Call the available listeners 292 | synchronized (mListenerHolderSet) { 293 | for (ListenerHolder listenerHolder : mListenerHolderSet) { 294 | listenerHolder.onRequestFinished(mRequest, resultCode, resultData); 295 | } 296 | } 297 | } 298 | } 299 | 300 | private final class ListenerHolder { 301 | 302 | private final WeakReference mListenerRef; 303 | private final int mHashCode; 304 | 305 | /* package */ ListenerHolder(RequestListener listener) { 306 | mListenerRef = new WeakReference(listener); 307 | mHashCode = 31 + listener.hashCode(); 308 | } 309 | 310 | /* package */ void onRequestFinished(Request request, int resultCode, Bundle resultData) { 311 | mRequestReceiverMap.remove(request); 312 | 313 | RequestListener listener = mListenerRef.get(); 314 | if (listener != null) { 315 | if (resultCode == RequestService.ERROR_CODE) { 316 | switch (resultData.getInt(RECEIVER_EXTRA_ERROR_TYPE)) { 317 | case ERROR_TYPE_DATA: 318 | listener.onRequestDataError(request); 319 | break; 320 | case ERROR_TYPE_CONNEXION: 321 | int statusCode = 322 | resultData.getInt(RECEIVER_EXTRA_CONNECTION_ERROR_STATUS_CODE); 323 | listener.onRequestConnectionError(request, statusCode); 324 | break; 325 | case ERROR_TYPE_CUSTOM: 326 | listener.onRequestCustomError(request, resultData); 327 | break; 328 | } 329 | } else { 330 | listener.onRequestFinished(request, resultData); 331 | } 332 | } 333 | } 334 | 335 | @Override 336 | public boolean equals(Object o) { 337 | if (o instanceof ListenerHolder) { 338 | ListenerHolder oHolder = (ListenerHolder) o; 339 | return mListenerRef != null && oHolder.mListenerRef != null 340 | && mHashCode == oHolder.mHashCode; 341 | } 342 | return false; 343 | } 344 | 345 | @Override 346 | public int hashCode() { 347 | return mHashCode; 348 | } 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/com/foxykeep/datadroid/service/MultiThreadedIntentService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2011 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package com.foxykeep.datadroid.service; 10 | 11 | import android.app.Service; 12 | import android.content.Context; 13 | import android.content.Intent; 14 | import android.os.Handler; 15 | import android.os.IBinder; 16 | import android.os.Looper; 17 | 18 | import java.util.ArrayList; 19 | import java.util.concurrent.ExecutorService; 20 | import java.util.concurrent.Executors; 21 | import java.util.concurrent.Future; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | /** 25 | * MultiThreadIntentService is a base class for {@link Service}s that handle asynchronous requests 26 | * (expressed as {@link Intent}s) on demand. Clients send requests through 27 | * {@link android.content.Context#startService(Intent)} calls; the service is started as needed, 28 | * handles each Intent in turn using a worker thread, and stops itself when it runs out of work. 29 | *

30 | * This "work queue processor" pattern is commonly used to offload tasks from an application's main 31 | * thread. The MultiThreadedIntentService class exists to simplify this pattern and take care of the 32 | * mechanics. To use it, extend MultiThreadedIntentService and implement 33 | * {@link #onHandleIntent(Intent)}. MultiThreadedIntentService will receive the Intents, launch a 34 | * worker thread, and stop the service as appropriate. 35 | *

36 | * All requests are handled on multiple worker threads -- they may take as long as necessary (and 37 | * will not block the application's main loop). By default only one concurrent worker thread is 38 | * used. You can modify the number of current worker threads by overriding 39 | * {@link #getMaximumNumberOfThreads()}. 40 | *

41 | * For obvious efficiency reasons, MultiThreadedIntentService won't stop itself as soon as all tasks 42 | * has been processed. It will only stop itself after a certain delay (about 30s). This optimization 43 | * prevents the system from creating new instances over and over again when tasks are sent. 44 | * 45 | * @author Foxykeep 46 | */ 47 | public abstract class MultiThreadedIntentService extends Service { 48 | 49 | private static final long STOP_SELF_DELAY = TimeUnit.SECONDS.toMillis(30L); 50 | 51 | private ExecutorService mThreadPool; 52 | private boolean mRedelivery; 53 | 54 | private ArrayList> mFutureList; 55 | 56 | private Handler mHandler; 57 | 58 | private final Runnable mStopSelfRunnable = new Runnable() { 59 | @Override 60 | public void run() { 61 | stopSelf(); 62 | } 63 | }; 64 | 65 | private final Runnable mWorkDoneRunnable = new Runnable() { 66 | @Override 67 | public void run() { 68 | if (Looper.getMainLooper().getThread() != Thread.currentThread()) { 69 | throw new IllegalStateException( 70 | "This runnable can only be called in the Main thread!"); 71 | } 72 | 73 | final ArrayList> futureList = mFutureList; 74 | for (int i = 0; i < futureList.size(); i++) { 75 | if (futureList.get(i).isDone()) { 76 | futureList.remove(i); 77 | i--; 78 | } 79 | } 80 | 81 | if (futureList.isEmpty()) { 82 | mHandler.postDelayed(mStopSelfRunnable, STOP_SELF_DELAY); 83 | } 84 | } 85 | }; 86 | 87 | /** 88 | * Sets intent redelivery preferences. Usually called from the constructor with your preferred 89 | * semantics. 90 | *

91 | * If enabled is true, {@link #onStartCommand(Intent, int, int)} will return 92 | * {@link Service#START_REDELIVER_INTENT}, so if this process dies before 93 | * {@link #onHandleIntent(Intent)} returns, the process will be restarted and the intent 94 | * redelivered. If multiple Intents have been sent, only the most recent one is guaranteed to be 95 | * redelivered. 96 | *

97 | * If enabled is false (the default), {@link #onStartCommand(Intent, int, int)} will return 98 | * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent dies along with it. 99 | */ 100 | public void setIntentRedelivery(boolean enabled) { 101 | mRedelivery = enabled; 102 | } 103 | 104 | @Override 105 | public void onCreate() { 106 | super.onCreate(); 107 | int maximumNumberOfThreads = getMaximumNumberOfThreads(); 108 | if (maximumNumberOfThreads <= 0) { 109 | throw new IllegalArgumentException("Maximum number of threads must be " + 110 | "strictly positive"); 111 | } 112 | mThreadPool = Executors.newFixedThreadPool(maximumNumberOfThreads); 113 | mHandler = new Handler(); 114 | mFutureList = new ArrayList>(); 115 | } 116 | 117 | @Override 118 | @SuppressWarnings("deprecation") 119 | public void onStart(Intent intent, int startId) { 120 | mHandler.removeCallbacks(mStopSelfRunnable); 121 | mFutureList.add(mThreadPool.submit(new IntentRunnable(intent))); 122 | } 123 | 124 | @Override 125 | public int onStartCommand(Intent intent, int flags, int startId) { 126 | onStart(intent, startId); 127 | return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; 128 | } 129 | 130 | @Override 131 | public void onDestroy() { 132 | super.onDestroy(); 133 | mThreadPool.shutdown(); 134 | } 135 | 136 | /** 137 | * Unless you provide binding for your service, you don't need to implement this method, because 138 | * the default implementation returns null. 139 | * 140 | * @see android.app.Service#onBind 141 | */ 142 | @Override 143 | public IBinder onBind(Intent intent) { 144 | return null; 145 | } 146 | 147 | /** 148 | * Define the maximum number of concurrent worker threads used to execute the incoming Intents. 149 | *

150 | * By default only one concurrent worker thread is used at the same time. Overrides this method 151 | * in subclasses to change this number. 152 | *

153 | * This method is called once in the {@link #onCreate()}. Modifying the value returned after the 154 | * {@link #onCreate()} is called will have no effect. 155 | * 156 | * @return The maximum number of concurrent worker threads 157 | */ 158 | protected int getMaximumNumberOfThreads() { 159 | return 1; 160 | } 161 | 162 | private class IntentRunnable implements Runnable { 163 | private final Intent mIntent; 164 | 165 | public IntentRunnable(Intent intent) { 166 | mIntent = intent; 167 | } 168 | 169 | public void run() { 170 | onHandleIntent(mIntent); 171 | mHandler.removeCallbacks(mWorkDoneRunnable); 172 | mHandler.post(mWorkDoneRunnable); 173 | } 174 | } 175 | 176 | /** 177 | * This method is invoked on the worker thread with a request to process. The processing happens 178 | * on a worker thread that runs independently from other application logic. When all requests 179 | * have been handled, the IntentService stops itself, so you should not call {@link #stopSelf}. 180 | * 181 | * @param intent The value passed to {@link Context#startService(Intent)}. 182 | */ 183 | abstract protected void onHandleIntent(Intent intent); 184 | } 185 | -------------------------------------------------------------------------------- /src/com/foxykeep/datadroid/service/RequestService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2011 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package com.foxykeep.datadroid.service; 10 | 11 | import com.foxykeep.datadroid.exception.ConnectionException; 12 | import com.foxykeep.datadroid.exception.CustomRequestException; 13 | import com.foxykeep.datadroid.exception.DataException; 14 | import com.foxykeep.datadroid.requestmanager.Request; 15 | import com.foxykeep.datadroid.requestmanager.RequestManager; 16 | import com.foxykeep.datadroid.util.DataDroidLog; 17 | 18 | import android.content.Context; 19 | import android.content.Intent; 20 | import android.os.Bundle; 21 | import android.os.ResultReceiver; 22 | 23 | /** 24 | * This class is the superclass of all the worker services you'll create. 25 | * 26 | * @author Foxykeep 27 | */ 28 | public abstract class RequestService extends MultiThreadedIntentService { 29 | 30 | /** 31 | * Interface to implement by your operations 32 | * 33 | * @author Foxykeep 34 | */ 35 | public interface Operation { 36 | /** 37 | * Execute the request and returns a {@link Bundle} containing the data to return. 38 | * 39 | * @param context The context to use for your operation. 40 | * @param request The request to execute. 41 | * @return A {@link Bundle} containing the data to return. If no data to return, null. 42 | * @throws ConnectionException Thrown when a connection error occurs. It will be propagated 43 | * to the {@link RequestManager} as a 44 | * {@link RequestManager#ERROR_TYPE_CONNEXION}. 45 | * @throws DataException Thrown when a problem occurs while managing the data of the 46 | * webservice. It will be propagated to the {@link RequestManager} as a 47 | * {@link RequestManager#ERROR_TYPE_DATA}. 48 | * @throws CustomRequestException Any other exception you may have to throw. A call to 49 | * {@link RequestService#onCustomRequestException(Request, 50 | * CustomRequestException)} will be made with the Exception thrown. 51 | */ 52 | public Bundle execute(Context context, Request request) throws ConnectionException, 53 | DataException, CustomRequestException; 54 | } 55 | 56 | private static final String LOG_TAG = RequestService.class.getSimpleName(); 57 | 58 | public static final String INTENT_EXTRA_RECEIVER = "com.foxykeep.datadroid.extra.receiver"; 59 | public static final String INTENT_EXTRA_REQUEST = "com.foxykeep.datadroid.extra.request"; 60 | 61 | private static final int SUCCESS_CODE = 0; 62 | public static final int ERROR_CODE = -1; 63 | 64 | /** 65 | * Proxy method for {@link #sendResult(ResultReceiver, Bundle, int)} when the work is a 66 | * success. 67 | * 68 | * @param receiver The result receiver received inside the {@link Intent}. 69 | * @param data A {@link Bundle} with the data to send back. 70 | */ 71 | private void sendSuccess(ResultReceiver receiver, Bundle data) { 72 | sendResult(receiver, data, SUCCESS_CODE); 73 | } 74 | 75 | /** 76 | * Proxy method for {@link #sendResult(ResultReceiver, Bundle, int)} when the work is a failure 77 | * due to the network. 78 | * 79 | * @param receiver The result receiver received inside the {@link Intent}. 80 | * @param exception The {@link ConnectionException} triggered. 81 | */ 82 | private void sendConnexionFailure(ResultReceiver receiver, ConnectionException exception) { 83 | Bundle data = new Bundle(); 84 | data.putInt(RequestManager.RECEIVER_EXTRA_ERROR_TYPE, RequestManager.ERROR_TYPE_CONNEXION); 85 | data.putInt(RequestManager.RECEIVER_EXTRA_CONNECTION_ERROR_STATUS_CODE, 86 | exception.getStatusCode()); 87 | sendResult(receiver, data, ERROR_CODE); 88 | } 89 | 90 | /** 91 | * Proxy method for {@link #sendResult(ResultReceiver, Bundle, int)} when the work is a failure 92 | * due to the data (parsing for example). 93 | * 94 | * @param receiver The result receiver received inside the {@link Intent}. 95 | */ 96 | private void sendDataFailure(ResultReceiver receiver) { 97 | Bundle data = new Bundle(); 98 | data.putInt(RequestManager.RECEIVER_EXTRA_ERROR_TYPE, RequestManager.ERROR_TYPE_DATA); 99 | sendResult(receiver, data, ERROR_CODE); 100 | } 101 | 102 | /** 103 | * Proxy method for {@link #sendResult(ResultReceiver, Bundle, int)} when the work is a failure 104 | * due to {@link CustomRequestException} being thrown. 105 | * 106 | * @param receiver The result receiver received inside the {@link Intent}. 107 | * @param data A {@link Bundle} the data to send back. 108 | */ 109 | private void sendCustomFailure(ResultReceiver receiver, Bundle data) { 110 | if (data == null) { 111 | data = new Bundle(); 112 | } 113 | data.putInt(RequestManager.RECEIVER_EXTRA_ERROR_TYPE, RequestManager.ERROR_TYPE_CUSTOM); 114 | sendResult(receiver, data, ERROR_CODE); 115 | } 116 | 117 | /** 118 | * Method used to send back the result to the {@link RequestManager}. 119 | * 120 | * @param receiver The result receiver received inside the {@link Intent}. 121 | * @param data A {@link Bundle} the data to send back. 122 | * @param code The success/error code to send back. 123 | */ 124 | private void sendResult(ResultReceiver receiver, Bundle data, int code) { 125 | DataDroidLog.d(LOG_TAG, "sendResult : " + ((code == SUCCESS_CODE) ? "Success" : "Failure")); 126 | 127 | if (receiver != null) { 128 | if (data == null) { 129 | data = new Bundle(); 130 | } 131 | 132 | receiver.send(code, data); 133 | } 134 | } 135 | 136 | @Override 137 | protected final void onHandleIntent(Intent intent) { 138 | Request request = intent.getParcelableExtra(INTENT_EXTRA_REQUEST); 139 | request.setClassLoader(getClassLoader()); 140 | 141 | ResultReceiver receiver = intent.getParcelableExtra(INTENT_EXTRA_RECEIVER); 142 | 143 | Operation operation = getOperationForType(request.getRequestType()); 144 | try { 145 | sendSuccess(receiver, operation.execute(this, request)); 146 | } catch (ConnectionException e) { 147 | DataDroidLog.e(LOG_TAG, "ConnectionException", e); 148 | sendConnexionFailure(receiver, e); 149 | } catch (DataException e) { 150 | DataDroidLog.e(LOG_TAG, "DataException", e); 151 | sendDataFailure(receiver); 152 | } catch (CustomRequestException e) { 153 | DataDroidLog.e(LOG_TAG, "Custom Exception", e); 154 | sendCustomFailure(receiver, onCustomRequestException(request, e)); 155 | } catch (RuntimeException e) { 156 | DataDroidLog.e(LOG_TAG, "RuntimeException", e); 157 | sendDataFailure(receiver); 158 | } 159 | } 160 | 161 | /** 162 | * Get the {@link Operation} corresponding to the given request type. 163 | * 164 | * @param requestType The request type (extracted from {@link Request}). 165 | * @return The corresponding {@link Operation}. 166 | */ 167 | public abstract Operation getOperationForType(int requestType); 168 | 169 | /** 170 | * Call if a {@link CustomRequestException} is thrown by an {@link Operation}. You may return a 171 | * Bundle containing data to return to the {@link RequestManager}. 172 | *

173 | * Default implementation return null. You may want to override this method in your 174 | * implementation of {@link RequestService} to execute specific action and/or return specific 175 | * data. 176 | * 177 | * @param request The {@link Request} which execution threw the exception. 178 | * @param exception The {@link CustomRequestException} thrown. 179 | * @return A {@link Bundle} containing data to return to the {@link RequestManager}. Default 180 | * implementation return null. 181 | */ 182 | protected Bundle onCustomRequestException(Request request, CustomRequestException exception) { 183 | return null; 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /src/com/foxykeep/datadroid/util/DataDroidLog.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2012 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package com.foxykeep.datadroid.util; 10 | 11 | import android.util.Log; 12 | 13 | import com.ftxgame.ftxcn.BuildConfig; 14 | 15 | /** 16 | * Centralized helper for logging information for DataDroid. 17 | *

18 | * To modify the log level, use the following command : 19 | * 20 | *

21 |  *   adb shell setprop log.tag.DataDroid <LOGLEVEL>
22 |  *   with LOGLEVEL being one of the following :
23 |  *     VERBOSE, DEBUG, INFO, WARN or ERROR
24 |  * 
25 | *

26 | * The default log level is DEBUG. 27 | *

28 | * Also the logs are disabled in a release build by default. If you want them to be enabled, modify 29 | * the value of {@link #ENABLE_LOGS_IN_RELEASE}. 30 | * 31 | * @author Foxykeep 32 | */ 33 | public final class DataDroidLog { 34 | 35 | /** 36 | * Primary log tag for games output. 37 | */ 38 | private static final String LOG_TAG = "DataDroid"; 39 | 40 | /** 41 | * Whether the logs are enabled in release builds or not. 42 | */ 43 | private static final boolean ENABLE_LOGS_IN_RELEASE = false; 44 | 45 | public static boolean canLog(int level) { 46 | return (ENABLE_LOGS_IN_RELEASE || BuildConfig.DEBUG) && Log.isLoggable(LOG_TAG, level); 47 | } 48 | 49 | public static void d(String tag, String message) { 50 | if (canLog(Log.DEBUG)) { 51 | Log.d(tag, message); 52 | } 53 | } 54 | 55 | public static void v(String tag, String message) { 56 | if (canLog(Log.VERBOSE)) { 57 | Log.v(tag, message); 58 | } 59 | } 60 | 61 | public static void i(String tag, String message) { 62 | if (canLog(Log.INFO)) { 63 | Log.i(tag, message); 64 | } 65 | } 66 | 67 | public static void i(String tag, String message, Throwable thr) { 68 | if (canLog(Log.INFO)) { 69 | Log.i(tag, message, thr); 70 | } 71 | } 72 | 73 | public static void w(String tag, String message) { 74 | if (canLog(Log.WARN)) { 75 | Log.w(tag, message); 76 | } 77 | } 78 | 79 | public static void w(String tag, String message, Throwable thr) { 80 | if (canLog(Log.WARN)) { 81 | Log.w(tag, message, thr); 82 | } 83 | } 84 | 85 | public static void e(String tag, String message) { 86 | if (canLog(Log.ERROR)) { 87 | Log.e(tag, message); 88 | } 89 | } 90 | 91 | public static void e(String tag, String message, Throwable thr) { 92 | if (canLog(Log.ERROR)) { 93 | Log.e(tag, message, thr); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/com/foxykeep/datadroid/util/ObjectUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2012 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package com.foxykeep.datadroid.util; 10 | 11 | 12 | /** 13 | * Utility methods for Objects 14 | * 15 | * @author Foxykeep 16 | */ 17 | public final class ObjectUtils { 18 | 19 | private ObjectUtils() { 20 | // No public constructor 21 | } 22 | 23 | /** 24 | * Perform a safe equals between 2 objects. 25 | *

26 | * It manages the case where the first object is null and it would have resulted in a 27 | * {@link NullPointerException} if o1.equals(o2) was used. 28 | * 29 | * @param o1 First object to check. 30 | * @param o2 Second object to check. 31 | * @return true if both objects are equal. false otherwise 32 | * @see java.lang.Object#equals(Object) uals() 33 | */ 34 | public static boolean safeEquals(Object o1, Object o2) { 35 | if (o1 == null) { 36 | return o2 == null; 37 | } else { 38 | return o1.equals(o2); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/com/ftxgame/ftxcn/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.ftxgame.ftxcn; 2 | 3 | import net.frakbot.imageviewex.Converters; 4 | import net.frakbot.imageviewex.ImageViewEx; 5 | import net.frakbot.imageviewex.ImageViewNext; 6 | import android.app.Activity; 7 | import android.os.Bundle; 8 | 9 | public class MainActivity extends Activity { 10 | 11 | private ImageViewEx imgEx1; 12 | private ImageViewEx imgEx2; 13 | private ImageViewNext imgNext1; 14 | private ImageViewNext imgNext2; 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | ImageViewNext.setMaximumNumberOfThreads(100); 20 | 21 | setContentView(R.layout.activity_main); 22 | 23 | ImageViewNext.setClassErrorDrawable(R.drawable.empty_newsthumb); 24 | ImageViewNext.setClassLoadingDrawable(R.drawable.loader); 25 | 26 | imgEx1 = (ImageViewEx) findViewById(R.id.imageViewEx1); 27 | imgEx2 = (ImageViewEx) findViewById(R.id.imageViewEx2); 28 | imgNext1 = (ImageViewNext) findViewById(R.id.imageViewNext1); 29 | imgNext2 = (ImageViewNext) findViewById(R.id.imageViewNext2); 30 | 31 | // 1、本地gif读取播放 32 | // 2、本地jpg/png图片读取 33 | // 3、URL gif图片读取播放 34 | // 4、URL jpg/png图片读取 35 | 36 | imgEx1.setSource(Converters.assetToByteArray(getAssets(), "test_gif.gif")); 37 | imgEx2.setBackgroundResource(R.drawable.test); 38 | 39 | imgNext1.setUrl("http://static.ftx.cn/images/T1DRhTBXCT1RCvBVdK"); 40 | imgNext2.setUrl("http://static.ftx.cn/images/T12RhTB4VT1RCvBVdK"); 41 | // img1.setDensity(DisplayMetrics.DENSITY_LOW); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/net/frakbot/cache/CacheHelper.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.cache; 2 | 3 | import java.io.BufferedOutputStream; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | import java.io.UnsupportedEncodingException; 8 | import java.security.MessageDigest; 9 | import java.security.NoSuchAlgorithmException; 10 | import java.util.Locale; 11 | 12 | import android.annotation.TargetApi; 13 | import android.content.Context; 14 | import android.os.Build; 15 | import android.os.Environment; 16 | 17 | import com.jakewharton.disklrucache.DiskLruCache.Editor; 18 | 19 | public class CacheHelper { 20 | 21 | public static File getDiskCacheDir(Context context, String uniqueName) { 22 | // Check if media is mounted or storage is built-in, if so, 23 | // try and use external cache dir, otherwise use internal cache dir. 24 | final String cachePath = 25 | Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || 26 | !isExternalStorageRemovable() ? 27 | getExternalCacheDir(context).getPath() : 28 | context.getCacheDir().getPath(); 29 | 30 | return new File(cachePath + File.separator + uniqueName); 31 | } 32 | 33 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 34 | public static boolean isExternalStorageRemovable() { 35 | return Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD || Environment.isExternalStorageRemovable(); 36 | } 37 | 38 | private static String getExternDir(String dir) { 39 | String path = Environment.getExternalStorageDirectory() 40 | .getAbsolutePath(); 41 | if(dir != null){ 42 | path += dir; 43 | } 44 | 45 | return path; 46 | } 47 | 48 | public static String getSubOfRootDir(Context context) { 49 | String rootDir = ""; 50 | if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)){ 51 | rootDir = getExternDir(rootDir) + "/ftxcn"; 52 | }else { 53 | rootDir = context.getCacheDir().getPath() + "/ftxcn"; 54 | } 55 | return rootDir; 56 | } 57 | 58 | public static File getExternalCacheDir(Context context) { 59 | if (hasExternalCacheDir()) { 60 | return context.getExternalCacheDir(); 61 | } 62 | 63 | // Before Froyo we need to construct the external cache dir ourselves 64 | final String cacheDir = getSubOfRootDir(context); 65 | System.out.println("cacheDir:" + cacheDir); 66 | return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir); 67 | } 68 | 69 | public static boolean hasExternalCacheDir() { 70 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO; 71 | } 72 | 73 | /** 74 | * Writes a byte array into a DiskLruCache {@link Editor}. 75 | * 76 | * @param source The input byte array. 77 | * @param editor The {@link Editor} to write the byte array into. 78 | * 79 | * @return true if there were no errors, false otherwise. 80 | * @throws IOException If there was an error while writing the file. 81 | */ 82 | public static boolean writeByteArrayToEditor(byte[] source, Editor editor) throws IOException { 83 | OutputStream out = null; 84 | try { 85 | out = new BufferedOutputStream(editor.newOutputStream(0), source.length); 86 | editor.newOutputStream(0).write(source); 87 | return true; 88 | } finally { 89 | if (out != null) { 90 | out.close(); 91 | } 92 | } 93 | } 94 | 95 | /** 96 | * Encodes URLs with the SHA-256 algorithm. 97 | * @param uri The URL to encode. 98 | * 99 | * @return The encoded URL. 100 | * 101 | * @throws NoSuchAlgorithmException If the SHA-256 algorithm is not found. 102 | * @throws UnsupportedEncodingException If the UTF-8 encoding is not supported. 103 | */ 104 | public static String UriToDiskLruCacheString(String uri) throws 105 | NoSuchAlgorithmException, 106 | UnsupportedEncodingException { 107 | MessageDigest digest = MessageDigest.getInstance("SHA-256"); 108 | byte[] convBytes = digest.digest(uri.getBytes("UTF-8")); 109 | String result; 110 | StringBuilder sb = new StringBuilder(); 111 | for (byte b : convBytes) { 112 | sb.append(String.format("%02X", b)); 113 | } 114 | result = sb.toString(); 115 | result = result.toLowerCase(Locale.US); 116 | return result; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/Converters.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.graphics.BitmapFactory.Options; 8 | import android.graphics.drawable.BitmapDrawable; 9 | import android.graphics.drawable.Drawable; 10 | import android.util.Log; 11 | 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | 16 | /** 17 | * Converters class, it's abstract and all of its methods are static. 18 | * 19 | * @author Francesco Pontillo, Sebastiano Poggi 20 | */ 21 | public abstract class Converters { 22 | private static final String TAG = Converters.class.getSimpleName(); 23 | 24 | /** 25 | * Converts a byte array into a BitmapDrawable, using the provided options. 26 | * 27 | * @param image The byte array representing the image. 28 | * @param opts The decoding options to use, or null if you'd like to use predefined 29 | * options (scaling will be not active). 30 | * @param context The Context for getting the Resources. 31 | * 32 | * @return The initialized BitmapDrawable. 33 | */ 34 | public static BitmapDrawable byteArrayToDrawable(byte[] image, Options opts, Context context) { 35 | if (opts == null) { 36 | Log.v(TAG, "opts is null, initializing without scaling"); 37 | opts = new Options(); 38 | opts.inScaled = false; 39 | } 40 | Bitmap bmp = BitmapFactory.decodeByteArray(image, 0, image.length, opts); 41 | // bmp.setDensity(DisplayMetrics.DENSITY_HIGH); 42 | return new BitmapDrawable(context.getResources(), bmp); 43 | } 44 | 45 | /** 46 | * Converts a byte array into a Bitmap, using the provided options. 47 | * 48 | * @param image The byte array representing the image. 49 | * @param opts The decoding options to use, or null if you'd like to use predefined 50 | * options (scaling will be not active). 51 | * 52 | * @return The initialized BitmapDrawable. 53 | */ 54 | public static Bitmap byteArrayToBitmap(byte[] image, Options opts) { 55 | if (opts == null) { 56 | Log.v(TAG, "opts is null, initializing without scaling"); 57 | opts = new Options(); 58 | opts.inScaled = false; 59 | } 60 | return BitmapFactory.decodeByteArray(image, 0, image.length, opts); 61 | } 62 | 63 | /** 64 | * Covnerts a Bitmap into a byte array. 65 | * 66 | * @param image The Bitmap to convert. 67 | * 68 | * @return The byte array representing the Bitmap (compressed in PNG). 69 | */ 70 | public static byte[] bitmapToByteArray(Bitmap image) { 71 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 72 | image.compress(Bitmap.CompressFormat.PNG, 100, stream); 73 | return stream.toByteArray(); 74 | } 75 | 76 | /** 77 | * Converts a Drawable into a byte array. 78 | * 79 | * @param image The Drawable to convertLa Drawable da convertire. 80 | * 81 | * @return The byte array representing the Drawable (compressed in PNG). 82 | */ 83 | public static byte[] drawableToByteArray(Drawable image) { 84 | Bitmap bitmap = ((BitmapDrawable) image).getBitmap(); 85 | return bitmapToByteArray(bitmap); 86 | } 87 | 88 | /** 89 | * Gets an asset from a provided AssetManager and its name in the directory and returns a 90 | * byte array representing the object content. 91 | * 92 | * @param assetManager An {@link AssetManager}. 93 | * @param asset String of the file name. 94 | * 95 | * @return byte[] representing the object content. 96 | */ 97 | public static byte[] assetToByteArray(AssetManager assetManager, String asset) { 98 | byte[] image = null; 99 | int b; 100 | InputStream is = null; 101 | ByteArrayOutputStream outStream = new ByteArrayOutputStream(); 102 | 103 | try { 104 | is = assetManager.open(asset); 105 | while ((b = is.read()) != -1) { 106 | outStream.write(b); 107 | } 108 | image = outStream.toByteArray(); 109 | } 110 | catch (IOException e) { 111 | Log.v(TAG, "Error while reading asset to byte array: " + asset, e); 112 | image = null; 113 | } 114 | finally { 115 | if (is != null) { 116 | try { 117 | is.close(); 118 | } 119 | catch (IOException ignored) { } 120 | } 121 | 122 | try { 123 | outStream.close(); 124 | } 125 | catch (IOException ignored) { } 126 | } 127 | 128 | return image; 129 | } 130 | 131 | /** 132 | * Converts an {@link InputStream} into a byte array. 133 | * 134 | * @param is The {@link InputStream} to convert. 135 | * @param size The size of the {@link InputStream}. 136 | * 137 | * @return The converted byte array. 138 | */ 139 | public static byte[] inputStreamToByteArray(InputStream is, int size) { 140 | ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); 141 | byte[] buffer = new byte[size]; 142 | 143 | int len = 0; 144 | try { 145 | while ((len = is.read(buffer)) != -1) { 146 | byteBuffer.write(buffer, 0, len); 147 | } 148 | } catch (IOException e) { 149 | e.printStackTrace(); 150 | } 151 | 152 | buffer = byteBuffer.toByteArray(); 153 | return buffer; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/ImageAlign.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex; 2 | 3 | /** 4 | * Enum that contains image alignments for ImageViewEx. 5 | * 6 | * @author Sebastiano Poggi, Francesco Pontillo 7 | * 8 | * @deprecated Use ScaleType.FIT_START and ScaleType.FIT_END instead. 9 | */ 10 | public enum ImageAlign { 11 | /** 12 | * No forced alignment. Image will be placed where the 13 | * scaleType dictates it to. 14 | */ 15 | NONE, 16 | 17 | /** 18 | * Force top alignment: the top edge is aligned with 19 | * the View top. 20 | */ 21 | TOP 22 | } 23 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/ImageViewNext.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | import net.frakbot.cache.CacheHelper; 7 | import net.frakbot.imageviewex.broadcastreceiver.ConnectivityChangeBroadcastReceiver; 8 | import net.frakbot.imageviewex.listener.ImageViewExRequestListener; 9 | import net.frakbot.imageviewex.requestmanager.ImageViewExRequestFactory; 10 | import net.frakbot.imageviewex.requestmanager.ImageViewExRequestManager; 11 | import android.content.Context; 12 | import android.content.IntentFilter; 13 | import android.graphics.drawable.AnimationDrawable; 14 | import android.graphics.drawable.Drawable; 15 | import android.os.Bundle; 16 | import android.support.v4.util.LruCache; 17 | import android.util.AttributeSet; 18 | import android.util.Log; 19 | 20 | import com.foxykeep.datadroid.requestmanager.Request; 21 | import com.foxykeep.datadroid.requestmanager.RequestManager.RequestListener; 22 | import com.ftxgame.ftxcn.BuildConfig; 23 | import com.jakewharton.disklrucache.DiskLruCache; 24 | 25 | /** 26 | * Extension of the ImageViewEx that handles the download and caching of 27 | * images and animated GIFs. 28 | * 29 | * @author Francesco Pontillo, Sebastiano Poggi 30 | */ 31 | public class ImageViewNext extends ImageViewEx { 32 | 33 | private static final String TAG = ImageViewNext.class.getSimpleName(); 34 | private static final int DISK_CACHE_VALUE_COUNT = 1; 35 | 36 | private Drawable mLoadingD; 37 | private static int mClassLoadingResId; 38 | private Drawable mErrorD; 39 | private static int mClassErrorResId; 40 | 41 | private boolean mAutoRetryFromNetwork; 42 | private static boolean mClassAutoRetryFromNetwork; 43 | private boolean hasFailedDownload; 44 | 45 | private String mUrl; 46 | private ImageLoadCompletionListener mLoadCallbacks; 47 | 48 | protected ImageViewExRequestManager mRequestManager; 49 | protected Request mCurrentRequest; 50 | protected RequestListener mCurrentRequestListener; 51 | 52 | private Context mContext; 53 | 54 | private static int mMemCacheSize = 10 * 1024 * 1024; // 10MiB 55 | private static LruCache mMemCache; 56 | private static int mAppVersion = 1; 57 | private static int mDiskCacheSize = 50 * 1024 * 1024; // 50MiB 58 | private static DiskLruCache mDiskCache; 59 | private static boolean mCacheInit = false; 60 | private static int mConcurrentThreads = 10; 61 | 62 | private ConnectivityChangeBroadcastReceiver mReceiver; 63 | private static final String RECEIVER_ACTION = android.net.ConnectivityManager.CONNECTIVITY_ACTION; 64 | 65 | /** Represents a cache level. */ 66 | public enum CacheLevel { 67 | /** The first level of cache: the memory cache */ 68 | MEMORY, 69 | /** The second level of cache: the disk cache */ 70 | DISK, 71 | /** No caching, direct fetching from the network */ 72 | NETWORK 73 | } 74 | 75 | /** {@inheritDoc} */ 76 | public ImageViewNext(Context context) { 77 | super(context); 78 | init(context); 79 | } 80 | 81 | /** 82 | * Creates an instance for the class. 83 | * Initializes the auto retry from network to true. 84 | * 85 | * @param context The context to initialize the instance into. 86 | * @param attrs The parameters to initialize the instance with. 87 | */ 88 | public ImageViewNext(Context context, AttributeSet attrs) { 89 | super(context, attrs); 90 | init(context); 91 | } 92 | 93 | /** 94 | * Initializes a few instance level variables. 95 | * 96 | * @param context The Context used for initialization. 97 | */ 98 | private void init(Context context) { 99 | mContext = context; 100 | mRequestManager = ImageViewExRequestManager.from(context); 101 | mClassAutoRetryFromNetwork = true; 102 | mAutoRetryFromNetwork = true; 103 | hasFailedDownload = false; 104 | } 105 | 106 | /** {@inheritDoc} */ 107 | @Override 108 | protected void onAttachedToWindow() { 109 | super.onAttachedToWindow(); 110 | registerReceiver(); 111 | } 112 | 113 | /** {@inheritDoc} */ 114 | @Override 115 | protected void onDetachedFromWindow() { 116 | super.onDetachedFromWindow(); 117 | unregisterReceiver(); 118 | } 119 | 120 | /** Register the {@link ConnectivityChangeBroadcastReceiver} for this instance. */ 121 | private void registerReceiver() { 122 | // If the receiver does not exist 123 | if (mReceiver == null) { 124 | mReceiver = new ConnectivityChangeBroadcastReceiver(this); 125 | final IntentFilter intentFilter = new IntentFilter(); 126 | intentFilter.addAction(RECEIVER_ACTION); 127 | mContext.registerReceiver(mReceiver, intentFilter); 128 | } 129 | } 130 | 131 | /** Unregister the {@link ConnectivityChangeBroadcastReceiver} for this instance. */ 132 | private void unregisterReceiver() { 133 | // If the receiver does exists 134 | if (mReceiver != null) { 135 | mContext.unregisterReceiver(mReceiver); 136 | mReceiver = null; 137 | } 138 | } 139 | 140 | /** Gets the current image loading callback, if any */ 141 | public ImageLoadCompletionListener getLoadCallbacks() { 142 | return mLoadCallbacks; 143 | } 144 | 145 | /** 146 | * Sets the image loading callback. 147 | * 148 | * @param loadCallbacks The listener instance, or null to clear it. 149 | */ 150 | public void setLoadCallbacks(ImageLoadCompletionListener loadCallbacks) { 151 | mLoadCallbacks = loadCallbacks; 152 | } 153 | 154 | /** @return The in-memory cache. */ 155 | public static LruCache getMemCache() { 156 | return mMemCache; 157 | } 158 | 159 | /** @return The disk cache. */ 160 | public static DiskLruCache getDiskCache() { 161 | return mDiskCache; 162 | } 163 | 164 | /** @return The in-memory cache size, in bits. */ 165 | public static int getMemCacheSize() { 166 | return mMemCacheSize; 167 | } 168 | 169 | /** @param memCacheSize The in-memory cache size to set, in bits. */ 170 | public static void setMemCacheSize(int memCacheSize) { 171 | mMemCacheSize = memCacheSize; 172 | } 173 | 174 | /** @return The version of the app. */ 175 | public static int getAppVersion() { 176 | return mAppVersion; 177 | } 178 | 179 | /** @param appVersion The app version to set. */ 180 | public static void setAppVersion(int appVersion) { 181 | ImageViewNext.mAppVersion = appVersion; 182 | } 183 | 184 | /** 185 | * Sets the image loading callbacks listener. 186 | * 187 | * @param l The listener, or null to clear it. 188 | */ 189 | public void setImageLoadCallbacks(ImageLoadCompletionListener l) { 190 | mLoadCallbacks = l; 191 | } 192 | 193 | /** 194 | * Gets the current image loading callbacks listener, if any. 195 | * 196 | * @return Returns the callbacks listener. 197 | */ 198 | public ImageLoadCompletionListener getImageLoadCallbacks() { 199 | return mLoadCallbacks; 200 | } 201 | 202 | /** @return The disk cache max size, in bits. */ 203 | public static int getDiskCacheSize() { 204 | return mDiskCacheSize; 205 | } 206 | 207 | /** @param diskCacheSize The disk cache max size to set, in bits. */ 208 | public static void setDiskCacheSize(int diskCacheSize) { 209 | ImageViewNext.mDiskCacheSize = diskCacheSize; 210 | } 211 | 212 | /** 213 | * Initializes both the in-memory and the disk-cache 214 | * at class-level, if it hasn't been done already. 215 | * This method is idempotent. 216 | */ 217 | public static void initCaches(Context context) { 218 | if (!mCacheInit) { 219 | mMemCache = new LruCache(mMemCacheSize) { 220 | protected int sizeOf(String key, byte[] value) { 221 | return value.length; 222 | } 223 | }; 224 | File diskCacheDir = 225 | CacheHelper.getDiskCacheDir(context, "imagecache"); 226 | try { 227 | mDiskCache = DiskLruCache.open( 228 | diskCacheDir, mAppVersion, DISK_CACHE_VALUE_COUNT, mDiskCacheSize); 229 | } 230 | catch (IOException ignored) { 231 | } 232 | mCacheInit = true; 233 | } 234 | } 235 | 236 | /** 237 | * Sets the loading {@link Drawable} to be used for every {@link ImageViewNext}. 238 | * 239 | * @param classLoadingDrawableResId the {@link int} resource ID of the Drawable 240 | * while loading an image. 241 | */ 242 | public static void setClassLoadingDrawable(int classLoadingDrawableResId) { 243 | mClassLoadingResId = classLoadingDrawableResId; 244 | } 245 | 246 | /** 247 | * Sets the loading {@link Drawable} to be used for this {@link ImageViewNext}. 248 | * 249 | * @param loadingDrawable the {@link Drawable} to display while loading an image. 250 | */ 251 | public void setLoadingDrawable(Drawable loadingDrawable) { 252 | mLoadingD = loadingDrawable; 253 | } 254 | 255 | /** 256 | * Gets the {@link Drawable} to display while loading an image. 257 | * 258 | * @return {@link Drawable} to display while loading. 259 | */ 260 | public Drawable getLoadingDrawable() { 261 | if (mLoadingD != null) { 262 | return mLoadingD; 263 | } 264 | else { 265 | return mClassLoadingResId > 0 ? getResources().getDrawable(mClassLoadingResId) : null; 266 | } 267 | } 268 | 269 | /** 270 | * Sets the error {@link Drawable} to be used for every {@link ImageViewNext}. 271 | * 272 | * @param classErrorDrawableResId the {@link int} resource ID of the Drawable 273 | * to display after an error getting an image. 274 | */ 275 | public static void setClassErrorDrawable(int classErrorDrawableResId) { 276 | mClassErrorResId = classErrorDrawableResId; 277 | } 278 | 279 | /** 280 | * Sets the error {@link Drawable} to be used for this {@link ImageViewNext}. 281 | * 282 | * @param errorDrawable the {@link Drawable} to display after an error getting an image. 283 | */ 284 | public void setErrorDrawable(Drawable errorDrawable) { 285 | mErrorD = errorDrawable; 286 | } 287 | 288 | /** 289 | * Gets the {@link Drawable} to display after an error loading an image. 290 | * 291 | * @return {@link Drawable} to display after an error loading an image. 292 | */ 293 | public Drawable getErrorDrawable() { 294 | return mErrorD != null ? mErrorD : getResources().getDrawable(mClassErrorResId); 295 | } 296 | 297 | /** 298 | * Checks if a request is already in progress. 299 | * 300 | * @return true if there is a pending request, false otherwise. 301 | */ 302 | private boolean isRequestInProgress() { 303 | return mCurrentRequest != null 304 | && mRequestManager.isRequestInProgress(mCurrentRequest); 305 | } 306 | 307 | /** Aborts the current request, if any, and stops everything else. */ 308 | private void abortEverything() { 309 | // Abort the current request before starting another one 310 | if (isRequestInProgress()) { 311 | mRequestManager.removeRequestListener(mCurrentRequestListener); 312 | } 313 | 314 | stop(); 315 | stopLoading(); 316 | } 317 | 318 | /** 319 | * Sets the content of the {@link ImageViewNext} with the data to be downloaded 320 | * from the provided URL. 321 | * 322 | * @param url The URL to download the image from. It can be an animated GIF. 323 | */ 324 | public void setUrl(String url) { 325 | mUrl = url; 326 | 327 | // Abort the pending request (if any) and stop animating/loading 328 | abortEverything(); 329 | 330 | // Start the whole retrieval chain 331 | getFromMemCache(url); 332 | } 333 | 334 | /** 335 | * Returns the current URL set to the {@link ImageViewNext}. 336 | * The URL will be returned regardless of the existence of 337 | * the image or of the caching/downloading progress. 338 | * 339 | * @return The URL set for this {@link ImageViewNext}. 340 | */ 341 | public String getUrl() { 342 | return mUrl; 343 | } 344 | 345 | /** 346 | * Returns true if this instance will automatically retry the download from 347 | * the network when it becomes available once again. 348 | * The instance level settings has priority over the class level's. 349 | * 350 | * @return true if the instance retries to download the image when the 351 | * network is once again available, false otherwise. 352 | */ 353 | public boolean isAutoRetryFromNetwork() { 354 | return mAutoRetryFromNetwork; 355 | } 356 | 357 | /** 358 | * Sets the value of auto retry from network for this instance, set it to 359 | * true if this instance has to automatically retry the download from the 360 | * network when it becomes available once again, false otherwise. The 361 | * instance level settings has priority over the class level's. 362 | *

363 | * If the instance was previously forbidden to auto-retry, it will be 364 | * allowed as soon as this method is called with a true argument. 365 | *

366 | * If the instance was previously allowed to auto-retry, it will be 367 | * forbidden as soon as this method is called with a false argument. 368 | * 369 | * @param autoRetryFromNetwork The instance value for the auto retry. 370 | */ 371 | 372 | public void setAutoRetryFromNetwork(boolean autoRetryFromNetwork) { 373 | boolean registerAfter; 374 | boolean unregisterAfter; 375 | 376 | // If nothing changes, do nothing 377 | if (mAutoRetryFromNetwork == autoRetryFromNetwork) return; 378 | 379 | // Set the "after" booleans 380 | registerAfter = !mAutoRetryFromNetwork; 381 | unregisterAfter = !autoRetryFromNetwork; 382 | 383 | // Set the state value 384 | mAutoRetryFromNetwork = autoRetryFromNetwork; 385 | 386 | // Register or unregister the receiver according to the new value 387 | if (registerAfter) { 388 | registerReceiver(); 389 | } 390 | else if (unregisterAfter) { 391 | unregisterReceiver(); 392 | } 393 | } 394 | 395 | /** 396 | * Returns true if every ImageViewNext will automatically retry the download from 397 | * the network when it becomes available once again. 398 | * 399 | * @return true if ImageViewNext retries to download the image when the 400 | * network is once again available, false otherwise. 401 | */ 402 | 403 | public static boolean isClassAutoRetryFromNetwork() { 404 | return ImageViewNext.mClassAutoRetryFromNetwork; 405 | } 406 | 407 | /** 408 | * Sets the value of auto retry from network for ImageViewNext, set it to true 409 | * if ImageViewNext has to automatically retry the download from 410 | * the network when it becomes available once again, false otherwise. 411 | *

412 | * All of the existing constructed instances won't be affected by this. 413 | * 414 | * @param classAutoRetryFromNetwork The instance value for the auto retry. 415 | */ 416 | 417 | public static void setClassAutoRetryFromNetwork( 418 | boolean classAutoRetryFromNetwork) { 419 | ImageViewNext.mClassAutoRetryFromNetwork = classAutoRetryFromNetwork; 420 | } 421 | 422 | /** 423 | * Checks if the auto retry can be applied for the current instance. 424 | * 425 | * @return true if this instance is allowed to auto retry network ops, false 426 | * otherwise. 427 | */ 428 | private boolean isAutoRetryTrueSomewhere() { 429 | return isAutoRetryFromNetwork() || isClassAutoRetryFromNetwork(); 430 | } 431 | 432 | /** 433 | * Tries to retrieve the image from network, if and only if: 434 | *

    435 | *
  • No requests are pending for this instance.
  • 436 | *
  • A previous download failed.
  • 437 | *
  • The instance-level or class-level auto retry is set to true, with 438 | * this priority.
  • 439 | *
440 | */ 441 | public void retryFromNetworkIfPossible() { 442 | // Only retry to get the image from the network: 443 | // - if no requests are in progress 444 | // - if the download previously failed 445 | // - auto retry is set to true for the instance or the class (in order) 446 | if (!isRequestInProgress() && hasFailedDownload && isAutoRetryTrueSomewhere()) { 447 | if (BuildConfig.DEBUG) Log.i(TAG, "Autoretry: true somewhere, retrying..."); 448 | // Abort the pending request (if any) and stop animating/loading 449 | abortEverything(); 450 | // Initalize caches 451 | ImageViewNext.initCaches(mContext); 452 | // Starts the retrieval from the network once again 453 | getFromNetwork(getUrl()); 454 | // Cross ye fingers 455 | } 456 | else { 457 | if (BuildConfig.DEBUG) Log.i(TAG, "Autoretry: false, sorry."); 458 | } 459 | } 460 | 461 | /** 462 | * Tries to get the image from the memory cache. 463 | * 464 | * @param url The URL to download the image from. It can be an animated GIF. 465 | */ 466 | private void getFromMemCache(String url) { 467 | if (BuildConfig.DEBUG) Log.i(TAG, "Memcache: getting for URL " + url + " @" + hashCode()); 468 | 469 | if (mLoadCallbacks != null) { 470 | mLoadCallbacks.onLoadStarted(this, CacheLevel.MEMORY); 471 | } 472 | 473 | // Get the URL from the input Bundle 474 | if (url == null || "".equals(url)) return; 475 | 476 | // Initializes the caches, if they're not initialized already 477 | ImageViewNext.initCaches(mContext); 478 | 479 | LruCache cache = ImageViewNext.getMemCache(); 480 | byte[] image = cache.get(url); 481 | 482 | if (image == null) { 483 | handleMemCacheMiss(); 484 | } 485 | else { 486 | onMemCacheHit(image, url); 487 | } 488 | } 489 | 490 | /** Generic function to handle the mem cache miss. */ 491 | private void handleMemCacheMiss() { 492 | // Calls the class callback 493 | onMemCacheMiss(); 494 | // Starts searching in the disk cache 495 | getFromDiskCache(getUrl()); 496 | } 497 | 498 | /** 499 | * Tries to get the image from the disk cache. 500 | * 501 | * @param url The URL to download the image from. It can be an animated GIF. 502 | */ 503 | private void getFromDiskCache(String url) { 504 | if (BuildConfig.DEBUG) Log.i(TAG, "Diskcache: getting for URL " + url + " @" + hashCode()); 505 | Request mRequest = 506 | ImageViewExRequestFactory.getImageDiskCacheRequest(url); 507 | mCurrentRequestListener = new ImageDiskCacheListener(this); 508 | mRequestManager.execute(mRequest, mCurrentRequestListener); 509 | 510 | if (mLoadCallbacks != null) { 511 | mLoadCallbacks.onLoadStarted(this, CacheLevel.DISK); 512 | } 513 | } 514 | 515 | /** 516 | * Tries to get the image from the network. 517 | * 518 | * @param url The URL to download the image from. It can be an animated GIF. 519 | */ 520 | private void getFromNetwork(String url) { 521 | if (BuildConfig.DEBUG) Log.i(TAG, "Network: getting for URL " + url + " @" + hashCode()); 522 | Request mRequest = 523 | ImageViewExRequestFactory.getImageDownloaderRequest(url); 524 | mCurrentRequestListener = new ImageDownloadListener(this); 525 | mRequestManager.execute(mRequest, mCurrentRequestListener); 526 | 527 | if (mLoadCallbacks != null) { 528 | mLoadCallbacks.onLoadStarted(this, CacheLevel.NETWORK); 529 | } 530 | } 531 | 532 | /** 533 | * Called when the image is got from whatever the source. 534 | * Override this to get the appropriate callback. 535 | * 536 | * @param image The image as a byte array. 537 | */ 538 | protected void onSuccess(byte[] image) { 539 | setByteArray(image); 540 | } 541 | 542 | /** 543 | * Called when the image is got from whatever the source. 544 | * Checks if the original URL matches the current one set 545 | * in the instance of ImageViewNext. 546 | * 547 | * @param image The image as a byte array. 548 | * @param url The URL of the retrieved image. 549 | */ 550 | private void onPreSuccess(byte[] image, String url) { 551 | // Only set the image if the current url equals to the retrieved image's url 552 | if (url != null && url.equals(getUrl())) { 553 | onSuccess(image); 554 | } 555 | } 556 | 557 | /** 558 | * Called when the image is got from the memory cache. 559 | * Override this to get the appropriate callback. 560 | * 561 | * @param image The image as a byte array. 562 | * @param url The URL of the retrieved image. 563 | */ 564 | protected void onMemCacheHit(byte[] image, String url) { 565 | if (BuildConfig.DEBUG) Log.i(TAG, "Memory cache HIT @" + hashCode()); 566 | onPreSuccess(image, url); 567 | 568 | if (mLoadCallbacks != null) { 569 | mLoadCallbacks.onLoadCompleted(this, CacheLevel.MEMORY); 570 | } 571 | } 572 | 573 | /** 574 | * Called when there is a memory cache miss for the image. 575 | * Override this to get the appropriate callback. 576 | */ 577 | protected void onMemCacheMiss() { 578 | Drawable loadingDrawable = getLoadingDrawable(); 579 | if (loadingDrawable != null) { 580 | ScaleType scaleType = getScaleType(); 581 | if (scaleType != null) { 582 | setScaleType(scaleType); 583 | } 584 | else { 585 | setScaleType(ScaleType.CENTER_INSIDE); 586 | } 587 | setImageDrawable(loadingDrawable); 588 | if (loadingDrawable instanceof AnimationDrawable) { 589 | ((AnimationDrawable) loadingDrawable).start(); 590 | } 591 | } 592 | else { 593 | setImageDrawable(mEmptyDrawable); // This also stops any ongoing loading process 594 | } 595 | 596 | if (mLoadCallbacks != null) { 597 | mLoadCallbacks.onLoadError(this, CacheLevel.MEMORY); 598 | } 599 | } 600 | 601 | /** 602 | * Called when the image is got from the disk cache. 603 | * Override this to get the appropriate callback. 604 | * 605 | * @param image The image as a byte array. 606 | * @param url The URL of the retrieved image. 607 | */ 608 | protected void onDiskCacheHit(byte[] image, String url) { 609 | if (BuildConfig.DEBUG) Log.i(TAG, "Disk cache HIT @" + hashCode()); 610 | onPreSuccess(image, url); 611 | 612 | if (mLoadCallbacks != null) { 613 | mLoadCallbacks.onLoadCompleted(this, CacheLevel.DISK); 614 | } 615 | } 616 | 617 | /** 618 | * Called when there is a disk cache miss for the image. 619 | * Override this to get the appropriate callback. 620 | */ 621 | protected void onDiskCacheMiss() { 622 | if (mLoadCallbacks != null) { 623 | mLoadCallbacks.onLoadError(this, CacheLevel.DISK); 624 | } 625 | } 626 | 627 | /** 628 | * Called when the image is got from the network. 629 | * Override this to get the appropriate callback. 630 | * 631 | * @param image The image as a byte array. 632 | * @param url The URL of the retrieved image. 633 | */ 634 | protected void onNetworkHit(byte[] image, String url) { 635 | if (BuildConfig.DEBUG) Log.i(TAG, "Network HIT @" + hashCode()); 636 | onPreSuccess(image, url); 637 | hasFailedDownload = false; 638 | 639 | if (mLoadCallbacks != null) { 640 | mLoadCallbacks.onLoadCompleted(this, CacheLevel.NETWORK); 641 | } 642 | } 643 | 644 | /** 645 | * Called when there is a network miss for the image, 646 | * usually a 404. 647 | * Override this to get the appropriate callback. 648 | */ 649 | protected void onNetworkMiss() { 650 | if (mLoadCallbacks != null) { 651 | mLoadCallbacks.onLoadError(this, CacheLevel.NETWORK); 652 | } 653 | hasFailedDownload = true; 654 | } 655 | 656 | /** 657 | * Called when the image could not be found anywhere. 658 | * Override this to get the appropriate callback. 659 | */ 660 | protected void onMiss() { 661 | Drawable errorDrawable = getErrorDrawable(); 662 | if (getErrorDrawable() != null) { 663 | ScaleType scaleType = getScaleType(); 664 | if (scaleType != null) { 665 | setScaleType(scaleType); 666 | } 667 | else { 668 | setScaleType(ScaleType.CENTER_INSIDE); 669 | } 670 | setImageDrawable(errorDrawable); 671 | if (errorDrawable instanceof AnimationDrawable) { 672 | ((AnimationDrawable) errorDrawable).start(); 673 | } 674 | } 675 | } 676 | 677 | /** 678 | * Sets the image from a byte array. 679 | * 680 | * @param image The image to set. 681 | */ 682 | private void setByteArray(final byte[] image) { 683 | if (image != null) { 684 | ScaleType scaleType = getScaleType(); 685 | if (scaleType != null) { 686 | setScaleType(scaleType); 687 | } 688 | setSource(image); 689 | } 690 | } 691 | 692 | /** 693 | * Returns the maximum number of concurrent worker threads 694 | * used to get images from cache/network. 695 | * 696 | * @return Maximum number of concurrent threads. 697 | */ 698 | public static int getMaximumNumberOfThreads() { 699 | return mConcurrentThreads; 700 | } 701 | 702 | /** 703 | * Define the maximum number of concurrent worker threads 704 | * used to get images from cache/network. 705 | * By default only 10 concurrent worker threads are used at 706 | * the same time. 707 | * The value will be set once and for all when the first 708 | * ImageViewNext is instantiated. Calling this function again 709 | * after an ImageViewNext is instantiated will have no effect. 710 | * 711 | * @param concurrentThreads The number of concurrent threads. 712 | */ 713 | public static void setMaximumNumberOfThreads(int concurrentThreads) { 714 | mConcurrentThreads = concurrentThreads; 715 | } 716 | 717 | /** 718 | * Operation listener for the disk cache retrieval operation. 719 | * 720 | * @author Francesco Pontillo 721 | */ 722 | private class ImageDiskCacheListener extends ImageViewExRequestListener { 723 | 724 | public ImageDiskCacheListener(ImageViewNext imageViewNext) { 725 | super(imageViewNext); 726 | } 727 | 728 | @Override 729 | public void onRequestFinished(Request request, Bundle resultData) { 730 | byte[] image = 731 | resultData.getByteArray(ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT); 732 | String url = 733 | resultData.getString(ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL); 734 | if (image == null) { 735 | handleMiss(); 736 | } 737 | else { 738 | mImageViewNext.onDiskCacheHit(image, url); 739 | } 740 | } 741 | 742 | @Override 743 | public void onRequestConnectionError(Request request, int statusCode) { 744 | handleMiss(); 745 | } 746 | 747 | @Override 748 | public void onRequestDataError(Request request) { 749 | handleMiss(); 750 | } 751 | 752 | @Override 753 | public void onRequestCustomError(Request request, Bundle resultData) { 754 | handleMiss(); 755 | } 756 | 757 | /** Generic function to handle the cache miss. */ 758 | private void handleMiss() { 759 | // Calls the class callback 760 | mImageViewNext.onDiskCacheMiss(); 761 | // Starts searching in the network 762 | getFromNetwork(mImageViewNext.getUrl()); 763 | } 764 | 765 | } 766 | 767 | /** 768 | * Operation listener for the network retrieval operation. 769 | * 770 | * @author Francesco Pontillo 771 | */ 772 | private class ImageDownloadListener extends ImageViewExRequestListener { 773 | 774 | public ImageDownloadListener(ImageViewNext imageViewNext) { 775 | super(imageViewNext); 776 | } 777 | 778 | @Override 779 | public void onRequestFinished(Request request, Bundle resultData) { 780 | byte[] image = 781 | resultData.getByteArray(ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT); 782 | String url = 783 | resultData.getString(ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL); 784 | if (image == null || image.length == 0) { 785 | handleMiss(); 786 | } 787 | else { 788 | mImageViewNext.onNetworkHit(image, url); 789 | } 790 | } 791 | 792 | @Override 793 | public void onRequestConnectionError(Request request, int statusCode) { 794 | handleMiss(); 795 | } 796 | 797 | @Override 798 | public void onRequestDataError(Request request) { 799 | handleMiss(); 800 | } 801 | 802 | @Override 803 | public void onRequestCustomError(Request request, Bundle resultData) { 804 | handleMiss(); 805 | } 806 | 807 | /** Generic function to handle the network miss. */ 808 | private void handleMiss() { 809 | // Calls the class callback 810 | mImageViewNext.onNetworkMiss(); 811 | // Calss the final miss class callback 812 | mImageViewNext.onMiss(); 813 | } 814 | } 815 | 816 | /** A simple interface for image loading callbacks. */ 817 | public interface ImageLoadCompletionListener { 818 | 819 | /** 820 | * Loading of a resource has been started by invoking {@link #setUrl(String)}. 821 | * 822 | * @param v The ImageViewNext on which the loading has begun 823 | * @param level The cache level involved. You will receive a pair of calls, one 824 | * to onLoadStarted and one to onLoadCompleted or to onLoadError, 825 | * for each cache level, in this order: memory->disk->network 826 | * (for MISS on both memory and disk caches) 827 | */ 828 | public void onLoadStarted(ImageViewNext v, CacheLevel level); 829 | 830 | /** 831 | * Loading of a resource has been completed. This corresponds to a cache HIT 832 | * for the memory and disk cache levels, or a successful download from the net. 833 | * 834 | * @param v The ImageViewNext on which the loading has completed 835 | * @param level The cache level involved. You will receive a pair of calls, one 836 | * to onLoadStarted and one to onLoadCompleted or to onLoadError, 837 | * for each cache level, in this order: memory->disk->network 838 | * (for MISS on both memory and disk caches). 839 | */ 840 | public void onLoadCompleted(ImageViewNext v, CacheLevel level); 841 | 842 | /** 843 | * Loading of a resource has failed. This corresponds to a cache MISS 844 | * for the memory and disk cache levels, or a successful download from the net. 845 | * 846 | * @param v The ImageViewNext on which the loading has begun 847 | * @param level The cache level involved. You will receive a pair of calls, one 848 | * to onLoadStarted and one to onLoadCompleted or to onLoadError, 849 | * for each cache level, in this order: memory->disk->network 850 | * (for MISS on both memory and disk caches) 851 | */ 852 | public void onLoadError(ImageViewNext v, CacheLevel level); 853 | } 854 | } 855 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/broadcastreceiver/ConnectivityChangeBroadcastReceiver.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex.broadcastreceiver; 2 | 3 | import net.frakbot.imageviewex.ImageViewNext; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.net.ConnectivityManager; 8 | import android.net.NetworkInfo; 9 | 10 | /** 11 | * BroadcastReceiver for receiving information about the network state. 12 | * 13 | * @author Francesco Pontillo 14 | */ 15 | public class ConnectivityChangeBroadcastReceiver extends BroadcastReceiver { 16 | 17 | private ImageViewNext mImageViewNext; 18 | 19 | /** 20 | * Constructor, initializes the ImageViewNext to be used to retry the 21 | * network operation after the connection is restored. 22 | * 23 | * @param imageViewNext 24 | * The ImageViewNext instance. 25 | */ 26 | public ConnectivityChangeBroadcastReceiver(ImageViewNext imageViewNext) { 27 | mImageViewNext = imageViewNext; 28 | } 29 | 30 | @Override 31 | public void onReceive(Context context, Intent intent) { 32 | // Get the NetworkInfo Parcelable 33 | NetworkInfo networkInfo = intent 34 | .getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); 35 | 36 | // Check for the connection 37 | boolean isConnected = networkInfo.isConnected(); 38 | if (isConnected) { 39 | mImageViewNext.retryFromNetworkIfPossible(); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/listener/ImageViewExRequestListener.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex.listener; 2 | 3 | import net.frakbot.imageviewex.ImageViewNext; 4 | 5 | import com.foxykeep.datadroid.requestmanager.RequestManager.RequestListener; 6 | 7 | public abstract class ImageViewExRequestListener implements RequestListener { 8 | protected ImageViewNext mImageViewNext; 9 | 10 | public ImageViewExRequestListener(ImageViewNext imageViewNext) { 11 | this.mImageViewNext = imageViewNext; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/operation/ImageDiskCacheOperation.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex.operation; 2 | 3 | import net.frakbot.cache.CacheHelper; 4 | import net.frakbot.imageviewex.Converters; 5 | import net.frakbot.imageviewex.ImageViewNext; 6 | import net.frakbot.imageviewex.requestmanager.ImageViewExRequestFactory; 7 | import android.content.Context; 8 | import android.os.Bundle; 9 | import android.support.v4.util.LruCache; 10 | 11 | import com.foxykeep.datadroid.exception.ConnectionException; 12 | import com.foxykeep.datadroid.exception.CustomRequestException; 13 | import com.foxykeep.datadroid.exception.DataException; 14 | import com.foxykeep.datadroid.requestmanager.Request; 15 | import com.foxykeep.datadroid.service.RequestService.Operation; 16 | import com.jakewharton.disklrucache.DiskLruCache; 17 | import com.jakewharton.disklrucache.DiskLruCache.Snapshot; 18 | 19 | /** 20 | * Operation to search for an image in the disk cache. 21 | * Requested input: 22 | * - ImageMemCacheOperation.PARAM_IMAGE_URL, the URL of the image 23 | * Given output: 24 | * - ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT, the byte array of the image 25 | * - ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL, the requested URL of the image 26 | * 27 | * @author Francesco Pontillo 28 | * 29 | */ 30 | public class ImageDiskCacheOperation implements Operation { 31 | 32 | public static final String PARAM_IMAGE_URL = 33 | "net.frakbot.imageviewex.extra.url"; 34 | 35 | @Override 36 | public Bundle execute(Context context, Request request) 37 | throws ConnectionException, DataException, CustomRequestException { 38 | 39 | // Get the URL from the input Bundle 40 | String url = request.getString(PARAM_IMAGE_URL); 41 | if (url == null || url.equals("")) throw new DataException("No value for URL " + url); 42 | 43 | // Initializes the caches, if they're not initialized already 44 | ImageViewNext.initCaches(context); 45 | 46 | // Get the entry 47 | DiskLruCache diskCache = ImageViewNext.getDiskCache(); 48 | Snapshot cacheEntry = null; 49 | try { 50 | cacheEntry = diskCache.get(CacheHelper.UriToDiskLruCacheString(url)); 51 | } catch (Exception e) { 52 | throw new DataException("DISK CACHE: Error while getting value for URL " + url); 53 | } 54 | 55 | byte[] image = null; 56 | 57 | // If the object is not null, convert it 58 | if (cacheEntry != null) { 59 | // Convert the InputStream 60 | image = Converters.inputStreamToByteArray( 61 | cacheEntry.getInputStream(0), 62 | (int)cacheEntry.getLength(0)); 63 | 64 | // Saves the image in the in-memory cache 65 | LruCache memCache = ImageViewNext.getMemCache(); 66 | memCache.put(url, image); 67 | } 68 | 69 | Bundle b = new Bundle(); 70 | b.putByteArray(ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT, image); 71 | b.putString(ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL, url); 72 | return b; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/operation/ImageDownloadOperation.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex.operation; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.v4.util.LruCache; 6 | import android.text.TextUtils; 7 | import android.util.Log; 8 | import com.foxykeep.datadroid.exception.ConnectionException; 9 | import com.foxykeep.datadroid.exception.CustomRequestException; 10 | import com.foxykeep.datadroid.exception.DataException; 11 | import com.foxykeep.datadroid.requestmanager.Request; 12 | import com.foxykeep.datadroid.service.RequestService.Operation; 13 | import com.jakewharton.disklrucache.DiskLruCache; 14 | import com.jakewharton.disklrucache.DiskLruCache.Editor; 15 | import net.frakbot.cache.CacheHelper; 16 | import net.frakbot.imageviewex.ImageViewNext; 17 | import net.frakbot.imageviewex.requestmanager.ImageViewExRequestFactory; 18 | import net.frakbot.remote.RemoteHelper; 19 | 20 | import java.io.IOException; 21 | 22 | /** 23 | * Operation to download an image from the network. 24 | * Requested input: 25 | * - ImageMemCacheOperation.PARAM_IMAGE_URL, the URL of the image 26 | * Given output: 27 | * - ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT, the byte array of the image 28 | * - ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL, the requested URL of the image 29 | * 30 | * @author Francesco Pontillo 31 | */ 32 | public class ImageDownloadOperation implements Operation { 33 | 34 | public static final String PARAM_IMAGE_URL = 35 | "net.frakbot.imageviewex.extra.url"; 36 | 37 | @Override 38 | public Bundle execute(Context context, Request request) 39 | throws ConnectionException, DataException, CustomRequestException { 40 | 41 | // Initializes the caches, if they're not initialized already 42 | ImageViewNext.initCaches(context); 43 | 44 | // Get the URL from the input Bundle 45 | String url = request.getString(PARAM_IMAGE_URL); 46 | if (TextUtils.isEmpty(url)) throw new DataException("No value for URL parameter"); 47 | 48 | byte[] image; 49 | try { 50 | image = RemoteHelper.download(url); 51 | } 52 | catch (IOException e) { 53 | throw new DataException("NETWORK: Error while getting value for URL " + url); 54 | } 55 | 56 | // If the object is not null 57 | if (image != null) { 58 | // Save into the disk cache 59 | DiskLruCache diskCache = ImageViewNext.getDiskCache(); 60 | try { 61 | Editor editor = diskCache.edit(CacheHelper.UriToDiskLruCacheString(url)); 62 | if (editor != null) { 63 | if (CacheHelper.writeByteArrayToEditor(image, editor)) { 64 | diskCache.flush(); 65 | editor.commit(); 66 | } 67 | else { 68 | editor.abort(); 69 | } 70 | } 71 | } 72 | catch (Exception e) { 73 | Log.w(ImageDownloadOperation.class.getSimpleName(), "Storage of image into the disk cache failed!"); 74 | } 75 | // Save into the memory cache 76 | LruCache memCache = ImageViewNext.getMemCache(); 77 | memCache.put(url, image); 78 | } 79 | 80 | Bundle b = new Bundle(); 81 | b.putByteArray(ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT, image); 82 | b.putString(ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL, url); 83 | return b; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/operation/ImageMemCacheOperation.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex.operation; 2 | 3 | import net.frakbot.imageviewex.ImageViewNext; 4 | import net.frakbot.imageviewex.requestmanager.ImageViewExRequestFactory; 5 | import android.content.Context; 6 | import android.os.Bundle; 7 | import android.support.v4.util.LruCache; 8 | 9 | import com.foxykeep.datadroid.exception.ConnectionException; 10 | import com.foxykeep.datadroid.exception.CustomRequestException; 11 | import com.foxykeep.datadroid.exception.DataException; 12 | import com.foxykeep.datadroid.requestmanager.Request; 13 | import com.foxykeep.datadroid.service.RequestService.Operation; 14 | 15 | /** 16 | * Operation to search for an image in the in-memory cache. 17 | * Requested input: 18 | * - ImageMemCacheOperation.PARAM_IMAGE_URL, the URL of the image 19 | * Given output: 20 | * - ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT, the byte array of the image 21 | * - ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL, the requested URL of the image 22 | * 23 | * @deprecated Retrieving in an async way from the mem cache is slow. 24 | * @author Francesco Pontillo 25 | * 26 | */ 27 | public class ImageMemCacheOperation implements Operation { 28 | 29 | public static final String PARAM_IMAGE_URL = 30 | "net.frakbot.imageviewex.extra.url"; 31 | 32 | @Override 33 | public Bundle execute(Context context, Request request) 34 | throws ConnectionException, DataException, CustomRequestException { 35 | // Get the URL from the input Bundle 36 | String url = request.getString(PARAM_IMAGE_URL); 37 | if (url == null || url.equals("")) throw new DataException("MEM CACHE: Empty URL " + url); 38 | 39 | // Initializes the caches, if they're not initialized already 40 | ImageViewNext.initCaches(context); 41 | 42 | LruCache cache = ImageViewNext.getMemCache(); 43 | byte[] image = cache.get(url); 44 | 45 | Bundle b = new Bundle(); 46 | b.putByteArray(ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT, image); 47 | b.putString(ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL, url); 48 | return b; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/requestmanager/ImageViewExRequestFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2012 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package net.frakbot.imageviewex.requestmanager; 10 | 11 | import net.frakbot.imageviewex.operation.ImageDiskCacheOperation; 12 | import net.frakbot.imageviewex.operation.ImageDownloadOperation; 13 | import net.frakbot.imageviewex.operation.ImageMemCacheOperation; 14 | 15 | import com.foxykeep.datadroid.requestmanager.Request; 16 | 17 | /** 18 | * Class used to create the {@link Request}s. 19 | * 20 | * @author Foxykeep, Francesco Pontillo 21 | */ 22 | @SuppressWarnings("deprecation") 23 | public final class ImageViewExRequestFactory { 24 | // Request types 25 | public static final int REQUEST_TYPE_IMAGE_MEM_CACHE = 0; 26 | public static final int REQUEST_TYPE_IMAGE_DISK_CACHE = 1; 27 | public static final int REQUEST_TYPE_IMAGE_DOWNLOAD = 2; 28 | 29 | // Response data 30 | public static final String BUNDLE_EXTRA_OBJECT = 31 | "net.frakbot.imageviewex.extra.object"; 32 | public static final String BUNDLE_EXTRA_IMAGE_URL = 33 | "net.frakbot.imageviewex.extra.imageUrl"; 34 | 35 | private ImageViewExRequestFactory() { 36 | // no public constructor 37 | } 38 | 39 | /** 40 | * Create the request to get an image from the memory cache. 41 | * 42 | * @param url The URL of the image. 43 | * @return The request. 44 | */ 45 | public static Request getImageMemCacheRequest(String url) { 46 | Request request = new Request(REQUEST_TYPE_IMAGE_MEM_CACHE); 47 | request.put(ImageMemCacheOperation.PARAM_IMAGE_URL, url); 48 | request.setMemoryCacheEnabled(true); 49 | return request; 50 | } 51 | 52 | /** 53 | * Create the request to get an image from the the disk cache. 54 | * 55 | * @param url The URL of the image. 56 | * @return The request. 57 | */ 58 | public static Request getImageDiskCacheRequest(String url) { 59 | Request request = new Request(REQUEST_TYPE_IMAGE_DISK_CACHE); 60 | request.put(ImageDiskCacheOperation.PARAM_IMAGE_URL, url); 61 | request.setMemoryCacheEnabled(true); 62 | return request; 63 | } 64 | 65 | /** 66 | * Create the request to get an image from the network. 67 | * 68 | * @param url The URL of the image. 69 | * @return The request. 70 | */ 71 | public static Request getImageDownloaderRequest(String url) { 72 | Request request = new Request(REQUEST_TYPE_IMAGE_DOWNLOAD); 73 | request.put(ImageDownloadOperation.PARAM_IMAGE_URL, url); 74 | request.setMemoryCacheEnabled(true); 75 | return request; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/requestmanager/ImageViewExRequestManager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2011 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package net.frakbot.imageviewex.requestmanager; 10 | 11 | import net.frakbot.imageviewex.service.ImageViewExService; 12 | 13 | import com.foxykeep.datadroid.requestmanager.RequestManager; 14 | 15 | import android.content.Context; 16 | 17 | /** 18 | * This class is used as a proxy to call the Service. It provides easy-to-use methods to call the 19 | * service and manages the Intent creation. It also assures that a request will not be sent again if 20 | * an exactly identical one is already in progress. 21 | * 22 | * @author Foxykeep, Francesco Pontillo 23 | */ 24 | public final class ImageViewExRequestManager extends RequestManager { 25 | 26 | // Singleton management 27 | private static ImageViewExRequestManager sInstance; 28 | 29 | public synchronized static ImageViewExRequestManager from(Context context) { 30 | if (sInstance == null) { 31 | sInstance = new ImageViewExRequestManager(context); 32 | } 33 | 34 | return sInstance; 35 | } 36 | 37 | private ImageViewExRequestManager(Context context) { 38 | super(context, ImageViewExService.class); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/service/ImageViewExService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2011 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package net.frakbot.imageviewex.service; 10 | 11 | import net.frakbot.imageviewex.ImageViewNext; 12 | import net.frakbot.imageviewex.operation.ImageDiskCacheOperation; 13 | import net.frakbot.imageviewex.operation.ImageDownloadOperation; 14 | import net.frakbot.imageviewex.operation.ImageMemCacheOperation; 15 | import net.frakbot.imageviewex.requestmanager.ImageViewExRequestFactory; 16 | import android.os.Bundle; 17 | 18 | import com.foxykeep.datadroid.exception.CustomRequestException; 19 | import com.foxykeep.datadroid.requestmanager.Request; 20 | import com.foxykeep.datadroid.service.RequestService; 21 | 22 | /** 23 | * This class is called by the {@link ImageViewExRequestManager} 24 | * through the {@link Intent} system. 25 | * 26 | * @author Foxykeep, Francesco Pontillo 27 | */ 28 | @SuppressWarnings("deprecation") 29 | public class ImageViewExService extends RequestService { 30 | 31 | @Override 32 | protected int getMaximumNumberOfThreads() { 33 | return ImageViewNext.getMaximumNumberOfThreads(); 34 | } 35 | 36 | @Override 37 | public Operation getOperationForType(int requestType) { 38 | switch (requestType) { 39 | case ImageViewExRequestFactory.REQUEST_TYPE_IMAGE_MEM_CACHE: 40 | return new ImageMemCacheOperation(); 41 | case ImageViewExRequestFactory.REQUEST_TYPE_IMAGE_DISK_CACHE: 42 | return new ImageDiskCacheOperation(); 43 | case ImageViewExRequestFactory.REQUEST_TYPE_IMAGE_DOWNLOAD: 44 | return new ImageDownloadOperation(); 45 | } 46 | return null; 47 | } 48 | 49 | @Override 50 | protected Bundle onCustomRequestException(Request request, CustomRequestException exception) { 51 | return super.onCustomRequestException(request, exception); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/net/frakbot/remote/RemoteHelper.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.remote; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.BufferedInputStream; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.net.HttpURLConnection; 9 | import java.net.URL; 10 | 11 | import com.squareup.okhttp.OkHttpClient; 12 | 13 | /** 14 | * Helper class that exposes some utility methods for retrieving 15 | * objects from the network. 16 | * 17 | * @author Francesco Pontillo 18 | */ 19 | public class RemoteHelper { 20 | private static final String LOG_TAG = "Loader"; 21 | 22 | private static final int defaultBufferSize = 2048; 23 | 24 | /** 25 | * Download an object from the network. 26 | * 27 | * @param resourceUrl The URL of then rsource. 28 | * 29 | * @throws IOException If the connection cannot be established. 30 | * @return Byte array of the downloaded object. 31 | */ 32 | public static byte[] download(String resourceUrl) throws IOException { 33 | OkHttpClient client = new OkHttpClient(); 34 | URL url = new URL(resourceUrl); 35 | HttpURLConnection connection = client.open(url); 36 | 37 | final int responseCode = connection.getResponseCode(); 38 | if (responseCode != HttpURLConnection.HTTP_OK) { 39 | Log.w(LOG_TAG, "Downloading from URL " + resourceUrl + " failed with response code " + responseCode); 40 | return null; 41 | } 42 | 43 | // determine the image size and allocate a buffer 44 | int fileSize = connection.getContentLength(); 45 | Log.d(LOG_TAG, "fetching " + resourceUrl 46 | + " (" + (fileSize <= 0 ? "size unknown" : Integer.toString(fileSize)) + ")"); 47 | 48 | BufferedInputStream istream = new BufferedInputStream(connection.getInputStream()); 49 | 50 | try { 51 | if (fileSize <= 0) { 52 | Log.w(LOG_TAG, 53 | "Server did not set a Content-Length header, will default to buffer size of " 54 | + defaultBufferSize + " bytes"); 55 | ByteArrayOutputStream buf = new ByteArrayOutputStream(defaultBufferSize); 56 | byte[] buffer = new byte[defaultBufferSize]; 57 | int bytesRead = 0; 58 | while (bytesRead != -1) { 59 | bytesRead = istream.read(buffer, 0, defaultBufferSize); 60 | if (bytesRead > 0) { 61 | buf.write(buffer, 0, bytesRead); 62 | } 63 | } 64 | return buf.toByteArray(); 65 | } 66 | else { 67 | byte[] data = new byte[fileSize]; 68 | 69 | int bytesRead = 0; 70 | int offset = 0; 71 | while (bytesRead != -1 && offset < fileSize) { 72 | bytesRead = istream.read(data, offset, fileSize - offset); 73 | offset += bytesRead; 74 | } 75 | return data; 76 | } 77 | } 78 | finally { 79 | // clean up 80 | try { 81 | istream.close(); 82 | connection.disconnect(); 83 | } 84 | catch (Exception ignore) { 85 | } 86 | } 87 | } 88 | 89 | } 90 | --------------------------------------------------------------------------------