Allow to pass large blobs and a new content provider to simplify this

Since AIDL is not for passing large data, a blob can be passed to APG by
a Uri. This Uri is opened as a file by APG and read/written to. Note the
file is overwritten by APG, so make sure it is a copy if you want to
keep the original.

With the ApgServiceBlobProvider, Apg has an own ContentProvider that can
be used like mentioned above. For now the data is stored in the dir
where APG stores other files and NOT DELETED after en/decryption. This
is tbd. It can only be accessed by an application with the permission
"org.thialfihar.android.apg.permission.STORE_BLOBS".

ApgCon has been updated accordingly and can handle blobs with `setBlob`
and `getBlobResult`. That is a really easy way to en/decrypt large data.

Note that encrypting by blob should only be used for large files (1MB+).
On all other cases, the data should be passed as as String through the
AIDl-Interface, so no temporary file must be created.

See ApgCon for a complete example of how to connect to the AIDL and use
it. Or use it in your own project!
This commit is contained in:
Markus Doits 2011-11-04 21:22:49 +01:00
parent a7294d50b1
commit ad16574657
10 changed files with 422 additions and 35 deletions

View File

@ -204,12 +204,17 @@
<intent-filter>
<action android:name="org.thialfihar.android.apg.IApgService"/>
</intent-filter>
<meta-data android:name="api_version" android:value="1" />
<meta-data android:name="api_version" android:value="2" />
</service>
<provider
android:readPermission="org.thialfihar.android.apg.permission.READ_KEY_DETAILS"
android:name="org.thialfihar.android.apg.provider.DataProvider"
android:authorities="org.thialfihar.android.apg.provider"/>
<provider
android:permission="org.thialfihar.android.apg.permission.STORE_BLOBS"
android:name="org.thialfihar.android.apg.provider.ApgServiceBlobProvider"
android:authorities="org.thialfihar.android.apg.provider.apgserviceblobprovider"/>
</application>
<uses-sdk android:minSdkVersion="3" android:targetSdkVersion="5" />
@ -219,10 +224,16 @@
android:label="@string/permission_read_key_details_label"
android:description="@string/permission_read_key_details_description"/>
<permission android:name="org.thialfihar.android.apg.permission.STORE_BLOBS"
android:protectionLevel="dangerous"
android:label="@string/permission_store_blobs_label"
android:description="@string/permission_store_blobs_description"/>
<uses-permission android:name="com.google.android.providers.gmail.permission.READ_GMAIL" />
<uses-permission android:name="com.google.android.gm.permission.READ_GMAIL" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.fsck.k9.permission.READ_ATTACHMENT" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="org.thialfihar.android.apg.permission.STORE_BLOBS"/>
</manifest>

View File

@ -288,6 +288,8 @@
<!-- permission strings -->
<string name="permission_read_key_details_label">Read key details from APG.</string>
<string name="permission_read_key_details_description">Read key details of public and secret keys stored in APG, such as key ID and user IDs. The keys themselves can NOT be read.</string>
<string name="permission_store_blobs_label">Store blobs to en/decrypt with APG.</string>
<string name="permission_store_blobs_description">Store and read files on the android file system through APG. It cannot read files of other applications.</string>
<!-- action strings -->
<string name="action_encrypt">Encrypt</string>

View File

