/** * Copyright 2008 - 2015 The Loon Game Engine Authors * * 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. * * @project loon * @author cping * @email:javachenpeng@yahoo.com * @version 0.5 */ package loon.android; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import loon.Assets; import loon.LRelease; import loon.Sound; import loon.canvas.Image; import loon.canvas.ImageImpl; import loon.utils.Scale; import loon.utils.StringUtils; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.methods.HttpGet; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Typeface; import android.net.http.AndroidHttpClient; public class AndroidAssets extends Assets { private final static String DEF_RES = "assets/"; public static interface Resource extends LRelease { InputStream getInputStream(); String getResourceName(); URI getURI(); } public static abstract class DataRes { String path; String name; InputStream in; URI uri; @Override public int hashCode() { return (name == null) ? super.hashCode() : name.hashCode(); } public void close() { if (in != null) { try { in.close(); in = null; } catch (IOException e) { } } if (uri != null) { uri = null; } } } public static class ClassRes extends DataRes implements Resource { private ClassLoader classLoader; public ClassRes(String path) { this(path, null); } public ClassRes(String path, ClassLoader classLoader) { this.path = path; this.name = "classpath://" + path; this.classLoader = classLoader; } @Override public InputStream getInputStream() { try { if (classLoader == null) { return (in = AndroidAssets.classLoader .getResourceAsStream(path)); } else { return (in = classLoader.getResourceAsStream(path)); } } catch (Exception e) { e.printStackTrace(); } return null; } @Override public String getResourceName() { return name; } @Override public URI getURI() { try { if (uri != null) { return uri; } return (uri = classLoader.getResource(path).toURI()); } catch (URISyntaxException ex) { throw new RuntimeException(ex); } } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } ClassRes other = (ClassRes) obj; if (name == null) { if (other.name != null) { return false; } } else if (!name.equals(other.name)) { return false; } return true; } @Override public int hashCode() { return super.hashCode(); } } public static class FileRes extends DataRes implements Resource { public FileRes(String path) { this.path = path; this.name = "file://" + path; } @Override public InputStream getInputStream() { try { if (in != null) { return in; } File file = new File(path); return (in = new FileInputStream(file)); } catch (FileNotFoundException e) { throw new RuntimeException("file " + name + " not found !", e); } } @Override public String getResourceName() { return name; } @Override public URI getURI() { try { if (uri != null) { return uri; } return (uri = new URL(path).toURI()); } catch (Exception e) { throw new RuntimeException(e); } } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } FileRes other = (FileRes) obj; if (name == null) { if (other.name != null) { return false; } } else if (!name.equals(other.name)) { return false; } return true; } @Override public int hashCode() { return super.hashCode(); } } public static class RemoteRes extends DataRes implements Resource { public RemoteRes(String url) { this.path = url; this.name = url; } @Override public InputStream getInputStream() { try { if (in != null) { return in; } return in = new URL(path).openStream(); } catch (Exception e) { throw new RuntimeException(e); } } @Override public String getResourceName() { return name; } @Override public URI getURI() { try { return new URL(path).toURI(); } catch (Exception e) { throw new RuntimeException(e); } } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } RemoteRes other = (RemoteRes) obj; if (name == null) { if (other.name != null) { return false; } } else if (!name.equals(other.name)) { return false; } return true; } @Override public int hashCode() { return super.hashCode(); } } public static class SDRes extends DataRes implements Resource { public SDRes(String path) { if (isMoutedSD()) { File f = android.os.Environment.getExternalStorageDirectory(); String tmp = f.getPath(); if (StringUtils.startsWith(path, '/')) { path = path.substring(1); } if (!StringUtils.endsWith(tmp, '/')) { path = tmp + "/" + path; } else { path = tmp + path; } } else { path = Loon.self.getCacheDir().getAbsolutePath(); path = StringUtils.replaceIgnoreCase(path, "\\", "/"); if (StringUtils.startsWith(path, '/') || StringUtils.startsWith(path, '\\')) { path = path.substring(1, path.length()); } } this.path = path; this.name = "sdcard://" + path; } public final static boolean isMoutedSD() { String sdState = android.os.Environment.getExternalStorageState(); return sdState.equals(android.os.Environment.MEDIA_MOUNTED); } @Override public InputStream getInputStream() { try { if (in != null) { return in; } return (in = new FileInputStream(new File(path))); } catch (FileNotFoundException e) { throw new RuntimeException("file " + name + " not found !", e); } } @Override public String getResourceName() { return name; } @Override public URI getURI() { try { if (uri != null) { return uri; } return (uri = new URL(path).toURI()); } catch (Exception e) { throw new RuntimeException(e); } } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } SDRes other = (SDRes) obj; if (name == null) { if (other.name != null) { return false; } } else if (!name.equals(other.name)) { return false; } return true; } @Override public int hashCode() { return super.hashCode(); } } public static Resource classRes(String path) { return new ClassRes(path); } public static Resource fileRes(String path) { return new FileRes(path); } public static Resource remoteRes(String path) { return new RemoteRes(path); } public static Resource sdRes(String path) { return new SDRes(path); } private InputStream filestream(String path) { try { File file = new File(path); if (file.exists()) { return new FileInputStream(file); } else { file = new File(StringUtils.replaceIgnoreCase(getPath(path), DEF_RES, "")); if (file.exists()) { return new FileInputStream(file); } else { return classLoader.getResourceAsStream(path); } } } catch (Throwable t) { return null; } } public InputStream strRes(final String path) { if (path == null) { return null; } InputStream in = filestream(path); if (in != null) { return in; } if (path.indexOf("->") == -1) { if (path.startsWith("sd:")) { in = sdRes(path.substring(3, path.length())).getInputStream(); } else if (path.startsWith("class:")) { in = classRes(path.substring(6, path.length())) .getInputStream(); } else if (path.startsWith("path:")) { in = fileRes(path.substring(5, path.length())).getInputStream(); } else if (path.startsWith("url:")) { in = remoteRes(path.substring(4, path.length())) .getInputStream(); } } return in; } private static ClassLoader classLoader; static { try { classLoader = AndroidAssets.class.getClassLoader(); } catch (Throwable ex) { classLoader = null; } } public class BitmapOptions extends BitmapFactory.Options { public Scale scale; } public interface BitmapOptionsAdjuster { void adjustOptions(String path, BitmapOptions options); } private final AndroidGame game; private final AssetManager assetMgr; private Scale assetScale = null; private BitmapOptionsAdjuster optionsAdjuster = new BitmapOptionsAdjuster() { public void adjustOptions(String path, BitmapOptions options) { } }; public AndroidAssets(AndroidGame game) { super(game.asyn()); Assets.pathPrefix = ""; this.game = game; this.assetMgr = game.activity.getResources().getAssets(); this.setPathPrefix(""); } public void setAssetScale(float scaleFactor) { this.assetScale = new Scale(scaleFactor); } public void setBitmapOptionsAdjuster(BitmapOptionsAdjuster optionsAdjuster) { this.optionsAdjuster = optionsAdjuster; } @Override public Image getRemoteImage(final String url, int width, int height) { final ImageImpl image = createImage(true, width, height, url); asyn.invokeAsync(new Runnable() { public void run() { try { BitmapOptions options = createOptions(url, false, Scale.ONE); Bitmap bmp = downloadBitmap(url, options); image.succeed(new ImageImpl.Data(options.scale, bmp, bmp .getWidth(), bmp.getHeight())); } catch (Exception error) { image.fail(error); } } }); return image; } protected AndroidAudio _audio; protected AndroidAudio getNativeAudio() { if (_audio == null) { _audio = new AndroidAudio(); } return _audio; } @Override public Sound getSound(String path) { if (_audio == null) { _audio = new AndroidAudio(); } return _audio.createSound(path); } @Override public Sound getMusic(String path) { if (_audio == null) { _audio = new AndroidAudio(); } return _audio.createMusic(path); } @Override public String getTextSync(String path) throws Exception { InputStream is = openAsset(path); try { StringBuilder fileData = new StringBuilder(1000); BufferedReader reader = new BufferedReader( new InputStreamReader(is)); char[] buf = new char[1024]; int numRead = 0; while ((numRead = reader.read(buf)) != -1) { String readData = String.valueOf(buf, 0, numRead); fileData.append(readData); } reader.close(); return fileData.toString(); } finally { is.close(); } } @Override public byte[] getBytesSync(String path) throws Exception { InputStream is = openAsset(path); try { ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; while (true) { int r = is.read(buf); if (r == -1) { break; } out.write(buf, 0, r); } return out.toByteArray(); } finally { is.close(); } } @Override protected ImageImpl createImage(boolean async, int rwid, int rhei, String source) { return new AndroidImage(game, async, rwid, rhei, source); } @Override protected ImageImpl.Data load(String path) throws Exception { Exception error = null; for (Scale.ScaledResource rsrc : assetScale().getScaledResources(path)) { try { InputStream is = openAsset(rsrc.path); try { BitmapOptions options = createOptions(path, true, rsrc.scale); Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); return new ImageImpl.Data(options.scale, bitmap, bitmap.getWidth(), bitmap.getHeight()); } finally { is.close(); } } catch (FileNotFoundException ex) { error = ex; } catch (Exception e) { error = e; break; } } game.log().warn("Could not load image: " + pathPrefix + path, error); throw error != null ? error : new FileNotFoundException(path); } Typeface getTypeface(String path) { return Typeface.createFromAsset(assetMgr, getPath(path)); } protected AssetFileDescriptor openAssetFd(String path) throws IOException { String fullPath = getPath(path); return assetMgr.openFd(fullPath); } protected Scale assetScale() { return (assetScale != null) ? assetScale : game.graphics().scale(); } protected InputStream openAsset(String path) throws IOException { String newPath = getPath(path); InputStream is = openResource(newPath); if (is == null) { is = assetMgr.open(newPath, AssetManager.ACCESS_STREAMING); } if (is == null) { throw new FileNotFoundException("not found resource: " + newPath); } return is; } public InputStream openResource(String resName) throws IOException { InputStream resource = strRes(resName); if (resource != null) { return resource; } if (resName.indexOf('\\') != -1) { resName = resName.replace('\\', '/'); } String fileName = resName.toLowerCase(); if (fileName.startsWith(DEF_RES) || fileName.startsWith('/' + DEF_RES)) { boolean flag = resName.startsWith("/"); String file; if (flag) { file = resName.substring(1); } else { file = resName; } int index = file.indexOf('/') + 1; if (index != -1) { file = resName.substring(index); } else { int length = file.length(); int size = file.lastIndexOf('/', 0) + 1; if (size < length) { file = file.substring(size, length); } } return this.assetMgr.open(file); } if (classLoader != null) { InputStream in = null; try { in = classLoader.getResourceAsStream(resName); } catch (Exception e) { } return in; } else { return this.assetMgr.open(resName); } } protected BitmapOptions createOptions(String path, boolean purgeable, Scale scale) { BitmapOptions options = new BitmapOptions(); options.inScaled = false; options.inDither = true; //options.inMutable = true; options.inPreferredConfig = game.graphics().preferredBitmapConfig; options.inPurgeable = purgeable; options.inInputShareable = true; options.scale = scale; optionsAdjuster.adjustOptions(path, options); return options; } protected Bitmap downloadBitmap(String url, BitmapOptions options) throws Exception { AndroidHttpClient client = AndroidHttpClient.newInstance("Android"); HttpGet getRequest = new HttpGet(url); try { HttpResponse response = client.execute(getRequest); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != HttpStatus.SC_OK) { Header[] headers = response.getHeaders("Location"); if (headers != null && headers.length > 0) { return downloadBitmap( headers[headers.length - 1].getValue(), options); } throw new Exception("Error " + statusCode + " while retrieving bitmap from " + url); } HttpEntity entity = response.getEntity(); if (entity == null) { throw new Exception("getEntity returned null for " + url); } InputStream in = null; try { in = entity.getContent(); return BitmapFactory.decodeStream(in, null, options); } finally { if (in != null) { in.close(); } entity.consumeContent(); } } catch (Exception e) { getRequest.abort(); game.reportError("bitmap from " + url, e); throw e; } finally { client.close(); } } }