mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-02 17:56:56 +01:00
redesigning cache and file download
This commit is contained in:
parent
5a263a3433
commit
c575303074
@ -68,6 +68,7 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
|
||||
String PROTOCOL_TWIDERE = SCHEME_TWIDERE + "://";
|
||||
|
||||
String AUTHORITY_TWIDERE_FILE = "twidere.file";
|
||||
String AUTHORITY_TWIDERE_CACHE = "twidere.cache";
|
||||
|
||||
String AUTHORITY_USER = "user";
|
||||
String AUTHORITY_HOME = "home";
|
||||
|
@ -24,9 +24,9 @@ import org.mariotaku.twidere.api.twitter.model.MediaEntity.Size;
|
||||
import org.mariotaku.twidere.api.twitter.model.MediaEntity.Type;
|
||||
import org.mariotaku.twidere.api.twitter.model.Status;
|
||||
import org.mariotaku.twidere.api.twitter.model.UrlEntity;
|
||||
import org.mariotaku.twidere.util.MediaPreviewUtils;
|
||||
import org.mariotaku.twidere.util.TwidereArrayUtils;
|
||||
import org.mariotaku.twidere.util.TwitterContentUtils;
|
||||
import org.mariotaku.twidere.util.media.preview.PreviewMediaExtractor;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@ -156,13 +156,8 @@ public class ParcelableMedia implements Parcelable {
|
||||
if (urlEntities != null) {
|
||||
for (final UrlEntity url : urlEntities) {
|
||||
final String expanded = url.getExpandedUrl();
|
||||
final String media_url = MediaPreviewUtils.getSupportedLink(expanded);
|
||||
if (expanded != null && media_url != null) {
|
||||
final ParcelableMedia media = new ParcelableMedia();
|
||||
media.type = TYPE_IMAGE;
|
||||
media.page_url = expanded;
|
||||
media.media_url = media_url;
|
||||
media.preview_url = media_url;
|
||||
final ParcelableMedia media = PreviewMediaExtractor.fromLink(expanded);
|
||||
if (media != null) {
|
||||
media.start = url.getStart();
|
||||
media.end = url.getEnd();
|
||||
list.add(media);
|
||||
|
@ -1,416 +0,0 @@
|
||||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
|
||||
import com.bluelinelabs.logansquare.LoganSquare;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject;
|
||||
|
||||
import org.mariotaku.restfu.Pair;
|
||||
import org.mariotaku.restfu.annotation.method.GET;
|
||||
import org.mariotaku.restfu.http.Endpoint;
|
||||
import org.mariotaku.restfu.http.RestHttpClient;
|
||||
import org.mariotaku.restfu.http.RestHttpRequest;
|
||||
import org.mariotaku.restfu.http.RestHttpResponse;
|
||||
import org.mariotaku.twidere.api.twitter.model.MediaEntity;
|
||||
import org.mariotaku.twidere.api.twitter.model.Status;
|
||||
import org.mariotaku.twidere.api.twitter.model.UrlEntity;
|
||||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
import org.mariotaku.twidere.model.RequestType;
|
||||
import org.mariotaku.twidere.util.HtmlLinkExtractor.HtmlLink;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static android.text.TextUtils.isEmpty;
|
||||
|
||||
public class MediaPreviewUtils {
|
||||
|
||||
public static final String AVAILABLE_URL_SCHEME_PREFIX = "(https?://)?";
|
||||
public static final String AVAILABLE_IMAGE_SHUFFIX = "(png|jpeg|jpg|gif|bmp)";
|
||||
public static final String SINA_WEIBO_IMAGES_AVAILABLE_SIZES = "(woriginal|large|thumbnail|bmiddle|wap[\\d]+|mw[\\d]+)";
|
||||
public static final String GOOGLE_IMAGES_AVAILABLE_SIZES = "((([whs]\\d+|no)\\-?)+)";
|
||||
|
||||
private static final String STRING_PATTERN_TWITTER_IMAGES_DOMAIN = "(p|pbs)\\.twimg\\.com";
|
||||
private static final String STRING_PATTERN_TWITTER_TON_DOMAIN = "(ton)\\.twitter\\.com";
|
||||
private static final String STRING_PATTERN_SINA_WEIBO_IMAGES_DOMAIN = "[\\w\\d]+\\.sinaimg\\.cn|[\\w\\d]+\\.sina\\.cn";
|
||||
private static final String STRING_PATTERN_LOCKERZ_DOMAIN = "lockerz\\.com";
|
||||
private static final String STRING_PATTERN_PLIXI_DOMAIN = "plixi\\.com";
|
||||
private static final String STRING_PATTERN_INSTAGRAM_DOMAIN = "instagr\\.am|instagram\\.com|www\\.instagram\\.com";
|
||||
private static final String STRING_PATTERN_TWITPIC_DOMAIN = "twitpic\\.com";
|
||||
private static final String STRING_PATTERN_IMGLY_DOMAIN = "img\\.ly";
|
||||
private static final String STRING_PATTERN_YFROG_DOMAIN = "yfrog\\.com";
|
||||
private static final String STRING_PATTERN_TWITGOO_DOMAIN = "twitgoo\\.com";
|
||||
private static final String STRING_PATTERN_MOBYPICTURE_DOMAIN = "moby\\.to";
|
||||
private static final String STRING_PATTERN_IMGUR_DOMAIN = "imgur\\.com|i\\.imgur\\.com";
|
||||
private static final String STRING_PATTERN_PHOTOZOU_DOMAIN = "photozou\\.jp";
|
||||
private static final String STRING_PATTERN_GOOGLE_IMAGES_DOMAIN = "(lh|gp|s)(\\d+)?\\.(ggpht|googleusercontent)\\.com";
|
||||
|
||||
private static final String STRING_PATTERN_IMAGES_NO_SCHEME = "[^://].+?\\." + AVAILABLE_IMAGE_SHUFFIX;
|
||||
private static final String STRING_PATTERN_TWITTER_IMAGES_NO_SCHEME = STRING_PATTERN_TWITTER_IMAGES_DOMAIN
|
||||
+ "(/media)?/([\\d\\w\\-_]+)\\." + AVAILABLE_IMAGE_SHUFFIX;
|
||||
private static final String STRING_PATTERN_SINA_WEIBO_IMAGES_NO_SCHEME = "("
|
||||
+ STRING_PATTERN_SINA_WEIBO_IMAGES_DOMAIN + ")" + "/" + SINA_WEIBO_IMAGES_AVAILABLE_SIZES
|
||||
+ "/(([\\d\\w]+)\\." + AVAILABLE_IMAGE_SHUFFIX + ")";
|
||||
private static final String STRING_PATTERN_LOCKERZ_NO_SCHEME = "(" + STRING_PATTERN_LOCKERZ_DOMAIN + ")"
|
||||
+ "/s/(\\w+)/?";
|
||||
private static final String STRING_PATTERN_PLIXI_NO_SCHEME = "(" + STRING_PATTERN_PLIXI_DOMAIN + ")"
|
||||
+ "/p/(\\w+)/?";
|
||||
private static final String STRING_PATTERN_INSTAGRAM_NO_SCHEME = "(" + STRING_PATTERN_INSTAGRAM_DOMAIN + ")"
|
||||
+ "/p/([_\\-\\d\\w]+)/?";
|
||||
private static final String STRING_PATTERN_TWITPIC_NO_SCHEME = STRING_PATTERN_TWITPIC_DOMAIN + "/([\\d\\w]+)/?";
|
||||
private static final String STRING_PATTERN_IMGLY_NO_SCHEME = STRING_PATTERN_IMGLY_DOMAIN + "/([\\w\\d]+)/?";
|
||||
private static final String STRING_PATTERN_YFROG_NO_SCHEME = STRING_PATTERN_YFROG_DOMAIN + "/([\\w\\d]+)/?";
|
||||
private static final String STRING_PATTERN_TWITGOO_NO_SCHEME = STRING_PATTERN_TWITGOO_DOMAIN + "/([\\d\\w]+)/?";
|
||||
private static final String STRING_PATTERN_MOBYPICTURE_NO_SCHEME = STRING_PATTERN_MOBYPICTURE_DOMAIN
|
||||
+ "/([\\d\\w]+)/?";
|
||||
private static final String STRING_PATTERN_IMGUR_NO_SCHEME = "(" + STRING_PATTERN_IMGUR_DOMAIN + ")"
|
||||
+ "/([\\d\\w]+)((?-i)s|(?-i)l)?(\\." + AVAILABLE_IMAGE_SHUFFIX + ")?";
|
||||
private static final String STRING_PATTERN_PHOTOZOU_NO_SCHEME = STRING_PATTERN_PHOTOZOU_DOMAIN
|
||||
+ "/photo/show/([\\d]+)/([\\d]+)/?";
|
||||
private static final String STRING_PATTERN_GOOGLE_IMAGES_NO_SCHEME = "(" + STRING_PATTERN_GOOGLE_IMAGES_DOMAIN
|
||||
+ ")" + "((/[\\w\\d\\-\\_]+)+)/" + GOOGLE_IMAGES_AVAILABLE_SIZES + "/.+";
|
||||
private static final String STRING_PATTERN_GOOGLE_PROXY_IMAGES_NO_SCHEME = "("
|
||||
+ STRING_PATTERN_GOOGLE_IMAGES_DOMAIN + ")" + "/proxy/([\\w\\d\\-\\_]+)="
|
||||
+ GOOGLE_IMAGES_AVAILABLE_SIZES;
|
||||
|
||||
private static final String STRING_PATTERN_IMAGES = AVAILABLE_URL_SCHEME_PREFIX + STRING_PATTERN_IMAGES_NO_SCHEME;
|
||||
private static final String STRING_PATTERN_TWITTER_IMAGES = AVAILABLE_URL_SCHEME_PREFIX
|
||||
+ STRING_PATTERN_TWITTER_IMAGES_NO_SCHEME;
|
||||
private static final String STRING_PATTERN_SINA_WEIBO_IMAGES = AVAILABLE_URL_SCHEME_PREFIX
|
||||
+ STRING_PATTERN_SINA_WEIBO_IMAGES_NO_SCHEME;
|
||||
private static final String STRING_PATTERN_LOCKERZ = AVAILABLE_URL_SCHEME_PREFIX + STRING_PATTERN_LOCKERZ_NO_SCHEME;
|
||||
private static final String STRING_PATTERN_PLIXI = AVAILABLE_URL_SCHEME_PREFIX + STRING_PATTERN_PLIXI_NO_SCHEME;
|
||||
private static final String STRING_PATTERN_INSTAGRAM = AVAILABLE_URL_SCHEME_PREFIX
|
||||
+ STRING_PATTERN_INSTAGRAM_NO_SCHEME;
|
||||
private static final String STRING_PATTERN_IMGLY = AVAILABLE_URL_SCHEME_PREFIX + STRING_PATTERN_IMGLY_NO_SCHEME;
|
||||
private static final String STRING_PATTERN_YFROG = AVAILABLE_URL_SCHEME_PREFIX + STRING_PATTERN_YFROG_NO_SCHEME;
|
||||
private static final String STRING_PATTERN_TWITGOO = AVAILABLE_URL_SCHEME_PREFIX + STRING_PATTERN_TWITGOO_NO_SCHEME;
|
||||
private static final String STRING_PATTERN_MOBYPICTURE = AVAILABLE_URL_SCHEME_PREFIX
|
||||
+ STRING_PATTERN_MOBYPICTURE_NO_SCHEME;
|
||||
private static final String STRING_PATTERN_IMGUR = AVAILABLE_URL_SCHEME_PREFIX + STRING_PATTERN_IMGUR_NO_SCHEME;
|
||||
private static final String STRING_PATTERN_PHOTOZOU = AVAILABLE_URL_SCHEME_PREFIX
|
||||
+ STRING_PATTERN_PHOTOZOU_NO_SCHEME;
|
||||
private static final String STRING_PATTERN_GOOGLE_IMAGES = AVAILABLE_URL_SCHEME_PREFIX
|
||||
+ STRING_PATTERN_GOOGLE_IMAGES_NO_SCHEME;
|
||||
private static final String STRING_PATTERN_GOOGLE_PROXY_IMAGES = AVAILABLE_URL_SCHEME_PREFIX
|
||||
+ STRING_PATTERN_GOOGLE_PROXY_IMAGES_NO_SCHEME;
|
||||
private static final String STRING_PATTERN_TWITTER_DM_IMAGES = AVAILABLE_URL_SCHEME_PREFIX
|
||||
+ STRING_PATTERN_TWITTER_IMAGES_NO_SCHEME;
|
||||
|
||||
public static final Pattern PATTERN_TWITTER_IMAGES = Pattern.compile(STRING_PATTERN_TWITTER_IMAGES,
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
public static final Pattern PATTERN_TWITTER_DM_IMAGES = Pattern.compile(STRING_PATTERN_TWITTER_DM_IMAGES,
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
public static final Pattern PATTERN_SINA_WEIBO_IMAGES = Pattern.compile(STRING_PATTERN_SINA_WEIBO_IMAGES,
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
public static final Pattern PATTERN_LOCKERZ = Pattern.compile(STRING_PATTERN_LOCKERZ, Pattern.CASE_INSENSITIVE);
|
||||
public static final Pattern PATTERN_PLIXI = Pattern.compile(STRING_PATTERN_PLIXI, Pattern.CASE_INSENSITIVE);
|
||||
|
||||
public static final Pattern PATTERN_INSTAGRAM = Pattern.compile(STRING_PATTERN_INSTAGRAM, Pattern.CASE_INSENSITIVE);
|
||||
public static final int INSTAGRAM_GROUP_ID = 3;
|
||||
|
||||
public static final Pattern PATTERN_IMGLY = Pattern.compile(STRING_PATTERN_IMGLY, Pattern.CASE_INSENSITIVE);
|
||||
public static final int IMGLY_GROUP_ID = 2;
|
||||
|
||||
public static final Pattern PATTERN_YFROG = Pattern.compile(STRING_PATTERN_YFROG, Pattern.CASE_INSENSITIVE);
|
||||
public static final int YFROG_GROUP_ID = 2;
|
||||
|
||||
public static final Pattern PATTERN_TWITGOO = Pattern.compile(STRING_PATTERN_TWITGOO, Pattern.CASE_INSENSITIVE);
|
||||
public static final int TWITGOO_GROUP_ID = 2;
|
||||
|
||||
public static final Pattern PATTERN_MOBYPICTURE = Pattern.compile(STRING_PATTERN_MOBYPICTURE,
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
public static final int MOBYPICTURE_GROUP_ID = 2;
|
||||
|
||||
public static final Pattern PATTERN_IMGUR = Pattern.compile(STRING_PATTERN_IMGUR, Pattern.CASE_INSENSITIVE);
|
||||
public static final int IMGUR_GROUP_ID = 3;
|
||||
|
||||
public static final Pattern PATTERN_PHOTOZOU = Pattern.compile(STRING_PATTERN_PHOTOZOU, Pattern.CASE_INSENSITIVE);
|
||||
public static final int PHOTOZOU_GROUP_ID = 3;
|
||||
|
||||
public static final Pattern PATTERN_GOOGLE_IMAGES = Pattern.compile(STRING_PATTERN_GOOGLE_IMAGES,
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
public static final int GOOGLE_IMAGES_GROUP_SERVER = 2;
|
||||
public static final int GOOGLE_IMAGES_GROUP_ID = 6;
|
||||
|
||||
public static final Pattern PATTERN_GOOGLE_PROXY_IMAGES = Pattern.compile(STRING_PATTERN_GOOGLE_PROXY_IMAGES,
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
public static final int GOOGLE_PROXY_IMAGES_GROUP_SERVER = 2;
|
||||
public static final int GOOGLE_PROXY_IMAGES_GROUP_ID = 6;
|
||||
|
||||
private static final Pattern[] SUPPORTED_PATTERNS = {PATTERN_TWITTER_IMAGES, PATTERN_INSTAGRAM,
|
||||
PATTERN_GOOGLE_IMAGES, PATTERN_GOOGLE_PROXY_IMAGES, PATTERN_SINA_WEIBO_IMAGES,
|
||||
PATTERN_IMGUR, PATTERN_IMGLY, PATTERN_YFROG, PATTERN_LOCKERZ, PATTERN_PLIXI, PATTERN_TWITGOO,
|
||||
PATTERN_MOBYPICTURE, PATTERN_PHOTOZOU, PATTERN_TWITTER_DM_IMAGES};
|
||||
|
||||
private static final String URL_PHOTOZOU_PHOTO_INFO = "https://api.photozou.jp/rest/photo_info.json";
|
||||
|
||||
public static ParcelableMedia getAllAvailableImage(final String link, final boolean fullImage) {
|
||||
try {
|
||||
return getAllAvailableImage(link, fullImage, null);
|
||||
} catch (final IOException e) {
|
||||
throw new AssertionError("This should never happen");
|
||||
}
|
||||
}
|
||||
|
||||
public static ParcelableMedia getAllAvailableImage(final String link, final boolean fullImage,
|
||||
final RestHttpClient client) throws IOException {
|
||||
if (link == null) return null;
|
||||
Matcher m;
|
||||
m = PATTERN_TWITTER_IMAGES.matcher(link);
|
||||
if (m.matches()) return getTwitterImage(link, fullImage);
|
||||
m = PATTERN_INSTAGRAM.matcher(link);
|
||||
if (m.matches())
|
||||
return getInstagramImage(RegexUtils.matcherGroup(m, INSTAGRAM_GROUP_ID), link, fullImage);
|
||||
m = PATTERN_GOOGLE_IMAGES.matcher(link);
|
||||
if (m.matches())
|
||||
return getGoogleImage(RegexUtils.matcherGroup(m, GOOGLE_IMAGES_GROUP_SERVER), RegexUtils.matcherGroup(m, GOOGLE_IMAGES_GROUP_ID),
|
||||
fullImage);
|
||||
m = PATTERN_GOOGLE_PROXY_IMAGES.matcher(link);
|
||||
if (m.matches())
|
||||
return getGoogleProxyImage(RegexUtils.matcherGroup(m, GOOGLE_PROXY_IMAGES_GROUP_SERVER),
|
||||
RegexUtils.matcherGroup(m, GOOGLE_PROXY_IMAGES_GROUP_ID), fullImage);
|
||||
m = PATTERN_SINA_WEIBO_IMAGES.matcher(link);
|
||||
if (m.matches()) return getSinaWeiboImage(link, fullImage);
|
||||
m = PATTERN_IMGUR.matcher(link);
|
||||
if (m.matches())
|
||||
return getImgurImage(RegexUtils.matcherGroup(m, IMGUR_GROUP_ID), link, fullImage);
|
||||
m = PATTERN_IMGLY.matcher(link);
|
||||
if (m.matches())
|
||||
return getImglyImage(RegexUtils.matcherGroup(m, IMGLY_GROUP_ID), link, fullImage);
|
||||
m = PATTERN_YFROG.matcher(link);
|
||||
if (m.matches())
|
||||
return getYfrogImage(RegexUtils.matcherGroup(m, YFROG_GROUP_ID), link, fullImage);
|
||||
m = PATTERN_LOCKERZ.matcher(link);
|
||||
if (m.matches()) return getLockerzAndPlixiImage(link, fullImage);
|
||||
m = PATTERN_PLIXI.matcher(link);
|
||||
if (m.matches()) return getLockerzAndPlixiImage(link, fullImage);
|
||||
m = PATTERN_TWITGOO.matcher(link);
|
||||
if (m.matches())
|
||||
return getTwitgooImage(RegexUtils.matcherGroup(m, TWITGOO_GROUP_ID), link, fullImage);
|
||||
m = PATTERN_MOBYPICTURE.matcher(link);
|
||||
if (m.matches())
|
||||
return getMobyPictureImage(RegexUtils.matcherGroup(m, MOBYPICTURE_GROUP_ID), link, fullImage);
|
||||
m = PATTERN_PHOTOZOU.matcher(link);
|
||||
if (m.matches())
|
||||
return getPhotozouImage(client, RegexUtils.matcherGroup(m, PHOTOZOU_GROUP_ID), link, fullImage);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ParcelableMedia[] getImagesInStatus(final String status_string, final boolean fullImage) {
|
||||
if (status_string == null) return new ParcelableMedia[0];
|
||||
final List<ParcelableMedia> images = new ArrayList<>();
|
||||
final HtmlLinkExtractor extractor = new HtmlLinkExtractor();
|
||||
for (final HtmlLink link : extractor.grabLinks(status_string)) {
|
||||
final ParcelableMedia spec = getAllAvailableImage(link.getLink(), fullImage);
|
||||
if (spec != null) {
|
||||
images.add(spec);
|
||||
}
|
||||
}
|
||||
return images.toArray(new ParcelableMedia[images.size()]);
|
||||
}
|
||||
|
||||
public static String getSupportedFirstLink(final Status status) {
|
||||
if (status == null) return null;
|
||||
final MediaEntity[] mediaEntities = status.getMediaEntities();
|
||||
if (mediaEntities != null) {
|
||||
for (final MediaEntity mediaEntity : mediaEntities) {
|
||||
final String expanded = TwitterContentUtils.getMediaUrl(mediaEntity);
|
||||
if (getSupportedLink(expanded) != null) return expanded;
|
||||
}
|
||||
}
|
||||
final UrlEntity[] urlEntities = status.getUrlEntities();
|
||||
if (urlEntities != null) {
|
||||
for (final UrlEntity urlEntity : urlEntities) {
|
||||
final String expanded = urlEntity.getExpandedUrl();
|
||||
if (getSupportedLink(expanded) != null) return expanded;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getSupportedFirstLink(final String html) {
|
||||
if (html == null) return null;
|
||||
final HtmlLinkExtractor extractor = new HtmlLinkExtractor();
|
||||
for (final HtmlLink link : extractor.grabLinks(html)) {
|
||||
if (getSupportedLink(link.getLink()) != null) return link.getLink();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getSupportedLink(final String link) {
|
||||
if (link == null) return null;
|
||||
for (final Pattern pattern : SUPPORTED_PATTERNS) {
|
||||
if (pattern.matcher(link).matches()) return link;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<String> getSupportedLinksInStatus(final String statusString) {
|
||||
if (statusString == null) return Collections.emptyList();
|
||||
final List<String> links = new ArrayList<>();
|
||||
final HtmlLinkExtractor extractor = new HtmlLinkExtractor();
|
||||
for (final HtmlLink link : extractor.grabLinks(statusString)) {
|
||||
final String spec = getSupportedLink(link.getLink());
|
||||
if (spec != null) {
|
||||
links.add(spec);
|
||||
}
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
public static boolean isLinkSupported(final String link) {
|
||||
if (link == null) return false;
|
||||
for (final Pattern pattern : SUPPORTED_PATTERNS) {
|
||||
if (pattern.matcher(link).matches()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ParcelableMedia getGoogleImage(final String server, final String id, final boolean fullImage) {
|
||||
if (isEmpty(server) || isEmpty(id)) return null;
|
||||
final String full = "https://" + server + id + "/s0/full";
|
||||
final String preview = fullImage ? full : "https://" + server + id + "/s480/full";
|
||||
return ParcelableMedia.newImage(preview, full);
|
||||
}
|
||||
|
||||
private static ParcelableMedia getGoogleProxyImage(final String server, final String id, final boolean fullImage) {
|
||||
if (isEmpty(server) || isEmpty(id)) return null;
|
||||
final String full = "https://" + server + "/proxy/" + id + "=s0";
|
||||
final String preview = fullImage ? full : "https://" + server + "/proxy/" + id + "=s480";
|
||||
return ParcelableMedia.newImage(preview, full);
|
||||
}
|
||||
|
||||
private static ParcelableMedia getImglyImage(final String id, final String orig, final boolean fullImage) {
|
||||
if (isEmpty(id)) return null;
|
||||
final String preview = String.format("http://img.ly/show/%s/%s", fullImage ? "full" : "medium", id);
|
||||
return ParcelableMedia.newImage(preview, orig);
|
||||
}
|
||||
|
||||
private static ParcelableMedia getImgurImage(final String id, final String orig, final boolean fullImage) {
|
||||
if (isEmpty(id)) return null;
|
||||
final String preview = fullImage ? String.format("http://i.imgur.com/%s.jpg", id) : String.format(
|
||||
"http://i.imgur.com/%sl.jpg", id);
|
||||
return ParcelableMedia.newImage(preview, orig);
|
||||
}
|
||||
|
||||
private static ParcelableMedia getInstagramImage(final String id, final String orig, final boolean fullImage) {
|
||||
if (isEmpty(id)) return null;
|
||||
final String preview = String.format("https://instagram.com/p/%s/media/?size=%s", id, fullImage ? "l" : "m");
|
||||
return ParcelableMedia.newImage(preview, orig);
|
||||
}
|
||||
|
||||
private static ParcelableMedia getLockerzAndPlixiImage(final String url, final boolean fullImage) {
|
||||
if (isEmpty(url)) return null;
|
||||
final String preview = String.format("https://api.plixi.com/api/tpapi.svc/imagefromurl?url=%s&size=%s", url,
|
||||
fullImage ? "big" : "small");
|
||||
return ParcelableMedia.newImage(preview, url);
|
||||
|
||||
}
|
||||
|
||||
private static ParcelableMedia getMobyPictureImage(final String id, final String orig, final boolean fullImage) {
|
||||
if (isEmpty(id)) return null;
|
||||
final String preview = String.format("http://moby.to/%s:%s", id, fullImage ? "full" : "thumb");
|
||||
return ParcelableMedia.newImage(preview, orig);
|
||||
}
|
||||
|
||||
private static ParcelableMedia getPhotozouImage(final RestHttpClient client, final String id, final String orig,
|
||||
final boolean fullImage) throws IOException {
|
||||
if (isEmpty(id)) return null;
|
||||
if (client != null) {
|
||||
final RestHttpRequest.Builder builder = new RestHttpRequest.Builder();
|
||||
builder.method(GET.METHOD);
|
||||
builder.url(Endpoint.constructUrl(URL_PHOTOZOU_PHOTO_INFO, Pair.create("photo_id", id)));
|
||||
builder.extra(RequestType.MEDIA);
|
||||
final RestHttpResponse response = client.execute(builder.build());
|
||||
final PhotoZouPhotoInfo info = LoganSquare.parse(response.getBody().stream(), PhotoZouPhotoInfo.class);
|
||||
if (info.info != null && info.info.photo != null) {
|
||||
final String key = fullImage ? info.info.photo.originalImageUrl : info.info.photo.imageUrl;
|
||||
return ParcelableMedia.newImage(key, orig);
|
||||
}
|
||||
}
|
||||
final String preview = String.format(Locale.US, "http://photozou.jp/p/img/%s", id);
|
||||
return ParcelableMedia.newImage(preview, orig);
|
||||
}
|
||||
|
||||
private static ParcelableMedia getSinaWeiboImage(final String url, final boolean fullImage) {
|
||||
if (isEmpty(url)) return null;
|
||||
final String full = url.replaceAll("/" + SINA_WEIBO_IMAGES_AVAILABLE_SIZES + "/", "/woriginal/");
|
||||
final String preview = fullImage ? full : url.replaceAll("/" + SINA_WEIBO_IMAGES_AVAILABLE_SIZES + "/",
|
||||
"/bmiddle/");
|
||||
return ParcelableMedia.newImage(preview, full);
|
||||
}
|
||||
|
||||
private static ParcelableMedia getTwitgooImage(final String id, final String orig, final boolean fullImage) {
|
||||
if (isEmpty(id)) return null;
|
||||
final String preview = String.format("http://twitgoo.com/show/%s/%s", fullImage ? "img" : "thumb", id);
|
||||
return ParcelableMedia.newImage(preview, orig);
|
||||
}
|
||||
|
||||
private static ParcelableMedia getTwitpicImage(final String id, final String orig, final boolean fullImage) {
|
||||
if (isEmpty(id)) return null;
|
||||
final String preview = String.format("http://twitpic.com/show/%s/%s", fullImage ? "large" : "thumb", id);
|
||||
return ParcelableMedia.newImage(preview, orig);
|
||||
}
|
||||
|
||||
private static ParcelableMedia getTwitterImage(final String url, final boolean fullImage) {
|
||||
if (isEmpty(url)) return null;
|
||||
final String full = (url + ":large").replaceFirst("https?://", "https://");
|
||||
final String preview = fullImage ? full : (url + ":medium").replaceFirst("https?://", "https://");
|
||||
return ParcelableMedia.newImage(preview, full);
|
||||
}
|
||||
|
||||
private static ParcelableMedia getYfrogImage(final String id, final String orig, final boolean fullImage) {
|
||||
if (isEmpty(id)) return null;
|
||||
final String preview = String.format("http://yfrog.com/%s:%s", id, fullImage ? "medium" : "iphone");
|
||||
return ParcelableMedia.newImage(preview, orig);
|
||||
}
|
||||
|
||||
@JsonObject
|
||||
public static final class PhotoZouPhotoInfo {
|
||||
|
||||
@JsonField(name = "info")
|
||||
public Info info;
|
||||
|
||||
@JsonObject
|
||||
public static final class Info {
|
||||
|
||||
@JsonField(name = "photo")
|
||||
public Photo photo;
|
||||
|
||||
@JsonObject
|
||||
public static final class Photo {
|
||||
@JsonField(name = "original_image_url")
|
||||
public String originalImageUrl;
|
||||
@JsonField(name = "image_url")
|
||||
public String imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package org.mariotaku.twidere.util.media.preview;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
import org.mariotaku.twidere.util.HtmlLinkExtractor;
|
||||
import org.mariotaku.twidere.util.media.preview.provider.InstagramProvider;
|
||||
import org.mariotaku.twidere.util.media.preview.provider.Provider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/1/1.
|
||||
*/
|
||||
public class PreviewMediaExtractor {
|
||||
|
||||
private static final Provider[] sProviders = {
|
||||
new InstagramProvider()
|
||||
};
|
||||
|
||||
@Nullable
|
||||
public static ParcelableMedia fromLink(@Nullable String link) {
|
||||
if (TextUtils.isEmpty(link)) return null;
|
||||
for (Provider provider : sProviders) {
|
||||
if (provider.supportsAuthority(getAuthority(link))) {
|
||||
return provider.from(Uri.parse(link));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getAuthority(@NonNull String link) {
|
||||
int start = link.indexOf("://");
|
||||
if (start < 0) return null;
|
||||
int end = link.indexOf('/', start + 3);
|
||||
if (end < 0) {
|
||||
end = link.length();
|
||||
}
|
||||
return link.substring(start + 3, end);
|
||||
}
|
||||
|
||||
public static boolean isSupported(@Nullable String link) {
|
||||
if (TextUtils.isEmpty(link)) return false;
|
||||
for (Provider provider : sProviders) {
|
||||
if (provider.supportsAuthority(getAuthority(link))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<String> getSupportedLinksInStatus(final String statusString) {
|
||||
if (statusString == null) return Collections.emptyList();
|
||||
final List<String> links = new ArrayList<>();
|
||||
final HtmlLinkExtractor extractor = new HtmlLinkExtractor();
|
||||
for (final HtmlLinkExtractor.HtmlLink link : extractor.grabLinks(statusString)) {
|
||||
final String linkString = link.getLink();
|
||||
if (isSupported(linkString)) {
|
||||
links.add(linkString);
|
||||
}
|
||||
}
|
||||
return links;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package org.mariotaku.twidere.util.media.preview.provider;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.WorkerThread;
|
||||
|
||||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/1/1.
|
||||
*/
|
||||
public class InstagramProvider implements Provider {
|
||||
@Override
|
||||
@WorkerThread
|
||||
public boolean supportsAuthority(String authority) {
|
||||
return "instagr.am".equals(authority) || "instagram.com".equals(authority) ||
|
||||
"www.instagram.com".equals(authority);
|
||||
}
|
||||
|
||||
@Override
|
||||
@WorkerThread
|
||||
public ParcelableMedia from(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package org.mariotaku.twidere.util.media.preview.provider;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.WorkerThread;
|
||||
|
||||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/1/1.
|
||||
*/
|
||||
public interface Provider {
|
||||
|
||||
@WorkerThread
|
||||
boolean supportsAuthority(@Nullable String authority);
|
||||
|
||||
@WorkerThread
|
||||
ParcelableMedia from(Uri uri);
|
||||
|
||||
}
|
@ -31,6 +31,8 @@ android {
|
||||
}
|
||||
|
||||
buildConfigField 'int', 'DATABASES_VERSION', '116'
|
||||
buildConfigField 'boolean', 'ENABLE_MEDIA_VIEWER', 'Boolean.parseBoolean("true")'
|
||||
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
@ -115,7 +117,6 @@ dependencies {
|
||||
compile 'com.lnikkila:extendedtouchview:0.1.0'
|
||||
compile 'com.google.dagger:dagger:2.0.2'
|
||||
compile 'org.attoparser:attoparser:1.4.0.RELEASE'
|
||||
compile 'com.jakewharton:disklrucache:2.0.2'
|
||||
googleCompile 'com.google.android.gms:play-services-maps:8.4.0'
|
||||
googleCompile 'com.google.maps.android:android-maps-utils:0.4'
|
||||
googleCompile ':YouTubeAndroidPlayerApi:1.2.2@jar'
|
||||
|
@ -79,7 +79,7 @@
|
||||
|
||||
<meta-data
|
||||
android:name="io.fabric.ApiKey"
|
||||
android:value="dc4ee756d3b705e00011782f1426fc8656ad3bd9" />
|
||||
android:value="dc4ee756d3b705e00011782f1426fc8656ad3bd9"/>
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.backup.api_key"
|
||||
@ -539,6 +539,10 @@
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths"/>
|
||||
</provider>
|
||||
<provider
|
||||
android:name=".provider.CacheProvider"
|
||||
android:authorities="twidere.cache"
|
||||
android:exported="false"/>
|
||||
|
||||
<receiver android:name=".receiver.ConnectivityStateReceiver">
|
||||
<intent-filter>
|
||||
|
@ -1,334 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* https://github.com/fhucho/simple-disk-cache
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package cz.fhucho.android.util;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.bluelinelabs.logansquare.LoganSquare;
|
||||
import com.jakewharton.disklrucache.DiskLruCache;
|
||||
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Adapted from https://github.com/fhucho/simple-disk-cache
|
||||
* License Apache 2.0
|
||||
*/
|
||||
public class SimpleDiskCache {
|
||||
|
||||
private static final int VALUE_IDX = 0;
|
||||
private static final int METADATA_IDX = 1;
|
||||
private static final List<File> usedDirs = new ArrayList<>();
|
||||
|
||||
private com.jakewharton.disklrucache.DiskLruCache diskLruCache;
|
||||
private int mAppVersion;
|
||||
|
||||
private SimpleDiskCache(File dir, int appVersion, long maxSize) throws IOException {
|
||||
mAppVersion = appVersion;
|
||||
diskLruCache = DiskLruCache.open(dir, appVersion, 2, maxSize);
|
||||
}
|
||||
|
||||
public static synchronized SimpleDiskCache open(File dir, int appVersion, long maxSize)
|
||||
throws IOException {
|
||||
if (usedDirs.contains(dir)) {
|
||||
throw new IllegalStateException("Cache dir " + dir.getAbsolutePath() + " was used before.");
|
||||
}
|
||||
|
||||
usedDirs.add(dir);
|
||||
|
||||
return new SimpleDiskCache(dir, appVersion, maxSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* User should be sure there are no outstanding operations.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void clear() throws IOException {
|
||||
File dir = diskLruCache.getDirectory();
|
||||
long maxSize = diskLruCache.getMaxSize();
|
||||
diskLruCache.delete();
|
||||
diskLruCache = DiskLruCache.open(dir, mAppVersion, 2, maxSize);
|
||||
}
|
||||
|
||||
public DiskLruCache getCache() {
|
||||
return diskLruCache;
|
||||
}
|
||||
|
||||
public InputStreamEntry getInputStream(String key) throws IOException {
|
||||
DiskLruCache.Snapshot snapshot = diskLruCache.get(toInternalKey(key));
|
||||
if (snapshot == null) return null;
|
||||
return new InputStreamEntry(snapshot, readMetadata(snapshot));
|
||||
}
|
||||
|
||||
public StringEntry getString(String key) throws IOException {
|
||||
DiskLruCache.Snapshot snapshot = diskLruCache.get(toInternalKey(key));
|
||||
if (snapshot == null) return null;
|
||||
|
||||
try {
|
||||
return new StringEntry(snapshot.getString(VALUE_IDX), readMetadata(snapshot));
|
||||
} finally {
|
||||
snapshot.close();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contains(String key) throws IOException {
|
||||
DiskLruCache.Snapshot snapshot = diskLruCache.get(toInternalKey(key));
|
||||
if (snapshot == null) return false;
|
||||
|
||||
snapshot.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
public OutputStream openStream(String key) throws IOException {
|
||||
return openStream(key, new HashMap<String, String>());
|
||||
}
|
||||
|
||||
public OutputStream openStream(String key, @NonNull Map<String, String> metadata)
|
||||
throws IOException {
|
||||
DiskLruCache.Editor editor = diskLruCache.edit(toInternalKey(key));
|
||||
try {
|
||||
writeMetadata(metadata, editor);
|
||||
BufferedOutputStream bos = new BufferedOutputStream(editor.newOutputStream(VALUE_IDX));
|
||||
return new CacheOutputStream(bos, editor);
|
||||
} catch (IOException e) {
|
||||
editor.abort();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void put(String key, InputStream is) throws IOException {
|
||||
put(key, is, new HashMap<String, String>());
|
||||
}
|
||||
|
||||
public void put(String key, InputStream is, @NonNull Map<String, String> annotations)
|
||||
throws IOException {
|
||||
OutputStream os = null;
|
||||
try {
|
||||
os = openStream(key, annotations);
|
||||
Utils.copyStream(is, os);
|
||||
} finally {
|
||||
if (os != null) os.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void put(String key, String value) throws IOException {
|
||||
put(key, value, new HashMap<String, String>());
|
||||
}
|
||||
|
||||
public void put(String key, String value, @NonNull Map<String, String> annotations)
|
||||
throws IOException {
|
||||
OutputStream cos = null;
|
||||
try {
|
||||
cos = openStream(key, annotations);
|
||||
cos.write(value.getBytes());
|
||||
} finally {
|
||||
if (cos != null) cos.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean remove(String key) {
|
||||
try {
|
||||
return diskLruCache.remove(toInternalKey(key));
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeMetadata(@NonNull Map<String, String> metadata, DiskLruCache.Editor editor)
|
||||
throws IOException {
|
||||
OutputStream os = null;
|
||||
try {
|
||||
os = new BufferedOutputStream(editor.newOutputStream(METADATA_IDX));
|
||||
LoganSquare.serialize(metadata, os, String.class);
|
||||
} finally {
|
||||
Utils.closeSilently(os);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Map<String, String> readMetadata(DiskLruCache.Snapshot snapshot)
|
||||
throws IOException {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = new BufferedInputStream(snapshot.getInputStream(METADATA_IDX));
|
||||
return LoganSquare.parseMap(is, String.class);
|
||||
} finally {
|
||||
Utils.closeSilently(is);
|
||||
}
|
||||
}
|
||||
|
||||
private String toInternalKey(String key) {
|
||||
return md5(key);
|
||||
}
|
||||
|
||||
private String md5(String s) {
|
||||
try {
|
||||
MessageDigest m = MessageDigest.getInstance("MD5");
|
||||
m.update(s.getBytes("UTF-8"));
|
||||
byte[] digest = m.digest();
|
||||
BigInteger bigInt = new BigInteger(1, digest);
|
||||
return bigInt.toString(16);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError();
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
private class CacheOutputStream extends FilterOutputStream {
|
||||
|
||||
private final DiskLruCache.Editor editor;
|
||||
private boolean failed = false;
|
||||
|
||||
private CacheOutputStream(OutputStream os, DiskLruCache.Editor editor) {
|
||||
super(os);
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
IOException closeException = null;
|
||||
try {
|
||||
super.close();
|
||||
} catch (IOException e) {
|
||||
closeException = e;
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
editor.abort();
|
||||
} else {
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
if (closeException != null) throw closeException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
try {
|
||||
super.flush();
|
||||
} catch (IOException e) {
|
||||
failed = true;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int oneByte) throws IOException {
|
||||
try {
|
||||
super.write(oneByte);
|
||||
} catch (IOException e) {
|
||||
failed = true;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NonNull byte[] buffer) throws IOException {
|
||||
try {
|
||||
super.write(buffer);
|
||||
} catch (IOException e) {
|
||||
failed = true;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NonNull byte[] buffer, int offset, int length) throws IOException {
|
||||
try {
|
||||
super.write(buffer, offset, length);
|
||||
} catch (IOException e) {
|
||||
failed = true;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class InputStreamEntry {
|
||||
private final DiskLruCache.Snapshot snapshot;
|
||||
@NonNull
|
||||
private final Map<String, String> metadata;
|
||||
private final long length;
|
||||
|
||||
public InputStreamEntry(DiskLruCache.Snapshot snapshot, @NonNull Map<String, String> metadata) {
|
||||
this.metadata = metadata;
|
||||
this.snapshot = snapshot;
|
||||
this.length = snapshot.getLength(VALUE_IDX);
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return snapshot.getInputStream(VALUE_IDX);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Map<String, String> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public long getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
snapshot.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class StringEntry {
|
||||
private final String string;
|
||||
@NonNull
|
||||
private final Map<String, String> metadata;
|
||||
|
||||
public StringEntry(String string, @NonNull Map<String, String> metadata) {
|
||||
this.string = string;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Map<String, String> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -65,9 +65,6 @@ import android.widget.Toast;
|
||||
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource;
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
|
||||
import com.davemorrissey.labs.subscaleview.decoder.DecoderFactory;
|
||||
import com.davemorrissey.labs.subscaleview.decoder.ImageDecoder;
|
||||
import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder;
|
||||
import com.pnikosis.materialishprogress.ProgressWheel;
|
||||
import com.sprylab.android.widget.TextureVideoView;
|
||||
|
||||
@ -79,9 +76,10 @@ import org.mariotaku.twidere.adapter.support.SupportFixedFragmentStatePagerAdapt
|
||||
import org.mariotaku.twidere.fragment.ProgressDialogFragment;
|
||||
import org.mariotaku.twidere.fragment.support.BaseSupportFragment;
|
||||
import org.mariotaku.twidere.fragment.support.ViewStatusDialogFragment;
|
||||
import org.mariotaku.twidere.loader.support.TileImageLoader;
|
||||
import org.mariotaku.twidere.loader.support.TileImageLoader.DownloadListener;
|
||||
import org.mariotaku.twidere.loader.support.TileImageLoader.Result;
|
||||
import org.mariotaku.twidere.loader.support.CacheDownloadLoader;
|
||||
import org.mariotaku.twidere.loader.support.CacheDownloadLoader.DownloadListener;
|
||||
import org.mariotaku.twidere.loader.support.CacheDownloadLoader.Result;
|
||||
import org.mariotaku.twidere.loader.support.FullImageDownloadLoader;
|
||||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
import org.mariotaku.twidere.model.ParcelableMedia.VideoInfo.Variant;
|
||||
import org.mariotaku.twidere.model.ParcelableStatus;
|
||||
@ -95,9 +93,6 @@ import org.mariotaku.twidere.util.PermissionUtils;
|
||||
import org.mariotaku.twidere.util.ThemeUtils;
|
||||
import org.mariotaku.twidere.util.TwitterCardFragmentFactory;
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
import org.mariotaku.twidere.util.VideoLoadingListener;
|
||||
import org.mariotaku.twidere.util.imageloader.TwidereSkiaImageDecoder;
|
||||
import org.mariotaku.twidere.util.imageloader.TwidereSkiaImageRegionDecoder;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -304,16 +299,16 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
|
||||
invalidateOptionsMenu();
|
||||
final ParcelableMedia media = getMedia();
|
||||
final long accountId = args.getLong(EXTRA_ACCOUNT_ID, -1);
|
||||
return new TileImageLoader(getActivity(), this, accountId, Uri.parse(media.media_url));
|
||||
return new FullImageDownloadLoader(getActivity(), this, Uri.parse(media.media_url), accountId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(final Loader<TileImageLoader.Result> loader, final TileImageLoader.Result data) {
|
||||
public void onLoadFinished(final Loader<CacheDownloadLoader.Result> loader, final CacheDownloadLoader.Result data) {
|
||||
if (data.cacheUri != null) {
|
||||
setImageViewVisibility(View.VISIBLE);
|
||||
mImageView.setImage(ImageSource.uri(data.cacheUri));
|
||||
} else {
|
||||
mImageView.recycle();
|
||||
recycleImageView();
|
||||
setImageViewVisibility(View.GONE);
|
||||
Utils.showErrorMessage(getActivity(), null, data.exception, true);
|
||||
}
|
||||
@ -322,8 +317,13 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
public void recycleImageView() {
|
||||
mImageView.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(final Loader<TileImageLoader.Result> loader) {
|
||||
public void onLoaderReset(final Loader<CacheDownloadLoader.Result> loader) {
|
||||
recycleImageView();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -343,14 +343,14 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressUpdate(final long downloaded) {
|
||||
public void onProgressUpdate(final long current, final long total) {
|
||||
if (mContentLength <= 0) {
|
||||
if (!mProgressBar.isSpinning()) {
|
||||
mProgressBar.spin();
|
||||
}
|
||||
return;
|
||||
}
|
||||
setLoadProgress(downloaded / mContentLength);
|
||||
setLoadProgress(current / mContentLength);
|
||||
}
|
||||
|
||||
protected void setImageViewVisibility(int visible) {
|
||||
@ -509,18 +509,6 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
mImageView.setOnClickListener(this);
|
||||
mImageView.setRegionDecoderFactory(new DecoderFactory<ImageRegionDecoder>() {
|
||||
@Override
|
||||
public ImageRegionDecoder make() {
|
||||
return new TwidereSkiaImageRegionDecoder(getContext());
|
||||
}
|
||||
});
|
||||
mImageView.setBitmapDecoderFactory(new DecoderFactory<ImageDecoder>() {
|
||||
@Override
|
||||
public ImageDecoder make() {
|
||||
return new TwidereSkiaImageDecoder(getContext());
|
||||
}
|
||||
});
|
||||
mImageView.setOnGenericMotionListener(new OnGenericMotionListener() {
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
@ -558,7 +546,7 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
|
||||
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(final Loader<TileImageLoader.Result> loader, final TileImageLoader.Result data) {
|
||||
public void onLoadFinished(final Loader<CacheDownloadLoader.Result> loader, final CacheDownloadLoader.Result data) {
|
||||
// if (data.hasData() && "image/gif".equals(data.options.outMimeType)) {
|
||||
// mGifImageView.setVisibility(View.VISIBLE);
|
||||
// setImageViewVisibility(View.GONE);
|
||||
@ -573,7 +561,7 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
|
||||
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(final Loader<TileImageLoader.Result> loader) {
|
||||
public void onLoaderReset(final Loader<CacheDownloadLoader.Result> loader) {
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -674,8 +662,9 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
|
||||
}
|
||||
}
|
||||
|
||||
public static final class VideoPageFragment extends AbsMediaPageFragment
|
||||
implements VideoLoadingListener, OnPreparedListener, OnErrorListener, OnCompletionListener, OnClickListener {
|
||||
public static final class VideoPageFragment extends AbsMediaPageFragment implements
|
||||
DownloadListener, LoaderCallbacks<Result>, OnPreparedListener, OnErrorListener,
|
||||
OnCompletionListener, OnClickListener {
|
||||
|
||||
private static final String[] SUPPORTED_VIDEO_TYPES;
|
||||
|
||||
@ -702,17 +691,25 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
|
||||
private Pair<String, String> mVideoUrlAndType;
|
||||
private MediaPlayer mMediaPlayer;
|
||||
private int mMediaPlayerError;
|
||||
private boolean mLoaderInitialized;
|
||||
|
||||
public boolean isLoopEnabled() {
|
||||
return getArguments().getBoolean(EXTRA_LOOP, false);
|
||||
}
|
||||
|
||||
public void loadVideo(boolean forceReload) {
|
||||
|
||||
public void loadVideo() {
|
||||
Pair<String, String> urlAndType = getBestVideoUrlAndType(getMedia());
|
||||
if (urlAndType == null || urlAndType.first == null) return;
|
||||
mVideoUrlAndType = urlAndType;
|
||||
// mHttpProxyCacheServer.getProxyUrl()
|
||||
// mVideoLoader.loadVideo(urlAndType.first, forceReload, this);
|
||||
getLoaderManager().destroyLoader(0);
|
||||
final Bundle args = new Bundle();
|
||||
args.putParcelable(EXTRA_URI, Uri.parse(urlAndType.first));
|
||||
if (!mLoaderInitialized) {
|
||||
getLoaderManager().initLoader(0, args, this);
|
||||
mLoaderInitialized = true;
|
||||
} else {
|
||||
getLoaderManager().restartLoader(0, args, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -774,12 +771,6 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoLoadingCancelled(String uri, VideoLoadingListener listener) {
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
mProgressBar.setProgress(0);
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBaseViewCreated(View view, Bundle savedInstanceState) {
|
||||
@ -795,39 +786,6 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
|
||||
mVideoControl = view.findViewById(R.id.video_control);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoLoadingComplete(String uri, VideoLoadingListener listener, File file) {
|
||||
mVideoView.setVideoURI(Uri.fromFile(file));
|
||||
mVideoFile = file;
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
mProgressBar.setProgress(0);
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoLoadingFailed(String uri, VideoLoadingListener listener, Exception e) {
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
mProgressBar.setProgress(0);
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoLoadingProgressUpdate(String uri, VideoLoadingListener listener, int current, int total) {
|
||||
if (total <= 0) {
|
||||
if (!mProgressBar.isSpinning()) {
|
||||
mProgressBar.spin();
|
||||
}
|
||||
return;
|
||||
}
|
||||
mProgressBar.setProgress(current / (float) total);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoLoadingStarted(String uri, VideoLoadingListener listener) {
|
||||
mProgressBar.setVisibility(View.VISIBLE);
|
||||
mProgressBar.spin();
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
@ -858,7 +816,7 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
|
||||
|
||||
mPlayPauseButton.setOnClickListener(this);
|
||||
mVolumeButton.setOnClickListener(this);
|
||||
loadVideo(false);
|
||||
loadVideo();
|
||||
updateVolume();
|
||||
}
|
||||
|
||||
@ -968,6 +926,58 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadError(Throwable t) {
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
mProgressBar.setProgress(0);
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadFinished() {
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
mProgressBar.setProgress(0);
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadStart(long total) {
|
||||
mProgressBar.setVisibility(View.VISIBLE);
|
||||
mProgressBar.spin();
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressUpdate(long current, long total) {
|
||||
if (total <= 0) {
|
||||
if (!mProgressBar.isSpinning()) {
|
||||
mProgressBar.spin();
|
||||
}
|
||||
return;
|
||||
}
|
||||
mProgressBar.setProgress(current / (float) total);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Result> onCreateLoader(int id, Bundle args) {
|
||||
final Uri uri = args.getParcelable(EXTRA_URI);
|
||||
return new FullImageDownloadLoader(getContext(), this, uri, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Result> loader, Result data) {
|
||||
mVideoView.setVideoURI(data.cacheUri);
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
mProgressBar.setProgress(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Result> loader) {
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
mProgressBar.setProgress(0);
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
private static class VideoPlayProgressRunnable implements Runnable {
|
||||
|
||||
private final Handler mHandler;
|
||||
@ -1033,7 +1043,7 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
|
||||
return true;
|
||||
}
|
||||
case R.id.refresh: {
|
||||
loadVideo(true);
|
||||
loadVideo();
|
||||
return true;
|
||||
}
|
||||
case R.id.share: {
|
||||
|
@ -20,102 +20,81 @@
|
||||
package org.mariotaku.twidere.loader.support;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import com.nostra13.universalimageloader.core.download.ImageDownloader;
|
||||
import com.nostra13.universalimageloader.cache.disc.DiskCache;
|
||||
import com.nostra13.universalimageloader.utils.IoUtils;
|
||||
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.mariotaku.twidere.util.SimpleDiskCacheUtils;
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
|
||||
import org.mariotaku.twidere.util.imageloader.AccountFullImageExtra;
|
||||
import org.mariotaku.twidere.util.io.ContentLengthInputStream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import cz.fhucho.android.util.SimpleDiskCache;
|
||||
import okio.ByteString;
|
||||
|
||||
public class TileImageLoader extends AsyncTaskLoader<TileImageLoader.Result> {
|
||||
public abstract class CacheDownloadLoader extends AsyncTaskLoader<CacheDownloadLoader.Result> {
|
||||
|
||||
private final Uri mUri;
|
||||
private final Handler mHandler;
|
||||
private final DownloadListener mListener;
|
||||
@Inject
|
||||
ImageDownloader mDownloader;
|
||||
@Inject
|
||||
SimpleDiskCache mDiskCache;
|
||||
|
||||
private final float mFallbackSize;
|
||||
private final long mAccountId;
|
||||
@Inject
|
||||
DiskCache mDiskCache;
|
||||
|
||||
public TileImageLoader(final Context context, final DownloadListener listener, final long accountId, final Uri uri) {
|
||||
public CacheDownloadLoader(final Context context, final DownloadListener listener, final Uri uri) {
|
||||
super(context);
|
||||
GeneralComponentHelper.build(context).inject(this);
|
||||
mHandler = new Handler();
|
||||
mAccountId = accountId;
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mUri = uri;
|
||||
mListener = listener;
|
||||
final Resources res = context.getResources();
|
||||
final DisplayMetrics dm = res.getDisplayMetrics();
|
||||
mFallbackSize = Math.max(dm.heightPixels, dm.widthPixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileImageLoader.Result loadInBackground() {
|
||||
public CacheDownloadLoader.Result loadInBackground() {
|
||||
if (mUri == null) {
|
||||
return Result.nullInstance();
|
||||
}
|
||||
final String scheme = mUri.getScheme();
|
||||
SimpleDiskCache.InputStreamEntry cacheEntry = null;
|
||||
File cacheFile = null;
|
||||
if ("http".equals(scheme) || "https".equals(scheme)) {
|
||||
final String uriString = mUri.toString();
|
||||
if (uriString == null) return Result.nullInstance();
|
||||
try {
|
||||
cacheEntry = mDiskCache.getInputStream(uriString);
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
if (isValid(cacheEntry)) {
|
||||
return Result.getInstance(Uri.fromParts("cache",
|
||||
ByteString.encodeUtf8(uriString).base64Url(), null));
|
||||
cacheFile = mDiskCache.get(uriString);
|
||||
if (isValid(cacheFile)) {
|
||||
return Result.getInstance(SimpleDiskCacheUtils.getCacheUri(uriString));
|
||||
}
|
||||
try {
|
||||
// from SD cache
|
||||
ContentLengthInputStream cis = null;
|
||||
final InputStream is = mDownloader.getStream(uriString, new AccountFullImageExtra(mAccountId));
|
||||
ContentLengthInputStream cis;
|
||||
final InputStream is = getStreamFromNetwork(uriString);
|
||||
if (is == null) return Result.nullInstance();
|
||||
try {
|
||||
final long length = is.available();
|
||||
mHandler.post(new DownloadStartRunnable(this, mListener, length));
|
||||
|
||||
final Map<String, String> metadata = new HashMap<>();
|
||||
metadata.put("length", String.valueOf(length));
|
||||
|
||||
cis = new ContentLengthInputStream(is, length);
|
||||
cis.setReadListener(new ContentLengthInputStream.ReadListener() {
|
||||
mDiskCache.save(uriString, cis, new IoUtils.CopyListener() {
|
||||
@Override
|
||||
public void onRead(long length, long position) {
|
||||
mHandler.post(new ProgressUpdateRunnable(mListener, position));
|
||||
public boolean onBytesCopied(int length, int position) {
|
||||
mHandler.post(new ProgressUpdateRunnable(mListener, position, length));
|
||||
return !isAbandoned();
|
||||
}
|
||||
});
|
||||
mDiskCache.put(uriString, cis, metadata);
|
||||
mHandler.post(new DownloadFinishRunnable(this, mListener));
|
||||
} finally {
|
||||
IoUtils.closeSilently(is);
|
||||
Utils.closeSilently(is);
|
||||
}
|
||||
cacheEntry = mDiskCache.getInputStream(uriString);
|
||||
if (isValid(cacheEntry)) {
|
||||
return Result.getInstance(Uri.fromParts("cache",
|
||||
ByteString.encodeUtf8(uriString).base64Url(), null));
|
||||
cacheFile = mDiskCache.get(uriString);
|
||||
if (isValid(cacheFile)) {
|
||||
return Result.getInstance(SimpleDiskCacheUtils.getCacheUri(uriString));
|
||||
} else {
|
||||
mDiskCache.remove(uriString);
|
||||
throw new IOException();
|
||||
@ -128,11 +107,10 @@ public class TileImageLoader extends AsyncTaskLoader<TileImageLoader.Result> {
|
||||
return Result.getInstance(mUri);
|
||||
}
|
||||
|
||||
private static boolean isValid(SimpleDiskCache.InputStreamEntry entry) {
|
||||
if (entry == null) return false;
|
||||
final Map<String, String> metadata = entry.getMetadata();
|
||||
final long length = NumberUtils.toLong(metadata.get("length"), -1);
|
||||
return length == -1 || entry.getLength() == length;
|
||||
protected abstract InputStream getStreamFromNetwork(String url) throws IOException;
|
||||
|
||||
private static boolean isValid(File entry) {
|
||||
return entry != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -148,7 +126,7 @@ public class TileImageLoader extends AsyncTaskLoader<TileImageLoader.Result> {
|
||||
|
||||
void onDownloadStart(long total);
|
||||
|
||||
void onProgressUpdate(long downloaded);
|
||||
void onProgressUpdate(long current, long total);
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
@ -175,11 +153,11 @@ public class TileImageLoader extends AsyncTaskLoader<TileImageLoader.Result> {
|
||||
|
||||
private final static class DownloadErrorRunnable implements Runnable {
|
||||
|
||||
private final TileImageLoader loader;
|
||||
private final CacheDownloadLoader loader;
|
||||
private final DownloadListener listener;
|
||||
private final Throwable t;
|
||||
|
||||
DownloadErrorRunnable(final TileImageLoader loader, final DownloadListener listener, final Throwable t) {
|
||||
DownloadErrorRunnable(final CacheDownloadLoader loader, final DownloadListener listener, final Throwable t) {
|
||||
this.loader = loader;
|
||||
this.listener = listener;
|
||||
this.t = t;
|
||||
@ -194,10 +172,10 @@ public class TileImageLoader extends AsyncTaskLoader<TileImageLoader.Result> {
|
||||
|
||||
private final static class DownloadFinishRunnable implements Runnable {
|
||||
|
||||
private final TileImageLoader loader;
|
||||
private final CacheDownloadLoader loader;
|
||||
private final DownloadListener listener;
|
||||
|
||||
DownloadFinishRunnable(final TileImageLoader loader, final DownloadListener listener) {
|
||||
DownloadFinishRunnable(final CacheDownloadLoader loader, final DownloadListener listener) {
|
||||
this.loader = loader;
|
||||
this.listener = listener;
|
||||
}
|
||||
@ -211,11 +189,11 @@ public class TileImageLoader extends AsyncTaskLoader<TileImageLoader.Result> {
|
||||
|
||||
private final static class DownloadStartRunnable implements Runnable {
|
||||
|
||||
private final TileImageLoader loader;
|
||||
private final CacheDownloadLoader loader;
|
||||
private final DownloadListener listener;
|
||||
private final long total;
|
||||
|
||||
DownloadStartRunnable(final TileImageLoader loader, final DownloadListener listener, final long total) {
|
||||
DownloadStartRunnable(final CacheDownloadLoader loader, final DownloadListener listener, final long total) {
|
||||
this.loader = loader;
|
||||
this.listener = listener;
|
||||
this.total = total;
|
||||
@ -231,17 +209,18 @@ public class TileImageLoader extends AsyncTaskLoader<TileImageLoader.Result> {
|
||||
private final static class ProgressUpdateRunnable implements Runnable {
|
||||
|
||||
private final DownloadListener listener;
|
||||
private final long current;
|
||||
private final long current, total;
|
||||
|
||||
ProgressUpdateRunnable(final DownloadListener listener, final long current) {
|
||||
ProgressUpdateRunnable(final DownloadListener listener, final long current, final long total) {
|
||||
this.listener = listener;
|
||||
this.current = current;
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (listener == null) return;
|
||||
listener.onProgressUpdate(current);
|
||||
listener.onProgressUpdate(current, total);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package org.mariotaku.twidere.loader.support;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.mariotaku.restfu.annotation.method.GET;
|
||||
import org.mariotaku.restfu.http.RestHttpClient;
|
||||
import org.mariotaku.restfu.http.RestHttpRequest;
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/1/1.
|
||||
*/
|
||||
public class FullImageDownloadLoader extends CacheDownloadLoader {
|
||||
|
||||
@Inject
|
||||
RestHttpClient mRestHttpClient;
|
||||
|
||||
private final long mAccountId;
|
||||
|
||||
public FullImageDownloadLoader(Context context, DownloadListener listener, Uri uri, long accountId) {
|
||||
super(context, listener, uri);
|
||||
GeneralComponentHelper.build(context).inject(this);
|
||||
mAccountId = accountId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputStream getStreamFromNetwork(String url) throws IOException {
|
||||
RestHttpRequest.Builder builder = new RestHttpRequest.Builder();
|
||||
builder.method(GET.METHOD);
|
||||
builder.url(url);
|
||||
return mRestHttpClient.execute(builder.build()).getBody().stream();
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package org.mariotaku.twidere.provider;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.nostra13.universalimageloader.cache.disc.DiskCache;
|
||||
|
||||
import org.mariotaku.twidere.util.SimpleDiskCacheUtils;
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/1/1.
|
||||
*/
|
||||
public class CacheProvider extends ContentProvider {
|
||||
@Inject
|
||||
DiskCache mSimpleDiskCache;
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
final Context context = getContext();
|
||||
assert context != null;
|
||||
GeneralComponentHelper.build(context).inject(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Uri insert(@NonNull Uri uri, ContentValues values) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
|
||||
try {
|
||||
final File file = mSimpleDiskCache.get(SimpleDiskCacheUtils.getCacheKey(uri));
|
||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from ContentResolver.java
|
||||
*/
|
||||
private static int modeToMode(String mode) {
|
||||
int modeBits;
|
||||
if ("r".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
|
||||
} else if ("w".equals(mode) || "wt".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
|
||||
| ParcelFileDescriptor.MODE_CREATE
|
||||
| ParcelFileDescriptor.MODE_TRUNCATE;
|
||||
} else if ("wa".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
|
||||
| ParcelFileDescriptor.MODE_CREATE
|
||||
| ParcelFileDescriptor.MODE_APPEND;
|
||||
} else if ("rw".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
|
||||
| ParcelFileDescriptor.MODE_CREATE;
|
||||
} else if ("rwt".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
|
||||
| ParcelFileDescriptor.MODE_CREATE
|
||||
| ParcelFileDescriptor.MODE_TRUNCATE;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid mode: " + mode);
|
||||
}
|
||||
return modeBits;
|
||||
}
|
||||
}
|
@ -110,7 +110,6 @@ import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
||||
import org.mariotaku.twidere.util.DataStoreUtils;
|
||||
import org.mariotaku.twidere.util.DatabaseQueryUtils;
|
||||
import org.mariotaku.twidere.util.ImagePreloader;
|
||||
import org.mariotaku.twidere.util.MediaPreviewUtils;
|
||||
import org.mariotaku.twidere.util.NotificationManagerWrapper;
|
||||
import org.mariotaku.twidere.util.ParseUtils;
|
||||
import org.mariotaku.twidere.util.PermissionsManager;
|
||||
@ -1727,9 +1726,9 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
||||
}
|
||||
if (mPreferences.getBoolean(KEY_PRELOAD_PREVIEW_IMAGES, false)) {
|
||||
final String textHtml = v.getAsString(Statuses.TEXT_HTML);
|
||||
for (final String link : MediaPreviewUtils.getSupportedLinksInStatus(textHtml)) {
|
||||
mImagePreloader.preloadImage(link);
|
||||
}
|
||||
// for (final String link : MediaPreviewUtils.getSupportedLinksInStatus(textHtml)) {
|
||||
// mImagePreloader.preloadImage(link);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
import org.mariotaku.twidere.util.TwidereLinkify.OnLinkClickListener;
|
||||
import org.mariotaku.twidere.util.media.preview.PreviewMediaExtractor;
|
||||
|
||||
import edu.tsinghua.hotmobi.HotMobiLogger;
|
||||
import edu.tsinghua.hotmobi.model.LinkEvent;
|
||||
@ -73,7 +74,7 @@ public class OnLinkClickHandler implements OnLinkClickListener, Constants {
|
||||
break;
|
||||
}
|
||||
case TwidereLinkify.LINK_TYPE_LINK: {
|
||||
if (MediaPreviewUtils.isLinkSupported(link)) {
|
||||
if (PreviewMediaExtractor.isSupported(link)) {
|
||||
openMedia(accountId, extraId, sensitive, link, start, end);
|
||||
} else {
|
||||
openLink(link);
|
||||
|
@ -0,0 +1,29 @@
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.mariotaku.twidere.TwidereConstants;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/1/1.
|
||||
*/
|
||||
public class SimpleDiskCacheUtils implements TwidereConstants {
|
||||
|
||||
public static Uri getCacheUri(String key) {
|
||||
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(AUTHORITY_TWIDERE_CACHE)
|
||||
.appendPath(ByteString.encodeUtf8(key).base64Url())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static String getCacheKey(Uri uri) {
|
||||
if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()))
|
||||
throw new IllegalArgumentException(uri.toString());
|
||||
if (!AUTHORITY_TWIDERE_CACHE.equals(uri.getAuthority()))
|
||||
throw new IllegalArgumentException(uri.toString());
|
||||
return ByteString.decodeBase64(uri.getLastPathSegment()).utf8();
|
||||
}
|
||||
}
|
@ -38,7 +38,6 @@ import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.mariotaku.twidere.util.MediaPreviewUtils.AVAILABLE_IMAGE_SHUFFIX;
|
||||
import static org.mariotaku.twidere.util.RegexUtils.matcherEnd;
|
||||
import static org.mariotaku.twidere.util.RegexUtils.matcherGroup;
|
||||
import static org.mariotaku.twidere.util.RegexUtils.matcherStart;
|
||||
@ -74,7 +73,7 @@ public final class TwidereLinkify implements Constants {
|
||||
|
||||
public static final String TWITTER_PROFILE_IMAGES_AVAILABLE_SIZES = "(bigger|normal|mini|reasonably_small)";
|
||||
private static final String STRING_PATTERN_TWITTER_PROFILE_IMAGES_NO_SCHEME = "(twimg[\\d\\w\\-]+\\.akamaihd\\.net|[\\w\\d]+\\.twimg\\.com)/profile_images/([\\d\\w\\-_]+)/([\\d\\w\\-_]+)_"
|
||||
+ TWITTER_PROFILE_IMAGES_AVAILABLE_SIZES + "(\\.?" + AVAILABLE_IMAGE_SHUFFIX + ")?";
|
||||
+ TWITTER_PROFILE_IMAGES_AVAILABLE_SIZES + "(\\.?" + "(png|jpeg|jpg|gif|bmp)" + ")?";
|
||||
private static final String STRING_PATTERN_TWITTER_PROFILE_IMAGES = AVAILABLE_URL_SCHEME_PREFIX
|
||||
+ STRING_PATTERN_TWITTER_PROFILE_IMAGES_NO_SCHEME;
|
||||
|
||||
|
@ -213,7 +213,6 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
|
||||
import org.mariotaku.twidere.service.RefreshService;
|
||||
import org.mariotaku.twidere.util.TwidereLinkify.HighlightStyle;
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils;
|
||||
import org.mariotaku.twidere.util.dagger.DependencyHolder;
|
||||
import org.mariotaku.twidere.util.menu.TwidereMenuInfo;
|
||||
import org.mariotaku.twidere.view.CardMediaContainer.OnMediaClickListener;
|
||||
import org.mariotaku.twidere.view.CardMediaContainer.PreviewStyle;
|
||||
@ -2166,6 +2165,12 @@ public final class Utils implements Constants {
|
||||
final ParcelableStatus status, final ParcelableDirectMessage message,
|
||||
final ParcelableMedia current, final ParcelableMedia[] media, Bundle options) {
|
||||
if (context == null || media == null) return;
|
||||
if (!BuildConfig.ENABLE_MEDIA_VIEWER) {
|
||||
final Uri parse = Uri.parse(current.page_url);
|
||||
context.startActivity(new Intent(Intent.ACTION_VIEW, parse));
|
||||
return;
|
||||
}
|
||||
// TODO: enable media viewer after finish cache design
|
||||
final Intent intent = new Intent(INTENT_ACTION_VIEW_MEDIA);
|
||||
intent.putExtra(EXTRA_ACCOUNT_ID, accountId);
|
||||
intent.putExtra(EXTRA_CURRENT_MEDIA, current);
|
||||
|
@ -28,7 +28,6 @@ import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
||||
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
|
||||
import com.nostra13.universalimageloader.core.download.ImageDownloader;
|
||||
import com.nostra13.universalimageloader.utils.L;
|
||||
import com.squareup.okhttp.Dns;
|
||||
import com.squareup.otto.Bus;
|
||||
@ -63,7 +62,6 @@ import java.io.IOException;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import cz.fhucho.android.util.SimpleDiskCache;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import edu.tsinghua.hotmobi.HotMobiLogger;
|
||||
@ -146,7 +144,7 @@ public class ApplicationModule implements Constants {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public ImageLoader imageLoader(ImageDownloader downloader, SharedPreferencesWrapper preferences) {
|
||||
public ImageLoader imageLoader(SharedPreferencesWrapper preferences, RestHttpClient client) {
|
||||
final ImageLoader loader = ImageLoader.getInstance();
|
||||
final ImageLoaderConfiguration.Builder cb = new ImageLoaderConfiguration.Builder(application);
|
||||
cb.threadPriority(Thread.NORM_PRIORITY - 2);
|
||||
@ -154,18 +152,12 @@ public class ApplicationModule implements Constants {
|
||||
cb.tasksProcessingOrder(QueueProcessingType.LIFO);
|
||||
// cb.memoryCache(new ImageMemoryCache(40));
|
||||
cb.diskCache(createDiskCache("images", preferences));
|
||||
cb.imageDownloader(downloader);
|
||||
cb.imageDownloader(new TwidereImageDownloader(application, preferences, client, true));
|
||||
L.writeDebugLogs(BuildConfig.DEBUG);
|
||||
loader.init(cb.build());
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public ImageDownloader imageDownloader(SharedPreferencesWrapper preferences, RestHttpClient client) {
|
||||
return new TwidereImageDownloader(application, preferences, client, true);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public ActivityTracker activityTracker() {
|
||||
@ -202,22 +194,10 @@ public class ApplicationModule implements Constants {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public SimpleDiskCache providesSimpleDiskCache() {
|
||||
final File cacheDir = new File(application.getCacheDir(), "files");
|
||||
final File reserveCacheDir = new File(application.getExternalCacheDir(), "files");
|
||||
return openCache(cacheDir, reserveCacheDir);
|
||||
public DiskCache providesDiskCache(SharedPreferencesWrapper preferences) {
|
||||
return createDiskCache("files", preferences);
|
||||
}
|
||||
|
||||
private SimpleDiskCache openCache(File cacheDir, File reserveCacheDir) {
|
||||
try {
|
||||
return SimpleDiskCache.open(cacheDir, BuildConfig.VERSION_CODE, 512 * 1024 * 1024);
|
||||
} catch (IOException e) {
|
||||
if (reserveCacheDir == null) throw new RuntimeException(e);
|
||||
return openCache(reserveCacheDir, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private DiskCache createDiskCache(final String dirName, SharedPreferencesWrapper preferences) {
|
||||
final File cacheDir = Utils.getExternalCacheDir(application, dirName);
|
||||
final File fallbackCacheDir = getInternalCacheDir(application, dirName);
|
||||
|
@ -41,8 +41,10 @@ import org.mariotaku.twidere.fragment.support.AccountsDashboardFragment;
|
||||
import org.mariotaku.twidere.fragment.support.BaseSupportDialogFragment;
|
||||
import org.mariotaku.twidere.fragment.support.BaseSupportFragment;
|
||||
import org.mariotaku.twidere.fragment.support.MessagesConversationFragment;
|
||||
import org.mariotaku.twidere.loader.support.TileImageLoader;
|
||||
import org.mariotaku.twidere.loader.support.CacheDownloadLoader;
|
||||
import org.mariotaku.twidere.loader.support.FullImageDownloadLoader;
|
||||
import org.mariotaku.twidere.preference.AccountsListPreference;
|
||||
import org.mariotaku.twidere.provider.CacheProvider;
|
||||
import org.mariotaku.twidere.provider.TwidereCommandProvider;
|
||||
import org.mariotaku.twidere.provider.TwidereDataProvider;
|
||||
import org.mariotaku.twidere.service.BackgroundOperationService;
|
||||
@ -51,8 +53,6 @@ import org.mariotaku.twidere.task.ManagedAsyncTask;
|
||||
import org.mariotaku.twidere.text.util.EmojiEditableFactory;
|
||||
import org.mariotaku.twidere.text.util.EmojiSpannableFactory;
|
||||
import org.mariotaku.twidere.util.MultiSelectEventHandler;
|
||||
import org.mariotaku.twidere.util.imageloader.TwidereSkiaImageDecoder;
|
||||
import org.mariotaku.twidere.util.imageloader.TwidereSkiaImageRegionDecoder;
|
||||
import org.mariotaku.twidere.util.net.TwidereProxySelector;
|
||||
import org.mariotaku.twidere.view.holder.StatusViewHolder;
|
||||
|
||||
@ -128,12 +128,12 @@ public interface GeneralComponent {
|
||||
|
||||
void inject(MessagesConversationFragment.SetReadStateTask object);
|
||||
|
||||
|
||||
void inject(DependencyHolder object);
|
||||
|
||||
void inject(TileImageLoader object);
|
||||
void inject(CacheDownloadLoader object);
|
||||
|
||||
void inject(TwidereSkiaImageRegionDecoder object);
|
||||
void inject(FullImageDownloadLoader object);
|
||||
|
||||
void inject(CacheProvider object);
|
||||
|
||||
void inject(TwidereSkiaImageDecoder object);
|
||||
}
|
||||
|
@ -48,12 +48,12 @@ import org.mariotaku.twidere.model.ParcelableAccount;
|
||||
import org.mariotaku.twidere.model.ParcelableCredentials;
|
||||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
import org.mariotaku.twidere.model.RequestType;
|
||||
import org.mariotaku.twidere.util.MediaPreviewUtils;
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
|
||||
import org.mariotaku.twidere.util.TwidereLinkify;
|
||||
import org.mariotaku.twidere.util.TwitterAPIFactory;
|
||||
import org.mariotaku.twidere.util.UserAgentUtils;
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
import org.mariotaku.twidere.util.media.preview.PreviewMediaExtractor;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
@ -108,7 +108,7 @@ public class TwidereImageDownloader extends BaseImageDownloader implements Const
|
||||
@Override
|
||||
protected InputStream getStreamFromNetwork(final String uriString, final Object extras) throws IOException {
|
||||
if (uriString == null) return null;
|
||||
final ParcelableMedia media = MediaPreviewUtils.getAllAvailableImage(uriString, extras instanceof FullImageExtra, mClient);
|
||||
final ParcelableMedia media = PreviewMediaExtractor.fromLink(uriString);
|
||||
try {
|
||||
final String mediaUrl = media != null ? media.media_url : uriString;
|
||||
if (isTwitterProfileImage(uriString)) {
|
||||
|
@ -1,116 +0,0 @@
|
||||
package org.mariotaku.twidere.util.imageloader;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.BitmapRegionDecoder;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.davemorrissey.labs.subscaleview.decoder.ImageDecoder;
|
||||
|
||||
import org.mariotaku.restfu.Utils;
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import cz.fhucho.android.util.SimpleDiskCache;
|
||||
import okio.ByteString;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link com.davemorrissey.labs.subscaleview.decoder.ImageDecoder}
|
||||
* using Android's {@link BitmapFactory}, based on the Skia library. This
|
||||
* works well in most circumstances and has reasonable performance, however it has some problems
|
||||
* with grayscale, indexed and CMYK images.
|
||||
*/
|
||||
public class TwidereSkiaImageDecoder implements ImageDecoder {
|
||||
|
||||
@Inject
|
||||
SimpleDiskCache mSimpleDiskCache;
|
||||
|
||||
private static final String FILE_PREFIX = "file://";
|
||||
private static final String CACHE_PREFIX = "cache:";
|
||||
private static final String ASSET_PREFIX = FILE_PREFIX + "/android_asset/";
|
||||
private static final String RESOURCE_PREFIX = ContentResolver.SCHEME_ANDROID_RESOURCE + "://";
|
||||
|
||||
public TwidereSkiaImageDecoder(Context context) {
|
||||
GeneralComponentHelper.build(context).inject(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Bitmap decode(Context context, Uri uri) throws Exception {
|
||||
String uriString = uri.toString();
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
Bitmap bitmap;
|
||||
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
|
||||
if (uriString.startsWith(RESOURCE_PREFIX)) {
|
||||
Resources res;
|
||||
String packageName = uri.getAuthority();
|
||||
if (context.getPackageName().equals(packageName)) {
|
||||
res = context.getResources();
|
||||
} else {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
res = pm.getResourcesForApplication(packageName);
|
||||
}
|
||||
|
||||
int id = 0;
|
||||
List<String> segments = uri.getPathSegments();
|
||||
int size = segments.size();
|
||||
if (size == 2 && segments.get(0).equals("drawable")) {
|
||||
String resName = segments.get(1);
|
||||
id = res.getIdentifier(resName, "drawable", packageName);
|
||||
} else if (size == 1 && TextUtils.isDigitsOnly(segments.get(0))) {
|
||||
try {
|
||||
id = Integer.parseInt(segments.get(0));
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
bitmap = BitmapFactory.decodeResource(context.getResources(), id, options);
|
||||
} else if (uriString.startsWith(ASSET_PREFIX)) {
|
||||
String assetName = uriString.substring(ASSET_PREFIX.length());
|
||||
bitmap = BitmapFactory.decodeStream(context.getAssets().open(assetName), null, options);
|
||||
} else if (uriString.startsWith(FILE_PREFIX)) {
|
||||
bitmap = BitmapFactory.decodeFile(uriString.substring(FILE_PREFIX.length()), options);
|
||||
} else if (uriString.startsWith(CACHE_PREFIX)) {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
final SimpleDiskCache.InputStreamEntry entry = mSimpleDiskCache.getInputStream(ByteString.decodeBase64(uri.getSchemeSpecificPart()).utf8());
|
||||
if (entry != null) {
|
||||
inputStream = entry.getInputStream();
|
||||
bitmap = BitmapFactory.decodeStream(inputStream, null, options);
|
||||
} else {
|
||||
throw new FileNotFoundException(uriString);
|
||||
}
|
||||
} finally {
|
||||
Utils.closeSilently(inputStream);
|
||||
}
|
||||
} else {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
inputStream = contentResolver.openInputStream(uri);
|
||||
bitmap = BitmapFactory.decodeStream(inputStream, null, options);
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bitmap == null) {
|
||||
throw new RuntimeException("Skia image region decoder returned null bitmap - image format may not be supported");
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
package org.mariotaku.twidere.util.imageloader;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.BitmapRegionDecoder;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder;
|
||||
|
||||
import org.mariotaku.restfu.Utils;
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import cz.fhucho.android.util.SimpleDiskCache;
|
||||
import okio.ByteString;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder}
|
||||
* using Android's {@link android.graphics.BitmapRegionDecoder}, based on the Skia library. This
|
||||
* works well in most circumstances and has reasonable performance due to the cached decoder instance,
|
||||
* however it has some problems with grayscale, indexed and CMYK images.
|
||||
*/
|
||||
public class TwidereSkiaImageRegionDecoder implements ImageRegionDecoder {
|
||||
|
||||
@Inject
|
||||
SimpleDiskCache mSimpleDiskCache;
|
||||
|
||||
private BitmapRegionDecoder decoder;
|
||||
private final Object decoderLock = new Object();
|
||||
|
||||
private static final String FILE_PREFIX = "file://";
|
||||
private static final String CACHE_PREFIX = "cache:";
|
||||
private static final String ASSET_PREFIX = FILE_PREFIX + "/android_asset/";
|
||||
private static final String RESOURCE_PREFIX = ContentResolver.SCHEME_ANDROID_RESOURCE + "://";
|
||||
|
||||
public TwidereSkiaImageRegionDecoder(Context context) {
|
||||
GeneralComponentHelper.build(context).inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point init(Context context, Uri uri) throws Exception {
|
||||
String uriString = uri.toString();
|
||||
if (uriString.startsWith(RESOURCE_PREFIX)) {
|
||||
Resources res;
|
||||
String packageName = uri.getAuthority();
|
||||
if (context.getPackageName().equals(packageName)) {
|
||||
res = context.getResources();
|
||||
} else {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
res = pm.getResourcesForApplication(packageName);
|
||||
}
|
||||
|
||||
int id = 0;
|
||||
List<String> segments = uri.getPathSegments();
|
||||
int size = segments.size();
|
||||
if (size == 2 && segments.get(0).equals("drawable")) {
|
||||
String resName = segments.get(1);
|
||||
id = res.getIdentifier(resName, "drawable", packageName);
|
||||
} else if (size == 1 && TextUtils.isDigitsOnly(segments.get(0))) {
|
||||
try {
|
||||
id = Integer.parseInt(segments.get(0));
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
decoder = BitmapRegionDecoder.newInstance(context.getResources().openRawResource(id), false);
|
||||
} else if (uriString.startsWith(ASSET_PREFIX)) {
|
||||
String assetName = uriString.substring(ASSET_PREFIX.length());
|
||||
decoder = BitmapRegionDecoder.newInstance(context.getAssets().open(assetName, AssetManager.ACCESS_RANDOM), false);
|
||||
} else if (uriString.startsWith(FILE_PREFIX)) {
|
||||
decoder = BitmapRegionDecoder.newInstance(uriString.substring(FILE_PREFIX.length()), false);
|
||||
} else if (uriString.startsWith(CACHE_PREFIX)) {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
final SimpleDiskCache.InputStreamEntry entry = mSimpleDiskCache.getInputStream(ByteString.decodeBase64(uri.getSchemeSpecificPart()).utf8());
|
||||
if (entry != null) {
|
||||
inputStream = entry.getInputStream();
|
||||
decoder = BitmapRegionDecoder.newInstance(inputStream, false);
|
||||
} else {
|
||||
throw new FileNotFoundException(uriString);
|
||||
}
|
||||
} finally {
|
||||
Utils.closeSilently(inputStream);
|
||||
}
|
||||
} else {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
inputStream = contentResolver.openInputStream(uri);
|
||||
decoder = BitmapRegionDecoder.newInstance(inputStream, false);
|
||||
} finally {
|
||||
Utils.closeSilently(inputStream);
|
||||
}
|
||||
}
|
||||
return new Point(decoder.getWidth(), decoder.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap decodeRegion(Rect sRect, int sampleSize) {
|
||||
synchronized (decoderLock) {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inSampleSize = sampleSize;
|
||||
options.inPreferredConfig = Config.RGB_565;
|
||||
Bitmap bitmap = decoder.decodeRegion(sRect, options);
|
||||
if (bitmap == null) {
|
||||
throw new RuntimeException("Skia image decoder returned null bitmap - image format may not be supported");
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return decoder != null && !decoder.isRecycled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
decoder.recycle();
|
||||
}
|
||||
}
|
@ -31,9 +31,13 @@ public class URLFileNameGenerator implements FileNameGenerator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generate(final String imageUri) {
|
||||
public String generate(String imageUri) {
|
||||
if (imageUri == null) return null;
|
||||
return mGenerator.generate(imageUri.replaceFirst("https?://", ""));
|
||||
int start = imageUri.indexOf("://");
|
||||
if (start == -1) {
|
||||
return mGenerator.generate(imageUri);
|
||||
}
|
||||
return mGenerator.generate(imageUri.substring(start + 3));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,8 +18,9 @@
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<FrameLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@ -34,7 +35,7 @@
|
||||
android:id="@+id/video_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true" />
|
||||
android:layout_centerInParent="true"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/video_view_overlay"
|
||||
@ -64,7 +65,7 @@
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/play"
|
||||
android:minWidth="@dimen/element_size_normal"
|
||||
android:src="@drawable/ic_action_play_arrow" />
|
||||
android:src="@drawable/ic_action_play_arrow"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/position_label"
|
||||
@ -76,7 +77,7 @@
|
||||
android:gravity="center"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="@dimen/text_size_extra_small"
|
||||
tools:text="--:--" />
|
||||
tools:text="--:--"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/video_view_progress"
|
||||
@ -88,7 +89,7 @@
|
||||
android:layout_toLeftOf="@+id/volume_button"
|
||||
android:layout_toRightOf="@+id/play_pause_button"
|
||||
android:layout_toStartOf="@+id/volume_button"
|
||||
android:indeterminate="false" />
|
||||
android:indeterminate="false"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/duration_label"
|
||||
@ -100,7 +101,7 @@
|
||||
android:gravity="center"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="@dimen/text_size_extra_small"
|
||||
tools:text="--:--" />
|
||||
tools:text="--:--"/>
|
||||
|
||||
|
||||
<ImageButton
|
||||
@ -113,7 +114,7 @@
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/mute"
|
||||
android:minWidth="@dimen/element_size_normal"
|
||||
android:src="@drawable/ic_action_speaker_max" />
|
||||
android:src="@drawable/ic_action_speaker_max"/>
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
|
Loading…
x
Reference in New Issue
Block a user