@ -2,6 +2,7 @@ package org.thialfihar.android.apg;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
@ -14,17 +15,19 @@ import org.thialfihar.android.apg.provider.KeyRings;
import org.thialfihar.android.apg.provider.Keys;
import org.thialfihar.android.apg.provider.UserIds;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
public class ApgService extends Service {
private final static String TAG = "ApgService";
private static final boolean LOCAL_LOGV = true;
private static final boolean LOCAL_LOGD = true;
public static final boolean LOCAL_LOGV = true;
public static final boolean LOCAL_LOGD = true;
@Override
public IBinder onBind(Intent intent) {
@ -33,7 +36,7 @@ public class ApgService extends Service {
}
/** error status */
private enum error {
private static enum error {
ARGUMENTS_MISSING,
APG_FAILURE,
NO_MATCHING_SECRET_KEY,
@ -44,9 +47,16 @@ public class ApgService extends Service {
return ordinal() + 100;
}
}
private static enum call {
encrypt_with_passphrase,
encrypt_with_public_key,
decrypt,
get_keys
}
/** all arguments that can be passed by calling application */
private enum arg {
public static enum arg {
MESSAGE, // message to encrypt or to decrypt
SYMMETRIC_PASSPHRASE, // key for symmetric en/decryption
PUBLIC_KEYS, // public keys for encryption
@ -58,10 +68,11 @@ public class ApgService extends Service {
SIGNATURE_KEY, // key for signing
PRIVATE_KEY_PASSPHRASE, // passphrase for encrypted private key
KEY_TYPE, // type of key (private or public)
BLOB, // blob passed
}
/** all things that might be returned */
private enum ret {
private static enum ret {
ERRORS, // string array list with errors
WARNINGS, // string array list with warnings
ERROR, // numeric error
@ -75,21 +86,18 @@ public class ApgService extends Service {
static {
HashSet<arg> args = new HashSet<arg>();
args.add(arg.SYMMETRIC_PASSPHRASE);
args.add(arg.MESSAGE);
FUNCTIONS_REQUIRED_ARGS.put("encrypt_with_passphrase", args);
FUNCTIONS_REQUIRED_ARGS.put(call.encrypt_with_passphrase.name(), args);
args = new HashSet<arg>();
args.add(arg.PUBLIC_KEYS);
args.add(arg.MESSAGE);
FUNCTIONS_REQUIRED_ARGS.put("encrypt_with_public_key", args);
FUNCTIONS_REQUIRED_ARGS.put(call.encrypt_with_public_key.name(), args);
args = new HashSet<arg>();
args.add(arg.MESSAGE);
FUNCTIONS_REQUIRED_ARGS.put("decrypt", args);
FUNCTIONS_REQUIRED_ARGS.put(call.decrypt.name(), args);
args = new HashSet<arg>();
args.add(arg.KEY_TYPE);
FUNCTIONS_REQUIRED_ARGS.put("get_keys", args);
FUNCTIONS_REQUIRED_ARGS.put(call.get_keys.name(), args);
}
/** optional arguments for each AIDL function */
@ -103,14 +111,18 @@ public class ApgService extends Service {
args.add(arg.COMPRESSION);
args.add(arg.PRIVATE_KEY_PASSPHRASE);
args.add(arg.SIGNATURE_KEY);
FUNCTIONS_OPTIONAL_ARGS.put("encrypt_with_passphrase", args);
FUNCTIONS_OPTIONAL_ARGS.put("encrypt_with_public_key", args);
args.add(arg.BLOB);
args.add(arg.MESSAGE);
FUNCTIONS_OPTIONAL_ARGS.put(call.encrypt_with_passphrase.name(), args);
FUNCTIONS_OPTIONAL_ARGS.put(call.encrypt_with_public_key.name(), args);
args = new HashSet<arg>();
args.add(arg.SYMMETRIC_PASSPHRASE);
args.add(arg.PUBLIC_KEYS);
args.add(arg.PRIVATE_KEY_PASSPHRASE);
FUNCTIONS_OPTIONAL_ARGS.put("decrypt", args);
args.add(arg.MESSAGE);
args.add(arg.BLOB);
FUNCTIONS_OPTIONAL_ARGS.put(call.decrypt.name(), args);
}
/** a map from ApgService parameters to function calls to get the default */
@ -136,6 +148,14 @@ public class ApgService extends Service {
Log.e(TAG, "Function method exception: " + e.getMessage());
}
}
private static void writeToOutputStream(InputStream is, OutputStream os) throws IOException {
byte[] buffer = new byte[8];
int len = 0;
while( (len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
}
private static Cursor getKeyEntries(HashMap<String, Object> pParams) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
@ -304,6 +324,16 @@ public class ApgService extends Service {
}
}
}
if(pFunction.equals(call.encrypt_with_passphrase.name()) ||
pFunction.equals(call.encrypt_with_public_key.name()) ||
pFunction.equals(call.decrypt.name())) {
// check that either MESSAGE or BLOB are there
if( !pArgs.containsKey(arg.MESSAGE.name()) && !pArgs.containsKey(arg.BLOB.name())) {
pReturn.getStringArrayList(ret.ERRORS.name()).add("Arguments missing: Neither MESSAGE nor BLOG found");
}
}
}
/**
@ -378,6 +408,7 @@ public class ApgService extends Service {
}
private boolean encrypt(Bundle pArgs, Bundle pReturn) {
boolean isBlob = pArgs.containsKey(arg.BLOB.name());
long pubMasterKeys[] = {};
if (pArgs.containsKey(arg.PUBLIC_KEYS.name())) {
@ -391,7 +422,17 @@ public class ApgService extends Service {
pubMasterKeys = getMasterKey(pubKeys, pReturn);
}
InputStream inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes());
InputStream inStream = null;
if(isBlob) {
ContentResolver cr = getContentResolver();
try {
inStream = cr.openInputStream(Uri.parse(pArgs.getString(arg.BLOB.name())));
} catch (Exception e) {
Log.e(TAG, "... exception on opening blob", e);
}
} else {
inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes());
}
InputData in = new InputData(inStream, 0); // XXX Size second param?
OutputStream out = new ByteArrayOutputStream();
@ -427,7 +468,18 @@ public class ApgService extends Service {
return false;
}
if( LOCAL_LOGV ) Log.v(TAG, "Encrypted");
pReturn.putString(ret.RESULT.name(), out.toString());
if(isBlob) {
ContentResolver cr = getContentResolver();
try {
OutputStream outStream = cr.openOutputStream(Uri.parse(pArgs.getString(arg.BLOB.name())));
writeToOutputStream(new ByteArrayInputStream(out.toString().getBytes()), outStream);
outStream.close();
} catch (Exception e) {
Log.e(TAG, "... exception on writing blob", e);
}
} else {
pReturn.putString(ret.RESULT.name(), out.toString());
}
return true;
}
@ -481,11 +533,24 @@ public class ApgService extends Service {
if (!prepareArgs("decrypt", pArgs, pReturn)) {
return false;
}
boolean isBlob = pArgs.containsKey(arg.BLOB.name());
String passphrase = pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) != null ? pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) : pArgs
.getString(arg.PRIVATE_KEY_PASSPHRASE.name());
InputStream inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes());
InputStream inStream = null;
if(isBlob) {
ContentResolver cr = getContentResolver();
try {
inStream = cr.openInputStream(Uri.parse(pArgs.getString(arg.BLOB.name())));
} catch (Exception e) {
Log.e(TAG, "... exception on opening blob", e);
}
} else {
inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes());
}
InputData in = new InputData(inStream, 0); // XXX what size in second parameter?
OutputStream out = new ByteArrayOutputStream();
if( LOCAL_LOGV ) Log.v(TAG, "About to decrypt");
@ -508,9 +573,20 @@ public class ApgService extends Service {
}
return false;
}
if( LOCAL_LOGV ) Log.v(TAG, "Decrypted");
if( LOCAL_LOGV ) Log.v(TAG, "... decrypted");
pReturn.putString(ret.RESULT.name(), out.toString());
if(isBlob) {
ContentResolver cr = getContentResolver();
try {
OutputStream outStream = cr.openOutputStream(Uri.parse(pArgs.getString(arg.BLOB.name())));
writeToOutputStream(new ByteArrayInputStream(out.toString().getBytes()), outStream);
outStream.close();
} catch (Exception e) {
Log.e(TAG, "... exception on writing blob", e);
}
} else {
pReturn.putString(ret.RESULT.name(), out.toString());
}
return true;
}

View File

@ -19,6 +19,9 @@ package org.thialfihar.android.apg;
import android.os.Environment;
public final class Constants {
public static final String tag = "APG";
public static final class path {
public static final String app_dir = Environment.getExternalStorageDirectory() + "/APG";
}

View File

@ -30,6 +30,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.text.ClipboardManager;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.AnimationUtils;
@ -189,19 +190,28 @@ public class DecryptActivity extends BaseActivity {
// ignore, then
}
} else if (Apg.Intent.DECRYPT.equals(mIntent.getAction())) {
Log.d(Constants.tag, "Apg Intent DECRYPT startet");
Bundle extras = mIntent.getExtras();
if (extras == null) {
Log.d(Constants.tag, "extra bundle was null");
extras = new Bundle();
} else {
Log.d(Constants.tag, "got extras");
}
mData = extras.getByteArray(Apg.EXTRA_DATA);
String textData = null;
if (mData == null) {
Log.d(Constants.tag, "EXTRA_DATA was null");
textData = extras.getString(Apg.EXTRA_TEXT);
} else {
Log.d(Constants.tag, "Got data from EXTRA_DATA");
}
if (textData != null) {
Log.d(Constants.tag, "textData null, matching text ...");
Matcher matcher = Apg.PGP_MESSAGE.matcher(textData);
if (matcher.matches()) {
Log.d(Constants.tag, "PGP_MESSAGE matched");
textData = matcher.group(1);
// replace non breakable spaces
textData = textData.replaceAll("\\xa0", " ");
@ -209,11 +219,14 @@ public class DecryptActivity extends BaseActivity {
} else {
matcher = Apg.PGP_SIGNED_MESSAGE.matcher(textData);
if (matcher.matches()) {
Log.d(Constants.tag, "PGP_SIGNED_MESSAGE matched");
textData = matcher.group(1);
// replace non breakable spaces
textData = textData.replaceAll("\\xa0", " ");
mMessage.setText(textData);
mDecryptButton.setText(R.string.btn_verify);
} else {
Log.d(Constants.tag, "Nothing matched!");
}
}
}

View File

@ -27,6 +27,11 @@ interface IApgService {
*
* (required)
* String "MESSAGE" = Message to encrypt
* OR
* String "BLOB" = ContentUri to a file handle
* with binary data to encrypt
* (Attention: file will be overwritten
* with encrypted content!)
*
* (optional)
* int "ENCRYPTION_ALGORYTHM" = Encryption Algorithm
@ -55,7 +60,8 @@ interface IApgService {
* String "PRIVATE_KEY_PASSPHRASE" = Passphrase for signing key
*
* Bundle returnVals (in addition to the ERRORS/WARNINGS above):
* String "RESULT" = Encrypted message
* If "MESSAGE" was set:
* String "RESULT" = Encrypted message
*/
/* Additional argument for function below:
@ -77,7 +83,12 @@ interface IApgService {
/* Bundle params:
* (required)
* String "MESSAGE" = Message to decrypt
* String "MESSAGE" = Message to dencrypt
* OR
* String "BLOB" = ContentUri to a file handle
* with binary data to dencrypt
* (Attention: file will be overwritten
* with dencrypted content!)
*
* (optional)
* String "SYMMETRIC_PASSPHRASE" = Symmetric passphrase for decryption
@ -86,7 +97,8 @@ interface IApgService {
* String "PRIVATE_KEY_PASSPHRASE" = Private keys's passphrase on asymmetric encryption
*
* Bundle return_vals:
* String "RESULT" = Decrypted message
* If "MESSAGE" was set:
* String "RESULT" = Decrypted message
*/
boolean decrypt(in Bundle params, out Bundle returnVals);

View File

@ -19,6 +19,9 @@ package org.thialfihar.android.apg;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
public final class Id {
public static final String TAG = "APG";
public static final class menu {
public static final int export = 0x21070001;
public static final int delete = 0x21070002;

View File

@ -0,0 +1,54 @@
package org.thialfihar.android.apg.provider;
import org.thialfihar.android.apg.ApgService;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.util.Log;
public class ApgServiceBlobDatabase extends SQLiteOpenHelper {
private static final String TAG = "ApgServiceBlobDatabase";
private static final int VERSION = 1;
private static final String NAME = "apg_service_blob_data";
private static final String TABLE = "data";
public ApgServiceBlobDatabase(Context context) {
super(context, NAME, null, VERSION);
if(ApgService.LOCAL_LOGD) Log.d(TAG, "constructor called");
}
@Override
public void onCreate(SQLiteDatabase db) {
if(ApgService.LOCAL_LOGD) Log.d(TAG, "onCreate() called");
db.execSQL("create table " + TABLE + " ( _id integer primary key autoincrement," +
"key text not null)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if(ApgService.LOCAL_LOGD) Log.d(TAG, "onUpgrade() called");
// no upgrade necessary yet
}
public Uri insert(ContentValues vals) {
if(ApgService.LOCAL_LOGD) Log.d(TAG, "insert() called");
SQLiteDatabase db = this.getWritableDatabase();
long newId = db.insert(TABLE, null, vals);
return ContentUris.withAppendedId(ApgServiceBlobProvider.CONTENT_URI, newId);
}
public Cursor query(String id, String key) {
if(ApgService.LOCAL_LOGD) Log.d(TAG, "query() called");
SQLiteDatabase db = this.getReadableDatabase();
return db.query(TABLE, new String[] {"_id"},
"_id = ? and key = ?", new String[] {id, key},
null, null, null);
}
}

View File

@ -0,0 +1,138 @@
package org.thialfihar.android.apg.provider;
import org.thialfihar.android.apg.ApgService;
import org.thialfihar.android.apg.Constants;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
public class ApgServiceBlobProvider extends ContentProvider {
private static final String TAG = "ApgServiceBlobProvider";
public static final Uri CONTENT_URI = Uri.parse("content://org.thialfihar.android.apg.provider.apgserviceblobprovider");
private static final String COLUMN_KEY = "key";
private static final String STORE_PATH = Constants.path.app_dir+"/ApgServiceBlobs";
private ApgServiceBlobDatabase mDb = null;
public ApgServiceBlobProvider() {
if(ApgService.LOCAL_LOGD) Log.d(TAG, "Constructor called");
File dir = new File(STORE_PATH);
dir.mkdirs();
if(ApgService.LOCAL_LOGD) Log.d(TAG, "Constructor finished");
}
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
if(ApgService.LOCAL_LOGD) Log.d(TAG, "delete() called");
// TODO Auto-generated method stub
return 0;
}
@Override
public String getType(Uri arg0) {
if(ApgService.LOCAL_LOGD) Log.d(TAG, "getType() called");
// not needed for now
return null;
}
@Override
public Uri insert(Uri uri, ContentValues ignored) {
if(ApgService.LOCAL_LOGD) Log.d(TAG, "insert() called");
// ContentValues are actually ignored, because we want to store a blob with no more information
// but have to create an record with the password generated here first
ContentValues vals = new ContentValues();
// Insert a random key in the database. This has to provided by the caller when updating or
// getting the blob
String password = UUID.randomUUID().toString();
vals.put(COLUMN_KEY, password);
Uri insertedUri = mDb.insert(vals);
return Uri.withAppendedPath(insertedUri, password);
}
@Override
public boolean onCreate() {
if(ApgService.LOCAL_LOGD) Log.d(TAG, "onCreate() called");
mDb = new ApgServiceBlobDatabase(getContext());
// TODO Auto-generated method stub
return true;
}
@Override
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) {
if(ApgService.LOCAL_LOGD) Log.d(TAG, "query() called");
// TODO Auto-generated method stub
return null;
}
@Override
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
if(ApgService.LOCAL_LOGD) Log.d(TAG, "update() called");
// TODO Auto-generated method stub
return 0;
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws SecurityException, FileNotFoundException {
if(ApgService.LOCAL_LOGD) Log.d(TAG, "openFile() called");
if(ApgService.LOCAL_LOGD) Log.d(TAG, "... with uri: "+uri.toString());
if(ApgService.LOCAL_LOGD) Log.d(TAG, "... with mode: "+mode);
List<String> segments = uri.getPathSegments();
if(segments.size() < 2) {
throw new SecurityException("Password not found in URI");
}
String id = segments.get(0);
String key = segments.get(1);
if(ApgService.LOCAL_LOGD) Log.d(TAG, "... got id: "+id);
if(ApgService.LOCAL_LOGD) Log.d(TAG, "... and key: "+key);
// get the data
Cursor result = mDb.query(id, key);
if(result.getCount() == 0) {
// either the key is wrong or no id exists
throw new FileNotFoundException("No file found with that ID and/or password");
}
File targetFile = new File(STORE_PATH, id);
if(mode.equals("w")) {
if(ApgService.LOCAL_LOGD) Log.d(TAG, "... will try to open file w");
if( !targetFile.exists() ) {
try {
targetFile.createNewFile();
} catch (IOException e) {
Log.e(TAG, "... got IEOException on creating new file", e);
throw new FileNotFoundException("Could not create file to write to");
}
}
return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE );
} else if(mode.equals("r")) {
if(ApgService.LOCAL_LOGD) Log.d(TAG, "... will try to open file r");
if( !targetFile.exists() ) {
throw new FileNotFoundException("Error: Could not find the file requested");
}
return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_READ_ONLY);
}
return null;
}
}

View File

@ -16,22 +16,27 @@
package org.thialfihar.android.apg.utils;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import org.thialfihar.android.apg.IApgService;
import org.thialfihar.android.apg.utils.ApgConInterface.OnCallFinishListener;
import android.content.Context;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import org.thialfihar.android.apg.IApgService;
import org.thialfihar.android.apg.utils.ApgConInterface.OnCallFinishListener;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
/**
* A APG-AIDL-Wrapper
@ -46,7 +51,7 @@ import org.thialfihar.android.apg.utils.ApgConInterface.OnCallFinishListener;
* </p>
*
* @author Markus Doits <markus.doits@googlemail.com>
* @version 1.0rc1
* @version 1.1rc1
*
*/
public class ApgCon {
@ -54,7 +59,8 @@ public class ApgCon {
private static final boolean LOCAL_LOGD = true;
private final static String TAG = "ApgCon";
private final static int API_VERSION = 1; // aidl api-version it expects
private final static int API_VERSION = 2; // aidl api-version it expects
private final static String BLOB_URI = "content://org.thialfihar.android.apg.provider.apgserviceblobprovider";
/**
* How many seconds to wait for a connection to AGP when connecting.
@ -80,7 +86,6 @@ public class ApgCon {
}
private final Context mContext;
private final error mConnectionStatus;
private boolean mAsyncRunning = false;
@ -187,9 +192,9 @@ public class ApgCon {
mWarningList.add("(LOCAL) Could not determine ApgService API");
tmpError = error.APG_API_MISSMATCH;
} else if (inf.metaData.getInt("api_version") != API_VERSION) {
Log.w(TAG, "Found ApgService API version" + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION);
Log.w(TAG, "Found ApgService API version " + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION);
Log.w(TAG, "This probably won't work!");
mWarningList.add("(LOCAL) Found ApgService API version" + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION);
mWarningList.add("(LOCAL) Found ApgService API version " + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION);
tmpError = error.APG_API_MISSMATCH;
} else {
if( LOCAL_LOGV ) Log.v(TAG, "Found api_version " + API_VERSION + ", everything should work");
@ -495,6 +500,41 @@ public class ApgCon {
}
mArgs.putIntegerArrayList(key, list);
}
/**
* Set up binary data to en/decrypt
*
* @param is
* InputStream to get the data from
*/
public void setBlob(InputStream is) {
if( LOCAL_LOGD ) Log.d(TAG, "setBlob() called");
// 1. get the new contentUri
ContentResolver cr = mContext.getContentResolver();
Uri contentUri = cr.insert(Uri.parse(BLOB_URI), new ContentValues());
// 2. insert binary data
OutputStream os = null;
try {
os = cr.openOutputStream(contentUri, "w");
} catch( Exception e ) {
Log.e(TAG, "... exception on setBlob", e);
}
byte[] buffer = new byte[8];
int len = 0;
try {
while( (len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
if(LOCAL_LOGD) Log.d(TAG, "... write finished, now closing");
os.close();
} catch (Exception e) {
Log.e(TAG, "... error on writing buffer", e);
}
mArgs.putString("BLOB", contentUri.toString() );
}
/**
* Clears all arguments
@ -628,17 +668,52 @@ public class ApgCon {
* {@link #hasNextError()} is false.
* </p>
*
* <p>
* Note: When handling binary data with {@link #setBlob(InputStream)}, you
* get your result with {@link #getBlobResult()}.
* </p>
*
* @return the mResult of the last {@link #call(String)} or
* {@link #callAsync(String)}.
*
* @see #reset()
* @see #clearResult()
* @see #getResultBundle()
* @see #getBlobResult()
*/
public String getResult() {
return mResult.getString(ret.RESULT.name());
}
/**
* Get the binary result
*
* <p>
* This gets your binary result. It only works if you called {@link #setBlob(InputStream)} before.
*
* If you did not call encrypt nor decrypt, this will be the same data as you inputed.
* </p>
*
* @return InputStream of the binary data which was en/decrypted
*
* @see #setBlob(InputStream)
* @see #getResult()
*/
public InputStream getBlobResult() {
if(mArgs.containsKey("BLOB")) {
ContentResolver cr = mContext.getContentResolver();
InputStream in = null;
try {
in = cr.openInputStream(Uri.parse(mArgs.getString("BLOB")));
} catch( Exception e ) {
Log.e(TAG, "Could not return blob in result", e);
}
return in;
} else {
return null;
}
}
/**
* Get the result bundle
*