完善阅读下载策略

This commit is contained in:
seven332 committed 2014-08-19 16:24:36 +08:00
1 parent 374ed3a515
commit a2bbe855e8
38 files changed
+1297 -2142

No files matched your search

+1
View File
@@ -26,6 +26,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:name=".AppContext"
+2 -2
View File
@@ -28,7 +28,7 @@ import android.app.Application;
import com.hippo.ehviewer.cache.ImageCache;
import com.hippo.ehviewer.data.Data;
import com.hippo.ehviewer.ehclient.EhClient;
import com.hippo.ehviewer.ehclient.ExDownloadManager;
import com.hippo.ehviewer.ehclient.ExDownloaderManager;
import com.hippo.ehviewer.util.Config;
import com.hippo.ehviewer.util.Crash;
import com.hippo.ehviewer.util.Download;
@@ -71,7 +71,7 @@ public class AppContext extends Application implements UncaughtExceptionHandler
Download.init(this);
Favorite.init(this);
Data.createInstance(this);
ExDownloadManager.createInstance();
ExDownloaderManager.createInstance();
MaterialToast.setContext(this);
// Do catch error prepare
@@ -1,43 +0,0 @@
/*
* Copyright (C) 2014 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer;
import com.hippo.ehviewer.data.GalleryInfo;
public class GalleryDownloadInfo {
public static final int NONE = 0x0;
public static final int DOWNLOADING = 0x1;
public static final int FINISHED = 0x2;
public GalleryInfo galleryInfo;
// For ui set progressbar
public int picNum;
public int downloadNum;
public int state;
public GalleryDownloadInfo(GalleryInfo galleryInfo) {
this(galleryInfo, NONE);
}
public GalleryDownloadInfo(GalleryInfo galleryInfo, int state) {
this.galleryInfo = galleryInfo;
this.state = state == DOWNLOADING ? NONE : FINISHED;
}
}
@@ -1,311 +0,0 @@
/*
* Copyright (C) 2014 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer;
import java.io.File;
import java.util.LinkedList;
import java.util.Queue;
import android.content.Context;
import com.hippo.ehviewer.data.GalleryInfo;
import com.hippo.ehviewer.data.PreviewList;
import com.hippo.ehviewer.ehclient.DetailParser;
import com.hippo.ehviewer.ehclient.EhClient;
import com.hippo.ehviewer.network.HttpHelper;
import com.hippo.ehviewer.util.Config;
import com.hippo.ehviewer.util.Utils;
/**
* Download a gallery
*
* @author Hippo
*
*/
// TODO For gallerys whose Resized is Originals,
// we can get download info from hathdl
public class GallerysDownloader implements Runnable {
private static final String TAG = GallerysDownloader.class.getSimpleName();
private static final int MAX_RETRY = 3;
private static final int OK = 0x0;
private static final int FAIL = 0x1;
private static final int STOP = 0x2;
private static final int MAX_DOWNLOAD_THREAD_NUM = 3;
private final Context mContext;
private final GalleryInfo mGalleryInfo;
private int mPageIndex;
private int mPageSum;
private int mPagePerDp;
private PreviewList[] mPages;
private final Thread mMainThread;
private final Queue<Integer> mRequstDetailPage;
private final Queue<Integer> mRequstPage;
private int downloadThreadNum = 0;
private final File mDownloadDir;
public GallerysDownloader(Context context, GalleryInfo galleryInfo) {
this(context, galleryInfo, 0);
}
public GallerysDownloader(Context context, GalleryInfo galleryInfo, int startIndex) {
mContext = context;
mGalleryInfo = galleryInfo;
mPageIndex = startIndex;
mDownloadDir = new File(Config.getDownloadPath(),
Utils.standardizeFilename(mGalleryInfo.gid + '-' + mGalleryInfo.title));
mDownloadDir.mkdirs();
// TODO What if these is a file with the same name
mMainThread = new Thread(this);
mRequstDetailPage = new LinkedList<Integer>();
mRequstPage = new LinkedList<Integer>();
}
public void start() {
mMainThread.start();
}
private int getDetailPageForPageIndex(int pageIndex) {
return pageIndex / mPagePerDp;
}
private boolean parserDetailPage(HttpHelper hh, DetailParser dp, int page, int mode) {
int times = 0;
while(times++ < MAX_RETRY) {
hh.reset();
dp.reset();
// For get more page url one time
hh.setPreviewMode(Config.PREVIEW_MODE_NORMAL);
String body = hh.get(EhClient.getDetailUrl(mGalleryInfo.gid, mGalleryInfo.token, page, Config.getMode()));
if (body == null)
continue;
if (dp.parser(body, mode) != mode)
continue;
return true;
}
return false;
}
/**
* Main thread body
*/
@Override
public void run() {
int detailPageMax = 0;
HttpHelper hh = new HttpHelper(mContext);
DetailParser dp = new DetailParser();
// Get detail info, pages, previewsum, previewPerPage
if (parserDetailPage(hh, dp, 0, DetailParser.DETAIL &
DetailParser.PREVIEW_INFO & DetailParser.PREVIEW)) {
// Get Ok
mPageSum = dp.pages;
mPagePerDp = dp.previewPerPage;
mPages = new PreviewList[dp.previewSum];
mPages[0] = dp.previewList;
detailPageMax = dp.pages / dp.previewPerPage;
} else {
// TODO Get error, do something
}
int startPageIndex = mPageIndex;
// Start get
for (downloadThreadNum = 0; downloadThreadNum < MAX_DOWNLOAD_THREAD_NUM; downloadThreadNum++) {
new DownloadThread().start();
}
// Start get other page from startindex
int detailPage = getDetailPageForPageIndex(startPageIndex);
// Already get page 0
if (detailPage == 0)
detailPage++;
for (; detailPage <= detailPageMax; detailPage++) {
if (parserDetailPage(hh, dp, detailPage, DetailParser.PREVIEW)) {
mPages[detailPage] = dp.previewList;
} else {
// TODO Get error, do something
}
}
}
class DownloadThread extends Thread {
private final HttpHelper mHttpHelper;
public DownloadThread() {
mHttpHelper = new HttpHelper(mContext);
}
private int getTargetPageIndex() {
synchronized(mRequstPage) {
if (mRequstPage.isEmpty()) {
return mPageIndex++;
} else {
return mRequstPage.poll();
}
}
}
/*
private String getImageUrlForPage() {
int times = 0;
while(times++ < MAX_RETRY) {
}
}
public int downloadImage(String pageUrl, int index) {
String body = mHttpHelper.get(pageUrl);
}
*/
@Override
public void run() {
downloadThreadNum--;
}
}
/*
private AppContext mAppContext;
private Data mData;
private List<DownloadInfo> mDownloadList;
private String[] mPageTokes;
private DownloadInfo curDownloadInfo;
private OnGalleryDownloadListener mListener;
private static DownloadInfo emptyDownloadInfo = new DownloadInfo(null);
public GallerysDownloader(AppContext appContext) {
mAppContext = appContext;
mPageTokes = null;
mData = mAppContext.getData();
mDownloadList = mData.getAllDownloads();
}
public void setOnGalleryDownloadListener(OnGalleryDownloadListener l) {
mListener = l;
}
public synchronized void add(GalleryInfo galleryInfo) {
DownloadInfo downloadInfo = new DownloadInfo(galleryInfo);
mData.addDownload(downloadInfo);
downloadInfo.setDownloadState(DownloadInfo.WAITING);
checkDownloadThread();
}
public synchronized boolean start(int index) {
DownloadInfo downloadInfo = mDownloadList.get(index);
if (downloadInfo.getDownloadState() == DownloadInfo.NONE
&& downloadInfo.getState() != DownloadInfo.COMPLETED) {
downloadInfo.setDownloadState(DownloadInfo.WAITING);
checkDownloadThread();
return true;
} else {
return false;
}
}
private synchronized void checkDownloadThread() {
if (curDownloadInfo == null) {
curDownloadInfo = emptyDownloadInfo;
new Thread(new DownloadGallery()).start();
}
}
private synchronized DownloadInfo getFirstWaiting() {
for (DownloadInfo downloadInfo : mDownloadList) {
if (downloadInfo.getDownloadState() == DownloadInfo.WAITING)
return downloadInfo;
}
return null;
}
private String getUrlContext(HttpHelper hp, String url) {
int times = 3;
String pageContent;
for (int i = 0; i < times; i++) {
pageContent = hp.get(url);
if (pageContent != null)
return pageContent;
}
return null;
}
private class DownloadGallery implements Runnable {
@Override
public void run() {
if ((curDownloadInfo = getFirstWaiting()) != null) {
curDownloadInfo.setDownloadState(DownloadInfo.DOWNLOADING);
if (mListener != null)
mListener.onGalleryDownloadStart(curDownloadInfo);
// get
GalleryInfo galleryInfo = curDownloadInfo.getGalleryInfo();
HttpHelper hp = new HttpHelper(mAppContext);
String url = EhClient.getDetailUrl(galleryInfo.gid, galleryInfo.token,
curDownloadInfo.getMode());
String pageContent = getUrlContext(hp, url);
if (pageContent == null) {
if (mListener != null)
mListener.onGalleryDownloadFailed(curDownloadInfo, hp.getEMsg());
// New turn
new Thread(new DownloadGallery()).start();
} else {
}
}
}
}
interface OnGalleryDownloadListener {
void onGalleryDownloadStart(DownloadInfo downloadInfo);
void onGalleryDownloadStop(DownloadInfo downloadInfo);
void onGalleryDownloadFailed(DownloadInfo downloadInfo, String eMsg);
void onGalleryDownloadCompleted(DownloadInfo downloadInfo);
}
*/
}
+1
View File
@@ -138,6 +138,7 @@ public class ImageLoader {
loadTask = mDownloadTasks.pop();
}
// TODO use proxy to get image
loadTask.bitmap = httpHelper.getImage(loadTask.url);
mImageCache.addBitmapToCache(loadTask.key, loadTask.bitmap);
@@ -76,19 +76,17 @@ public class LargePreviewList extends PreviewList {
View view = inflater.inflate(R.layout.preview_item, null);
((TextView)view.findViewById(R.id.text)).setText(String.valueOf(index + 1));
final int finalIndex = index;
final String url = item.mPageUrl;
final int _index = index;
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Add to read in Data
Intent intent = new Intent(mActivity,
GalleryActivity.class);
intent.putExtra("url", url);
intent.putExtra("gid", mPi.getGid());
intent.putExtra("title", mPi.getTitle());
intent.putExtra("firstPage", finalIndex);
//intent.putExtra("pageSum", mPi.pages);
intent.putExtra(GalleryActivity.KEY_GID, mPi.getGid());
intent.putExtra(GalleryActivity.KEY_TOKEN, mPi.getToken());
intent.putExtra(GalleryActivity.KEY_TITLE, mPi.getTitle());
intent.putExtra(GalleryActivity.KEY_START_INDEX, _index);
mActivity.startActivity(intent);
}
});
@@ -118,19 +118,17 @@ public class NormalPreviewList extends PreviewList{
((TextView)view.findViewById(R.id.text)).setText(String.valueOf(index + 1));
final int finalIndex = index;
final String url = item.url;
final int _index = index;
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Add to read in Data
Intent intent = new Intent(mActivity,
GalleryActivity.class);
intent.putExtra("url", url);
intent.putExtra("gid", mPi.getGid());
intent.putExtra("title", mPi.getTitle());
intent.putExtra("firstPage", finalIndex);
//intent.putExtra("pageSum", mGi.pages);
intent.putExtra(GalleryActivity.KEY_GID, mPi.getGid());
intent.putExtra(GalleryActivity.KEY_TOKEN, mPi.getToken());
intent.putExtra(GalleryActivity.KEY_TITLE, mPi.getTitle());
intent.putExtra(GalleryActivity.KEY_START_INDEX, _index);
mActivity.startActivity(intent);
}
});
@@ -17,7 +17,6 @@
package com.hippo.ehviewer.ehclient;
import java.io.File;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -45,16 +44,10 @@ import com.hippo.ehviewer.data.GalleryPopular;
import com.hippo.ehviewer.data.ListUrls;
import com.hippo.ehviewer.data.LofiGalleryDetail;
import com.hippo.ehviewer.data.PreviewList;
import com.hippo.ehviewer.gallery.data.ImageSet;
import com.hippo.ehviewer.network.Downloader;
import com.hippo.ehviewer.network.HttpHelper;
import com.hippo.ehviewer.service.DownloadService;
import com.hippo.ehviewer.ui.DownloadInfo;
import com.hippo.ehviewer.util.Config;
import com.hippo.ehviewer.util.Download;
import com.hippo.ehviewer.util.Future;
import com.hippo.ehviewer.util.FutureListener;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.util.ThreadPool;
import com.hippo.ehviewer.util.ThreadPool.Job;
import com.hippo.ehviewer.util.ThreadPool.JobContext;
@@ -982,341 +975,6 @@ public class EhClient {
});
}
public interface OnDownloadMangaListener {
public void onDownloadMangaStart(String id);
public void onDownloadMangaStart(String id, int pageSum, int startIndex);
public void onDownloadMangaStop(String id);
public void onDownloadMangaOver(String id, boolean ok);
public void onDownloadPage(String id, int pageSum, int index);
public void onDownloadPageProgress(String id, int pageSum,
int index, float totalSize, float downloadSize);
public void onDownloadMangaAllStart();
public void onDownloadMangaAllOver();
}
// TODO 下载前检查文件是否已存在
/****** DownloadMangaManager ******/
public class DownloadMangaManager {
private DownloadInfo curDownloadInfo = null;
private Downloader.Controlor curControlor = null;
private final ArrayList<DownloadInfo> mDownloadQueue = new ArrayList<DownloadInfo>();
private final Object taskLock = new Object();
private OnDownloadMangaListener listener;
private DownloadService mService;
private final boolean mStop = false;
public void init() {
}
public void setOnDownloadMangaListener(OnDownloadMangaListener listener) {
this.listener = listener;
}
public void setDownloadService(DownloadService service) {
this.mService = service;
}
/**
* Add a download task to task queue
*
* @param detailUrlStr
* @param foldName
* @return
*/
public void add(DownloadInfo di) {
synchronized (taskLock) {
mDownloadQueue.add(di);
if (curDownloadInfo == null && mDownloadQueue.size() != 0)
start();
}
}
public void cancel(String id) {
synchronized (taskLock) {
DownloadInfo di = Download.get(id);
if (di != null) {
di.status = DownloadInfo.STOP;
mDownloadQueue.remove(di);
if (!id.equals(getCurDownloadId())) {
if (curControlor != null)
curControlor.stop();
mService.notifyUpdate();
Download.notify(id);
}
}
}
}
public String getCurDownloadId() {
synchronized (taskLock) {
if (curDownloadInfo == null)
return null;
else
return curDownloadInfo.gid;
}
}
public boolean isWaiting(String str) {
synchronized (taskLock) {
for (DownloadInfo di : mDownloadQueue) {
if (di.gid.equals(str))
return true;
}
}
return false;
}
private void start(){
if(curDownloadInfo != null){
return;
}
new Thread(new Runnable(){
@Override
public void run() {
listener.onDownloadMangaAllStart();
Parser parser = new Parser();
Downloader imageDownloader = new Downloader(mAppContext);
imageDownloader.setOnDownloadListener(new Downloader.OnDownloadListener() {
@Override
public void onDownloadStartConnect() {
}
@Override
public void onDownloadStartDownload(int totalSize) {
if (curDownloadInfo != null) {
curDownloadInfo.downloadSize = 0;
curDownloadInfo.totalSize = totalSize/1024.0f;
mService.notifyUpdate(curDownloadInfo.gid, curDownloadInfo.lastStartIndex, ImageSet.STATE_LOADING);
}
}
@Override
public void onDownloadStatusUpdate(int downloadSize,
int totalSize) {
curDownloadInfo.downloadSize = downloadSize/1024.0f;
curDownloadInfo.totalSize = totalSize/1024.0f;
mService.notifyUpdate();
}
@Override
public void onDownloadOver(int status, String eMsg) {
if (status == Downloader.COMPLETED)
mService.notifyUpdate(curDownloadInfo.gid, curDownloadInfo.lastStartIndex, ImageSet.STATE_LOADED);
else
mService.notifyUpdate(curDownloadInfo.gid, curDownloadInfo.lastStartIndex, ImageSet.STATE_FAIL);
}
});
while(mDownloadQueue.size() > 0){
synchronized (taskLock) {
curDownloadInfo = mDownloadQueue.get(0);
curDownloadInfo.status = DownloadInfo.DOWNLOADING;
mDownloadQueue.remove(0);
}
Download.notify(String.valueOf(curDownloadInfo.gid));
if (curDownloadInfo.type == DownloadInfo.DETAIL_URL) {
listener.onDownloadMangaStart(curDownloadInfo.gid);
mService.notifyUpdate();
if (parser.getFirstPagePageSumForDetail(curDownloadInfo.detailUrlStr)) { // Get page info
curDownloadInfo.type = DownloadInfo.PAGE_URL;
curDownloadInfo.lastStartIndex = 0;
curDownloadInfo.pageSum = parser.getPageSum();
curDownloadInfo.pageUrlStr = parser.getFirstPage();
Download.notify(curDownloadInfo.gid);
} else { // If get info error
listener.onDownloadMangaOver(curDownloadInfo.gid, false);
curDownloadInfo.status = DownloadInfo.FAILED;
mService.notifyUpdate();
Download.notify(String.valueOf(curDownloadInfo.gid));
continue;
}
}
//Create folder
File folder = new File(Config.getDownloadPath() + File.separatorChar + Utils.standardizeFilename(curDownloadInfo.title)); // TODO For title contain invailed char
if (!folder.mkdirs() && !folder.isDirectory()) {
listener.onDownloadMangaOver(curDownloadInfo.gid, false);
curDownloadInfo.status = DownloadInfo.FAILED;
mService.notifyUpdate();
Download.notify(String.valueOf(curDownloadInfo.gid));
continue;
}
String nextPage = curDownloadInfo.pageUrlStr;
curDownloadInfo.pageUrlStr = null;
String imageUrlStr = null;
boolean mComplete = true;
boolean mStop = false;
// Get page
while (!nextPage.equals(curDownloadInfo.pageUrlStr)) {
curDownloadInfo.pageUrlStr = nextPage;
Download.notify(String.valueOf(curDownloadInfo.gid));
listener.onDownloadPage(curDownloadInfo.gid, curDownloadInfo.pageSum, curDownloadInfo.lastStartIndex);
mService.notifyUpdate();
if (parser.getPageInfoSumForPage(curDownloadInfo.pageUrlStr)) {
nextPage = parser.getNextPage();
imageUrlStr = parser.getImageUrlStr();
// Check stop
if (curDownloadInfo.status == DownloadInfo.STOP) {
listener.onDownloadMangaStop(curDownloadInfo.gid);
mService.notifyUpdate();
Download.notify(String.valueOf(curDownloadInfo.gid));
mComplete = false;
mStop = true;
break;
}
try {
// TODO
Log.d(TAG, folder.getPath());
curControlor = imageDownloader.resetData(folder.getPath(),
String.format("%05d", curDownloadInfo.lastStartIndex + 1) + "." + Utils.getExtension(imageUrlStr),
imageUrlStr);
imageDownloader.run();
curDownloadInfo.downloadSize = 0;
curDownloadInfo.totalSize = 0;
int downloadStatus = imageDownloader.getStatus();
if (downloadStatus == Downloader.STOPED) { // If stop by user
listener.onDownloadMangaStop(curDownloadInfo.gid);
mService.notifyUpdate();
Download.notify(String.valueOf(curDownloadInfo.gid));
mComplete = false;
mStop = true;
break;
} else if (downloadStatus != Downloader.COMPLETED) { // If get image error
Log.e(TAG, "Download image error, downloadStatus is " + downloadStatus);
mComplete = false;
break;
}
} catch (MalformedURLException e) {
e.printStackTrace();
mComplete = false;
break;
}
} else {
mComplete = false;
break;
}
curDownloadInfo.lastStartIndex++;
}
if (mComplete) {
listener.onDownloadMangaOver(curDownloadInfo.gid, true);
curDownloadInfo.status = DownloadInfo.COMPLETED;
mService.notifyUpdate();
Download.notify(String.valueOf(curDownloadInfo.gid));
} else if (!mStop) {
listener.onDownloadMangaOver(curDownloadInfo.gid, false);
curDownloadInfo.status = DownloadInfo.FAILED;
mService.notifyUpdate();
Download.notify(String.valueOf(curDownloadInfo.gid));
}
}
synchronized (taskLock) {
curDownloadInfo = null;
listener.onDownloadMangaAllOver();
}
}
}).start();
}
public boolean isEmpty() {
synchronized (taskLock) {
return curDownloadInfo == null;
}
}
}
private class Parser {
private int errorMegId;
private int pageSum;
private String firstPage;
private String prePage;
private String nextPage;
private String imageUrlStr;
public int getPageSum() {
return pageSum;
}
public String getFirstPage() {
return firstPage;
}
public String getPrePage() {
return prePage;
}
public String getNextPage() {
return nextPage;
}
public String getImageUrlStr() {
return imageUrlStr;
}
public boolean getFirstPagePageSumForDetail(String detailUrlStr) {
HttpHelper hp = new HttpHelper(mAppContext);
String pageContent = hp.get(detailUrlStr);
if (pageContent == null)
return false;
Pattern p = Pattern.compile("<p class=\"ip\">Showing [\\d|,]+ - [\\d|,]+ of ([\\d|,]+) images</p>.+<div id=\"gdt\"><div[^<>]*>(?:<div[^<>]*>)?<a[^<>]*href=\"([^<>\"]+)\"[^<>]*>");
Matcher m = p.matcher(pageContent);
if (m.find()) {
pageSum = Integer.parseInt(m.group(1).replace(",", ""));
firstPage = m.group(2);
return true;
}
return false;
}
/**
* Get page info, previous page, next page, image url
*
* @param pageUrlStr
* @return True if get
*/
public boolean getPageInfoSumForPage(String pageUrlStr) {
HttpHelper hp = new HttpHelper(mAppContext);
String pageContent = hp.get(pageUrlStr);
if (pageContent == null)
return false;
Pattern p = Pattern.compile("<a[^<>]*id=\"prev\"[^<>]*href=\"([^<>\"]+)\"><img[^<>]*/></a>.+<a[^<>]id=\"next\"[^<>]*href=\"([^<>\"]+)\"><img[^<>]*/></a>.+<img[^<>]*src=\"([^<>\"]+?)\"[^<>]*style=\"[^<>\"]*\"[^<>]*/>");
Matcher m = p.matcher(pageContent);
if (m.find()) {
prePage = m.group(1);
nextPage = m.group(2);
imageUrlStr = Utils.unescapeXml(m.group(3));
return true;
}
return false;
}
}
// Post comment
public interface OnCommentListener {
void onSuccess(List<Comment> comments);
+240 -93
View File
@@ -23,8 +23,10 @@ import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import android.content.Context;
@@ -33,7 +35,7 @@ import android.os.Process;
import com.hippo.ehviewer.AppContext;
import com.hippo.ehviewer.network.HttpHelper;
import com.hippo.ehviewer.util.AutoExpandArray;
import com.hippo.ehviewer.util.Config;
import com.hippo.ehviewer.util.EhUtils;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.util.Utils;
@@ -44,55 +46,74 @@ public class ExDownloader implements Runnable {
private static final int WORKER_NUM = 3;
private final Context mContext;
private final ExDownloadManager mManager;
private final ExDownloaderManager mManager;
private final int mGid;
private final String mToken;
private final String mTitle;
private final int mMode;
private int mPreviewPageNum = -1;
private int mPreviewPerPage = -1;
private int mImageNum = -1;
private volatile int mPreviewPageNum = -1;
private volatile int mPreviewPerPage = -1;
private volatile int mImageNum = -1;
private final AutoExpandArray<String> mPageTokeArray = new AutoExpandArray<String>();
private final AutoExpandArray<String> mImageFilenameArray = new AutoExpandArray<String>();
private volatile int mOwnerNum = 0;
private Thread mMainThread = null;
/** Only work for EhClient.MODE_LOFI **/
/** Only work for EhClient.MODE_LOFI when do not know image number **/
private int mCurMaxPreviewPage = -1;
private volatile int mStartIndex = 0;
private final Queue<Integer> mRequestIndexArray = new ConcurrentLinkedQueue<Integer>();
private final Queue<Integer> mNoTokenIndexArray = new ConcurrentLinkedQueue<Integer>();
private final Queue<Integer> mRequestPageIndexArray = new ConcurrentLinkedQueue<Integer>();
/** The set contains the requsting index **/
private final Set<Integer> mRequstingIndexSet = new HashSet<Integer>();
private volatile int mCurRequestPageIndex = -1;
private final Worker[] mWorkerArray = new Worker[WORKER_NUM];
private final HttpHelper.DownloadControlor[] mControlorArray = new HttpHelper.DownloadControlor[WORKER_NUM];
private final Object mPageTokenLock = new Object();
private final Object mWorkerLock = new Object();
private final Object mWorkerGetIndexLock = new Object();
private volatile boolean mPauseWork = false;
private volatile boolean mStopWork = false;
private final File mDir;
private ListenerForImageSet mLfis;
private static final int IO_BUFFER_SIZE = 8 * 1024;
public interface ListenerForImageSet {
public void onDownloadStart(int index);
public void onDownloading(int index, float percent);
public void onDownloadComplete(int index);
public void onDownloadFail(int index);
}
public interface ListenerForDownload {
public void onDownloading(int index);
}
ExDownloader(int gid, String token, String title, int mode) {
mContext = AppContext.getInstance();
mManager = ExDownloadManager.getInstance();
mManager = ExDownloaderManager.getInstance();
mGid = gid;
mToken = token;
mTitle = title;
mMode = mode;
// Make sure dir
mDir = new File(Config.getDownloadPath(),
Utils.standardizeFilename(mGid + "-" + mTitle));
if (!mDir.exists()) {
mDir.mkdirs();
} else if (mDir.isFile()) {
mDir.delete();
mDir.mkdirs();
}
mDir = EhUtils.getGalleryDir(mGid, mTitle);
Utils.ensureDir(mDir, true);
}
public void setListenerForImageSet(ListenerForImageSet l) {
mLfis = l;
}
void occupy() {
@@ -107,6 +128,39 @@ public class ExDownloader implements Runnable {
return mOwnerNum == 0;
}
void stop() {
// TODO stop all thread, release all resources
mStopWork = true;
// Stop download
for (HttpHelper.DownloadControlor c : mControlorArray) {
if (c != null)
c.stop();
}
// Wake all thread
synchronized(mPageTokenLock) {mPageTokenLock.notifyAll();}
synchronized(mWorkerGetIndexLock) {mWorkerGetIndexLock.notifyAll();}
synchronized(mWorkerLock) {mWorkerLock.notifyAll();}
}
void pause(boolean pause) {
mPauseWork = pause;
if (mPauseWork) {
// Stop download
for (HttpHelper.DownloadControlor c : mControlorArray)
if (c != null) c.stop();
// Wake all thread
synchronized(mPageTokenLock) {mPageTokenLock.notifyAll();}
synchronized(mWorkerGetIndexLock) {mWorkerGetIndexLock.notifyAll();}
synchronized(mWorkerLock) {mWorkerLock.notifyAll();}
} else {
// Reset download
for (HttpHelper.DownloadControlor c : mControlorArray)
if (c != null) c.reset();
ensureStart();
ensureWorkers();
}
}
public int getGid() {
return mGid;
}
@@ -116,7 +170,7 @@ public class ExDownloader implements Runnable {
}
public void setStartIndex(int startIndex) {
if (mImageNum != -1 && mStartIndex >= mImageNum)
if (mImageNum != -1 && startIndex >= mImageNum)
mStartIndex = 0;
else
mStartIndex = startIndex;
@@ -125,14 +179,49 @@ public class ExDownloader implements Runnable {
ensureWorkers();
}
public void setTargetIndex(int startIndex) {
if (mImageNum != -1 && mStartIndex >= mImageNum) {
mRequestIndexArray.offer(startIndex);
ensureStart();
ensureWorkers();
public void setTargetIndex(int index) {
if (mImageNum != -1 && index >= mImageNum)
return;
mRequestIndexArray.offer(index);
ensureStart();
ensureWorkers();
}
public int getMaxEnsureIndex() {
if (mImageNum != -1) {
return mImageNum;
} else {
return Math.max(Math.max(mStartIndex - 1,
Math.max(mCurMaxPreviewPage, mPreviewPageNum - 1) * mPreviewPerPage),
mPageTokeArray.maxValidIndex());
}
}
/**
* If do not know image num, return -1.
* You should call getMaxEnsureIndex().
*
* @return
*/
public int getImageNum() {
return mImageNum;
}
public AutoExpandArray<String> getImageFilenameArray() {
return mImageFilenameArray;
}
/**
* If targer index image is downloading, return true
*
* @param index
* @return
*/
public boolean isDownloading(int index) {
return mRequstingIndexSet.contains(index);
}
private synchronized void ensureStart() {
if (mMainThread != null)
return;
@@ -196,10 +285,14 @@ public class ExDownloader implements Runnable {
}
mImageNum = Integer.parseInt(Utils.readAsciiLine(is));
if (mImageNum != -1)
if (mImageNum != -1) {
mPageTokeArray.setCapacity(mImageNum);
else if (mPreviewPageNum != -1 && mPreviewPerPage != -1)
mImageFilenameArray.setCapacity(mImageNum);
}
else if (mPreviewPageNum != -1 && mPreviewPerPage != -1) {
mPageTokeArray.setCapacity(mPreviewPageNum * mPreviewPerPage);
mImageFilenameArray.setCapacity(mPreviewPageNum * mPreviewPerPage);
}
// read page token info
try {
@@ -288,6 +381,7 @@ public class ExDownloader implements Runnable {
mPreviewPageNum = edp.previewPageNum;
mImageNum = edp.imageNum;
mPageTokeArray.setCapacity(mPreviewPageNum * mPreviewPerPage);
mImageFilenameArray.setCapacity(mPreviewPageNum * mPreviewPerPage);
}
}
for (int i = 0; i < pageTokenArray.size(); i++)
@@ -301,27 +395,21 @@ public class ExDownloader implements Runnable {
}
private void ensureWorkers() {
if (mPreviewPerPage == -1)
return;
synchronized(mWorkerArray) {
for (int i = 0; i < mWorkerArray.length; i++) {
if (mWorkerArray[i] == null) {
Worker worker = new Worker();
Worker worker = new Worker(i);
worker.start();
mWorkerArray[i] = worker;
mControlorArray[i] = new HttpHelper.DownloadControlor();
}
}
}
}
public int getMaxEnsureIndex() {
if (mImageNum != -1) {
return mImageNum;
} else {
return Math.max(Math.max(mStartIndex - 1,
Math.max(mCurMaxPreviewPage, mPreviewPageNum - 1) * mPreviewPerPage),
mPageTokeArray.maxValidIndex());
}
}
// TODO for download , when to claim download completed
@Override
public void run() {
@@ -337,7 +425,10 @@ public class ExDownloader implements Runnable {
// A loop to get page token
while (true) {
Log.d(TAG, "A new loop to get page token");
// Check stop
if (mStopWork)
break;
int noTokenIndex = -1;
int pageIndex = -1;
@@ -357,6 +448,9 @@ public class ExDownloader implements Runnable {
}
}
Log.d(TAG, "noTokenIndex = " + noTokenIndex);
Log.d(TAG, "pageIndex = " + pageIndex);
if (noTokenIndex != -1) {
if (mPageTokeArray.get(noTokenIndex) == null) {
getDetailInfo(noTokenIndex / mPreviewPerPage, ediFile, false);
@@ -373,18 +467,22 @@ public class ExDownloader implements Runnable {
}
} catch (Throwable e) {
// TODO handle error here
} finally {
synchronized (this) {
mMainThread = null;
}
}
Log.d(TAG, "ExDownloader over");
}
private class Worker extends Thread {
public Worker() {
private final int mIndex;
public Worker(int index) {
super();
mIndex = index;
setPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
@@ -408,12 +506,20 @@ public class ExDownloader implements Runnable {
private int getTargetIndex() {
synchronized(mWorkerGetIndexLock) {
while (true) {
// Check stop
if (mStopWork || mPauseWork)
return -1;
if (!mRequestIndexArray.isEmpty()) {
return mRequestIndexArray.poll();
} else if (mImageNum != -1 && mStartIndex == mImageNum) {
return -1;
} else if (mImageNum == -1 && mStartIndex > getMaxEnsureIndex()) {
// If do not sure the index is valid
Log.d(TAG, "mPreviewPerPage = " + mPreviewPerPage);
addRequstPageIndex(getMaxEnsureIndex() / mPreviewPerPage);
try {
mWorkerGetIndexLock.wait();
@@ -425,47 +531,57 @@ public class ExDownloader implements Runnable {
}
}
private String getExtension(String name, String defautl) {
int index = name.lastIndexOf('.');
if (index == -1 || index == name.length() - 1)
return defautl;
else
return name.substring(index + 1);
}
private String getImageName(int index, String url) {
return String.format("%08d.%s", index + 1, getExtension(url, "jpg"));
}
private String[] getPossibleFilenames(int index) {
String prefix = String.format("%08d.", index + 1);
return new String[]{prefix + "jpg", prefix + "jpeg", prefix + "png", prefix + "gif"};
}
@Override
public void run() {
HttpDownloadListener listener = new HttpDownloadListener();
HttpHelper hh = new HttpHelper(mContext);
ImagePageParser ipp = new ImagePageParser();
while (true) {
// Check stop
if (mStopWork || mPauseWork)
break;
int targetIndex = getTargetIndex();
if (targetIndex == -1)
break;
// Check is the index is requsted
if (mRequstingIndexSet.contains(targetIndex))
continue;
// Check is this already downloaded
boolean isAlreadyDownloaded = false;
for (String possibleFilename : getPossibleFilenames(targetIndex)) {
File file = new File(mDir, possibleFilename);
if (file.exists()) {
// First check mImageFilenameArray
String imageFilename = mImageFilenameArray.get(targetIndex);
if (imageFilename != null) {
File file = new File(mDir, imageFilename);
if (file.exists())
isAlreadyDownloaded = true;
break;
} else {
// Can not find image file name, just guess
for (String possibleFilename : EhUtils.getPossibleImageFilenames(targetIndex)) {
File file = new File(mDir, possibleFilename);
if (file.exists()) {
isAlreadyDownloaded = true;
mImageFilenameArray.set(targetIndex, possibleFilename);
break;
}
}
}
if (isAlreadyDownloaded)
continue;
String pageToken;
mRequstingIndexSet.add(targetIndex);
listener.index = targetIndex;
String pageToken = null;
while (true) {
// Check stop
if (mStopWork || mPauseWork)
break;
pageToken = mPageTokeArray.get(targetIndex);
if (pageToken == null) {
addNoTokenIndex(targetIndex);
@@ -474,75 +590,106 @@ public class ExDownloader implements Runnable {
catch (InterruptedException e) {}
}
} else {
// Jump out of loop
// Get valid page token, jump out of loop
break;
}
}
for (int i = 0; i < 2; i++) {
// i == 1, add "?nl=48"
hh.reset();
ipp.reset();
String body = hh.get(EhClient.getPageUrl(mGid, pageToken, targetIndex + 1, mMode)
+ (i == 1 ? "?nl=48" : ""));
if (ipp.parser(body)) {
String imageUrl = ipp.imageUrl;
String filename = getImageName(targetIndex, imageUrl);
for (int j = 0; j < 2; j++) {
// j == 0, use proxy
}
} else {
// TODO parser error, Do somthing
break;
}
}
// Parser image page
for (int i = 0; i < 2; i++) {
for (int i = 0; i < 2 && !mStopWork && !mPauseWork; i++) {
hh.reset();
ipp.reset();
String body = hh.get(EhClient.getPageUrl(mGid, pageToken, targetIndex + 1, mMode)
+ (i == 1 ? "?nl=48" : ""));
if (ipp.parser(body)) {
if (ipp.parser(body, mMode)) {
// Download image
HttpHelper.DownloadControlor c = mControlorArray[mIndex];
String imageUrl = ipp.imageUrl;
String filename = getImageName(targetIndex, imageUrl);
String filename = EhUtils.getImageFilename(targetIndex, Utils.getExtension(imageUrl, "jpg"));
// Just put filename to mImageFilenameArray
mImageFilenameArray.set(targetIndex, filename);
hh.reset();
if (hh.download(imageUrl, mDir, filename, false, null, null) == null) {
if (hh.download(imageUrl, mDir, filename, false, c, listener) == null) {
hh.reset();
if (hh.download(imageUrl, mDir, filename, true, null, null) == null) {
if (hh.download(imageUrl, mDir, filename, true, c, listener) == null) {
if (i == 1) {
// TODO download error
Log.d(TAG, "download error");
}
} else {
// download ok
Log.d(TAG, "download ok proxy");
break;
}
} else {
// download ok
Log.d(TAG, "download ok");
break;
}
} else {
// TODO parser error, Do somthing
Log.d(TAG, "parser error");
break;
}
}
// Remove from requsting set
mRequstingIndexSet.remove(targetIndex);
}
// Worker over
synchronized(mWorkerArray) {
// Remove from mWorkerArray
for (int i = 0; i < mWorkerArray.length; i++) {
if (mWorkerArray[i] == this) {
mWorkerArray[i] = null;
break;
}
}
mWorkerArray[mIndex] = null;
mControlorArray[mIndex] = null;
}
Log.d(TAG, "Worker over");
Log.d(TAG, "ExDownloader Worker over");
}
}
public class HttpDownloadListener implements HttpHelper.OnDownloadListener {
public int index;
private float lastPercent;
@Override
public void onDownloadStartConnect() {
lastPercent = 0.0f;
if (mLfis != null)
mLfis.onDownloadStart(index);
}
@Override
public void onDownloadStartDownload(int totalSize) {
lastPercent = 0.0f;
}
@Override
public void onDownloadStatusUpdate(int downloadSize, int totalSize) {
if (totalSize == -1)
return;
float percent = (float)downloadSize / totalSize;
if (percent - lastPercent >= 0.05f) {
lastPercent = percent;
if (mLfis != null)
mLfis.onDownloading(index, percent);
}
}
@Override
public void onDownloadOver(int status, String eMsg) {
if (status == HttpHelper.DOWNLOAD_OK_CODE)
mRequstingIndexSet.remove(index);
if (mLfis != null && status == HttpHelper.DOWNLOAD_OK_CODE)
mLfis.onDownloadComplete(index);
else if (mLfis != null && status == HttpHelper.DOWNLOAD_FAIL_CODE)
mLfis.onDownloadFail(index);
lastPercent = 0.0f;
}
}
}
@@ -23,25 +23,25 @@ import android.util.SparseArray;
import com.hippo.ehviewer.AppContext;
public class ExDownloadManager {
public class ExDownloaderManager {
private static final String TAG = ExDownloadManager.class.getSimpleName();
private static final String TAG = ExDownloaderManager.class.getSimpleName();
private final Context mContext;
private final SparseArray<ExDownloader> mExDownloadList;
private final File mExDownloadInfoDir;
private static ExDownloadManager sInstance;
private static ExDownloaderManager sInstance;
public static void createInstance() {
sInstance = new ExDownloadManager();
sInstance = new ExDownloaderManager();
}
public static ExDownloadManager getInstance() {
public static ExDownloaderManager getInstance() {
return sInstance;
}
private ExDownloadManager() {
private ExDownloaderManager() {
mContext = AppContext.getInstance();
mExDownloadList = new SparseArray<ExDownloader>();
// Init download info dir
@@ -70,7 +70,9 @@ public class ExDownloadManager {
public synchronized void freeExDownloader(ExDownloader exDownloader) {
exDownloader.free();
if (exDownloader.isOrphans())
if (exDownloader.isOrphans()) {
exDownloader.stop();
mExDownloadList.remove(exDownloader.getGid());
}
}
}
@@ -33,19 +33,42 @@ public class ImagePageParser {
token = null;
}
public boolean parser(String body) {
Pattern p = Pattern.compile("<div id=\"i3\">.+?<img id=\"img\" src=\"(.+?)\""
+ ".+?"
+ "<div id=\"i5\"><div class=\"sb\"><a href=\"(?:http|https)://.+?/g/(\\d+)/(\\w+)");
Matcher m = p.matcher(body);
public boolean parser(String body, int mode) {
Pattern p;
Matcher m;
if (mode ==EhClient.MODE_LOFI) {
p = Pattern.compile("<img id=\"sm\" src=\"([^\"]+)\"[^>]+>");
m = p.matcher(body);
if (m.find())
imageUrl = Utils.unescapeXml(m.group(1));
else
return false;
p = Pattern.compile("<a href=\"(?:http|https)://.+?/g/(\\d+)/(\\w+)/?\">Back</a>");
m = p.matcher(body);
if (m.find()) {
gid = Integer.parseInt(m.group(1));
token = m.group(2);
return true;
} else {
return false;
}
if (m.find()) {
imageUrl = Utils.unescapeXml(m.group(1));
gid = Integer.parseInt(m.group(2));
token = m.group(3);
return true;
} else {
return false;
p = Pattern.compile("<div id=\"i3\">.+?<img id=\"img\" src=\"(.+?)\""
+ ".+?"
+ "<div id=\"i5\"><div class=\"sb\"><a href=\"(?:http|https)://.+?/g/(\\d+)/(\\w+)");
m = p.matcher(body);
if (m.find()) {
imageUrl = Utils.unescapeXml(m.group(1));
gid = Integer.parseInt(m.group(2));
token = m.group(3);
return true;
} else {
return false;
}
}
}
}
File diff suppressed because it is too large. Load diff
@@ -16,8 +16,8 @@
package com.hippo.ehviewer.gallery.anim;
import com.hippo.ehviewer.gallery.common.Utils;
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
import com.hippo.ehviewer.util.MathUtils;
public class AlphaAnimation extends CanvasAnimation {
private final float mStartAlpha;
@@ -42,7 +42,7 @@ public class AlphaAnimation extends CanvasAnimation {
@Override
protected void onCalculate(float progress) {
mCurrentAlpha = Utils.clamp(mStartAlpha
mCurrentAlpha = MathUtils.clamp(mStartAlpha
+ (mEndAlpha - mStartAlpha) * progress, 0f, 1f);
}
}
@@ -18,7 +18,7 @@ package com.hippo.ehviewer.gallery.anim;
import android.view.animation.Interpolator;
import com.hippo.ehviewer.gallery.common.Utils;
import com.hippo.ehviewer.util.MathUtils;
// Animation calculates a value according to the current input time.
//
@@ -81,7 +81,7 @@ abstract public class Animation {
if (mStartTime == NO_ANIMATION) return false;
if (mStartTime == ANIMATION_START) mStartTime = currentTimeMillis;
int elapse = (int) (currentTimeMillis - mStartTime);
float x = Utils.clamp((float) elapse / mDuration, 0f, 1f);
float x = MathUtils.clamp((float) elapse / mDuration, 0f, 1f);
Interpolator i = mInterpolator;
onCalculate(i != null ? i.getInterpolation(x) : x);
if (elapse >= mDuration) mStartTime = NO_ANIMATION;
@@ -1,340 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.gallery.common;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import com.hippo.ehviewer.util.Log;
import java.io.Closeable;
import java.io.IOException;
import java.io.InterruptedIOException;
public class Utils {
private static final String TAG = "Utils";
private static final String DEBUG_TAG = "GalleryDebug";
private static final long POLY64REV = 0x95AC9329AC4BC9B5L;
private static final long INITIALCRC = 0xFFFFFFFFFFFFFFFFL;
private static long[] sCrcTable = new long[256];
private static final boolean IS_DEBUG_BUILD =
Build.TYPE.equals("eng") || Build.TYPE.equals("userdebug");
private static final String MASK_STRING = "********************************";
// Throws AssertionError if the input is false.
public static void assertTrue(boolean cond) {
if (!cond) {
throw new AssertionError();
}
}
// Throws AssertionError with the message. We had a method having the form
// assertTrue(boolean cond, String message, Object ... args);
// However a call to that method will cause memory allocation even if the
// condition is false (due to autoboxing generated by "Object ... args"),
// so we don't use that anymore.
public static void fail(String message, Object ... args) {
throw new AssertionError(
args.length == 0 ? message : String.format(message, args));
}
// Throws NullPointerException if the input is null.
public static <T> T checkNotNull(T object) {
if (object == null) throw new NullPointerException();
return object;
}
// Returns true if two input Object are both null or equal
// to each other.
public static boolean equals(Object a, Object b) {
return (a == b) || (a == null ? false : a.equals(b));
}
// Returns the next power of two.
// Returns the input if it is already power of 2.
// Throws IllegalArgumentException if the input is <= 0 or
// the answer overflows.
public static int nextPowerOf2(int n) {
if (n <= 0 || n > (1 << 30)) throw new IllegalArgumentException("n is invalid: " + n);
n -= 1;
n |= n >> 16;
n |= n >> 8;
n |= n >> 4;
n |= n >> 2;
n |= n >> 1;
return n + 1;
}
// Returns the previous power of two.
// Returns the input if it is already power of 2.
// Throws IllegalArgumentException if the input is <= 0
public static int prevPowerOf2(int n) {
if (n <= 0) throw new IllegalArgumentException();
return Integer.highestOneBit(n);
}
// Returns the input value x clamped to the range [min, max].
public static int clamp(int x, int min, int max) {
if (x > max) return max;
if (x < min) return min;
return x;
}
// Returns the input value x clamped to the range [min, max].
public static float clamp(float x, float min, float max) {
if (x > max) return max;
if (x < min) return min;
return x;
}
// Returns the input value x clamped to the range [min, max].
public static long clamp(long x, long min, long max) {
if (x > max) return max;
if (x < min) return min;
return x;
}
public static boolean isOpaque(int color) {
return color >>> 24 == 0xFF;
}
public static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
/**
* A function thats returns a 64-bit crc for string
*
* @param in input string
* @return a 64-bit crc value
*/
public static final long crc64Long(String in) {
if (in == null || in.length() == 0) {
return 0;
}
return crc64Long(getBytes(in));
}
static {
// http://bioinf.cs.ucl.ac.uk/downloads/crc64/crc64.c
long part;
for (int i = 0; i < 256; i++) {
part = i;
for (int j = 0; j < 8; j++) {
long x = ((int) part & 1) != 0 ? POLY64REV : 0;
part = (part >> 1) ^ x;
}
sCrcTable[i] = part;
}
}
public static final long crc64Long(byte[] buffer) {
long crc = INITIALCRC;
for (int k = 0, n = buffer.length; k < n; ++k) {
crc = sCrcTable[(((int) crc) ^ buffer[k]) & 0xff] ^ (crc >> 8);
}
return crc;
}
public static byte[] getBytes(String in) {
byte[] result = new byte[in.length() * 2];
int output = 0;
for (char ch : in.toCharArray()) {
result[output++] = (byte) (ch & 0xFF);
result[output++] = (byte) (ch >> 8);
}
return result;
}
public static void closeSilently(Closeable c) {
if (c == null) return;
try {
c.close();
} catch (IOException t) {
Log.w(TAG, "close fail ", t);
}
}
public static int compare(long a, long b) {
return a < b ? -1 : a == b ? 0 : 1;
}
public static int ceilLog2(float value) {
int i;
for (i = 0; i < 31; i++) {
if ((1 << i) >= value) break;
}
return i;
}
public static int floorLog2(float value) {
int i;
for (i = 0; i < 31; i++) {
if ((1 << i) > value) break;
}
return i - 1;
}
public static void closeSilently(ParcelFileDescriptor fd) {
try {
if (fd != null) fd.close();
} catch (Throwable t) {
Log.w(TAG, "fail to close", t);
}
}
public static void closeSilently(Cursor cursor) {
try {
if (cursor != null) cursor.close();
} catch (Throwable t) {
Log.w(TAG, "fail to close", t);
}
}
public static float interpolateAngle(
float source, float target, float progress) {
// interpolate the angle from source to target
// We make the difference in the range of [-179, 180], this is the
// shortest path to change source to target.
float diff = target - source;
if (diff < 0) diff += 360f;
if (diff > 180) diff -= 360f;
float result = source + diff * progress;
return result < 0 ? result + 360f : result;
}
public static float interpolateScale(
float source, float target, float progress) {
return source + progress * (target - source);
}
public static String ensureNotNull(String value) {
return value == null ? "" : value;
}
public static float parseFloatSafely(String content, float defaultValue) {
if (content == null) return defaultValue;
try {
return Float.parseFloat(content);
} catch (NumberFormatException e) {
return defaultValue;
}
}
public static int parseIntSafely(String content, int defaultValue) {
if (content == null) return defaultValue;
try {
return Integer.parseInt(content);
} catch (NumberFormatException e) {
return defaultValue;
}
}
public static boolean isNullOrEmpty(String exifMake) {
return TextUtils.isEmpty(exifMake);
}
public static void waitWithoutInterrupt(Object object) {
try {
object.wait();
} catch (InterruptedException e) {
Log.w(TAG, "unexpected interrupt: " + object);
}
}
public static boolean handleInterrruptedException(Throwable e) {
// A helper to deal with the interrupt exception
// If an interrupt detected, we will setup the bit again.
if (e instanceof InterruptedIOException
|| e instanceof InterruptedException) {
Thread.currentThread().interrupt();
return true;
}
return false;
}
/**
* @return String with special XML characters escaped.
*/
public static String escapeXml(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0, len = s.length(); i < len; ++i) {
char c = s.charAt(i);
switch (c) {
case '<': sb.append("&lt;"); break;
case '>': sb.append("&gt;"); break;
case '\"': sb.append("&quot;"); break;
case '\'': sb.append("&#039;"); break;
case '&': sb.append("&amp;"); break;
default: sb.append(c);
}
}
return sb.toString();
}
public static String getUserAgent(Context context) {
PackageInfo packageInfo;
try {
packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
} catch (NameNotFoundException e) {
throw new IllegalStateException("getPackageInfo failed");
}
return String.format("%s/%s; %s/%s/%s/%s; %s/%s/%s",
packageInfo.packageName,
packageInfo.versionName,
Build.BRAND,
Build.DEVICE,
Build.MODEL,
Build.ID,
Build.VERSION.SDK_INT,
Build.VERSION.RELEASE,
Build.VERSION.INCREMENTAL);
}
public static String[] copyOf(String[] source, int newSize) {
String[] result = new String[newSize];
newSize = Math.min(source.length, newSize);
System.arraycopy(source, 0, result, 0, newSize);
return result;
}
// Mask information for debugging only. It returns <code>info.toString()</code> directly
// for debugging build (i.e., 'eng' and 'userdebug') and returns a mask ("****")
// in release build to protect the information (e.g. for privacy issue).
public static String maskDebugInfo(Object info) {
if (info == null) return null;
String s = info.toString();
int length = Math.min(s.length(), MASK_STRING.length());
return IS_DEBUG_BUILD ? s : MASK_STRING.substring(0, length);
}
// This method should be ONLY used for debugging.
public static void debug(String message, Object ... args) {
Log.v(DEBUG_TAG, String.format(message, args));
}
}
@@ -1,60 +0,0 @@
/*
* Copyright (C) 2014 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.gallery.data;
import java.io.File;
import java.util.Set;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import com.hippo.ehviewer.service.DownloadService;
public class DownloadImageSet extends ImageSet {
public DownloadImageSet(Context context, int gid, File folder, int size,
int startIndex, int endIndex, Set<Integer> failIndexSet) {
super(context, gid, folder, size, startIndex, endIndex, failIndexSet);
IntentFilter filter = new IntentFilter();
filter.addAction(DownloadService.ACTION_UPDATE);
mContext.registerReceiver(mReceiver, filter);
}
public void unregisterReceiver() {
mContext.unregisterReceiver(mReceiver);
}
private BroadcastReceiver mReceiver = new BroadcastReceiver(){
@Override
public void onReceive(Context context, Intent intent) {
String gid;
if(intent.getAction().equals(DownloadService.ACTION_UPDATE)
&& (gid = intent.getStringExtra(DownloadService.KEY_GID)) != null
&& gid.equals(mGid)) {
int index = intent.getIntExtra(DownloadService.KEY_INDEX, -1);
int state = intent.getIntExtra(DownloadService.KEY_STATE, -1);
if (index != -1 && state != -1) {
changeState(index, state);
}
}
}
};
}
+204 -196
View File
@@ -19,244 +19,252 @@ package com.hippo.ehviewer.gallery.data;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Set;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Movie;
import android.util.SparseArray;
import com.hippo.ehviewer.util.Future;
import com.hippo.ehviewer.util.FutureListener;
import com.hippo.ehviewer.util.ThreadPool;
import com.hippo.ehviewer.util.ThreadPool.Job;
import com.hippo.ehviewer.util.ThreadPool.JobContext;
import com.hippo.ehviewer.AppHandler;
import com.hippo.ehviewer.ehclient.ExDownloader;
import com.hippo.ehviewer.ehclient.ExDownloaderManager;
import com.hippo.ehviewer.util.AutoExpandArray;
import com.hippo.ehviewer.util.Config;
import com.hippo.ehviewer.util.EhUtils;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.util.Utils;
// TODO 初期建立一个线程检索文件夹中所有文件以寻找到所有图片文件
/**
* @author Hippo
*
* ImageSet 用于记录漫画的下载信息
* 对于已完成下载的则无特殊之处。
* 对于未完成下载的要记录,开始下载之处,目前下载截止之处,
* 以及下载失败之处。
* 同时拥有检查该目录识别图片文件。
*/
public class ImageSet {
public class ImageSet implements ExDownloader.ListenerForImageSet {
@SuppressWarnings("unused")
private static final String TAG = "ImageSet";
private static final String TAG = ImageSet.class.getSimpleName();
private static final String GIF_EXTENSION = "gif";
/**
* These are code reture by getImage()
*/
public static final int RESULT_NONE = 0x0;
public static final int RESULT_DOWNLOADING = 0x1;
public static final int RESULT_DECODE = 0x2;
public static final int TYPE_NONE = 0x0;
public static final int TYPE_BITMAP = 0x1;
public static final int TYPE_MOVIE = 0x2;
private final ExDownloaderManager mEdManager;
private final ExDownloader mExDownloader;
public static final int INVALID_ID = -1;
private final File mDir;
private final AutoExpandArray<String> mImageFilenameArray;
private ImageListener mListener;
private final Queue<DecodeInfo> mDecodeQueue = new ConcurrentLinkedQueue<DecodeInfo>();
private final SparseArray<Float> mPercentMap = new SparseArray<Float>(5);
private final DecodeWorker mDecodeWorker;
/** If true, then wake Worker, worker will stop **/
private volatile boolean mStopWork = false;
public static final int STATE_NONE = 0;
public static final int STATE_LOADING = 1;
public static final int STATE_LOADED = 2;
public static final int STATE_FAIL = 3;
public class DecodeInfo {
public int index;
public String filename;
protected Context mContext;
protected int mGid;
protected File mFolder;
protected int mSize;
private final SparseArray<ImageData> mImagesDate;
private final ThreadPool mThreadPool;
private OnStateChangeListener mOnStateChangeListener;
public interface OnStateChangeListener {
void onStateChange(int index, int state);
public DecodeInfo(int index, String filename) {
this.index = index;
this.filename = filename;
}
}
public interface OnDecodeOverListener {
public interface ImageListener {
/**
* May return Bitmap or Movie
* It will be invoke when ExDownloader download successful
*
* @param res
* @param index
*/
void onDecodeOver(Object res, int index);
void onGetImage(int index);
void onDownloading(int index, float percent);
/**
* It will be invoke when decode image over
*
* @param index
* @param res
*/
void onDecodeOver(int index, Object res);
}
private class ImageData {
int state;
String fileName;
int type;
public ImageSet(int gid, String token, String title, int startIndex) {
public ImageData() {
state = STATE_NONE;
fileName = null;
type = TYPE_NONE;
}
mDir = EhUtils.getGalleryDir(gid, title);
Utils.ensureDir(mDir, true);
// Init ExDownloader
mEdManager = ExDownloaderManager.getInstance();
mExDownloader = mEdManager.getExDownloader(gid, token, title, Config.getMode());
mExDownloader.setStartIndex(startIndex);
mExDownloader.setListenerForImageSet(this);
mImageFilenameArray = mExDownloader.getImageFilenameArray();
// Start decode worker
mDecodeWorker = new DecodeWorker();
mDecodeWorker.start();
}
// [startIndex, endIndex)
public ImageSet(Context context, int gid, File folder, int size, int startIndex, int endIndex,
Set<Integer> failIndexSet) {
if (folder == null || !folder.isDirectory())
size = 0;
// TODO
/*
if (folder == null)
throw new IllegalArgumentException("Folder is null");
if (!folder.isDirectory())
throw new IllegalArgumentException("Folder is not directory, path is " + folder.getPath());
*/
if (size < 0)
size = 0;
if (endIndex > size)
endIndex = size;
if (startIndex < 0)
startIndex = 0;
if (startIndex > endIndex)
startIndex = endIndex;
// TODO
/*
if (size < 0 || startIndex < 0
|| startIndex > endIndex || endIndex > size)
throw new IllegalArgumentException("size or index value error, size = "
+ size + ", startIndex = " + startIndex
+ ", endIndex = " + endIndex + ", path is " + folder.getPath());
*/
mContext = context;
mGid = gid;
mFolder = folder;
mSize = size;
// TODO sometimes size is too large
mImagesDate = new SparseArray<ImageData>(mSize);
int i = 0;
for (; i < startIndex; i++) {
mImagesDate.append(i, new ImageData());
}
for (; i < endIndex; i++) {
if (failIndexSet != null && failIndexSet.contains(i)) {
ImageData imageData = new ImageData();
imageData.state = STATE_FAIL;
mImagesDate.append(i, imageData);
}
else {
// Get file name only when you need it
ImageData imageData = new ImageData();
imageData.state = STATE_LOADED;
mImagesDate.append(i, imageData);
}
}
for (; i < size; i++) {
mImagesDate.append(i, new ImageData());
}
mThreadPool = new ThreadPool(1, 1);
public boolean isStop() {
return mStopWork;
}
public int getSize() {
return mSize;
return mExDownloader.getImageNum();
}
public void setOnStateChangeListener(OnStateChangeListener l) {
mOnStateChangeListener = l;
public int getEnsureSize() {
return mExDownloader.getMaxEnsureIndex() + 1;
}
public int getImage(final int index, final OnDecodeOverListener listener) {
final ImageData imageData = mImagesDate.get(index);
int state;
if (imageData == null)
state = INVALID_ID;
else
state = imageData.state;
if (state == STATE_LOADED && listener != null) {
mThreadPool.submit(new Job<Object>() {
@Override
public Object run(JobContext jc) {
Object res = null;
if (imageData.fileName == null
&& !getFileForName(getFileNameForIndex(index), imageData))
return null;
public void setImageListener(ImageListener l) {
mListener = l;
}
File file = new File(mFolder, imageData.fileName);
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
if (imageData.type == TYPE_BITMAP) {
BitmapFactory.Options opt = new BitmapFactory.Options();
// TODO why only ARGB_8888 always work well, other may slit image
opt.inPreferredConfig = Bitmap.Config.ARGB_8888;
res = BitmapFactory.decodeStream(fis, null, opt);
} else if (imageData.type == TYPE_MOVIE) {
res = Movie.decodeStream(fis);
} else {
// TYPE_NONE or something else, get error
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fis != null)
Utils.closeQuietly(fis);
}
return res;
public void setTargetIndex(int index) {
mExDownloader.setTargetIndex(index);
}
public Object getImage(int index) {
// Try to get file name
String filename = mImageFilenameArray.get(index);
if (filename == null) {
// Just guess filename
for (String possibleFilename : EhUtils.getPossibleImageFilenames(index)) {
File file = new File(mDir, possibleFilename);
if (file.exists()) {
// Get filename
filename = possibleFilename;
mImageFilenameArray.set(index, filename);
break;
}
}, new FutureListener<Object>() {
@Override
public void onFutureDone(Future<Object> future) {
listener.onDecodeOver(future.get(), index);
}
});
}
return state;
}
@SuppressLint("DefaultLocale")
public String getFileNameForIndex(int index) {
return String.format("%05d", index + 1);
}
public boolean getFileForName(String name, ImageData imageData) {
String[] list = mFolder.list();
for (String item : list) {
if(name.equals(Utils.getName(item))) {
imageData.fileName = item;
if (Utils.getExtension(item).toLowerCase().equals(GIF_EXTENSION))
imageData.type = TYPE_MOVIE;
else
imageData.type = TYPE_BITMAP;
return true;
}
}
return false;
}
public void changeState(int index, int state) {
final ImageData imageData = mImagesDate.get(index);
if(imageData != null) {
imageData.state = state;
if (mOnStateChangeListener != null) {
mOnStateChangeListener.onStateChange(index, state);
}
Float percent;
if ((percent = mPercentMap.get(index)) != null) {
return percent;
} else if (mExDownloader.isDownloading(index)) {
// downloading
return RESULT_DOWNLOADING;
} else if (filename == null || !new File(mDir, filename).exists()) {
// Target index has not being downloading
return RESULT_NONE;
} else {
mDecodeQueue.offer(new DecodeInfo(index, filename));
// wake decode worker
synchronized(mDecodeQueue) {mDecodeQueue.notify();}
return RESULT_DECODE;
}
}
/**
* mSize is faithful
*
* You must call it when you do not need it any more
*/
public void scan() {
String[] fileList = mFolder.list();
public void free() {
mStopWork = true;
// wake decode worker to wash wash sleep
synchronized(mDecodeQueue) {mDecodeQueue.notify();}
// Remove listener for ImageSet
mExDownloader.setListenerForImageSet(null);
// Free ExDownloader
mEdManager.freeExDownloader(mExDownloader);
}
private class DecodeWorker extends Thread {
@Override
public void run() {
DecodeInfo decodeInfo;
while (true) {
// Check stop work or not
if (mStopWork)
break;
// Get decode task
synchronized (mDecodeQueue) {
if (mDecodeQueue.isEmpty()) {
try {
mDecodeQueue.wait();
} catch (InterruptedException e) {}
continue;
}
decodeInfo = mDecodeQueue.poll();
}
// do decode
final int index = decodeInfo.index;
String filename = decodeInfo.filename;
boolean isGif = Utils.getExtension(filename, "jpg").equals("gif");
FileInputStream fis;
Object res = null;
try {
fis = new FileInputStream(new File(mDir, filename));
if (isGif) {
res = Movie.decodeStream(fis);
} else {
BitmapFactory.Options opt = new BitmapFactory.Options();
// TODO why only ARGB_8888 always work well, other may slit image
opt.inPreferredConfig = Bitmap.Config.ARGB_8888;
res = BitmapFactory.decodeStream(fis, null, opt);
}
} catch (FileNotFoundException e) {}
final Object _res = res;
// Post to UI thread
AppHandler.getInstance().post(new Runnable() {
@Override
public void run() {
if (mListener != null)
mListener.onDecodeOver(index, _res);
}
});
}
Log.d(TAG, "DecodeWorker stop working");
}
}
@Override
public void onDownloadStart(final int index) {
AppHandler.getInstance().post(new Runnable() {
@Override
public void run() {
if (mListener != null)
mListener.onDownloading(index, 0.0f);
}
});
}
@Override
public void onDownloading(final int index, final float percent) {
mPercentMap.append(index, percent);
AppHandler.getInstance().post(new Runnable() {
@Override
public void run() {
if (mListener != null)
mListener.onDownloading(index, percent);
}
});
}
@Override
public void onDownloadComplete(final int index) {
mPercentMap.remove(index);
AppHandler.getInstance().post(new Runnable() {
@Override
public void run() {
if (mListener != null)
mListener.onGetImage(index);
}
});
}
@Override
public void onDownloadFail(int index) {
mPercentMap.remove(index);
}
}
@@ -16,13 +16,13 @@
package com.hippo.ehviewer.gallery.glrenderer;
import android.graphics.RectF;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.gallery.common.Utils;
import java.util.WeakHashMap;
import android.graphics.RectF;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.util.MathUtils;
// BasicTexture is a Texture corresponds to a real GL texture.
// The state of a BasicTexture indicates whether its data is loaded to GL memory.
// If a BasicTexture is loaded into GL memory, it has a GL texture id.
@@ -79,8 +79,8 @@ public abstract class BasicTexture implements Texture {
public void setSize(int width, int height) {
mWidth = width;
mHeight = height;
mTextureWidth = width > 0 ? Utils.nextPowerOf2(width) : 0;
mTextureHeight = height > 0 ? Utils.nextPowerOf2(height) : 0;
mTextureWidth = width > 0 ? MathUtils.nextPowerOf2(width) : 0;
mTextureHeight = height > 0 ? MathUtils.nextPowerOf2(height) : 0;
if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) {
Log.w(TAG, String.format("texture is too large: %d x %d",
mTextureWidth, mTextureHeight), new Exception());
@@ -138,16 +138,16 @@ public abstract class BasicTexture implements Texture {
public void draw(GLCanvas canvas, int x, int y) {
canvas.drawTexture(this, x, y, getWidth(), getHeight());
}
@Override
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
canvas.drawTexture(this, x, y, w, h);
}
public void draw(GLCanvas canvas, RectF source, RectF target) {
canvas.drawTexture(this, source, target);
}
// onBind is called before GLCanvas binds this texture.
// It should make sure the data is uploaded to GL memory.
abstract protected boolean onBind(GLCanvas canvas);
@@ -16,7 +16,7 @@
package com.hippo.ehviewer.gallery.glrenderer;
import com.hippo.ehviewer.gallery.common.Utils;
import com.hippo.ehviewer.util.Utils;
// ColorTexture is a texture which fills the rectangle with the specified color.
public class ColorTexture implements Texture {
@@ -16,8 +16,8 @@
package com.hippo.ehviewer.gallery.glrenderer;
import com.hippo.ehviewer.gallery.common.Utils;
import com.hippo.ehviewer.gallery.ui.AnimationTime;
import com.hippo.ehviewer.util.MathUtils;
// FadeTexture is a texture which fades the given texture along the time.
public abstract class FadeTexture implements Texture {
@@ -72,7 +72,7 @@ public abstract class FadeTexture implements Texture {
protected float getRatio() {
float r = (float)(now() - mStartTime) / DURATION;
return Utils.clamp(1.0f - r, 0.0f, 1.0f);
return MathUtils.clamp(1.0f - r, 0.0f, 1.0f);
}
private long now() {
@@ -16,19 +16,6 @@
package com.hippo.ehviewer.gallery.glrenderer;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.RectF;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.gallery.common.Utils;
import com.hippo.ehviewer.util.IntArray;
import junit.framework.Assert;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -40,6 +27,18 @@ import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11Ext;
import javax.microedition.khronos.opengles.GL11ExtensionPack;
import junit.framework.Assert;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.RectF;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import com.hippo.ehviewer.util.IntArray;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.util.Utils;
public class GLES11Canvas implements GLCanvas {
@SuppressWarnings("unused")
private static final String TAG = "GLCanvasImp";
@@ -54,7 +53,7 @@ public class GLES11Canvas implements GLCanvas {
0, 0, 1, 1, // used for drawing a line
0, 0, 0, 1, 1, 1, 1, 0}; // used for drawing the outline of a rectangle
private GL11 mGL;
private final GL11 mGL;
private final float mMatrixValues[] = new float[16];
private final float mTextureMatrixValues[] = new float[16];
@@ -65,9 +64,9 @@ public class GLES11Canvas implements GLCanvas {
private final float mTextureColor[] = new float[4];
private int mBoxCoords;
private final int mBoxCoords;
private GLState mGLState;
private final GLState mGLState;
private final ArrayList<RawTexture> mTargetStack = new ArrayList<RawTexture>();
private float mAlpha;
@@ -81,8 +80,8 @@ public class GLES11Canvas implements GLCanvas {
private final IntArray mDeleteBuffers = new IntArray();
private int mScreenWidth;
private int mScreenHeight;
private boolean mBlendEnabled = true;
private int mFrameBuffer[] = new int[1];
private final boolean mBlendEnabled = true;
private final int mFrameBuffer[] = new int[1];
private static float[] sCropRect = new float[4];
private RawTexture mTargetTexture;
@@ -592,7 +591,7 @@ public class GLES11Canvas implements GLCanvas {
private int mTextureTarget = GL11.GL_TEXTURE_2D;
private boolean mBlendEnabled = true;
private float mLineWidth = 1.0f;
private boolean mLineSmooth = false;
private final boolean mLineSmooth = false;
public GLState(GL11 gl) {
mGL = gl;
@@ -959,7 +958,7 @@ public class GLES11Canvas implements GLCanvas {
mGL.glBindTexture(target, texture.getId());
GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
}
@Override
public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap) {
int target = texture.getTarget();
@@ -15,16 +15,6 @@
*/
package com.hippo.ehviewer.gallery.glrenderer;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.RectF;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.util.IntArray;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -32,6 +22,16 @@ import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.RectF;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import com.hippo.ehviewer.util.IntArray;
import com.hippo.ehviewer.util.Log;
public class GLES20Canvas implements GLCanvas {
// ************** Constants **********************
private static final String TAG = GLES20Canvas.class.getSimpleName();
@@ -138,7 +138,7 @@ public class GLES20Canvas implements GLCanvas {
// Keep track of restore state
private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE];
private float[] mAlphas = new float[INITIAL_RESTORE_STATE_SIZE];
private IntArray mSaveFlags = new IntArray();
private final IntArray mSaveFlags = new IntArray();
private int mCurrentAlphaIndex = 0;
private int mCurrentMatrixIndex = 0;
@@ -148,20 +148,20 @@ public class GLES20Canvas implements GLCanvas {
private int mHeight;
// Projection matrix
private float[] mProjectionMatrix = new float[MATRIX_SIZE];
private final float[] mProjectionMatrix = new float[MATRIX_SIZE];
// Screen size for when we aren't bound to a texture
private int mScreenWidth;
private int mScreenHeight;
// GL programs
private int mDrawProgram;
private int mTextureProgram;
private int mOesTextureProgram;
private int mMeshProgram;
private final int mDrawProgram;
private final int mTextureProgram;
private final int mOesTextureProgram;
private final int mMeshProgram;
// GL buffer containing BOX_COORDINATES
private int mBoxCoordinates;
private final int mBoxCoordinates;
// Handle indices -- common
private static final int INDEX_POSITION = 0;
@@ -251,10 +251,10 @@ public class GLES20Canvas implements GLCanvas {
// Buffer for framebuffer IDs -- we keep track so we can switch the attached
// texture.
private int[] mFrameBuffer = new int[1];
private final int[] mFrameBuffer = new int[1];
// Bound textures.
private ArrayList<RawTexture> mTargetTextures = new ArrayList<RawTexture>();
private final ArrayList<RawTexture> mTargetTextures = new ArrayList<RawTexture>();
// Temporary variables used within calculations
private final float[] mTempMatrix = new float[32];
@@ -935,9 +935,11 @@ public class GLES20Canvas implements GLCanvas {
int target = texture.getTarget();
GLES20.glBindTexture(target, texture.getId());
checkError();
GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
// TODO Maybe multi-thread error
if (!bitmap.isRecycled())
GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
}
@Override
public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap) {
int target = texture.getTarget();
@@ -16,16 +16,16 @@
package com.hippo.ehviewer.gallery.glrenderer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import com.hippo.ehviewer.gallery.common.Utils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import com.hippo.ehviewer.util.MathUtils;
// NinePatchTexture is a texture backed by a NinePatch resource.
//
@@ -36,7 +36,7 @@ public class NinePatchTexture extends ResourceTexture {
@SuppressWarnings("unused")
private static final String TAG = "NinePatchTexture";
private NinePatchChunk mChunk;
private SmallCache<NinePatchInstance> mInstanceCache
private final SmallCache<NinePatchInstance> mInstanceCache
= new SmallCache<NinePatchInstance>();
public NinePatchTexture(Context context, int resId) {
@@ -80,8 +80,8 @@ public class NinePatchTexture extends ResourceTexture {
private static class SmallCache<V> {
private static final int CACHE_SIZE = 16;
private static final int CACHE_SIZE_START_MOVE = CACHE_SIZE / 2;
private int[] mKey = new int[CACHE_SIZE];
private V[] mValue = (V[]) new Object[CACHE_SIZE];
private final int[] mKey = new int[CACHE_SIZE];
private final V[] mValue = (V[]) new Object[CACHE_SIZE];
private int mCount; // number of items in this cache
// Puts a value into the cache. If the cache is full, also returns
@@ -262,7 +262,7 @@ class NinePatchInstance {
*/
private static int stretch(
float x[], float u[], int div[], int source, int target) {
int textureSize = Utils.nextPowerOf2(source);
int textureSize = MathUtils.nextPowerOf2(source);
float textureBound = (float) source / textureSize;
float stretch = 0;
@@ -16,16 +16,15 @@
package com.hippo.ehviewer.gallery.glrenderer;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.opengl.GLUtils;
import com.hippo.ehviewer.util.Log;
import junit.framework.Assert;
import java.util.HashMap;
import javax.microedition.khronos.opengles.GL11;
import junit.framework.Assert;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.opengl.GLUtils;
// UploadedTextures use a Bitmap for the content of the texture.
//
// Subclasses should implement onGetBitmap() to provide the Bitmap and
@@ -211,12 +210,10 @@ public abstract class UploadedTexture extends BasicTexture {
private void uploadToCanvas(GLCanvas canvas) {
Bitmap bitmap = getBitmap();
if (bitmap != null) {
if (bitmap != null && !bitmap.isRecycled()) {
try {
int bWidth = bitmap.getWidth();
int bHeight = bitmap.getHeight();
int width = bWidth + mBorder * 2;
int height = bHeight + mBorder * 2;
int texWidth = getTextureWidth();
int texHeight = getTextureHeight();
@@ -232,7 +229,7 @@ public abstract class UploadedTexture extends BasicTexture {
int format = GLUtils.getInternalFormat(bitmap);
int type = GLUtils.getType(bitmap);
Config config = bitmap.getConfig();
canvas.initializeTextureSize(this, format, type);
canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
@@ -16,6 +16,15 @@
package com.hippo.ehviewer.gallery.ui;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
@@ -26,15 +35,11 @@ import android.os.Build;
import android.os.Process;
import android.os.SystemClock;
import android.util.AttributeSet;
import com.hippo.ehviewer.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.View;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.gallery.anim.CanvasAnimation;
import com.hippo.ehviewer.gallery.common.ApiHelper;
import com.hippo.ehviewer.gallery.common.Utils;
import com.hippo.ehviewer.gallery.glrenderer.BasicTexture;
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
import com.hippo.ehviewer.gallery.glrenderer.GLES11Canvas;
@@ -42,15 +47,8 @@ import com.hippo.ehviewer.gallery.glrenderer.GLES20Canvas;
import com.hippo.ehviewer.gallery.glrenderer.UploadedTexture;
import com.hippo.ehviewer.gallery.util.GalleryUtils;
import com.hippo.ehviewer.gallery.util.MotionEventHelper;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.util.Utils;
// The root component of all <code>GLView</code>s. The rendering is done in GL
// thread while the event handling is done in the main thread. To synchronize
@@ -89,7 +87,7 @@ public class GLRootView extends GLSurfaceView
private int mCompensation;
// mCompensationMatrix maps the coordinates of touch events. It is kept sync
// with mCompensation.
private Matrix mCompensationMatrix = new Matrix();
private final Matrix mCompensationMatrix = new Matrix();
private int mDisplayRotation;
private int mFlags = FLAG_NEED_LAYOUT;
@@ -198,7 +196,7 @@ public class GLRootView extends GLSurfaceView
}
}
private Runnable mRequestRenderOnAnimationFrame = new Runnable() {
private final Runnable mRequestRenderOnAnimationFrame = new Runnable() {
@Override
public void run() {
superRequestRender();
@@ -16,17 +16,17 @@
package com.hippo.ehviewer.gallery.ui;
import java.util.ArrayList;
import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.os.SystemClock;
import com.hippo.ehviewer.util.Log;
import android.view.MotionEvent;
import com.hippo.ehviewer.gallery.anim.CanvasAnimation;
import com.hippo.ehviewer.gallery.common.Utils;
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
import java.util.ArrayList;
import com.hippo.ehviewer.util.Log;
import com.hippo.ehviewer.util.Utils;
// GLView is a UI component. It can render to a GLCanvas and accept touch
// events. A GLView may have zero or more child GLView and they form a tree
@@ -16,46 +16,12 @@
package com.hippo.ehviewer.gallery.util;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.ConditionVariable;
import android.os.Environment;
import android.os.StatFs;
import android.provider.MediaStore;
import android.util.DisplayMetrics;
import com.hippo.ehviewer.util.Log;
import android.view.WindowManager;
import com.hippo.ehviewer.util.ThreadPool.CancelListener;
import com.hippo.ehviewer.util.ThreadPool.JobContext;
import java.io.File;
import java.util.Arrays;
import java.util.Locale;
public class GalleryUtils {
private static final String TAG = "GalleryUtils";
private static final String MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
private static final String MAPS_CLASS_NAME = "com.google.android.maps.MapsActivity";
public static final String MIME_TYPE_IMAGE = "image/*";
public static final String MIME_TYPE_VIDEO = "video/*";
public static final String MIME_TYPE_PANORAMA360 = "application/vnd.google.panorama360+jpg";
public static final String MIME_TYPE_ALL = "*/*";
private static float sPixelDensity = -1f;
public static void initialize(Context context) {
DisplayMetrics metrics = new DisplayMetrics();
WindowManager wm = (WindowManager)
context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(metrics);
sPixelDensity = metrics.density;
}
private static final String TAG = GalleryUtils.class.getSimpleName();
public static float[] intColorToFloatARGBArray(int from) {
return new float[] {
@@ -66,29 +32,6 @@ public class GalleryUtils {
};
}
public static float dpToPixel(float dp) {
return sPixelDensity * dp;
}
public static int dpToPixel(int dp) {
return Math.round(dpToPixel((float) dp));
}
public static int meterToPixel(float meter) {
// 1 meter = 39.37 inches, 1 inch = 160 dp.
return Math.round(dpToPixel(meter * 39.37f * 160));
}
public static byte[] getBytes(String in) {
byte[] result = new byte[in.length() * 2];
int output = 0;
for (char ch : in.toCharArray()) {
result[output++] = (byte) (ch & 0xFF);
result[output++] = (byte) (ch >> 8);
}
return result;
}
// Below are used the detect using database in the render thread. It only
// works most of the time, but that's ok because it's for debugging only.
@@ -107,147 +50,4 @@ public class GalleryUtils {
}
}
}
private static final double RAD_PER_DEG = Math.PI / 180.0;
private static final double EARTH_RADIUS_METERS = 6367000.0;
public static double fastDistanceMeters(double latRad1, double lngRad1,
double latRad2, double lngRad2) {
if ((Math.abs(latRad1 - latRad2) > RAD_PER_DEG)
|| (Math.abs(lngRad1 - lngRad2) > RAD_PER_DEG)) {
return accurateDistanceMeters(latRad1, lngRad1, latRad2, lngRad2);
}
// Approximate sin(x) = x.
double sineLat = (latRad1 - latRad2);
// Approximate sin(x) = x.
double sineLng = (lngRad1 - lngRad2);
// Approximate cos(lat1) * cos(lat2) using
// cos((lat1 + lat2)/2) ^ 2
double cosTerms = Math.cos((latRad1 + latRad2) / 2.0);
cosTerms = cosTerms * cosTerms;
double trigTerm = sineLat * sineLat + cosTerms * sineLng * sineLng;
trigTerm = Math.sqrt(trigTerm);
// Approximate arcsin(x) = x
return EARTH_RADIUS_METERS * trigTerm;
}
public static double accurateDistanceMeters(double lat1, double lng1,
double lat2, double lng2) {
double dlat = Math.sin(0.5 * (lat2 - lat1));
double dlng = Math.sin(0.5 * (lng2 - lng1));
double x = dlat * dlat + dlng * dlng * Math.cos(lat1) * Math.cos(lat2);
return (2 * Math.atan2(Math.sqrt(x), Math.sqrt(Math.max(0.0,
1.0 - x)))) * EARTH_RADIUS_METERS;
}
public static final double toMile(double meter) {
return meter / 1609;
}
// For debugging, it will block the caller for timeout millis.
public static void fakeBusy(JobContext jc, int timeout) {
final ConditionVariable cv = new ConditionVariable();
jc.setCancelListener(new CancelListener() {
@Override
public void onCancel() {
cv.open();
}
});
cv.block(timeout);
jc.setCancelListener(null);
}
public static void startCameraActivity(Context context) {
Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
public static String formatLatitudeLongitude(String format, double latitude,
double longitude) {
// We need to specify the locale otherwise it may go wrong in some language
// (e.g. Locale.FRENCH)
return String.format(Locale.ENGLISH, format, latitude, longitude);
}
public static void showOnMap(Context context, double latitude, double longitude) {
try {
// We don't use "geo:latitude,longitude" because it only centers
// the MapView to the specified location, but we need a marker
// for further operations (routing to/from).
// The q=(lat, lng) syntax is suggested by geo-team.
String uri = formatLatitudeLongitude("http://maps.google.com/maps?f=q&q=(%f,%f)",
latitude, longitude);
ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME,
MAPS_CLASS_NAME);
Intent mapsIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse(uri)).setComponent(compName);
context.startActivity(mapsIntent);
} catch (ActivityNotFoundException e) {
// Use the "geo intent" if no GMM is installed
Log.e(TAG, "GMM activity not found!", e);
String url = formatLatitudeLongitude("geo:%f,%f", latitude, longitude);
Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
context.startActivity(mapsIntent);
}
}
public static void setViewPointMatrix(
float matrix[], float x, float y, float z) {
// The matrix is
// -z, 0, x, 0
// 0, -z, y, 0
// 0, 0, 1, 0
// 0, 0, 1, -z
Arrays.fill(matrix, 0, 16, 0);
matrix[0] = matrix[5] = matrix[15] = -z;
matrix[8] = x;
matrix[9] = y;
matrix[10] = matrix[11] = 1;
}
public static int getBucketId(String path) {
return path.toLowerCase().hashCode();
}
// Return the local path that matches the given bucketId. If no match is
// found, return null
public static String searchDirForPath(File dir, int bucketId) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
String path = file.getAbsolutePath();
if (GalleryUtils.getBucketId(path) == bucketId) {
return path;
} else {
path = searchDirForPath(file, bucketId);
if (path != null) return path;
}
}
}
}
return null;
}
public static boolean hasSpaceForSize(long size) {
String state = Environment.getExternalStorageState();
if (!Environment.MEDIA_MOUNTED.equals(state)) {
return false;
}
String path = Environment.getExternalStorageDirectory().getPath();
try {
StatFs stat = new StatFs(path);
return stat.getAvailableBlocks() * (long) stat.getBlockSize() > size;
} catch (Exception e) {
Log.i(TAG, "Fail to access external storage", e);
}
return false;
}
}
+19 -4
View File
@@ -271,6 +271,9 @@ public class HttpHelper {
} catch (Exception e) {
mException = e;
e.printStackTrace();
// For download, if need stop, just stop
if (mException instanceof StopRequestException)
break;
} finally {
if (conn != null)
conn.disconnect();
@@ -464,7 +467,6 @@ public class HttpHelper {
sb.append(URLEncoder.encode(arg[1], "UTF-8"));
i++;
}
Log.d(TAG, sb.toString());
out.writeBytes(sb.toString());
out.flush();
out.close();
@@ -646,7 +648,8 @@ public class HttpHelper {
// If just
// Bitmap bmp = BitmapFactory.decodeStream(conn.getInputStream(), null, Ui.getBitmapOpt());
// bitmap might be incomplete.
FastByteArrayOutputStream fbaos = new FastByteArrayOutputStream(24 * 1024); // 24K
int size = conn.getContentLength();
FastByteArrayOutputStream fbaos = new FastByteArrayOutputStream(size == -1 ? 24 * 1024 : (size + 100));
Utils.copy(conn.getInputStream(), fbaos);
Bitmap bmp = BitmapFactory.decodeByteArray(fbaos.getBuffer(), 0, fbaos.size(), Ui.getBitmapOpt());
@@ -659,13 +662,17 @@ public class HttpHelper {
public void onGetException(Exception e) {}
}
public interface OnDownloadListener {
public static interface OnDownloadListener {
public void onDownloadStartConnect();
/**
* If totalSize -1 for can't get length info
* @param totalSize
*/
public void onDownloadStartDownload(int totalSize);
/**
* If totalSize -1 for can't get length info
* @param totalSize
*/
public void onDownloadStatusUpdate(int downloadSize, int totalSize);
public void onDownloadOver(int status, String eMsg);
}
@@ -714,6 +721,9 @@ public class HttpHelper {
@Override
public void onBeforeConnect(HttpURLConnection conn) throws Exception {
if (mControlor != null && mControlor.isStop())
throw new StopRequestException();
if (mIsProxy) {
conn.setDoOutput(true);
conn.setDoInput(true);
@@ -761,6 +771,10 @@ public class HttpHelper {
@Override
public Object onAfterConnect(HttpURLConnection conn) throws Exception {
// Check stop
if (mControlor != null && mControlor.isStop())
throw new StopRequestException();
mContentLength = getContentLength(conn);
if (mListener != null)
mListener.onDownloadStartDownload(mContentLength);
@@ -786,7 +800,7 @@ public class HttpHelper {
if (e instanceof StopRequestException)
mListener.onDownloadOver(DOWNLOAD_STOP_CODE, getEMsg());
else
mListener.onDownloadOver(DOWNLOAD_STOP_CODE, getEMsg());
mListener.onDownloadOver(DOWNLOAD_FAIL_CODE, getEMsg());
}
}
@@ -796,6 +810,7 @@ public class HttpHelper {
mReceivedSize = 0;
while (true) {
// Check stop first
if (mControlor != null && mControlor.isStop())
throw new StopRequestException();
@@ -26,8 +26,6 @@ import android.os.IBinder;
import com.hippo.ehviewer.AppContext;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.ehclient.EhClient;
import com.hippo.ehviewer.ehclient.EhClient.DownloadMangaManager;
import com.hippo.ehviewer.ui.DownloadInfo;
import com.hippo.ehviewer.util.Download;
import com.hippo.ehviewer.util.Log;
@@ -46,7 +44,7 @@ public class DownloadService extends Service {
private NotificationManager mNotifyManager;
private DownloadMangaManager downloadMangaManager;
//private DownloadMangaManager downloadMangaManager;
@Override
public void onCreate() {
@@ -58,6 +56,7 @@ public class DownloadService extends Service {
mNotifyManager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
/*
downloadMangaManager = EhClient.getInstance().new DownloadMangaManager();
downloadMangaManager.setDownloadService(this);
@@ -159,6 +158,7 @@ public class DownloadService extends Service {
}
});
*/
}
private void setNotification(Notification.Builder builder) {
@@ -237,11 +237,11 @@ public class DownloadService extends Service {
|| di.status == DownloadInfo.WAITING)
return;
di.status = DownloadInfo.WAITING;
downloadMangaManager.add(di);
//downloadMangaManager.add(di);
}
public void cancel(String id) {
downloadMangaManager.cancel(id);
//downloadMangaManager.cancel(id);
}
public void notifyUpdate(){
+43 -1
View File
@@ -17,6 +17,7 @@
package com.hippo.ehviewer.ui;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
@@ -24,12 +25,23 @@ import android.view.WindowManager;
import android.widget.RelativeLayout;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.gallery.GalleryView;
import com.hippo.ehviewer.gallery.data.ImageSet;
import com.hippo.ehviewer.gallery.ui.GLRootView;
import com.hippo.ehviewer.widget.AlertButton;
import com.hippo.ehviewer.widget.DialogBuilder;
public class GalleryActivity extends AbstractActivity {
@SuppressWarnings("unused")
private final String TAG = GalleryActivity.class.getSimpleName();
private final static String TAG = GalleryActivity.class.getSimpleName();
public final static String KEY_GID = "gid";
public final static String KEY_TOKEN = "token";
public final static String KEY_TITLE = "title";
public final static String KEY_START_INDEX = "start_index";
private RelativeLayout mainView;
private ImageSet mImageSet;
@Override
public void onOrientationChanged(int paddingTop, int paddingBottom) {
@@ -70,5 +82,35 @@ public class GalleryActivity extends AbstractActivity {
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
Intent intent = getIntent();
int gid = intent.getIntExtra(KEY_GID, -1);
String token = intent.getStringExtra(KEY_TOKEN);
String title = intent.getStringExtra(KEY_TITLE);
int startIndex = intent.getIntExtra(KEY_START_INDEX, 0);
if (gid == -1 || token == null || title == null) {
new DialogBuilder(this).setTitle(R.string.error)
.setMessage("数据错误!").setPositiveButton("关闭",
new View.OnClickListener() {
@Override
public void onClick(View v) {
((AlertButton)v).dialog.dismiss();
finish();
}
}).create().show();
} else {
mImageSet = new ImageSet(gid, token, title, startIndex);
GalleryView isv = new GalleryView(getApplicationContext(), mImageSet, startIndex);
GLRootView glrv= (GLRootView)findViewById(R.id.gl_root_view);
glrv.setContentPane(isv);
}
}
@Override
public void onDestroy() {
super.onDestroy();
mImageSet.free();
}
}
@@ -821,19 +821,13 @@ public class GalleryDetailActivity extends AbstractActivity
MaterialToast.showToast(R.string.toast_add_download);
} else if (v == mReadButton) {
//mData.addRead(mGalleryInfo);
if (mGalleryInfo instanceof GalleryDetail) {
GalleryDetail galleryDetail = (GalleryDetail)mGalleryInfo;
Intent intent = new Intent(this,
GalleryActivity.class);
intent.putExtra("url", galleryDetail.firstPage);
intent.putExtra("gid", mGalleryInfo.gid);
intent.putExtra("title", mGalleryInfo.title);
intent.putExtra("firstPage", 0);
intent.putExtra("pageSum", galleryDetail.pages);
startActivity(intent);
} else {
// TODO
}
Intent intent = new Intent(this,
GalleryActivity.class);
intent.putExtra(GalleryActivity.KEY_GID, mGalleryInfo.gid);
intent.putExtra(GalleryActivity.KEY_TOKEN, mGalleryInfo.token);
intent.putExtra(GalleryActivity.KEY_TITLE, mGalleryInfo.title);
intent.putExtra(GalleryActivity.KEY_START_INDEX, 0);
startActivity(intent);
} else if (v == mCategory) {
finish();
Intent intent = new Intent(this, GalleryListActivity.class);
@@ -1175,7 +1169,7 @@ public class GalleryDetailActivity extends AbstractActivity
PreviewImpl previewImpl = (PreviewImpl)mGalleryInfo;
previewImpl.setPreview(page, pageList);
pageList.setData(GalleryDetailActivity.this,
GalleryDetailActivity.this, previewImpl, page); // TODO how about LofiGalleryDetail
GalleryDetailActivity.this, previewImpl, page);
pageList.addPreview(mPreview);
if (isLastPage && mGalleryInfo instanceof LofiDetailImpl) {
@@ -1059,9 +1059,9 @@ public class GalleryListActivity extends AbstractGalleryActivity
case 5: // Download
// TODO Download Activity
//intent = new Intent(GalleryListActivity.this,
//DownloadActivity.class);
//startActivity(intent);
intent = new Intent(GalleryListActivity.this,
GalleryActivity.class);
startActivity(intent);
break;
case 6: // Settings
@@ -53,10 +53,7 @@ import android.widget.TextView;
import com.hippo.ehviewer.AppContext;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.UpdateHelper;
import com.hippo.ehviewer.ehclient.EhClient;
import com.hippo.ehviewer.ehclient.EhInfo;
import com.hippo.ehviewer.ehclient.ExDownloadManager;
import com.hippo.ehviewer.ehclient.ExDownloader;
import com.hippo.ehviewer.network.Downloader;
import com.hippo.ehviewer.preference.AutoListPreference;
import com.hippo.ehviewer.util.Config;
@@ -114,13 +111,6 @@ public class SettingsActivity extends AbstractPreferenceActivity {
mListView = getListView();
mListView.setClipToPadding(false);
// Test here
ExDownloadManager edm = ExDownloadManager.getInstance();
ExDownloader ed = edm.getExDownloader(728773, "1f90a6e00c", "[BaseSon] Shiki to Hitsuji to Urau Tsuki", EhClient.MODE_EX);
ed.setStartIndex(0);
}
@Override
@@ -67,7 +67,7 @@ public class AutoExpandArray<E> {
@SuppressWarnings("unchecked")
public synchronized E get(int index) {
if (index >= array.length) {
if (index >= array.length || index < 0) {
return null;
}
return (E)array[index];
+51
View File
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2014 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.util;
import java.io.File;
public final class EhUtils {
/**
* Get gallery for read and download
* @param gid
* @param title
* @return
*/
public static File getGalleryDir(int gid, String title) {
return new File(Config.getDownloadPath(),
Utils.standardizeFilename(gid + "-" + title));
}
/**
* Index start from 0
*
* @param index
* @param extension
* @return
*/
public static String getImageFilename(int index, String extension) {
return String.format("%08d.%s", index + 1, extension);
}
public static String[] getPossibleImageFilenames(int index) {
String prefix = String.format("%08d.", index + 1);
return new String[]{prefix + "jpg", prefix + "jpeg", prefix + "png", prefix + "gif"};
}
}
@@ -153,6 +153,67 @@ public final class MathUtils {
* ((value - minStart) / (minStop - minStart));
}
/**
* Returns the input value x clamped to the range [min, max].
*
* @param x
* @param min
* @param max
* @return
*/
public static int clamp(int x, int min, int max) {
if (x > max) return max;
if (x < min) return min;
return x;
}
/**
* Returns the input value x clamped to the range [min, max].
*
* @param x
* @param min
* @param max
* @return
*/
public static float clamp(float x, float min, float max) {
if (x > max) return max;
if (x < min) return min;
return x;
}
/**
* Returns the input value x clamped to the range [min, max].
* @param x
* @param min
* @param max
* @return
*/
public static long clamp(long x, long min, long max) {
if (x > max) return max;
if (x < min) return min;
return x;
}
/**
* Returns the next power of two.
* Returns the input if it is already power of 2.
* Throws IllegalArgumentException if the input is <= 0 or
* the answer overflows.
*
* @param n
* @return
*/
public static int nextPowerOf2(int n) {
if (n <= 0 || n > (1 << 30)) throw new IllegalArgumentException("n is invalid: " + n);
n -= 1;
n |= n >> 16;
n |= n >> 8;
n |= n >> 4;
n |= n >> 2;
n |= n >> 1;
return n + 1;
}
public static int random(int howbig) {
return (int) (sRandom.nextFloat() * howbig);
}
+19 -19
View File
@@ -21,26 +21,26 @@ import android.os.Handler;
import android.os.Message;
public abstract class TimeRunner {
private static final int INTERVAL = 5;
private static final int RUNNING = 0;
private static final int START = 1;
private static final int END = 2;
private boolean mRunInThread = true;
private boolean mListenerInThread = true;
private boolean mRunInThread = false;
private boolean mListenerInThread = false;
private OnTimeListener mListener;
public interface OnTimeListener {
public void onStart();
public void onEnd();
}
// TODO
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -56,30 +56,30 @@ public abstract class TimeRunner {
}
}
};
private int mDuration = 0;
private int mDelay = 0;
public void setOnTimerListener(OnTimeListener listener) {
mListener = listener;
}
public void setDuration(int duration) {
mDuration = duration;
}
public void setRunInThread(boolean runInThread) {
mRunInThread = runInThread;
}
public void setListenerInThread(boolean listenerInThread) {
mListenerInThread = listenerInThread;
}
public void start() {
start(0);
}
public void start(int delay) {
mDelay = delay;
new Thread(new Runnable() {
@@ -99,7 +99,7 @@ public abstract class TimeRunner {
mHandler.sendMessage(msg);
}
}
long startTime = System.currentTimeMillis();
for (int runningTime = 0; runningTime < mDuration; runningTime = (int)(System.currentTimeMillis() - startTime)) {
if (mRunInThread) {
@@ -117,7 +117,7 @@ public abstract class TimeRunner {
e.printStackTrace();
}
}
if (mListener != null) {
if (mListenerInThread) {
mListener.onEnd();
@@ -130,6 +130,6 @@ public abstract class TimeRunner {
}
}).start();
}
protected abstract void run(float interpolatedTime, int runningTime);
}
+72
View File
@@ -30,9 +30,11 @@ import java.util.List;
import java.util.Locale;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Vibrator;
import android.view.View;
public final class Utils {
@@ -581,4 +583,74 @@ public final class Utils {
}
return result.toString();
}
/**
* Try to make dir exists
*
* @param dir
* @param force
*/
public static void ensureDir(File dir, boolean force) {
if (!dir.exists()) {
dir.mkdirs();
} else if (force && dir.isFile()) {
dir.delete();
dir.mkdirs();
}
}
/**
* Get extension from target string, int lower case
*
* @param name
* @param defautl
* @return
*/
public static String getExtension(String name, String defautl) {
int index = name.lastIndexOf('.');
if (index == -1 || index == name.length() - 1)
return defautl;
else
return name.substring(index + 1).toLowerCase();
}
/**
* Return true if point is in the rect
*
* @param area
* @param x
* @param y
* @return
*/
public static boolean isInArea(int[] area, int x, int y) {
if (area.length != 4)
throw new IllegalArgumentException(
"area's length should be 4, but it's length is " + area.length);
if (x >= area[0] && x < area[2] && y >= area[1] && y < area[3])
return true;
else
return false;
}
public static boolean isOpaque(int color) {
return color >>> 24 == 0xFF;
}
/**
* Throws AssertionError if the input is false.
* @param cond
*/
public static void assertTrue(boolean cond) {
if (!cond) {
throw new AssertionError();
}
}
/**
* vibrator
*/
public static void vibrator(Context context, long milliseconds) {
Vibrator v = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
v.vibrate(milliseconds);
}
}