/*******************************************************************************
* Copyright 2009 Robot Media SL
*
* 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 net.robotmedia.acv.comic;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import net.robotmedia.acv.Constants;
import net.robotmedia.acv.utils.FileUtils;
import net.robotmedia.acv.utils.StringUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import android.webkit.WebView;
import android.webkit.WebView.PictureListener;
import android.webkit.WebViewClient;
public class ACVComic extends Comic {
private class ACVZipComic extends ZipComic {
private TreeMap<String, String> unorderedThumbnails; // Do no initialize; used from super constructor
private TreeMap<String, String> unorderedOriginals; // Do no initialize; used from super constructor
protected ACVZipComic(String path) {
super(path);
this.sort(unorderedThumbnails, thumbnails);
this.sort(unorderedOriginals, originals);
}
private TreeMap<String, String> classify(String path) {
{
final String imagePattern = ACVComic.this.getImageNamePattern();
if (imagePattern != null) {
if (path.matches(imagePattern)) {
return unorderedScreens;
}
}
}
{
final String thumbnailPattern = ACVComic.this.getThumbnailNamePattern();
if (thumbnailPattern != null) {
if (path.matches(thumbnailPattern)) {
if (unorderedThumbnails == null) {
unorderedThumbnails = new TreeMap<String, String>();
}
return unorderedThumbnails;
}
}
}
{
final String originalPattern = ACVComic.this.getOriginalNamePattern();
if (originalPattern != null) {
if (path.matches(originalPattern)) {
if (unorderedOriginals == null) {
unorderedOriginals = new TreeMap<String, String>();
}
return unorderedOriginals;
}
}
}
// Legacy code
String[] splitPath = path.split("/");
if (isLegacyThumbnail(splitPath)) {
if (unorderedThumbnails == null) {
unorderedThumbnails = new TreeMap<String, String>();
}
return unorderedThumbnails;
} else {
return unorderedScreens;
}
}
/**
*
* @param splitEntryName
* @return
*/
private boolean isLegacyThumbnail(String[] splitEntryName) {
int i = 0;
while (i < splitEntryName.length) {
if (splitEntryName[i] != null
&& splitEntryName[i].toLowerCase().equals(
THUMBNAIL_FOLDER.toLowerCase())) {
return true;
} else {
i++;
}
}
return false;
}
@Override
protected void processEntry(ZipEntry entry) {
String entryName = entry.getName();
if (FileUtils.isHidden(entryName)) {
return;
}
String extension = FileUtils.getFileExtension(entryName);
if (FileUtils.isImage(extension)) {
final String key = this.addLeadingZeroes(entryName);
TreeMap<String, String> bucket = classify(entryName);
bucket.put(key, entryName);
} else if (FileUtils.isVideo(extension)) {
String[] split = entryName.split("\\.");
if (split.length > 1) {
String numberSuffix = split[split.length - 2];
Pattern pattern = Pattern.compile("\\d+$");
Matcher matcher = pattern.matcher(numberSuffix);
if (matcher.find()) {
numberSuffix = matcher.group();
int index = Integer.parseInt(numberSuffix);
File file = extract(entry, entry.getName());
if (file != null) {
ACVScreen screen = getOrCreateACVScreen(index);
screen.setVideoFile(file);
}
}
}
} else if (FileUtils.isAudio(extension) || FileUtils.isFont(entryName) || FileUtils.isWebpage(entryName)) {
final File file = extract(entry, entry.getName());
ACVComic.this.addFile(file.getName(), file);
}
}
protected void processMetadata(ZipEntry entry) {
InputStream is = null;
try {
is = getInputStream(entry);
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xml = factory.newPullParser();
xml.setInput(is, null);
ACVParser.parse(xml, ACVComic.this);
is.close();
} catch (Exception e) {
this.error();
e.printStackTrace();
if (is != null) {
try { is.close(); } catch (Exception e1) {}
}
}
}
@Override
protected void setZip(ZipFile zip) {
super.setZip(zip);
ZipEntry entry = zip.getEntry(METADATA_FILE);
if (entry == null) {
entry = zip.getEntry(LEGACY_METADATA_FILE);
}
if (entry != null) {
processMetadata(entry);
}
}
private void sort(TreeMap<String, String> unordered, ArrayList<String> ordered) {
if (unordered != null) {
ArrayList<String> orderedKeys = new ArrayList<String>(unordered.keySet());
for (int i = 0; i < orderedKeys.size(); i++) {
ordered.add(unordered.get(orderedKeys.get(i)));
}
}
}
}
public static final String THUMBNAIL_FOLDER = "z_tn";
public static final String COMIC_URI_PREFIX = "comic://";
@Deprecated
protected static final String LEGACY_METADATA_FILE = "comic.xml";
protected static final String METADATA_FILE = "acv.xml";
private ACVZipComic innerComic;
private ArrayList<String> thumbnails = new ArrayList<String>();
private ArrayList<String> originals = new ArrayList<String>();
public ACVComic(String comicPath) {
super(comicPath);
innerComic = new ACVZipComic(path);
init();
}
@Override
public void destroy() {
innerComic.destroy();
}
public String getContentBaseURL() {
final File dir = new File(Environment.getExternalStorageDirectory(), this.getRelativePath());
return Uri.fromFile(dir).toString() + "/";
}
protected InputStream getInputStream(String resource) {
try {
return new FileInputStream(this.getFile(resource));
} catch (FileNotFoundException e) {
Log.w(this.getClass().getSimpleName(), "Resource not found", e);
return null;
}
}
@Override
public int getLength() {
return innerComic.getLength();
}
@Override
public Drawable getScreen(int position) {
return innerComic.getScreen(position);
}
@Override
public Drawable getThumbnail(int position) {
if (position < thumbnails.size()) {
String entryName = thumbnails.get(position);
InputStream is = null;
Drawable drawable = null;
try {
is = innerComic.getInputStream(entryName);
drawable = Drawable.createFromStream(is, entryName);
is.close();
} catch (Exception e) {
if (is != null) {
try { is.close(); } catch (Exception e1) {}
}
}
return drawable;
} else {
return null;
}
}
@Override
public Uri getUri(int position) {
return innerComic.getUri(position);
}
private void init() {
if (this.getID() == null) {
final String fileName = new File(path).getName();
this.setId(fileName);
}
}
@Override
public void prepareScreen(int position) {
innerComic.prepareScreen(position);
}
public static class Message {
public int index;
public String text;
public String uri;
public String nonMarketUri;
}
protected static final String VALUE_NONE = "none";
protected static final String VALUE_LANDSCAPE = "landscape";
protected static final String VALUE_PORTRAIT = "portrait";
protected static final int NOT_SPECIFIED = -1;
private int specificationVersion = 1;
private int minViewerVersion = NOT_SPECIFIED;
private HashMap<Integer, ACVScreen> acvScreens = new HashMap<Integer, ACVScreen>();
private String bgcolorString;
private String scaleMode = Constants.SCALE_MODE_BEST_VALUE;
private String defaultTransition = Constants.TRANSITION_MODE_NONE_VALUE;
private String imageNamePattern;
private String thumbnailNamePattern;
private String originalNamePattern;
private HashMap<String, File> files = new HashMap<String, File>();
private String direction = Constants.DIRECTION_LEFT_TO_RIGHT_VALUE;
private boolean paid;
private int orientation = Configuration.ORIENTATION_LANDSCAPE;
protected void addFile(String name, File file) {
files.put(name, file);
}
public ACVScreen getACVScreen(int index) {
return acvScreens.get(index);
}
@Override
public Integer getBackgroundColor(int index) {
ACVScreen screen = acvScreens.get(index);
String screenBgcolorString = null;
if (screen != null) {
screenBgcolorString = screen.getBgcolorString();
}
if (screenBgcolorString == null) {
screenBgcolorString = bgcolorString;
}
if (VALUE_NONE.equals(screenBgcolorString)) {
return Color.TRANSPARENT;
} else if (screenBgcolorString != null) {
return Color.parseColor(screenBgcolorString);
} else {
return Color.TRANSPARENT;
}
}
public Integer getBackgroundColor(int screenIndex, int frameIndex) {
ACVScreen screen = acvScreens.get(screenIndex);
if (screen != null) {
ACVFrame frame = screen.getFrame(frameIndex);
if (frame != null) {
String frameBgcolorString = frame.getBgcolorString();
if (frameBgcolorString != null) {
if (VALUE_NONE.equals(frameBgcolorString)) {
return null;
} else {
return Color.parseColor(frameBgcolorString);
}
}
}
}
return getBackgroundColor(screenIndex);
}
private Bitmap getBitmap(ACVContent content, final WebView w, int containerWidth, int containerHeight) {
final Rect rect = content.createRect(containerWidth, containerHeight);
final CountDownLatch signal = new CountDownLatch(1);
final Bitmap b = Bitmap.createBitmap(rect.width(), rect.height(), Bitmap.Config.RGB_565);
final AtomicBoolean ready = new AtomicBoolean(false);
final String html = this.getContentFromSource(content);
final String baseURL = this.getContentBaseURL();
w.post(new Runnable() {
@Override
public void run() {
w.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
ready.set(true);
}
});
w.setPictureListener(new PictureListener() {
@Override
public void onNewPicture(WebView view, Picture picture) {
if (ready.get()) {
final Canvas c = new Canvas(b);
view.draw(c);
w.setPictureListener(null);
signal.countDown();
}
}
});
w.layout(0, 0, rect.width(), rect.height());
w.loadDataWithBaseURL(baseURL, html, "text/html", "UTF-8", null);
}
});
try {
signal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return b;
}
public String getContentFromSource(ACVContent content) {
final String source = content.getSource();
final String simpleContent = content.getContent();
if (source == null)
return simpleContent;
final InputStream is = this.getInputStream(source);
if (is == null)
return simpleContent;
try {
String contentFromSource = StringUtils.convertStreamToString(is, "UTF-8");
if (simpleContent != null) {
contentFromSource = contentFromSource.replaceFirst(Pattern.quote(ACVContent.PLACEHOLDER_CONTENT), simpleContent);
}
return contentFromSource;
} catch (Exception e) {
Log.w(this.getClass().getSimpleName(), "Could not read content source", e);
return simpleContent;
}
}
public List<ACVContent> getContents(int screenIndex) {
final ACVScreen screen = acvScreens.get(screenIndex);
return screen != null ? screen.getContents() : new ArrayList<ACVContent>();
}
public List<ACVContent> getContents(int screenIndex, int frameIndex) {
final ACVFrame frame = getFrame(screenIndex, frameIndex);
return frame != null ? frame.getContents() : new ArrayList<ACVContent>();
}
public String getDescription(int index) {
final ACVScreen screen = acvScreens.get(index);
return screen != null ? screen.getDescription() : null;
}
public String getDirection() {
return direction;
}
public long getDuration(int index) {
final ACVScreen screen = getACVScreen(index);
return screen != null ? screen.getDuration() : Math.round(ACVScreen.DEFAULT_DURATION * 1000);
}
public long getDuration(int screenIndex, int frameIndex) {
float durationInSeconds = ACVFrame.DEFAULT_DURATION;
ACVScreen screen = acvScreens.get(screenIndex);
if (screen != null) {
ACVFrame frame = screen.getFrame(frameIndex);
if (frame != null) {
durationInSeconds = frame.getDuration();
}
}
long duration = Math.round(durationInSeconds * 1000);
return duration;
}
protected File getFile(String name) {
return files.get(name);
}
private ACVFrame getFrame(int screenIndex, int frameIndex) {
ACVScreen screen = acvScreens.get(screenIndex);
if (screen != null) {
return screen.getFrame(frameIndex);
} else {
return null;
}
}
@Override
public int getFramesSize(int index) {
ACVScreen screen = acvScreens.get(index);
if (screen != null) {
return screen.framesSize();
}
return 0;
}
protected String getImageNamePattern() {
return imageNamePattern;
}
public Message getMessage(int index) {
ACVScreen screen = acvScreens.get(index);
return screen != null ? screen.getMessage() : null;
}
public ACVScreen getOrCreateACVScreen(int index) {
if (acvScreens == null) {
acvScreens = new HashMap<Integer, ACVScreen>();
}
ACVScreen screen = acvScreens.get(index);
if (screen == null) {
screen = new ACVScreen(index);
}
acvScreens.put(index, screen);
return screen;
}
public int getOrientation() {
return orientation;
}
protected String getOriginalNamePattern() {
return originalNamePattern;
}
@Override
public String getScaleMode() {
return scaleMode;
}
public Drawable getScreenWithContents(WebView w, int screenIndex) {
final List<ACVContent> contents = this.getContents(screenIndex);
final Drawable drawable = this.getScreen(screenIndex);
if (contents.size() == 0) {
return drawable;
}
// TODO: Cache modified bitmap
final Bitmap original = ((BitmapDrawable) drawable).getBitmap();
final Bitmap result = original.copy(Config.RGB_565, true);
original.recycle();
final int width = result.getWidth();
final int height = result.getHeight();
final Canvas canvas = new Canvas(result);
for (ACVContent content : contents) {
final Rect contentRect = content.createRect(width, height);
final Bitmap contentBitmap = getBitmap(content, w, width, height);
canvas.drawBitmap(contentBitmap, contentRect.left, contentRect.top, null);
contentBitmap.recycle();
}
return new BitmapDrawable(result);
}
public File getSound(int index) {
final ACVScreen screen = acvScreens.get(index);
if (screen != null) {
final String soundName = screen.getSound();
if (soundName != null) {
return files.get(soundName);
}
}
return null;
}
public File getSound(int screenIndex, int frameIndex) {
final ACVScreen screen = acvScreens.get(screenIndex);
if (screen != null) {
final ACVFrame frame = screen.getFrame(frameIndex);
if (frame != null) {
final String soundName = frame.getSound();
if (soundName != null) {
return files.get(soundName);
}
}
}
return null;
}
protected String getThumbnailNamePattern() {
return thumbnailNamePattern;
}
public ArrayList<ACVScreen> getTitledScreens() {
ArrayList<ACVScreen> titledScreens = new ArrayList<ACVScreen>();
final int length = getLength();
for (int i = 0; i < length; i++) {
ACVScreen screen = acvScreens.get(i);
if (screen != null && screen.getTitle() != null && screen.getTitle().trim().length() > 0) {
titledScreens.add(screen);
}
}
return titledScreens;
}
public String getTransition(int index) {
// Legacy code. The first version of the ACV specification defined the
// transition to a screen in its previous screen.
if (specificationVersion == 1) {
index -= 1;
}
ACVScreen screen = acvScreens.get(index);
if (screen != null) {
String transition = screen.getTransition();
if (transition != null) {
return transition;
}
}
return defaultTransition;
}
public String getTransition(int screenIndex, int frameIndex) {
final ACVScreen screen = getACVScreen(screenIndex);
return screen != null ? screen.getTransition(frameIndex) : null;
}
public long getTransitionDuration(int index) {
final ACVScreen screen = getACVScreen(index);
return screen != null ? screen.getTransitionDuration() : Math.round(ACVScreen.DEFAULT_TRANSITION_DURATION * 1000);
}
public long getTransitionDuration(int screenIndex, int frameIndex) {
float transitionDurationInSeconds = ACVFrame.DEFAULT_TRANSITION_DURATION;
ACVScreen screen = acvScreens.get(screenIndex);
if (screen != null) {
ACVFrame frame = screen.getFrame(frameIndex);
if (frame != null) {
transitionDurationInSeconds = frame.getTransitionDuration();
}
}
long transitionDuration = Math.round(transitionDurationInSeconds * 1000);
return transitionDuration;
}
public File getVideoFile(int index) {
ACVScreen screen = acvScreens.get(index);
return screen != null ? screen.getVideoFile() : null;
}
public boolean hasVibration(int index) {
ACVScreen screen = acvScreens.get(index);
return screen != null ? screen.isVibrate() : false;
}
public boolean isAutoplay(int screenIndex) {
ACVScreen screen = acvScreens.get(screenIndex);
return screen != null ? screen.isAutoplay() : false;
}
public boolean isAutoplay(int screenIndex, int frameIndex) {
ACVScreen screen = acvScreens.get(screenIndex);
if (screen != null) {
ACVFrame frame = screen.getFrame(frameIndex);
if (frame != null) {
return frame.isAutoplay();
}
}
return ACVFrame.DEFAULT_AUTOPLAY;
}
@Override
public boolean isCompatible(int version) {
if (minViewerVersion == NOT_SPECIFIED) {
return true;
} else {
return minViewerVersion <= version;
}
}
public boolean isLeftToRight() {
return Constants.DIRECTION_LEFT_TO_RIGHT_VALUE.equals(direction);
}
public boolean isPaid() {
return paid;
}
public boolean isVibrate(int screenIndex, int frameIndex) {
ACVScreen screen = acvScreens.get(screenIndex);
if (screen != null) {
ACVFrame frame = screen.getFrame(frameIndex);
if (frame != null) {
return frame.isVibrate();
}
}
return ACVFrame.DEFAULT_VIBRATE;
}
public Rect rectForSize(int screenIndex, int frameIndex, int width, int height) {
final ACVFrame frame = getFrame(screenIndex, frameIndex);
return frame != null ? frame.createRect(width, height) : null;
}
public void registerSound(String sound) {
// Do nothing;
}
public void setBgcolorString(String bgcolorString) {
this.bgcolorString = bgcolorString;
}
public void setDirection(String direction) {
this.direction = direction;
}
protected void setImageNamePattern(String imageNamePattern) {
this.imageNamePattern = imageNamePattern;
}
public void setImageStartsAt(int starstAt) {
// TODO Auto-generated method stub
}
public void setLength(int length) {
// Do nothing
}
public void setMinViewerVersion(int minViewerVersion) {
this.minViewerVersion = minViewerVersion;
}
public void setOrientation(int orientation) {
this.orientation = orientation;
}
protected void setOriginalNamePattern(String originalNamePattern) {
this.originalNamePattern = originalNamePattern;
}
public void setPaid(boolean paid) {
this.paid = paid;
}
public void setScaleMode(String scaleMode) {
this.scaleMode = scaleMode;
}
public void setSpecificationVersion(int specificationVersion) {
this.specificationVersion = specificationVersion;
}
protected void setThumbnailNamePattern(String thumbnailNamePattern) {
this.thumbnailNamePattern = thumbnailNamePattern;
}
public void setThumbnailStartsAt(int startsAt) {
// TODO Auto-generated method stub
}
public void setTitle(String title) {
this.name = title;
}
public void setTransition(String transition) {
this.defaultTransition = transition;
}
}