All Downloads are FREE. Search and download functionalities are using the official Maven repository.

playn.android.AndroidAssets Maven / Gradle / Ivy

There is a newer version: 1.9.1
Show newest version
/**
 * Copyright 2011 The PlayN 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.
 */
package playn.android;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

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.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;

import pythagoras.f.MathUtil;

import playn.core.AbstractAssets;
import playn.core.Image;
import playn.core.AsyncImage;
import playn.core.Sound;
import playn.core.gl.Scale;
import playn.core.util.Callback;
import static playn.core.PlayN.log;

public class AndroidAssets extends AbstractAssets {

  /** See {@link #setBitmapOptionsAdjuster}. */
  public interface BitmapOptionsAdjuster {
    /** Adjusts the {@link BitmapFactory#Options} based on the app's special requirements.
     * @param path the path passed to {@link #getImage} or URL passed to {@link #getRemoteImage}.*/
    void adjustOptions(String path, BitmapFactory.Options options);
  }

  private final AndroidPlatform platform;
  private String pathPrefix = null;
  private Scale assetScale = null;

  private BitmapOptionsAdjuster optionsAdjuster = new BitmapOptionsAdjuster() {
    public void adjustOptions(String path, BitmapFactory.Options options) {} // noop!
  };

  AndroidAssets(AndroidPlatform platform) {
    super(platform);
    this.platform = platform;
  }

  public void setPathPrefix(String prefix) {
    if (prefix.startsWith("/") || prefix.endsWith("/")) {
      throw new IllegalArgumentException("Prefix must not start or end with '/'.");
    }
    pathPrefix = (prefix.length() == 0) ? prefix : (prefix + "/");
  }

  /**
   * Configures the default scale to use for assets. This allows one to use higher resolution
   * imagery than the device might normally. For example, one can supply scale 2 here, and
   * configure the graphics scale to 1.25 in order to use iOS Retina graphics (640x960) on a WXGA
   * (480x800) device.
   */
  public void setAssetScale(float scaleFactor) {
    this.assetScale = new Scale(scaleFactor);
  }

  /**
   * Configures a class that will adjust the {@link BitmapFactory#Options} used to decode loaded
   * bitmaps. An app may wish to use different bitmap configs for different images (say {@code
   * RGB_565} for its non-transparent images) or adjust the dithering settings.
   */
  public void setBitmapOptionsAdjuster(BitmapOptionsAdjuster optionsAdjuster) {
    this.optionsAdjuster = optionsAdjuster;
  }

  @Override
  public Image getRemoteImage(final String url, float width, float height) {
    final AndroidAsyncImage image = new AndroidAsyncImage(platform.graphics().ctx, width, height);
    platform.invokeAsync(new Runnable() {
      public void run () {
        try {
          setImageLater(image, downloadBitmap(url), Scale.ONE);
        } catch (Exception error) {
          setErrorLater(image, error);
        }
      }
    });
    return image;
  }

  @Override
  public Sound getSound(String path) {
    return platform.audio().createSound(path + ".mp3");
  }

  @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
  protected Image createStaticImage(Bitmap bitmap, Scale scale) {
    return new AndroidImage(platform.graphics().ctx, bitmap, scale);
  }

  @Override
  protected AsyncImage createAsyncImage(float width, float height) {
    return new AndroidAsyncImage(platform.graphics().ctx, width, height);
  }

  @Override
  protected Image loadImage(String path, ImageReceiver recv) {
    Exception error = null;
    for (Scale.ScaledResource rsrc : assetScale().getScaledResources(path)) {
      try {
        InputStream is = openAsset(rsrc.path);
        try {
          return recv.imageLoaded(decodeBitmap(path, is), rsrc.scale);
        } finally {
          is.close();
        }
      } catch (FileNotFoundException fnfe) {
        error = fnfe; // keep going, checking for lower resolution images
      } catch (Exception e) {
        error = e;
        break; // the image was broken not missing, stop here
      }
    }
    platform.log().warn("Could not load image: " + pathPrefix + path, error);
    return recv.loadFailed(error != null ? error : new FileNotFoundException(path));
  }

  /**
   * Copies a resource from our APK into a temporary file and returns a handle on that file.
   *
   * @param path the path to the to-be-cached asset.
   * @param cacheName the name to use for the cache file.
   */
  File cacheAsset(String path, String cacheName) throws IOException {
    InputStream in = openAsset(path);
    File cachedFile = new File(platform.activity.getCacheDir(), cacheName);
    try {
      FileOutputStream out = new FileOutputStream(cachedFile);
      try {
        byte[] buffer = new byte[16 * 1024];
        while (true) {
          int r = in.read(buffer);
          if (r < 0)
            break;
          out.write(buffer, 0, r);
        }
      } finally {
        out.close();
      }
    } finally {
      in.close();
    }
    return cachedFile;
  }

  private Scale assetScale () {
    return (assetScale != null) ? assetScale : platform.graphics().ctx.scale;
  }

  /**
   * Attempts to open the asset with the given name, throwing an {@link IOException} in case of
   * failure.
   */
  private InputStream openAsset(String path) throws IOException {
    String fullPath = normalizePath(pathPrefix + path);
    InputStream is = getClass().getClassLoader().getResourceAsStream(fullPath);
    if (is == null)
      throw new FileNotFoundException("Missing resource: " + fullPath);
    return is;
  }

  private Bitmap decodeBitmap(String path, InputStream is) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inScaled = false; // don't scale bitmaps based on device parameters
    options.inDither = true;
    options.inPreferredConfig = platform.graphics().preferredBitmapConfig;
    // give the game an opportunity to customize the bitmap options based on the image path
    optionsAdjuster.adjustOptions(path, options);
    return BitmapFactory.decodeStream(is, null, options);
  }

  // Taken from
  // http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html
  private Bitmap downloadBitmap(String url) 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) {
        // we could check the status, but we'll just assume that if there's a Location header,
        // we should follow it
        Header[] headers = response.getHeaders("Location");
        if (headers != null && headers.length > 0) {
          return downloadBitmap(headers[headers.length-1].getValue());
        }
        throw new Exception("Error " + statusCode + " while retrieving bitmap from " + url);
      }

      HttpEntity entity = response.getEntity();
      if (entity == null)
        throw new Exception("Error: getEntity returned null for " + url);

      InputStream inputStream = null;
      try {
        inputStream = entity.getContent();
        return decodeBitmap(url, inputStream);
      } finally {
        if (inputStream != null)
          inputStream.close();
        entity.consumeContent();
      }

    } catch (Exception e) {
      // Could provide a more explicit error message for IOException or IllegalStateException
      getRequest.abort();
      Log.w("ImageDownloader", "Error while retrieving bitmap from " + url, e);
      throw e;

    } finally {
      client.close();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy