diff --git a/src/org/thialfihar/android/apg/Apg.java b/src/org/thialfihar/android/apg/Apg.java index fd9d29ccc..dc7090ea6 100644 --- a/src/org/thialfihar/android/apg/Apg.java +++ b/src/org/thialfihar/android/apg/Apg.java @@ -183,6 +183,14 @@ public class Apg { return; } + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + File dir = new File(Constants.path.app_dir); + if (!dir.exists() && !dir.mkdirs()) { + // ignore this for now, it's not crucial + // that the directory doesn't exist at this point + } + } + loadKeyRings(context, Id.type.public_key); loadKeyRings(context, Id.type.secret_key); diff --git a/src/org/thialfihar/android/apg/BaseActivity.java b/src/org/thialfihar/android/apg/BaseActivity.java index 09de2ae5a..3365270dd 100644 --- a/src/org/thialfihar/android/apg/BaseActivity.java +++ b/src/org/thialfihar/android/apg/BaseActivity.java @@ -25,7 +25,6 @@ import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.util.Log; public class BaseActivity extends Activity implements Runnable, ProgressDialogUpdater, @@ -142,6 +141,21 @@ public class BaseActivity extends Activity @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { + case Id.request.secret_keys: { + if (resultCode == RESULT_OK) { + Bundle bundle = data.getExtras(); + long newId = bundle.getLong("selectedKeyId"); + if (getSecretKeyId() != newId) { + Apg.setPassPhrase(null); + } + setSecretKeyId(newId); + } else { + setSecretKeyId(0); + Apg.setPassPhrase(null); + } + break; + } + default: { break; } @@ -208,7 +222,6 @@ public class BaseActivity extends Activity } public void passPhraseCallback(String passPhrase) { - Log.e("oink", "setting pass phrase to " + passPhrase); Apg.setPassPhrase(passPhrase); } diff --git a/src/org/thialfihar/android/apg/Constants.java b/src/org/thialfihar/android/apg/Constants.java new file mode 100644 index 000000000..fdd71578e --- /dev/null +++ b/src/org/thialfihar/android/apg/Constants.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * 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 org.thialfihar.android.apg; + +import android.os.Environment; + +public final class Constants { + public static final class path { + public static final String app_dir = Environment.getExternalStorageDirectory() + "/APG"; + } + + public static final class pref { + public static final String has_seen_change_log = "seenChangeLogDialog" + Apg.VERSION; + } +} diff --git a/src/org/thialfihar/android/apg/EditKeyActivity.java b/src/org/thialfihar/android/apg/EditKeyActivity.java index 6f13efabb..b44cc145d 100644 --- a/src/org/thialfihar/android/apg/EditKeyActivity.java +++ b/src/org/thialfihar/android/apg/EditKeyActivity.java @@ -36,7 +36,6 @@ import android.os.Bundle; import android.os.Message; import android.text.InputType; import android.text.method.PasswordTransformationMethod; -import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -116,7 +115,6 @@ public class EditKeyActivity extends BaseActivity implements OnClickListener { } public boolean havePassPhrase() { - Log.e("oink", "password is " + Apg.getPassPhrase()); return (Apg.getPassPhrase() != null && !Apg.getPassPhrase().equals("")) || (mNewPassPhrase != null && mNewPassPhrase.equals("")); } diff --git a/src/org/thialfihar/android/apg/EncryptFileActivity.java b/src/org/thialfihar/android/apg/EncryptFileActivity.java index a9b05c687..461938a14 100644 --- a/src/org/thialfihar/android/apg/EncryptFileActivity.java +++ b/src/org/thialfihar/android/apg/EncryptFileActivity.java @@ -16,10 +16,13 @@ package org.thialfihar.android.apg; -import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SignatureException; @@ -32,11 +35,14 @@ import org.bouncycastle2.openpgp.PGPPublicKeyRing; import org.bouncycastle2.openpgp.PGPSecretKey; import org.bouncycastle2.openpgp.PGPSecretKeyRing; import org.openintents.intents.FileManager; +import org.thialfihar.android.apg.Apg.GeneralException; +import android.app.Dialog; import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.os.Environment; import android.os.Message; import android.view.View; import android.view.View.OnClickListener; @@ -51,44 +57,49 @@ import android.widget.Toast; import android.widget.TabHost.TabSpec; public class EncryptFileActivity extends BaseActivity { + private final String TAB_ASYMMETRIC = "TAB_ASYMMETRIC"; + private final String TAB_SYMMETRIC = "TAB_SYMMETRIC"; + private TabHost mTabHost = null; private EditText mFilename = null; private ImageButton mBrowse = null; private CheckBox mSign = null; private TextView mMainUserId = null; private TextView mMainUserIdRest = null; - private ListView mList; + private ListView mPublicKeyList = null; private Button mEncryptButton = null; private long mEncryptionKeyIds[] = null; + private String mInputFilename = null; + private String mOutputFilename = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.encrypt_file); - TabHost tabHost = (TabHost) findViewById(R.id.tab_host); - tabHost.setup(); + mTabHost = (TabHost) findViewById(R.id.tab_host); + mTabHost.setup(); - TabSpec ts1 = tabHost.newTabSpec("TAB_ASYMMETRIC"); + TabSpec ts1 = mTabHost.newTabSpec(TAB_ASYMMETRIC); ts1.setIndicator(getString(R.string.tab_asymmetric), getResources().getDrawable(R.drawable.key)); ts1.setContent(R.id.tab_asymmetric); - tabHost.addTab(ts1); + mTabHost.addTab(ts1); - TabSpec ts2 = tabHost.newTabSpec("TAB_SYMMETRIC"); + TabSpec ts2 = mTabHost.newTabSpec(TAB_SYMMETRIC); ts2.setIndicator(getString(R.string.tab_symmetric), getResources().getDrawable(R.drawable.encrypted)); ts2.setContent(R.id.tab_symmetric); - tabHost.addTab(ts2); + mTabHost.addTab(ts2); - tabHost.setCurrentTab(0); + mTabHost.setCurrentTab(0); Vector keyRings = (Vector) Apg.getPublicKeyRings().clone(); Collections.sort(keyRings, new Apg.PublicKeySorter()); - mList = (ListView) findViewById(R.id.public_key_list); - mList.setAdapter(new SelectPublicKeyListAdapter(mList, keyRings)); + mPublicKeyList = (ListView) findViewById(R.id.public_key_list); + mPublicKeyList.setAdapter(new SelectPublicKeyListAdapter(mPublicKeyList, keyRings)); mFilename = (EditText) findViewById(R.id.filename); mBrowse = (ImageButton) findViewById(R.id.btn_browse); @@ -180,17 +191,60 @@ public class EncryptFileActivity extends BaseActivity { } private void encryptClicked() { - if (getSecretKeyId() != 0 && Apg.getPassPhrase() == null) { - showDialog(Id.dialog.pass_phrase); - } else { - encryptStart(); + String currentFilename = mFilename.getText().toString(); + if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { + mInputFilename = mFilename.getText().toString(); + File file = new File(mInputFilename); + mOutputFilename = Constants.path.app_dir + "/" + file.getName() + ".gpg"; } + + if (mInputFilename.equals("")) { + Toast.makeText(this, "Select a file first.", Toast.LENGTH_SHORT).show(); + return; + } + + if (mTabHost.getCurrentTabTag().equals(TAB_ASYMMETRIC)) { + Vector vector = new Vector(); + for (int i = 0; i < mPublicKeyList.getCount(); ++i) { + if (mPublicKeyList.isItemChecked(i)) { + vector.add(mPublicKeyList.getItemIdAtPosition(i)); + } + } + if (vector.size() > 0) { + mEncryptionKeyIds = new long[vector.size()]; + for (int i = 0; i < vector.size(); ++i) { + mEncryptionKeyIds[i] = vector.get(i); + } + } else { + mEncryptionKeyIds = null; + } + + boolean encryptIt = mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0; + if (getSecretKeyId() == 0 && !encryptIt) { + Toast.makeText(this, "Select a signature key or encryption keys.", + Toast.LENGTH_SHORT).show(); + return; + } + + if (getSecretKeyId() != 0 && Apg.getPassPhrase() == null) { + showDialog(Id.dialog.pass_phrase); + return; + } + } else { + + } + + askForOutputFilename(); } @Override public void passPhraseCallback(String passPhrase) { super.passPhraseCallback(passPhrase); - encryptStart(); + askForOutputFilename(); + } + + private void askForOutputFilename() { + showDialog(Id.dialog.output_filename); } private void encryptStart() { @@ -205,19 +259,35 @@ public class EncryptFileActivity extends BaseActivity { Message msg = new Message(); try { - InputStream in = new FileInputStream(mFilename.getText().toString()); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) { - Apg.encrypt(in, out, true, mEncryptionKeyIds, getSecretKeyId(), - Apg.getPassPhrase(), this); - data.putString("message", new String(out.toByteArray())); - } else { - Apg.signText(in, out, getSecretKeyId(), - Apg.getPassPhrase(), HashAlgorithmTags.SHA256, this); - data.putString("message", new String(out.toByteArray())); + if (mInputFilename.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath()) || + mOutputFilename.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + throw new GeneralException("external storage not ready"); + } } - } catch (IOException e) { + + InputStream in = new FileInputStream(mInputFilename); + OutputStream out = new FileOutputStream(mOutputFilename); + + if (mTabHost.getCurrentTabTag().equals(TAB_ASYMMETRIC)) { + boolean encryptIt = mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0; + + if (encryptIt) { + Apg.encrypt(in, out, true, mEncryptionKeyIds, getSecretKeyId(), + Apg.getPassPhrase(), this); + } else { + Apg.signText(in, out, getSecretKeyId(), + Apg.getPassPhrase(), HashAlgorithmTags.SHA256, this); + } + } else { + + } + + out.close(); + } catch (FileNotFoundException e) { + error = "file not found: " + e.getMessage(); + } + catch (IOException e) { error = e.getMessage(); } catch (PGPException e) { error = e.getMessage(); @@ -235,12 +305,50 @@ public class EncryptFileActivity extends BaseActivity { if (error != null) { data.putString("error", error); + // delete the file if an error occurred + File file = new File(mOutputFilename); + file.delete(); } msg.setData(data); sendMessage(msg); } + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case Id.dialog.output_filename: { + return FileDialog.build(this, "Encrypt to file", + "Please specify which file to encrypt to.\n" + + "WARNING! File will be overwritten if it exists.", + mOutputFilename, + new FileDialog.OnClickListener() { + + @Override + public void onOkClick(String filename) { + removeDialog(Id.dialog.output_filename); + mOutputFilename = filename; + encryptStart(); + } + + @Override + public void onCancelClick() { + removeDialog(Id.dialog.output_filename); + } + }, + getString(R.string.filemanager_title_save), + getString(R.string.filemanager_btn_save), + Id.request.output_filename); + } + + default: { + break; + } + } + + return super.onCreateDialog(id); + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { @@ -261,20 +369,21 @@ public class EncryptFileActivity extends BaseActivity { return; } - case Id.request.secret_keys: { - if (resultCode == RESULT_OK) { - Bundle bundle = data.getExtras(); - long newId = bundle.getLong("selectedKeyId"); - if (getSecretKeyId() != newId) { - Apg.setPassPhrase(null); + case Id.request.output_filename: { + if (resultCode == RESULT_OK && data != null) { + String filename = data.getDataString(); + if (filename != null) { + // Get rid of URI prefix: + if (filename.startsWith("file://")) { + filename = filename.substring(7); + } + // replace %20 and so on + filename = Uri.decode(filename); + + FileDialog.setFilename(filename); } - setSecretKeyId(newId); - } else { - setSecretKeyId(0); - Apg.setPassPhrase(null); } - updateView(); - break; + return; } default: { diff --git a/src/org/thialfihar/android/apg/EncryptMessageActivity.java b/src/org/thialfihar/android/apg/EncryptMessageActivity.java index d89e62bd0..4b808ba85 100644 --- a/src/org/thialfihar/android/apg/EncryptMessageActivity.java +++ b/src/org/thialfihar/android/apg/EncryptMessageActivity.java @@ -178,6 +178,9 @@ public class EncryptMessageActivity extends BaseActivity { try { boolean encryptIt = mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0; + if (getSecretKeyId() == 0 && !encryptIt) { + throw new Apg.GeneralException("no signature key or encryption key selected"); + } String message = mMessage.getText().toString(); if (!encryptIt) { @@ -198,12 +201,11 @@ public class EncryptMessageActivity extends BaseActivity { if (encryptIt) { Apg.encrypt(in, out, true, mEncryptionKeyIds, getSecretKeyId(), Apg.getPassPhrase(), this); - data.putString("message", new String(out.toByteArray())); } else { Apg.signText(in, out, getSecretKeyId(), Apg.getPassPhrase(), HashAlgorithmTags.SHA256, this); - data.putString("message", new String(out.toByteArray())); } + data.putString("message", new String(out.toByteArray())); } catch (IOException e) { error = e.getMessage(); } catch (PGPException e) { @@ -288,22 +290,6 @@ public class EncryptMessageActivity extends BaseActivity { break; } - case Id.request.secret_keys: { - if (resultCode == RESULT_OK) { - Bundle bundle = data.getExtras(); - long newId = bundle.getLong("selectedKeyId"); - if (getSecretKeyId() != newId) { - Apg.setPassPhrase(null); - } - setSecretKeyId(newId); - } else { - setSecretKeyId(0); - Apg.setPassPhrase(null); - } - updateView(); - break; - } - default: { break; } diff --git a/src/org/thialfihar/android/apg/FileDialog.java b/src/org/thialfihar/android/apg/FileDialog.java index 653ca1005..891606362 100644 --- a/src/org/thialfihar/android/apg/FileDialog.java +++ b/src/org/thialfihar/android/apg/FileDialog.java @@ -37,6 +37,7 @@ public class FileDialog { private static Activity mActivity; private static String mFileManagerTitle; private static String mFileManagerButton; + private static int mRequestCode; public static interface OnClickListener { public void onCancelClick(); @@ -45,7 +46,8 @@ public class FileDialog { public static AlertDialog build(Activity activity, String title, String message, String defaultFile, OnClickListener onClickListener, - String fileManagerTitle, String fileManagerButton) { + String fileManagerTitle, String fileManagerButton, + int requestCode) { LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); AlertDialog.Builder alert = new AlertDialog.Builder(activity); @@ -67,6 +69,7 @@ public class FileDialog { }); mFileManagerTitle = fileManagerTitle; mFileManagerButton = fileManagerButton; + mRequestCode = requestCode; alert.setView(view); @@ -107,7 +110,7 @@ public class FileDialog { intent.putExtra(FileManager.EXTRA_BUTTON_TEXT, mFileManagerButton); try { - mActivity.startActivityForResult(intent, Id.request.filename); + mActivity.startActivityForResult(intent, mRequestCode); } catch (ActivityNotFoundException e) { // No compatible file manager was found. Toast.makeText(mActivity, R.string.no_filemanager_installed, Toast.LENGTH_SHORT).show(); diff --git a/src/org/thialfihar/android/apg/Id.java b/src/org/thialfihar/android/apg/Id.java index 7a57b706d..92932a7d6 100644 --- a/src/org/thialfihar/android/apg/Id.java +++ b/src/org/thialfihar/android/apg/Id.java @@ -48,6 +48,7 @@ public final class Id { public static final int public_keys = 0x21070001; public static final int secret_keys = 0x21070002; public static final int filename = 0x21070003; + public static final int output_filename = 0x21070004; } public static final class dialog { @@ -67,6 +68,7 @@ public final class Id { public static final int new_account = 0x2107000e; public static final int about = 0x2107000f; public static final int change_log = 0x21070010; + public static final int output_filename = 0x21070011; } public static final class task { diff --git a/src/org/thialfihar/android/apg/MainActivity.java b/src/org/thialfihar/android/apg/MainActivity.java index 8cd9df6bf..b1c656207 100644 --- a/src/org/thialfihar/android/apg/MainActivity.java +++ b/src/org/thialfihar/android/apg/MainActivity.java @@ -52,9 +52,6 @@ import android.widget.Toast; import android.widget.AdapterView.OnItemClickListener; public class MainActivity extends BaseActivity { - - private static String PREF_SEEN_CHANGE_LOG = "seenChangeLogDialog" + Apg.VERSION; - private ListView mAccounts = null; @Override @@ -116,7 +113,7 @@ public class MainActivity extends BaseActivity { registerForContextMenu(mAccounts); SharedPreferences prefs = getPreferences(MODE_PRIVATE); - if (!prefs.getBoolean(PREF_SEEN_CHANGE_LOG, false)) { + if (!prefs.getBoolean(Constants.pref.has_seen_change_log, false)) { showDialog(Id.dialog.change_log); } } @@ -227,6 +224,7 @@ public class MainActivity extends BaseActivity { new SpannableString("Read the warnings!\n\n" + "Changes:\n" + " * OI File Manager support\n" + + " * file encryption\n" + "\n" + "WARNING: be careful editing your existing keys, as they " + "WILL be stripped of certificates right now.\n" + @@ -253,7 +251,7 @@ public class MainActivity extends BaseActivity { MainActivity.this.removeDialog(Id.dialog.change_log); SharedPreferences prefs = getPreferences(MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(PREF_SEEN_CHANGE_LOG, true); + editor.putBoolean(Constants.pref.has_seen_change_log, true); editor.commit(); } }); diff --git a/src/org/thialfihar/android/apg/PublicKeyListActivity.java b/src/org/thialfihar/android/apg/PublicKeyListActivity.java index d6a2aa577..417d7e626 100644 --- a/src/org/thialfihar/android/apg/PublicKeyListActivity.java +++ b/src/org/thialfihar/android/apg/PublicKeyListActivity.java @@ -192,7 +192,8 @@ public class PublicKeyListActivity extends BaseActivity { } }, getString(R.string.filemanager_title_open), - getString(R.string.filemanager_btn_open)); + getString(R.string.filemanager_btn_open), + Id.request.filename); } case Id.dialog.export_key: { @@ -228,7 +229,8 @@ public class PublicKeyListActivity extends BaseActivity { } }, getString(R.string.filemanager_title_save), - getString(R.string.filemanager_btn_save)); + getString(R.string.filemanager_btn_save), + Id.request.filename); } default: { diff --git a/src/org/thialfihar/android/apg/SecretKeyListActivity.java b/src/org/thialfihar/android/apg/SecretKeyListActivity.java index 9eff85d24..f2f89fae9 100644 --- a/src/org/thialfihar/android/apg/SecretKeyListActivity.java +++ b/src/org/thialfihar/android/apg/SecretKeyListActivity.java @@ -217,7 +217,8 @@ public class SecretKeyListActivity extends BaseActivity implements OnChildClickL } }, getString(R.string.filemanager_title_open), - getString(R.string.filemanager_btn_open)); + getString(R.string.filemanager_btn_open), + Id.request.filename); } case Id.dialog.export_key: { @@ -254,7 +255,8 @@ public class SecretKeyListActivity extends BaseActivity implements OnChildClickL } }, getString(R.string.filemanager_title_save), - getString(R.string.filemanager_btn_save)); + getString(R.string.filemanager_btn_save), + Id.request.filename); } case Id.dialog.pass_phrase: {