From a2bbe855e8608c82c2e2c8ac38a41e02cfeeffc1 Mon Sep 17 00:00:00 2001 From: seven332 Date: Tue, 19 Aug 2014 16:24:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=98=85=E8=AF=BB=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AndroidManifest.xml | 1 + src/com/hippo/ehviewer/AppContext.java | 4 +- .../hippo/ehviewer/GalleryDownloadInfo.java | 43 - .../hippo/ehviewer/GallerysDownloader.java | 311 ------- src/com/hippo/ehviewer/ImageLoader.java | 1 + .../hippo/ehviewer/data/LargePreviewList.java | 12 +- .../ehviewer/data/NormalPreviewList.java | 12 +- src/com/hippo/ehviewer/ehclient/EhClient.java | 342 -------- .../hippo/ehviewer/ehclient/ExDownloader.java | 333 +++++--- ...dManager.java => ExDownloaderManager.java} | 16 +- .../ehviewer/ehclient/ImagePageParser.java | 45 +- .../hippo/ehviewer/gallery/GalleryView.java | 788 ++++++++++-------- .../ehviewer/gallery/anim/AlphaAnimation.java | 4 +- .../ehviewer/gallery/anim/Animation.java | 4 +- .../hippo/ehviewer/gallery/common/Utils.java | 340 -------- .../gallery/data/DownloadImageSet.java | 60 -- .../hippo/ehviewer/gallery/data/ImageSet.java | 400 ++++----- .../gallery/glrenderer/BasicTexture.java | 20 +- .../gallery/glrenderer/ColorTexture.java | 2 +- .../gallery/glrenderer/FadeTexture.java | 4 +- .../gallery/glrenderer/GLES11Canvas.java | 39 +- .../gallery/glrenderer/GLES20Canvas.java | 44 +- .../gallery/glrenderer/NinePatchTexture.java | 18 +- .../gallery/glrenderer/UploadedTexture.java | 17 +- .../hippo/ehviewer/gallery/ui/GLRootView.java | 28 +- src/com/hippo/ehviewer/gallery/ui/GLView.java | 8 +- .../ehviewer/gallery/util/GalleryUtils.java | 204 +---- .../hippo/ehviewer/network/HttpHelper.java | 23 +- .../ehviewer/service/DownloadService.java | 10 +- .../hippo/ehviewer/ui/GalleryActivity.java | 44 +- .../ehviewer/ui/GalleryDetailActivity.java | 22 +- .../ehviewer/ui/GalleryListActivity.java | 6 +- .../hippo/ehviewer/ui/SettingsActivity.java | 10 - .../hippo/ehviewer/util/AutoExpandArray.java | 2 +- src/com/hippo/ehviewer/util/EhUtils.java | 51 ++ src/com/hippo/ehviewer/util/MathUtils.java | 61 ++ src/com/hippo/ehviewer/util/TimeRunner.java | 38 +- src/com/hippo/ehviewer/util/Utils.java | 72 ++ 38 files changed, 1297 insertions(+), 2142 deletions(-) delete mode 100644 src/com/hippo/ehviewer/GalleryDownloadInfo.java delete mode 100644 src/com/hippo/ehviewer/GallerysDownloader.java rename src/com/hippo/ehviewer/ehclient/{ExDownloadManager.java => ExDownloaderManager.java} (84%) delete mode 100644 src/com/hippo/ehviewer/gallery/common/Utils.java delete mode 100644 src/com/hippo/ehviewer/gallery/data/DownloadImageSet.java create mode 100644 src/com/hippo/ehviewer/util/EhUtils.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f8e51485d..846025d05 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -26,6 +26,7 @@ + mRequstDetailPage; - private final Queue 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(); - mRequstPage = new LinkedList(); - } - - - - - 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 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); - } - */ -} diff --git a/src/com/hippo/ehviewer/ImageLoader.java b/src/com/hippo/ehviewer/ImageLoader.java index 6ee76720e..6ea547b0f 100644 --- a/src/com/hippo/ehviewer/ImageLoader.java +++ b/src/com/hippo/ehviewer/ImageLoader.java @@ -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); diff --git a/src/com/hippo/ehviewer/data/LargePreviewList.java b/src/com/hippo/ehviewer/data/LargePreviewList.java index 6750577f6..7116d22f6 100644 --- a/src/com/hippo/ehviewer/data/LargePreviewList.java +++ b/src/com/hippo/ehviewer/data/LargePreviewList.java @@ -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); } }); diff --git a/src/com/hippo/ehviewer/data/NormalPreviewList.java b/src/com/hippo/ehviewer/data/NormalPreviewList.java index 3bfd6f76d..24731b629 100644 --- a/src/com/hippo/ehviewer/data/NormalPreviewList.java +++ b/src/com/hippo/ehviewer/data/NormalPreviewList.java @@ -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); } }); diff --git a/src/com/hippo/ehviewer/ehclient/EhClient.java b/src/com/hippo/ehviewer/ehclient/EhClient.java index 89edb6dcc..3298d3664 100644 --- a/src/com/hippo/ehviewer/ehclient/EhClient.java +++ b/src/com/hippo/ehviewer/ehclient/EhClient.java @@ -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 mDownloadQueue = new ArrayList(); - 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("

Showing [\\d|,]+ - [\\d|,]+ of ([\\d|,]+) images

.+
]*>(?:]*>)?]*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("]*id=\"prev\"[^<>]*href=\"([^<>\"]+)\">]*/>.+]id=\"next\"[^<>]*href=\"([^<>\"]+)\">]*/>.+]*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 comments); diff --git a/src/com/hippo/ehviewer/ehclient/ExDownloader.java b/src/com/hippo/ehviewer/ehclient/ExDownloader.java index 5e5d987d6..7f7e3076a 100644 --- a/src/com/hippo/ehviewer/ehclient/ExDownloader.java +++ b/src/com/hippo/ehviewer/ehclient/ExDownloader.java @@ -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 mPageTokeArray = new AutoExpandArray(); + private final AutoExpandArray mImageFilenameArray = new AutoExpandArray(); 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 mRequestIndexArray = new ConcurrentLinkedQueue(); private final Queue mNoTokenIndexArray = new ConcurrentLinkedQueue(); private final Queue mRequestPageIndexArray = new ConcurrentLinkedQueue(); + + /** The set contains the requsting index **/ + private final Set mRequstingIndexSet = new HashSet(); + 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 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; } } } diff --git a/src/com/hippo/ehviewer/ehclient/ExDownloadManager.java b/src/com/hippo/ehviewer/ehclient/ExDownloaderManager.java similarity index 84% rename from src/com/hippo/ehviewer/ehclient/ExDownloadManager.java rename to src/com/hippo/ehviewer/ehclient/ExDownloaderManager.java index aced35d8b..5612aa43a 100644 --- a/src/com/hippo/ehviewer/ehclient/ExDownloadManager.java +++ b/src/com/hippo/ehviewer/ehclient/ExDownloaderManager.java @@ -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 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(); // 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()); + } } } diff --git a/src/com/hippo/ehviewer/ehclient/ImagePageParser.java b/src/com/hippo/ehviewer/ehclient/ImagePageParser.java index 3e499804d..830d17e5d 100644 --- a/src/com/hippo/ehviewer/ehclient/ImagePageParser.java +++ b/src/com/hippo/ehviewer/ehclient/ImagePageParser.java @@ -33,19 +33,42 @@ public class ImagePageParser { token = null; } - public boolean parser(String body) { - Pattern p = Pattern.compile("
.+?
]+>"); + m = p.matcher(body); + if (m.find()) + imageUrl = Utils.unescapeXml(m.group(1)); + else + return false; + + p = Pattern.compile("Back"); + 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("
.+?
= mSize) - mCurIndex = 0; - + mCurIndex = MathUtils.clamp(mCurIndex, 0, mSize - 1); + setState(); - + mGestureRecognizer = new GestureRecognizer(mContext, new MyGestureListener()); setBackgroundColor(GalleryUtils.intColorToFloatARGBArray(BACKGROUND_COLOR)); - + showItems = new ShowItem[TARGET_INDEX_SIZE]; - + // Init config scaleMode = Config.getPageScalingMode(); startMode = Config.getStartPosition(); - - // - imageSet.setOnStateChangeListener(new ImageSet.OnStateChangeListener() { - @Override - public void onStateChange(int index, int state) { - int targetIndex = index - mCurIndex + 1; - if (targetIndex >= PRE_TARGET_INDEX - && targetIndex <= NEXT_TARGET_INDEX) { - loadImage(index); - } - } - }); + + mImageSet.setImageListener(this); + + // Start task to check size + AppHandler.getInstance().postDelayed(this, 500); + + // Try to make current index show image + if (mCurIndex != startIndex) + mImageSet.setTargetIndex(mCurIndex); + + // First time to load image in onLayout() } - + + // The task to update size + @Override + public void run() { + checkSize(); + if (!isEnsureSize && !mImageSet.isStop()) + AppHandler.getInstance().postDelayed(this, 500); + } + + /** + * Check size + */ + private void checkSize() { + int size = mImageSet.getSize(); + if (size == -1) { + isEnsureSize = false; + mSize = mImageSet.getEnsureSize(); + } else { + isEnsureSize = true; + mSize = size; + } + } + + /** + * Set current page state, first page or last page + */ private void setState() { mState = STATE_NONE; if (mCurIndex == 0) - mState |= STATE_FIRST; + mState = STATE_FIRST; if (mCurIndex == mSize - 1) - mState |= STATE_LAST; + mState = STATE_LAST; } - + + private boolean checkIndex(int index) { + int targetIndex = index - mCurIndex + 1; + return targetIndex == CUR_TARGET_INDEX || + (mState != STATE_FIRST && targetIndex == PRE_TARGET_INDEX) || + (mState != STATE_LAST && targetIndex == NEXT_TARGET_INDEX); + } + + @Override + public void onGetImage(int index) { + if (checkIndex(index)) { + loadImage(index); + } + } + + @Override + public void onDownloading(int index, float percent) { + int targetIndex = index - mCurIndex + 1; + if (checkIndex(index)) { + // Free what do not need + ShowItem showItem = showItems[targetIndex]; + if (showItem != null) + showItem.recycle(); + + showItems[targetIndex] = new Text( + String.format("正在加载 %d%%", Math.round(percent * 100))); + resetSizePosition(targetIndex); + } + } + + @Override + public void onDecodeOver(int index, Object res) { + int targetIndex = index - mCurIndex + 1; + + if (!checkIndex(index)) { + // If it do not need any more, free + if (res != null && res instanceof Bitmap) + ((Bitmap)res).recycle(); + } else { + // Free what do not need + ShowItem showItem = showItems[targetIndex]; + if (showItem != null) + showItem.recycle(); + + if (res == null) { + showItems[targetIndex] = new Text("读取图片错误"); // TODO + } else { + if (res instanceof Bitmap) { + BitmapImage bi = new BitmapImage(); + bi.load((Bitmap)res); + showItems[targetIndex] = bi; + } else { + MovieImage mi = new MovieImage(); + mi.load((Movie)res); + showItems[targetIndex] = mi; + } + } + resetSizePosition(targetIndex); + } + } + private void drawTapArea(GLCanvas canvas) { // Background canvas.fillRect(0, 0, mWidth, mHeight, TAP_AREA_MASK_COLOR); - + drawRect(canvas, leftArea); drawRect(canvas, topArea); drawRect(canvas, rightArea); @@ -245,25 +328,25 @@ public class GalleryView extends GLView { bottomText.draw(canvas); centerText.draw(canvas); } - + private void drawRect(GLCanvas canvas, int[] rect) { - + int left = rect[0]; int top = rect[1]; int right = rect[2]; int bottom = rect[3]; - + GLPaint paint = new GLPaint(LINE_COLOR, LINE_WIDTH); canvas.drawLine(left, top, right, top, paint); canvas.drawLine(left, bottom, right, bottom, paint); canvas.drawLine(left, top, left, bottom, paint); canvas.drawLine(right, top, right, bottom, paint); } - + @Override protected void render(GLCanvas canvas) { super.render(canvas); - + boolean hasMovie = false; ShowItem item; switch (mScrollState) { @@ -274,7 +357,7 @@ public class GalleryView extends GLView { if (item instanceof MovieImage) hasMovie |= true; break; - + case SCROLL_LEFT: case SCROLL_ANIME_LEFT: item = showItems[PRE_TARGET_INDEX]; @@ -282,14 +365,14 @@ public class GalleryView extends GLView { item.draw(canvas, scrollXOffset, scrollYOffset); if (item instanceof MovieImage) hasMovie |= true; - + item = showItems[CUR_TARGET_INDEX]; if (item != null) item.draw(canvas, scrollXOffset, scrollYOffset); if (item instanceof MovieImage) hasMovie |= true; break; - + case SCROLL_RIGHT: case SCROLL_ANIME_RIGHT: item = showItems[CUR_TARGET_INDEX]; @@ -297,7 +380,7 @@ public class GalleryView extends GLView { item.draw(canvas, scrollXOffset, scrollYOffset); if (item instanceof MovieImage) hasMovie |= true; - + item = showItems[NEXT_TARGET_INDEX]; if (item != null) item.draw(canvas, scrollXOffset, scrollYOffset); @@ -305,30 +388,29 @@ public class GalleryView extends GLView { hasMovie |= true; break; } - + // Add mInit to make show the leftArea or other is not null if (mShowTapArea && mInit) drawTapArea(canvas); - + // TODO Mask to reduce brightness //canvas.fillRect(0, 0, mWidth, mHeight, MASK_COLOR); - - + if (hasMovie) invalidate(); } - + private void resetSizePosition() { resetSizePosition(PRE_TARGET_INDEX); resetSizePosition(CUR_TARGET_INDEX); resetSizePosition(NEXT_TARGET_INDEX); } - + private void resetSizePosition(int targeIndex) { ShowItem showItem = showItems[targeIndex]; if (showItem == null) return; - + int sumXOffset; switch (targeIndex) { case PRE_TARGET_INDEX: @@ -342,7 +424,7 @@ public class GalleryView extends GLView { sumXOffset = 0; break; } - + if (showItem instanceof Text) { Text text = (Text)showItem; int xOffset; @@ -354,12 +436,9 @@ public class GalleryView extends GLView { rect.top = yOffset; rect.right = rect.left + text.width; rect.bottom = rect.top + text.height; - return; - } - - if (showItem instanceof Image) { + } else if (showItem instanceof Image) { Image image = (Image)showItem; - + // Set scale int showWidth = 0; int showHeight = 0; @@ -401,7 +480,7 @@ public class GalleryView extends GLView { showHeight = (int)(image.height * mScale); break; } - + // adjust scale if (image.imageScale < SCALE_MIN) { image.imageScale = SCALE_MIN; @@ -412,7 +491,7 @@ public class GalleryView extends GLView { showWidth = (int)(image.width * SCALE_MAX); showHeight = (int)(image.height * SCALE_MAX); } - + // set start position int xOffset; int yOffset; @@ -439,31 +518,31 @@ public class GalleryView extends GLView { yOffset = (mHeight - showHeight)/2; break; } - + rect.left = sumXOffset + xOffset; rect.right = rect.left + showWidth; rect.top = yOffset; rect.bottom = rect.top + showHeight; - + // adjust position adjustPosition(image); } - + invalidate(); } - + private void setCenterInArea(int[] area, ShowItem showItem) { int xOffset = ((area[2] - area[0]) - showItem.width)/2; int yOffset = ((area[3] - area[1]) - showItem.height)/2; - + Rect rect = showItem.mRect; - + rect.left = area[0] + xOffset; rect.right = rect.left + showItem.width; rect.top = area[1] + yOffset; rect.bottom = rect.top + showItem.height; } - + private void setTapArea() { leftArea = new int[]{ (int)(LEFT_AREA_SCALE[0] * mWidth), @@ -490,7 +569,7 @@ public class GalleryView extends GLView { (int)(CENTER_AREA_SCALE[1] * mHeight), (int)(CENTER_AREA_SCALE[2] * mWidth), (int)(CENTER_AREA_SCALE[3] * mHeight)}; - + if (leftText == null) leftText = new Text(mContext.getString(R.string.pre_page), TAP_AREA_TEXT_SIZE, TAP_AREA_TEXT_COLOR); @@ -506,20 +585,21 @@ public class GalleryView extends GLView { if (centerText == null) centerText = new Text(mContext.getString(R.string.menu), TAP_AREA_TEXT_SIZE, TAP_AREA_TEXT_COLOR); - + setCenterInArea(leftArea, leftText); setCenterInArea(topArea, topText); setCenterInArea(rightArea, rightText); setCenterInArea(bottomArea, bottomText); setCenterInArea(centerArea, centerText); } - + @Override protected void onLayout( boolean changeSize, int left, int top, int right, int bottom) { + // Get View size mWidth = right - left; mHeight = bottom - top; - + if (!mInit) { mInit = true; loadImage(mCurIndex); @@ -530,39 +610,46 @@ public class GalleryView extends GLView { } else { resetSizePosition(); } + + // When screen orientation change, need to reset tap area setTapArea(); + invalidate(); } - + + /** + * Requset target image from ImageSet, + * + * @param index + */ private void loadImage(int index) { + if (!checkIndex(index)) + return; + int targetIndex = index - mCurIndex + 1; - int state = mImageSet.getImage(index, new DecodeImageListener()); - ShowItem showItem = showItems[targetIndex]; - if (showItem != null) - showItem.recycle(); - if (state == ImageSet.STATE_LOADED) - showItems[targetIndex] = new EmptyItem(); - else { - showItems[targetIndex] = new Text(getErrorStateString(state)); + Object obj = mImageSet.getImage(index); + + if (showItems[targetIndex] != null) + showItems[targetIndex].recycle(); + + if (obj instanceof Integer) { + int state = (Integer)obj; + if (state == ImageSet.RESULT_DECODE) { + // TODO maybe i should show a decode text + showItems[targetIndex] = new EmptyItem(); + } else if (state == ImageSet.RESULT_DOWNLOADING) { + showItems[targetIndex] = new Text("正在下载"); // TODO + } else if (state == ImageSet.RESULT_NONE) { + showItems[targetIndex] = new Text("尚未加载"); // TODO + } + } else if (obj instanceof Float) { + showItems[targetIndex] = new Text( + String.format("正在加载 %d%%", Math.round((Float)obj * 100))); } + resetSizePosition(targetIndex); } - - private String getErrorStateString(int state) { - switch (state) { - case ImageSet.INVALID_ID: - return "无效的 index"; - case ImageSet.STATE_NONE: - return "尚未加载"; - case ImageSet.STATE_LOADING: - return "正在加载"; - case ImageSet.STATE_FAIL: - return "加载失败"; - default: - return "错误的代码"; - } - } - + /** * If cur page is first page return true * @return @@ -570,7 +657,7 @@ public class GalleryView extends GLView { private boolean isFirstPage() { return (mState & STATE_FIRST) != 0; } - + /** * If cur page is last page return true * @return @@ -578,37 +665,9 @@ public class GalleryView extends GLView { private boolean isLastPage() { return (mState & STATE_LAST) != 0; } - - class DecodeImageListener implements ImageSet.OnDecodeOverListener { - @Override - public void onDecodeOver(Object res, int index) { - int targetIndex = index - mCurIndex + 1; - - if (targetIndex < 0 || targetIndex > 2 - || !(showItems[targetIndex] instanceof EmptyItem)) {// If it do not need any more - if (res != null && res instanceof Bitmap) - ((Bitmap)res).recycle(); - } else { - if (res == null) { - showItems[targetIndex] = new Text("读取图片错误"); // TODO - } else { - if (res instanceof Bitmap) { - BitmapImage bi = new BitmapImage(); - bi.load((Bitmap)res); - showItems[targetIndex] = bi; - } else { - MovieImage mi = new MovieImage(); - mi.load((Movie)res); - showItems[targetIndex] = mi; - } - } - resetSizePosition(targetIndex); - } - } - } - + /** - * + * * @param mode * true zoom in, false zoom out * @return @@ -616,7 +675,7 @@ public class GalleryView extends GLView { private boolean zoom(boolean mode) { ShowItem curShowItem; curShowItem = showItems[CUR_TARGET_INDEX]; - + if (curShowItem == null || !(curShowItem instanceof Image)) return false; Image image = (Image)curShowItem; @@ -637,21 +696,21 @@ public class GalleryView extends GLView { } image.imageScale = newScale; mScale = newScale; - + Rect rect = image.mRect; int width = (int)(image.width * newScale); int height = (int)(image.height * newScale); int xOffset = (width - rect.width())/2; int yOffset = (height - rect.height())/2; - + rect.set(rect.left - xOffset, rect.top - yOffset, rect.right + xOffset, rect.bottom + yOffset); - + adjustPosition(curShowItem); - + invalidate(); return true; } - + /** * You'd better resetSizePosition(PRE_TARGET_INDEX) before * @return @@ -659,7 +718,7 @@ public class GalleryView extends GLView { private boolean goToPrePage() { if (isFirstPage()) return false; - + ShowItem showItem; showItem = showItems[NEXT_TARGET_INDEX]; if (showItem != null) @@ -667,7 +726,7 @@ public class GalleryView extends GLView { showItems[NEXT_TARGET_INDEX] = showItems[CUR_TARGET_INDEX]; showItems[CUR_TARGET_INDEX] = showItems[PRE_TARGET_INDEX]; showItems[PRE_TARGET_INDEX] = null; - + // adjust rect showItem = showItems[NEXT_TARGET_INDEX]; if (showItem != null) { @@ -677,19 +736,19 @@ public class GalleryView extends GLView { if (showItem != null) { showItem.mRect.offset(mWidth, 0); } - + mCurIndex--; setState(); loadImage(mCurIndex-1); - + if (mOnScrollPageListener != null) { mOnScrollPageListener.onScrollPage(mCurIndex); } - + invalidate(); return true; } - + /** * You'd better resetSizePosition(NEXT_TARGET_INDEX) before * @return @@ -697,7 +756,7 @@ public class GalleryView extends GLView { private boolean goToNextPage() { if (isLastPage()) return false; - + ShowItem showItem; showItem = showItems[PRE_TARGET_INDEX]; if (showItem != null) @@ -714,27 +773,53 @@ public class GalleryView extends GLView { if (showItem != null) { showItem.mRect.offset(-mWidth, 0); } - + mCurIndex++; setState(); loadImage(mCurIndex+1); - + if (mOnScrollPageListener != null) { mOnScrollPageListener.onScrollPage(mCurIndex); } - + invalidate(); return true; } - + @Override protected boolean onTouch(MotionEvent event) { mGestureRecognizer.onTouchEvent(event); return true; } - + private class MyGestureListener implements GestureRecognizer.Listener { - + + private boolean isScale = false; + + private final TimeRunner mToPreTimeRunner = new TimeRunner() { + @Override + protected void run(float interpolatedTime, int runningTime) { + scrollXOffset = (int)(stopScrollXOffset + ((mWidth - stopScrollXOffset) * interpolatedTime)); + invalidate(); + } + }; + + private final TimeRunner mToNextTimeRunner = new TimeRunner() { + @Override + protected void run(float interpolatedTime, int runningTime) { + scrollXOffset = (int)(stopScrollXOffset + ((-mWidth - stopScrollXOffset) * interpolatedTime)); + invalidate(); + } + }; + + private final TimeRunner mReturnTimeRunner = new TimeRunner() { + @Override + protected void run(float interpolatedTime, int runningTime) { + scrollXOffset = (int)((1 - interpolatedTime) * stopScrollXOffset); + invalidate(); + } + }; + public MyGestureListener() { mToPreTimeRunner.setOnTimerListener(new TimeRunner.OnTimeListener() { @Override @@ -767,17 +852,7 @@ public class GalleryView extends GLView { } }); } - - private 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; - } - + @Override public boolean onSingleTapConfirmed(float x, float y) { if (mScrollState != SCROLL_NONE) { @@ -787,77 +862,80 @@ public class GalleryView extends GLView { mShowTapAreaTurn = false; return false; } - + if ( showItems[CUR_TARGET_INDEX] == null || showItems[CUR_TARGET_INDEX] instanceof Text) { if (mOnTapTextListener != null) mOnTapTextListener.onTapText(mCurIndex); //return true; } - + if (leftArea == null || topArea == null || rightArea == null || bottomArea == null || centerArea == null) return true; - - if (isInArea(leftArea, (int)x, (int)y)) { + + if (Utils.isInArea(leftArea, (int)x, (int)y)) { // TODO goto bottom first the to pre page resetSizePosition(PRE_TARGET_INDEX); if (!goToPrePage() && mOnEdgeListener != null) mOnEdgeListener.onFirstPageEdge(); - } else if (isInArea(topArea, (int)x, (int)y)) { + } else if (Utils.isInArea(topArea, (int)x, (int)y)) { zoom(true); - } else if (isInArea(rightArea, (int)x, (int)y)) { + } else if (Utils.isInArea(rightArea, (int)x, (int)y)) { // TODO goto bottom first the to pre page resetSizePosition(NEXT_TARGET_INDEX); if (!goToNextPage() && mOnEdgeListener != null) mOnEdgeListener.onLastPageEdge(); - } else if (isInArea(bottomArea, (int)x, (int)y)) { + } else if (Utils.isInArea(bottomArea, (int)x, (int)y)) { zoom(false); - } else if (isInArea(centerArea, (int)x, (int)y)) { + } else if (Utils.isInArea(centerArea, (int)x, (int)y)) { mShowTapArea = true; mShowTapAreaTurn = true; invalidate(); } else { // Can't catch tap } - + return true; } - + @Override public boolean onDoubleTap(float x, float y) { Log.d(TAG, "onDoubleTap"); return true; } - + @Override public void onLongPress(MotionEvent e) { - // TODO Auto-generated method stub - + if (isScale) + return; + + Utils.vibrator(mContext, 100); + mImageSet.setTargetIndex(mCurIndex); } - + @Override public boolean onScrollBegin(float dx, float dy, float totalX, float totalY) { return this.onScroll(dx, dy, totalX, totalY); } - + /** * dx 和 totalX 符号相反,为啥 */ @Override public boolean onScroll(float dx, float dy, float totalX, float totalY) { - + if (isScale) return false; - + ShowItem curShowItem = showItems[CUR_TARGET_INDEX]; if (curShowItem == null) // If not load now return true; - + switch(mScrollState) { case SCROLL_ANIME_LEFT: case SCROLL_ANIME_RIGHT: @@ -865,7 +943,7 @@ public class GalleryView extends GLView { case SCROLL_NONE: boolean changePage = false; Rect rect = curShowItem.mRect; - + // Check change page or not if (curShowItem == null || !(curShowItem instanceof Image)) changePage = true; @@ -912,12 +990,12 @@ public class GalleryView extends GLView { if (rect.height() <= mHeight) actDy = 0; rect.offset(actDx, actDy); - + // Fix position adjustPosition(curShowItem); } break; - + case SCROLL_LEFT: scrollXOffset -= dx; if (scrollXOffset <= 0) { @@ -925,7 +1003,7 @@ public class GalleryView extends GLView { mScrollState = SCROLL_NONE; } break; - + case SCROLL_RIGHT: scrollXOffset -= dx; if (scrollXOffset >= 0) { @@ -934,26 +1012,26 @@ public class GalleryView extends GLView { } break; } - + invalidate(); return true; } - + @Override public boolean onScrollEnd() { - + if (isScale) return false; - + switch(mScrollState) { case SCROLL_ANIME_LEFT: case SCROLL_ANIME_RIGHT: return false; - + case SCROLL_LEFT: stopScrollXOffset = scrollXOffset; stopScrollYOffset = scrollYOffset; - + mScrollState = SCROLL_ANIME_LEFT; if (stopScrollXOffset > mWidth * CHANGE_PAGE_PROPORTION) { // Go to pre page mToPreTimeRunner.setDuration((int)(MILLSEC_PER_DIX * (mWidth-stopScrollXOffset))); @@ -966,7 +1044,7 @@ public class GalleryView extends GLView { case SCROLL_RIGHT: stopScrollXOffset = scrollXOffset; stopScrollYOffset = scrollYOffset; - + mScrollState = SCROLL_ANIME_RIGHT; if (-stopScrollXOffset > mWidth * CHANGE_PAGE_PROPORTION) { // Go to next page mToNextTimeRunner.setDuration((int)(MILLSEC_PER_DIX * (mWidth+stopScrollXOffset))); @@ -979,7 +1057,7 @@ public class GalleryView extends GLView { } return true; } - + @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { @@ -996,39 +1074,39 @@ public class GalleryView extends GLView { public boolean onScale(float focusX, float focusY, float scale) { if (mScrollState == SCROLL_ANIME_LEFT || mScrollState == SCROLL_ANIME_RIGHT) return true; - + if (mScrollState == SCROLL_LEFT || mScrollState == SCROLL_RIGHT) { scrollXOffset = 0; scrollYOffset = 0; mScrollState = SCROLL_NONE; } - + ShowItem curShowItem = showItems[CUR_TARGET_INDEX]; if (curShowItem == null || !(curShowItem instanceof Image)) return true; - + Image image = (Image)curShowItem; - + float newScale = image.imageScale * scale; if (newScale > SCALE_MAX || newScale < SCALE_MIN) return true; image.imageScale = newScale; mScale = newScale; - + Rect rect = image.mRect; int left = rect.left; int top = rect.top; - + left = (int)(focusX - ((focusX - left) * scale)); top = (int)(focusY - ((focusY - top) * scale)); - + rect.set(left, top, (int)(left + (image.width * image.imageScale)), (int)(top + (image.height * image.imageScale))); - + // adjust adjustPosition(image); - + invalidate(); - + return true; } @@ -1046,55 +1124,30 @@ public class GalleryView extends GLView { @Override public void onUp() { mShowEdegTip = true; - - + + // if use onSingleTapUp, use below /* * if (mShowTapAreaTurn) { mShowTapAreaTurn = false; */ } - - // *** TimeRunner *** // - TimeRunner mToPreTimeRunner = new TimeRunner() { - @Override - protected void run(float interpolatedTime, int runningTime) { - scrollXOffset = (int)(stopScrollXOffset + ((mWidth - stopScrollXOffset) * interpolatedTime)); - invalidate(); - } - }; - - TimeRunner mToNextTimeRunner = new TimeRunner() { - @Override - protected void run(float interpolatedTime, int runningTime) { - scrollXOffset = (int)(stopScrollXOffset + ((-mWidth - stopScrollXOffset) * interpolatedTime)); - invalidate(); - } - }; - - TimeRunner mReturnTimeRunner = new TimeRunner() { - @Override - protected void run(float interpolatedTime, int runningTime) { - scrollXOffset = (int)((1 - interpolatedTime) * stopScrollXOffset); - invalidate(); - } - }; } - + /** * If side is shorter then parent's, make it in parent's center * If side is longer then parent's, make sure it fill parent - * + * * @param showItem */ private void adjustPosition(ShowItem showItem) { Rect rect = showItem.mRect; int showWidth = rect.width(); int showHeight = rect.height(); - + int sumXOffset; int sumYOffset = 0; - + int targetIndex = getTargetIndex(showItem); switch (targetIndex) { case PRE_TARGET_INDEX: @@ -1107,7 +1160,7 @@ public class GalleryView extends GLView { default: sumXOffset = 0; } - + if (showWidth > mWidth) { int fixXOffset = rect.left - sumXOffset; if (fixXOffset > 0) { @@ -1135,7 +1188,7 @@ public class GalleryView extends GLView { rect.offsetTo(rect.left, top); } } - + public int getTargetIndex(ShowItem showItem) { if (mImageSet == null) return INVALID_ID; @@ -1145,7 +1198,7 @@ public class GalleryView extends GLView { } return INVALID_ID; } - + private abstract class ShowItem { protected int width = 1; protected int height = 1; @@ -1156,15 +1209,15 @@ public class GalleryView extends GLView { public abstract void draw(GLCanvas canvas, int xOffset, int yOffset); public abstract void recycle(); } - + private class EmptyItem extends ShowItem{ - + private ColorTexture mTexture; - + public EmptyItem() { mTexture = new ColorTexture(BACKGROUND_COLOR); } - + @Override public void recycle() { mTexture = null; @@ -1172,15 +1225,16 @@ public class GalleryView extends GLView { @Override public void draw(GLCanvas canvas, int xOffset, int yOffset) { - mTexture.draw(canvas, xOffset, yOffset); + if (mTexture != null) + mTexture.draw(canvas, xOffset, yOffset); } } - - + + private abstract class Image extends ShowItem{ private UploadedTexture mTexture; public float imageScale = 1; - + /** * You must call init before draw * @param texture @@ -1189,92 +1243,92 @@ public class GalleryView extends GLView { mTexture = texture; imageScale = 1; } - + @Override public void draw(GLCanvas canvas, int xOffset, int yOffset) { - if (mTexture != null) { - int targetIndex; - if ((targetIndex = getTargetIndex(this)) == INVALID_ID) - mTexture.draw(canvas, mRect.left + xOffset, mRect.top + yOffset, mRect.width(), mRect.height()); - else { - - int leftBound = mWidth; - int topBound = 0; - int rightBound = 2 * mWidth; - int bottomBound = mHeight; - - switch (targetIndex) { - case PRE_TARGET_INDEX: - leftBound = -leftBound; - rightBound = 0; - break; - case CUR_TARGET_INDEX: - rightBound = leftBound; - leftBound = 0; - break; - case NEXT_TARGET_INDEX: - default: - break; - } - - int left = mRect.left; - int top = mRect.top; - int right = mRect.right; - int bottom = mRect.bottom; - - // Only show what in the own box - // TODO only what can be seen - if (left < leftBound || top < topBound || right > rightBound || bottom > bottomBound) { - RectF source = new RectF(); - RectF target = new RectF(); - - if (left < leftBound) { - target.left = leftBound; - source.left = (leftBound - left) / imageScale; - } else { - target.left = left; - source.left = 0; - } - if (top < topBound) { - target.top = topBound; - source.top = (topBound - top) / imageScale; - } else { - target.top = top; - source.top = 0; - } - if (right > rightBound) { - target.right = rightBound; - source.right = width - ((right - rightBound) / imageScale); - } else { - target.right = right; - source.right = width; - } - if (bottom > bottomBound) { - target.bottom = bottomBound; - source.bottom = height - ((bottom - bottomBound) / imageScale); - } else { - target.bottom = bottom; - source.bottom = height; - } - - target.left += xOffset; - target.top += yOffset; - target.right += xOffset; - target.bottom += yOffset; - - mTexture.draw(canvas, source, target); + if (mTexture == null) + return; + + int targetIndex; + if ((targetIndex = getTargetIndex(this)) == INVALID_ID) + mTexture.draw(canvas, mRect.left + xOffset, mRect.top + yOffset, mRect.width(), mRect.height()); + else { + int leftBound = mWidth; + int topBound = 0; + int rightBound = 2 * mWidth; + int bottomBound = mHeight; + + switch (targetIndex) { + case PRE_TARGET_INDEX: + leftBound = -leftBound; + rightBound = 0; + break; + case CUR_TARGET_INDEX: + rightBound = leftBound; + leftBound = 0; + break; + case NEXT_TARGET_INDEX: + default: + break; + } + + int left = mRect.left; + int top = mRect.top; + int right = mRect.right; + int bottom = mRect.bottom; + + // Only show what in the own box + // TODO only what can be seen + if (left < leftBound || top < topBound || right > rightBound || bottom > bottomBound) { + RectF source = new RectF(); + RectF target = new RectF(); + + if (left < leftBound) { + target.left = leftBound; + source.left = (leftBound - left) / imageScale; } else { - mTexture.draw(canvas, left + xOffset, top + yOffset, right - left, bottom - top); + target.left = left; + source.left = 0; } + if (top < topBound) { + target.top = topBound; + source.top = (topBound - top) / imageScale; + } else { + target.top = top; + source.top = 0; + } + if (right > rightBound) { + target.right = rightBound; + source.right = width - ((right - rightBound) / imageScale); + } else { + target.right = right; + source.right = width; + } + if (bottom > bottomBound) { + target.bottom = bottomBound; + source.bottom = height - ((bottom - bottomBound) / imageScale); + } else { + target.bottom = bottom; + source.bottom = height; + } + + target.left += xOffset; + target.top += yOffset; + target.right += xOffset; + target.bottom += yOffset; + + mTexture.draw(canvas, source, target); + } else { + mTexture.draw(canvas, left + xOffset, top + yOffset, right - left, bottom - top); } } } } - + private class BitmapImage extends Image{ private BitmapTexture mTexture; private Bitmap mContextBmp; - + public void load(Bitmap bmp) { if (mTexture != null) recycle(); @@ -1282,66 +1336,66 @@ public class GalleryView extends GLView { mTexture = new BitmapTexture(bmp); width = mContextBmp.getWidth(); height = mContextBmp.getHeight(); - + super.init(mTexture); } - + @Override public void recycle() { if (mTexture == null) return; - + mTexture.recycle(); mContextBmp.recycle(); - + mTexture = null; mContextBmp = null; } } - + private class MovieImage extends Image{ private MovieTexture mTexture; private Movie mContextMovie; - + public void load(Movie movie) { if (mTexture != null) recycle(); mContextMovie = movie; mTexture = MovieTexture.newInstance(mContextMovie); - + width = mContextMovie.width(); height = mContextMovie.height(); - + super.init(mTexture); } - + @Override public void recycle() { if (mTexture == null) return; - + mTexture.recycle(); - + mTexture = null; mContextMovie = null; } } - + private class Text extends ShowItem{ private StringTexture mTexture; - + public Text(String str) { load(str); } - + public Text(String str, float size, int color) { load(str, size, color); } - + public void load(String str) { load(str, 100, -1); } - + public void load(String str, float size, int color) { if (mTexture != null) recycle(); @@ -1349,13 +1403,13 @@ public class GalleryView extends GLView { width = mTexture.getWidth(); height = mTexture.getHeight(); } - + @Override public void draw(GLCanvas canvas, int xOffset, int yOffset) { if (mTexture != null) mTexture.draw(canvas, mRect.left + xOffset, mRect.top + yOffset); } - + @Override public void recycle() { if (mTexture == null) @@ -1365,5 +1419,3 @@ public class GalleryView extends GLView { } } - - diff --git a/src/com/hippo/ehviewer/gallery/anim/AlphaAnimation.java b/src/com/hippo/ehviewer/gallery/anim/AlphaAnimation.java index 91422b77e..56baf5e49 100644 --- a/src/com/hippo/ehviewer/gallery/anim/AlphaAnimation.java +++ b/src/com/hippo/ehviewer/gallery/anim/AlphaAnimation.java @@ -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); } } diff --git a/src/com/hippo/ehviewer/gallery/anim/Animation.java b/src/com/hippo/ehviewer/gallery/anim/Animation.java index 67b7623ae..5f2f1046d 100644 --- a/src/com/hippo/ehviewer/gallery/anim/Animation.java +++ b/src/com/hippo/ehviewer/gallery/anim/Animation.java @@ -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; diff --git a/src/com/hippo/ehviewer/gallery/common/Utils.java b/src/com/hippo/ehviewer/gallery/common/Utils.java deleted file mode 100644 index 0110e4038..000000000 --- a/src/com/hippo/ehviewer/gallery/common/Utils.java +++ /dev/null @@ -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 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("<"); break; - case '>': sb.append(">"); break; - case '\"': sb.append("""); break; - case '\'': sb.append("'"); break; - case '&': sb.append("&"); 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 info.toString() 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)); - } -} diff --git a/src/com/hippo/ehviewer/gallery/data/DownloadImageSet.java b/src/com/hippo/ehviewer/gallery/data/DownloadImageSet.java deleted file mode 100644 index b5f143ecd..000000000 --- a/src/com/hippo/ehviewer/gallery/data/DownloadImageSet.java +++ /dev/null @@ -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 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); - } - } - } - }; - -} diff --git a/src/com/hippo/ehviewer/gallery/data/ImageSet.java b/src/com/hippo/ehviewer/gallery/data/ImageSet.java index c4c95246a..965f5f59c 100644 --- a/src/com/hippo/ehviewer/gallery/data/ImageSet.java +++ b/src/com/hippo/ehviewer/gallery/data/ImageSet.java @@ -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 mImageFilenameArray; + private ImageListener mListener; + private final Queue mDecodeQueue = new ConcurrentLinkedQueue(); + private final SparseArray mPercentMap = new SparseArray(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 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 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(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() { - @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() { - @Override - public void onFutureDone(Future 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); } } diff --git a/src/com/hippo/ehviewer/gallery/glrenderer/BasicTexture.java b/src/com/hippo/ehviewer/gallery/glrenderer/BasicTexture.java index 90084d491..ec10f7738 100644 --- a/src/com/hippo/ehviewer/gallery/glrenderer/BasicTexture.java +++ b/src/com/hippo/ehviewer/gallery/glrenderer/BasicTexture.java @@ -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); diff --git a/src/com/hippo/ehviewer/gallery/glrenderer/ColorTexture.java b/src/com/hippo/ehviewer/gallery/glrenderer/ColorTexture.java index 5777ed455..86f6ea8e3 100644 --- a/src/com/hippo/ehviewer/gallery/glrenderer/ColorTexture.java +++ b/src/com/hippo/ehviewer/gallery/glrenderer/ColorTexture.java @@ -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 { diff --git a/src/com/hippo/ehviewer/gallery/glrenderer/FadeTexture.java b/src/com/hippo/ehviewer/gallery/glrenderer/FadeTexture.java index f80f5c519..e8f177866 100644 --- a/src/com/hippo/ehviewer/gallery/glrenderer/FadeTexture.java +++ b/src/com/hippo/ehviewer/gallery/glrenderer/FadeTexture.java @@ -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() { diff --git a/src/com/hippo/ehviewer/gallery/glrenderer/GLES11Canvas.java b/src/com/hippo/ehviewer/gallery/glrenderer/GLES11Canvas.java index b65b4de07..4b36dd802 100644 --- a/src/com/hippo/ehviewer/gallery/glrenderer/GLES11Canvas.java +++ b/src/com/hippo/ehviewer/gallery/glrenderer/GLES11Canvas.java @@ -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 mTargetStack = new ArrayList(); 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(); diff --git a/src/com/hippo/ehviewer/gallery/glrenderer/GLES20Canvas.java b/src/com/hippo/ehviewer/gallery/glrenderer/GLES20Canvas.java index 8dd3ee99b..e59ce8c5c 100644 --- a/src/com/hippo/ehviewer/gallery/glrenderer/GLES20Canvas.java +++ b/src/com/hippo/ehviewer/gallery/glrenderer/GLES20Canvas.java @@ -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 mTargetTextures = new ArrayList(); + private final ArrayList mTargetTextures = new ArrayList(); // 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(); diff --git a/src/com/hippo/ehviewer/gallery/glrenderer/NinePatchTexture.java b/src/com/hippo/ehviewer/gallery/glrenderer/NinePatchTexture.java index fa3248ca6..fc2deee44 100644 --- a/src/com/hippo/ehviewer/gallery/glrenderer/NinePatchTexture.java +++ b/src/com/hippo/ehviewer/gallery/glrenderer/NinePatchTexture.java @@ -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 mInstanceCache + private final SmallCache mInstanceCache = new SmallCache(); public NinePatchTexture(Context context, int resId) { @@ -80,8 +80,8 @@ public class NinePatchTexture extends ResourceTexture { private static class SmallCache { 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; diff --git a/src/com/hippo/ehviewer/gallery/glrenderer/UploadedTexture.java b/src/com/hippo/ehviewer/gallery/glrenderer/UploadedTexture.java index 76e487ec8..7e6d2b402 100644 --- a/src/com/hippo/ehviewer/gallery/glrenderer/UploadedTexture.java +++ b/src/com/hippo/ehviewer/gallery/glrenderer/UploadedTexture.java @@ -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); diff --git a/src/com/hippo/ehviewer/gallery/ui/GLRootView.java b/src/com/hippo/ehviewer/gallery/ui/GLRootView.java index ab01ae1b2..7ac3f7524 100644 --- a/src/com/hippo/ehviewer/gallery/ui/GLRootView.java +++ b/src/com/hippo/ehviewer/gallery/ui/GLRootView.java @@ -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 GLViews. 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(); diff --git a/src/com/hippo/ehviewer/gallery/ui/GLView.java b/src/com/hippo/ehviewer/gallery/ui/GLView.java index 180a2fd7d..22841248c 100644 --- a/src/com/hippo/ehviewer/gallery/ui/GLView.java +++ b/src/com/hippo/ehviewer/gallery/ui/GLView.java @@ -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 diff --git a/src/com/hippo/ehviewer/gallery/util/GalleryUtils.java b/src/com/hippo/ehviewer/gallery/util/GalleryUtils.java index 80dd1c2b0..348ce71b2 100644 --- a/src/com/hippo/ehviewer/gallery/util/GalleryUtils.java +++ b/src/com/hippo/ehviewer/gallery/util/GalleryUtils.java @@ -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; - } } diff --git a/src/com/hippo/ehviewer/network/HttpHelper.java b/src/com/hippo/ehviewer/network/HttpHelper.java index c25a0ad6a..f48c228a6 100644 --- a/src/com/hippo/ehviewer/network/HttpHelper.java +++ b/src/com/hippo/ehviewer/network/HttpHelper.java @@ -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(); diff --git a/src/com/hippo/ehviewer/service/DownloadService.java b/src/com/hippo/ehviewer/service/DownloadService.java index 538784337..585e4f8cc 100644 --- a/src/com/hippo/ehviewer/service/DownloadService.java +++ b/src/com/hippo/ehviewer/service/DownloadService.java @@ -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(){ diff --git a/src/com/hippo/ehviewer/ui/GalleryActivity.java b/src/com/hippo/ehviewer/ui/GalleryActivity.java index 177ff8c50..32097314d 100644 --- a/src/com/hippo/ehviewer/ui/GalleryActivity.java +++ b/src/com/hippo/ehviewer/ui/GalleryActivity.java @@ -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(); } } diff --git a/src/com/hippo/ehviewer/ui/GalleryDetailActivity.java b/src/com/hippo/ehviewer/ui/GalleryDetailActivity.java index 9d70d5ad0..dd2e7b504 100644 --- a/src/com/hippo/ehviewer/ui/GalleryDetailActivity.java +++ b/src/com/hippo/ehviewer/ui/GalleryDetailActivity.java @@ -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) { diff --git a/src/com/hippo/ehviewer/ui/GalleryListActivity.java b/src/com/hippo/ehviewer/ui/GalleryListActivity.java index af798e284..877575212 100644 --- a/src/com/hippo/ehviewer/ui/GalleryListActivity.java +++ b/src/com/hippo/ehviewer/ui/GalleryListActivity.java @@ -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 diff --git a/src/com/hippo/ehviewer/ui/SettingsActivity.java b/src/com/hippo/ehviewer/ui/SettingsActivity.java index b313f032c..7d009a2b4 100644 --- a/src/com/hippo/ehviewer/ui/SettingsActivity.java +++ b/src/com/hippo/ehviewer/ui/SettingsActivity.java @@ -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 diff --git a/src/com/hippo/ehviewer/util/AutoExpandArray.java b/src/com/hippo/ehviewer/util/AutoExpandArray.java index af94b829c..f8c44ca27 100644 --- a/src/com/hippo/ehviewer/util/AutoExpandArray.java +++ b/src/com/hippo/ehviewer/util/AutoExpandArray.java @@ -67,7 +67,7 @@ public class AutoExpandArray { @SuppressWarnings("unchecked") public synchronized E get(int index) { - if (index >= array.length) { + if (index >= array.length || index < 0) { return null; } return (E)array[index]; diff --git a/src/com/hippo/ehviewer/util/EhUtils.java b/src/com/hippo/ehviewer/util/EhUtils.java new file mode 100644 index 000000000..f9da08ec1 --- /dev/null +++ b/src/com/hippo/ehviewer/util/EhUtils.java @@ -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"}; + } + +} diff --git a/src/com/hippo/ehviewer/util/MathUtils.java b/src/com/hippo/ehviewer/util/MathUtils.java index 898f29075..60526c259 100644 --- a/src/com/hippo/ehviewer/util/MathUtils.java +++ b/src/com/hippo/ehviewer/util/MathUtils.java @@ -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); } diff --git a/src/com/hippo/ehviewer/util/TimeRunner.java b/src/com/hippo/ehviewer/util/TimeRunner.java index f0725f80f..d9fe28b99 100644 --- a/src/com/hippo/ehviewer/util/TimeRunner.java +++ b/src/com/hippo/ehviewer/util/TimeRunner.java @@ -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); } diff --git a/src/com/hippo/ehviewer/util/Utils.java b/src/com/hippo/ehviewer/util/Utils.java index 2ba75c1cf..4522f0a9a 100644 --- a/src/com/hippo/ehviewer/util/Utils.java +++ b/src/com/hippo/ehviewer/util/Utils.java @@ -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); + } }