redesigning cache and file download

This commit is contained in:
Mariotaku Lee 2016-01-01 18:57:18 +08:00
parent 5a263a3433
commit c575303074
25 changed files with 463 additions and 1199 deletions

View File

@ -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";

View File

@ -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);

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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'

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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: {

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
// }
}
}
}

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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)) {

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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));
}
}

View File

@ -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>