/*
* Copyright (c) 2013-2015 by appPlant UG. All rights reserved.
*
* @APPPLANT_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://opensource.org/licenses/Apache-2.0/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPPLANT_LICENSE_HEADER_END@
*/
package de.appplant.cordova.plugin.notification;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.StrictMode;
import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.UUID;
/**
* Util class to map unified asset URIs to native URIs. URIs like file:///
* map to absolute paths while file:// point relatively to the www folder
* within the asset resources. And res:// means a resource from the native
* res folder. Remote assets are accessible via http:// for example.
*/
class AssetUtil {
// Name of the storage folder
private static final String STORAGE_FOLDER = "/localnotification";
// Placeholder URI for default sound
private static final String DEFAULT_SOUND = "res://platform_default";
// Ref to the context passed through the constructor to access the
// resources and app directory.
private final Context context;
/**
* Constructor
*
* @param context
* Application context
*/
private AssetUtil(Context context) {
this.context = context;
}
/**
* Static method to retrieve class instance.
*
* @param context
* Application context
*/
static AssetUtil getInstance(Context context) {
return new AssetUtil(context);
}
/**
* Parse path path to native URI.
*
* @param path
* Path to path file
*/
Uri parseSound (String path) {
if (path == null || path.isEmpty())
return Uri.EMPTY;
if (path.equalsIgnoreCase(DEFAULT_SOUND)) {
return RingtoneManager.getDefaultUri(RingtoneManager
.TYPE_NOTIFICATION);
}
return parse(path);
}
/**
* The URI for a path.
*
* @param path
* The given path
*/
Uri parse (String path) {
if (path.startsWith("res:")) {
return getUriForResourcePath(path);
} else if (path.startsWith("file:///")) {
return getUriFromPath(path);
} else if (path.startsWith("file://")) {
return getUriFromAsset(path);
} else if (path.startsWith("http")){
return getUriFromRemote(path);
}
return Uri.EMPTY;
}
/**
* URI for a file.
*
* @param path
* Absolute path like file:///...
*
* @return
* URI pointing to the given path
*/
private Uri getUriFromPath(String path) {
String absPath = path.replaceFirst("file://", "");
File file = new File(absPath);
if (!file.exists()) {
Log.e("Asset", "File not found: " + file.getAbsolutePath());
return Uri.EMPTY;
}
return Uri.fromFile(file);
}
/**
* URI for an asset.
*
* @param path
* Asset path like file://...
*
* @return
* URI pointing to the given path
*/
private Uri getUriFromAsset(String path) {
String resPath = path.replaceFirst("file:/", "www");
String fileName = resPath.substring(resPath.lastIndexOf('/') + 1);
File file = getTmpFile(fileName);
if (file == null) {
Log.e("Asset", "Missing external cache dir");
return Uri.EMPTY;
}
try {
AssetManager assets = context.getAssets();
FileOutputStream outStream = new FileOutputStream(file);
InputStream inputStream = assets.open(resPath);
copyFile(inputStream, outStream);
outStream.flush();
outStream.close();
return Uri.fromFile(file);
} catch (Exception e) {
Log.e("Asset", "File not found: assets/" + resPath);
e.printStackTrace();
}
return Uri.EMPTY;
}
/**
* The URI for a resource.
*
* @param path
* The given relative path
*
* @return
* URI pointing to the given path
*/
private Uri getUriForResourcePath(String path) {
String resPath = path.replaceFirst("res://", "");
int resId = getResIdForDrawable(resPath);
File file = getTmpFile();
if (resId == 0) {
Log.e("Asset", "File not found: " + resPath);
return Uri.EMPTY;
}
if (file == null) {
Log.e("Asset", "Missing external cache dir");
return Uri.EMPTY;
}
try {
Resources res = context.getResources();
FileOutputStream outStream = new FileOutputStream(file);
InputStream inputStream = res.openRawResource(resId);
copyFile(inputStream, outStream);
outStream.flush();
outStream.close();
return Uri.fromFile(file);
} catch (Exception e) {
e.printStackTrace();
}
return Uri.EMPTY;
}
/**
* Uri from remote located content.
*
* @param path
* Remote address
*
* @return
* Uri of the downloaded file
*/
private Uri getUriFromRemote(String path) {
File file = getTmpFile();
if (file == null) {
Log.e("Asset", "Missing external cache dir");
return Uri.EMPTY;
}
try {
URL url = new URL(path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
StrictMode.ThreadPolicy policy =
new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
connection.setRequestProperty("Connection", "close");
connection.setConnectTimeout(5000);
connection.connect();
InputStream input = connection.getInputStream();
FileOutputStream outStream = new FileOutputStream(file);
copyFile(input, outStream);
outStream.flush();
outStream.close();
return Uri.fromFile(file);
} catch (MalformedURLException e) {
Log.e("Asset", "Incorrect URL");
e.printStackTrace();
} catch (FileNotFoundException e) {
Log.e("Asset", "Failed to create new File from HTTP Content");
e.printStackTrace();
} catch (IOException e) {
Log.e("Asset", "No Input can be created from http Stream");
e.printStackTrace();
}
return Uri.EMPTY;
}
/**
* Copy content from input stream into output stream.
*
* @param in
* The input stream
* @param out
* The output stream
*/
private void copyFile(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
}
/**
* Resource ID for drawable.
*
* @param resPath
* Resource path as string
*/
int getResIdForDrawable(String resPath) {
int resId = getResIdForDrawable(getPkgName(), resPath);
if (resId == 0) {
resId = getResIdForDrawable("android", resPath);
}
return resId;
}
/**
* Resource ID for drawable.
*
* @param clsName
* Relative package or global android name space
* @param resPath
* Resource path as string
*/
int getResIdForDrawable(String clsName, String resPath) {
String drawable = getBaseName(resPath);
int resId = 0;
try {
Class<?> cls = Class.forName(clsName + ".R$drawable");
resId = (Integer) cls.getDeclaredField(drawable).get(Integer.class);
} catch (Exception ignore) {}
return resId;
}
/**
* Convert drawable resource to bitmap.
*
* @param drawable
* Drawable resource name
*/
Bitmap getIconFromDrawable (String drawable) {
Resources res = context.getResources();
int iconId;
iconId = getResIdForDrawable(getPkgName(), drawable);
if (iconId == 0) {
iconId = getResIdForDrawable("android", drawable);
}
if (iconId == 0) {
iconId = android.R.drawable.screen_background_dark_transparent;
}
return BitmapFactory.decodeResource(res, iconId);
}
/**
* Convert URI to Bitmap.
*
* @param uri
* Internal image URI
*/
Bitmap getIconFromUri (Uri uri) throws IOException {
InputStream input = context.getContentResolver().openInputStream(uri);
return BitmapFactory.decodeStream(input);
}
/**
* Extract name of drawable resource from path.
*
* @param resPath
* Resource path as string
*/
private String getBaseName (String resPath) {
String drawable = resPath;
if (drawable.contains("/")) {
drawable = drawable.substring(drawable.lastIndexOf('/') + 1);
}
if (resPath.contains(".")) {
drawable = drawable.substring(0, drawable.lastIndexOf('.'));
}
return drawable;
}
/**
* Returns a file located under the external cache dir of that app.
*
* @return
* File with a random UUID name
*/
private File getTmpFile () {
// If random UUID is not be enough see
// https://github.com/LukePulverenti/cordova-plugin-local-notifications/blob/267170db14044cbeff6f4c3c62d9b766b7a1dd62/src/android/notification/AssetUtil.java#L255
return getTmpFile(UUID.randomUUID().toString());
}
/**
* Returns a file located under the external cache dir of that app.
*
* @param name
* The name of the file
* @return
* File with the provided name
*/
private File getTmpFile (String name) {
File dir = context.getExternalCacheDir();
if (dir == null) {
Log.e("Asset", "Missing external cache dir");
return null;
}
String storage = dir.toString() + STORAGE_FOLDER;
//noinspection ResultOfMethodCallIgnored
new File(storage).mkdir();
return new File(storage, name);
}
/**
* Package name specified by context.
*/
private String getPkgName () {
return context.getPackageName();
}
}