new directory structure
This commit is contained in:
51
org_apg/src/org/apg/ui/AboutActivity.java
Normal file
51
org_apg/src/org/apg/ui/AboutActivity.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package org.apg.ui;
|
||||
|
||||
import org.apg.Constants;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class AboutActivity extends Activity {
|
||||
Activity mActivity;
|
||||
|
||||
/**
|
||||
* Instantiate View for this Activity
|
||||
*/
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.about_activity);
|
||||
|
||||
mActivity = this;
|
||||
|
||||
TextView versionText = (TextView) findViewById(R.id.about_version);
|
||||
versionText.setText(getString(R.string.about_version) + " " + getVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current package version.
|
||||
*
|
||||
* @return The current version.
|
||||
*/
|
||||
private String getVersion() {
|
||||
String result = "";
|
||||
try {
|
||||
PackageManager manager = mActivity.getPackageManager();
|
||||
PackageInfo info = manager.getPackageInfo(mActivity.getPackageName(), 0);
|
||||
|
||||
result = String.format("%s (%s)", info.versionName, info.versionCode);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.w(Constants.TAG, "Unable to get application version: " + e.getMessage());
|
||||
result = "Unable to get application version.";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
436
org_apg/src/org/apg/ui/BaseActivity.java
Normal file
436
org_apg/src/org/apg/ui/BaseActivity.java
Normal file
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.AskForSecretKeyPassPhrase;
|
||||
import org.apg.Constants;
|
||||
import org.apg.Id;
|
||||
import org.apg.PausableThread;
|
||||
import org.apg.Preferences;
|
||||
import org.apg.ProgressDialogUpdater;
|
||||
import org.apg.Service;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class BaseActivity extends Activity implements Runnable, ProgressDialogUpdater,
|
||||
AskForSecretKeyPassPhrase.PassPhraseCallbackInterface {
|
||||
|
||||
private ProgressDialog mProgressDialog = null;
|
||||
private PausableThread mRunningThread = null;
|
||||
private Thread mDeletingThread = null;
|
||||
|
||||
private long mSecretKeyId = 0;
|
||||
private String mDeleteFile = null;
|
||||
|
||||
protected Preferences mPreferences;
|
||||
|
||||
private Handler mHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
handlerCallback(msg);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mPreferences = Preferences.getPreferences(this);
|
||||
setLanguage(this, mPreferences.getLanguage());
|
||||
|
||||
Apg.initialize(this);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
startCacheService(this, mPreferences);
|
||||
}
|
||||
|
||||
public static void startCacheService(Activity activity, Preferences preferences) {
|
||||
Intent intent = new Intent(activity, Service.class);
|
||||
intent.putExtra(Service.EXTRA_TTL, preferences.getPassPhraseCacheTtl());
|
||||
activity.startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences).setIcon(
|
||||
android.R.drawable.ic_menu_preferences);
|
||||
menu.add(0, Id.menu.option.about, 1, R.string.menu_about).setIcon(
|
||||
android.R.drawable.ic_menu_info_details);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case Id.menu.option.about: {
|
||||
startActivity(new Intent(this, AboutActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.option.preferences: {
|
||||
startActivity(new Intent(this, PreferencesActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.option.search: {
|
||||
startSearch("", false, null, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
// in case it is a progress dialog
|
||||
mProgressDialog = new ProgressDialog(this);
|
||||
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
mProgressDialog.setCancelable(false);
|
||||
switch (id) {
|
||||
case Id.dialog.encrypting: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_initializing));
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
case Id.dialog.decrypting: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_initializing));
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
case Id.dialog.saving: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_saving));
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
case Id.dialog.importing: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_importing));
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
case Id.dialog.exporting: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_exporting));
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
case Id.dialog.deleting: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_initializing));
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
case Id.dialog.querying: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_querying));
|
||||
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
mProgressDialog.setCancelable(false);
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
case Id.dialog.signing: {
|
||||
mProgressDialog.setMessage(this.getString(R.string.progress_signing));
|
||||
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
mProgressDialog.setCancelable(false);
|
||||
return mProgressDialog;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mProgressDialog = null;
|
||||
|
||||
switch (id) {
|
||||
|
||||
case Id.dialog.pass_phrase: {
|
||||
return AskForSecretKeyPassPhrase.createDialog(this, getSecretKeyId(), this);
|
||||
}
|
||||
|
||||
case Id.dialog.pass_phrases_do_not_match: {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.error);
|
||||
alert.setMessage(R.string.passPhrasesDoNotMatch);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.pass_phrases_do_not_match);
|
||||
}
|
||||
});
|
||||
alert.setCancelable(false);
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
case Id.dialog.no_pass_phrase: {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.error);
|
||||
alert.setMessage(R.string.passPhraseMustNotBeEmpty);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.no_pass_phrase);
|
||||
}
|
||||
});
|
||||
alert.setCancelable(false);
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
case Id.dialog.delete_file: {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.warning);
|
||||
alert.setMessage(this.getString(R.string.fileDeleteConfirmation, getDeleteFile()));
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.delete_file);
|
||||
final File file = new File(getDeleteFile());
|
||||
showDialog(Id.dialog.deleting);
|
||||
mDeletingThread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
Bundle data = new Bundle();
|
||||
data.putInt(Constants.extras.STATUS, Id.message.delete_done);
|
||||
try {
|
||||
Apg.deleteFileSecurely(BaseActivity.this, file, BaseActivity.this);
|
||||
} catch (FileNotFoundException e) {
|
||||
data.putString(Apg.EXTRA_ERROR, BaseActivity.this.getString(
|
||||
R.string.error_fileNotFound, file));
|
||||
} catch (IOException e) {
|
||||
data.putString(Apg.EXTRA_ERROR, BaseActivity.this.getString(
|
||||
R.string.error_fileDeleteFailed, file));
|
||||
}
|
||||
Message msg = new Message();
|
||||
msg.setData(data);
|
||||
sendMessage(msg);
|
||||
}
|
||||
});
|
||||
mDeletingThread.start();
|
||||
}
|
||||
});
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.delete_file);
|
||||
}
|
||||
});
|
||||
alert.setCancelable(true);
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
|
||||
@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();
|
||||
setSecretKeyId(bundle.getLong(Apg.EXTRA_KEY_ID));
|
||||
} else {
|
||||
setSecretKeyId(Id.key.none);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
public void setProgress(int resourceId, int progress, int max) {
|
||||
setProgress(getString(resourceId), progress, max);
|
||||
}
|
||||
|
||||
public void setProgress(int progress, int max) {
|
||||
Message msg = new Message();
|
||||
Bundle data = new Bundle();
|
||||
data.putInt(Constants.extras.STATUS, Id.message.progress_update);
|
||||
data.putInt(Constants.extras.PROGRESS, progress);
|
||||
data.putInt(Constants.extras.PROGRESS_MAX, max);
|
||||
msg.setData(data);
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
public void setProgress(String message, int progress, int max) {
|
||||
Message msg = new Message();
|
||||
Bundle data = new Bundle();
|
||||
data.putInt(Constants.extras.STATUS, Id.message.progress_update);
|
||||
data.putString(Constants.extras.MESSAGE, message);
|
||||
data.putInt(Constants.extras.PROGRESS, progress);
|
||||
data.putInt(Constants.extras.PROGRESS_MAX, max);
|
||||
msg.setData(data);
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
public void handlerCallback(Message msg) {
|
||||
Bundle data = msg.getData();
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int type = data.getInt(Constants.extras.STATUS);
|
||||
switch (type) {
|
||||
case Id.message.progress_update: {
|
||||
String message = data.getString(Constants.extras.MESSAGE);
|
||||
if (mProgressDialog != null) {
|
||||
if (message != null) {
|
||||
mProgressDialog.setMessage(message);
|
||||
}
|
||||
mProgressDialog.setMax(data.getInt(Constants.extras.PROGRESS_MAX));
|
||||
mProgressDialog.setProgress(data.getInt(Constants.extras.PROGRESS));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.message.delete_done: {
|
||||
mProgressDialog = null;
|
||||
deleteDoneCallback(msg);
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.message.import_done: // intentionally no break
|
||||
case Id.message.export_done: // intentionally no break
|
||||
case Id.message.query_done: // intentionally no break
|
||||
case Id.message.done: {
|
||||
mProgressDialog = null;
|
||||
doneCallback(msg);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void doneCallback(Message msg) {
|
||||
|
||||
}
|
||||
|
||||
public void deleteDoneCallback(Message msg) {
|
||||
removeDialog(Id.dialog.deleting);
|
||||
mDeletingThread = null;
|
||||
|
||||
Bundle data = msg.getData();
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
String message;
|
||||
if (error != null) {
|
||||
message = getString(R.string.errorMessage, error);
|
||||
} else {
|
||||
message = getString(R.string.fileDeleteSuccessful);
|
||||
}
|
||||
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
public void passPhraseCallback(long keyId, String passPhrase) {
|
||||
Apg.setCachedPassPhrase(keyId, passPhrase);
|
||||
}
|
||||
|
||||
public void sendMessage(Message msg) {
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
public PausableThread getRunningThread() {
|
||||
return mRunningThread;
|
||||
}
|
||||
|
||||
public void startThread() {
|
||||
mRunningThread = new PausableThread(this);
|
||||
mRunningThread.start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
|
||||
}
|
||||
|
||||
public void setSecretKeyId(long id) {
|
||||
mSecretKeyId = id;
|
||||
}
|
||||
|
||||
public long getSecretKeyId() {
|
||||
return mSecretKeyId;
|
||||
}
|
||||
|
||||
protected void setDeleteFile(String deleteFile) {
|
||||
mDeleteFile = deleteFile;
|
||||
}
|
||||
|
||||
protected String getDeleteFile() {
|
||||
return mDeleteFile;
|
||||
}
|
||||
|
||||
public static void setLanguage(Context context, String language) {
|
||||
Locale locale;
|
||||
if (language == null || language.equals("")) {
|
||||
locale = Locale.getDefault();
|
||||
} else {
|
||||
locale = new Locale(language);
|
||||
}
|
||||
Configuration config = new Configuration();
|
||||
config.locale = locale;
|
||||
context.getResources().updateConfiguration(config,
|
||||
context.getResources().getDisplayMetrics());
|
||||
}
|
||||
}
|
||||
807
org_apg/src/org/apg/ui/DecryptActivity.java
Normal file
807
org_apg/src/org/apg/ui/DecryptActivity.java
Normal file
@@ -0,0 +1,807 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Constants;
|
||||
import org.apg.DataDestination;
|
||||
import org.apg.DataSource;
|
||||
import org.apg.FileDialog;
|
||||
import org.apg.Id;
|
||||
import org.apg.InputData;
|
||||
import org.apg.PausableThread;
|
||||
import org.apg.provider.DataProvider;
|
||||
import org.apg.util.Compatibility;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ViewFlipper;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.Security;
|
||||
import java.security.SignatureException;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
public class DecryptActivity extends BaseActivity {
|
||||
private long mSignatureKeyId = 0;
|
||||
|
||||
private Intent mIntent;
|
||||
|
||||
private boolean mReturnResult = false;
|
||||
private String mReplyTo = null;
|
||||
private String mSubject = null;
|
||||
private boolean mSignedOnly = false;
|
||||
private boolean mAssumeSymmetricEncryption = false;
|
||||
|
||||
private EditText mMessage = null;
|
||||
private LinearLayout mSignatureLayout = null;
|
||||
private ImageView mSignatureStatusImage = null;
|
||||
private TextView mUserId = null;
|
||||
private TextView mUserIdRest = null;
|
||||
|
||||
private ViewFlipper mSource = null;
|
||||
private TextView mSourceLabel = null;
|
||||
private ImageView mSourcePrevious = null;
|
||||
private ImageView mSourceNext = null;
|
||||
|
||||
private Button mDecryptButton = null;
|
||||
private Button mReplyButton = null;
|
||||
|
||||
private int mDecryptTarget;
|
||||
|
||||
private EditText mFilename = null;
|
||||
private CheckBox mDeleteAfter = null;
|
||||
private ImageButton mBrowse = null;
|
||||
|
||||
private String mInputFilename = null;
|
||||
private String mOutputFilename = null;
|
||||
|
||||
private Uri mContentUri = null;
|
||||
private byte[] mData = null;
|
||||
private boolean mReturnBinary = false;
|
||||
|
||||
private DataSource mDataSource = null;
|
||||
private DataDestination mDataDestination = null;
|
||||
|
||||
private long mUnknownSignatureKeyId = 0;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.decrypt);
|
||||
|
||||
mSource = (ViewFlipper) findViewById(R.id.source);
|
||||
mSourceLabel = (TextView) findViewById(R.id.sourceLabel);
|
||||
mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious);
|
||||
mSourceNext = (ImageView) findViewById(R.id.sourceNext);
|
||||
|
||||
mSourcePrevious.setClickable(true);
|
||||
mSourcePrevious.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_right_in));
|
||||
mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_right_out));
|
||||
mSource.showPrevious();
|
||||
updateSource();
|
||||
}
|
||||
});
|
||||
|
||||
mSourceNext.setClickable(true);
|
||||
OnClickListener nextSourceClickListener = new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_left_in));
|
||||
mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_left_out));
|
||||
mSource.showNext();
|
||||
updateSource();
|
||||
}
|
||||
};
|
||||
mSourceNext.setOnClickListener(nextSourceClickListener);
|
||||
|
||||
mSourceLabel.setClickable(true);
|
||||
mSourceLabel.setOnClickListener(nextSourceClickListener);
|
||||
|
||||
mMessage = (EditText) findViewById(R.id.message);
|
||||
mDecryptButton = (Button) findViewById(R.id.btn_decrypt);
|
||||
mReplyButton = (Button) findViewById(R.id.btn_reply);
|
||||
mSignatureLayout = (LinearLayout) findViewById(R.id.signature);
|
||||
mSignatureStatusImage = (ImageView) findViewById(R.id.ic_signature_status);
|
||||
mUserId = (TextView) findViewById(R.id.mainUserId);
|
||||
mUserIdRest = (TextView) findViewById(R.id.mainUserIdRest);
|
||||
|
||||
// measure the height of the source_file view and set the message view's min height to that,
|
||||
// so it fills mSource fully... bit of a hack.
|
||||
View tmp = findViewById(R.id.sourceFile);
|
||||
tmp.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
|
||||
int height = tmp.getMeasuredHeight();
|
||||
mMessage.setMinimumHeight(height);
|
||||
|
||||
mFilename = (EditText) findViewById(R.id.filename);
|
||||
mBrowse = (ImageButton) findViewById(R.id.btn_browse);
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
openFile();
|
||||
}
|
||||
});
|
||||
|
||||
mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterDecryption);
|
||||
|
||||
// default: message source
|
||||
mSource.setInAnimation(null);
|
||||
mSource.setOutAnimation(null);
|
||||
while (mSource.getCurrentView().getId() != R.id.sourceMessage) {
|
||||
mSource.showNext();
|
||||
}
|
||||
|
||||
mIntent = getIntent();
|
||||
if (Intent.ACTION_VIEW.equals(mIntent.getAction())) {
|
||||
Uri uri = mIntent.getData();
|
||||
try {
|
||||
InputStream attachment = getContentResolver().openInputStream(uri);
|
||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||
byte bytes[] = new byte[1 << 16];
|
||||
int length;
|
||||
while ((length = attachment.read(bytes)) > 0) {
|
||||
byteOut.write(bytes, 0, length);
|
||||
}
|
||||
byteOut.close();
|
||||
String data = new String(byteOut.toByteArray());
|
||||
mMessage.setText(data);
|
||||
} catch (FileNotFoundException e) {
|
||||
// ignore, then
|
||||
} catch (IOException e) {
|
||||
// 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", " ");
|
||||
mMessage.setText(textData);
|
||||
} 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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
mReplyTo = extras.getString(Apg.EXTRA_REPLY_TO);
|
||||
mSubject = extras.getString(Apg.EXTRA_SUBJECT);
|
||||
} else if (Apg.Intent.DECRYPT_FILE.equals(mIntent.getAction())) {
|
||||
mInputFilename = mIntent.getDataString();
|
||||
if ("file".equals(mIntent.getScheme())) {
|
||||
mInputFilename = Uri.decode(mInputFilename.substring(7));
|
||||
}
|
||||
mFilename.setText(mInputFilename);
|
||||
guessOutputFilename();
|
||||
mSource.setInAnimation(null);
|
||||
mSource.setOutAnimation(null);
|
||||
while (mSource.getCurrentView().getId() != R.id.sourceFile) {
|
||||
mSource.showNext();
|
||||
}
|
||||
} else if (Apg.Intent.DECRYPT_AND_RETURN.equals(mIntent.getAction())) {
|
||||
mContentUri = mIntent.getData();
|
||||
Bundle extras = mIntent.getExtras();
|
||||
if (extras == null) {
|
||||
extras = new Bundle();
|
||||
}
|
||||
|
||||
mReturnBinary = extras.getBoolean(Apg.EXTRA_BINARY, false);
|
||||
|
||||
if (mContentUri == null) {
|
||||
mData = extras.getByteArray(Apg.EXTRA_DATA);
|
||||
String data = extras.getString(Apg.EXTRA_TEXT);
|
||||
if (data != null) {
|
||||
Matcher matcher = Apg.PGP_MESSAGE.matcher(data);
|
||||
if (matcher.matches()) {
|
||||
data = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
data = data.replaceAll("\\xa0", " ");
|
||||
mMessage.setText(data);
|
||||
} else {
|
||||
matcher = Apg.PGP_SIGNED_MESSAGE.matcher(data);
|
||||
if (matcher.matches()) {
|
||||
data = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
data = data.replaceAll("\\xa0", " ");
|
||||
mMessage.setText(data);
|
||||
mDecryptButton.setText(R.string.btn_verify);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mReturnResult = true;
|
||||
}
|
||||
|
||||
if (mSource.getCurrentView().getId() == R.id.sourceMessage
|
||||
&& mMessage.getText().length() == 0) {
|
||||
|
||||
CharSequence clipboardText = Compatibility.getClipboardText(this);
|
||||
|
||||
String data = "";
|
||||
if (clipboardText != null) {
|
||||
Matcher matcher = Apg.PGP_MESSAGE.matcher(clipboardText);
|
||||
if (!matcher.matches()) {
|
||||
matcher = Apg.PGP_SIGNED_MESSAGE.matcher(clipboardText);
|
||||
}
|
||||
if (matcher.matches()) {
|
||||
data = matcher.group(1);
|
||||
mMessage.setText(data);
|
||||
Toast.makeText(this, R.string.usingClipboardContent, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
mSignatureLayout.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (mSignatureKeyId == 0) {
|
||||
return;
|
||||
}
|
||||
PGPPublicKeyRing key = Apg.getPublicKeyRing(mSignatureKeyId);
|
||||
if (key != null) {
|
||||
Intent intent = new Intent(DecryptActivity.this, KeyServerQueryActivity.class);
|
||||
intent.setAction(Apg.Intent.LOOK_UP_KEY_ID);
|
||||
intent.putExtra(Apg.EXTRA_KEY_ID, mSignatureKeyId);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mDecryptButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
decryptClicked();
|
||||
}
|
||||
});
|
||||
|
||||
mReplyButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
replyClicked();
|
||||
}
|
||||
});
|
||||
mReplyButton.setVisibility(View.INVISIBLE);
|
||||
|
||||
if (mReturnResult) {
|
||||
mSourcePrevious.setClickable(false);
|
||||
mSourcePrevious.setEnabled(false);
|
||||
mSourcePrevious.setVisibility(View.INVISIBLE);
|
||||
|
||||
mSourceNext.setClickable(false);
|
||||
mSourceNext.setEnabled(false);
|
||||
mSourceNext.setVisibility(View.INVISIBLE);
|
||||
|
||||
mSourceLabel.setClickable(false);
|
||||
mSourceLabel.setEnabled(false);
|
||||
}
|
||||
|
||||
updateSource();
|
||||
|
||||
if (mSource.getCurrentView().getId() == R.id.sourceMessage
|
||||
&& (mMessage.getText().length() > 0 || mData != null || mContentUri != null)) {
|
||||
mDecryptButton.performClick();
|
||||
}
|
||||
}
|
||||
|
||||
private void openFile() {
|
||||
String filename = mFilename.getText().toString();
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
|
||||
intent.setData(Uri.parse("file://" + filename));
|
||||
intent.setType("*/*");
|
||||
|
||||
try {
|
||||
startActivityForResult(intent, Id.request.filename);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// No compatible file manager was found.
|
||||
Toast.makeText(this, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void guessOutputFilename() {
|
||||
mInputFilename = mFilename.getText().toString();
|
||||
File file = new File(mInputFilename);
|
||||
String filename = file.getName();
|
||||
if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) {
|
||||
filename = filename.substring(0, filename.length() - 4);
|
||||
}
|
||||
mOutputFilename = Constants.path.APP_DIR + "/" + filename;
|
||||
}
|
||||
|
||||
private void updateSource() {
|
||||
switch (mSource.getCurrentView().getId()) {
|
||||
case R.id.sourceFile: {
|
||||
mSourceLabel.setText(R.string.label_file);
|
||||
mDecryptButton.setText(R.string.btn_decrypt);
|
||||
break;
|
||||
}
|
||||
|
||||
case R.id.sourceMessage: {
|
||||
mSourceLabel.setText(R.string.label_message);
|
||||
mDecryptButton.setText(R.string.btn_decrypt);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void decryptClicked() {
|
||||
if (mSource.getCurrentView().getId() == R.id.sourceFile) {
|
||||
mDecryptTarget = Id.target.file;
|
||||
} else {
|
||||
mDecryptTarget = Id.target.message;
|
||||
}
|
||||
initiateDecryption();
|
||||
}
|
||||
|
||||
private void initiateDecryption() {
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
String currentFilename = mFilename.getText().toString();
|
||||
if (mInputFilename == null || !mInputFilename.equals(currentFilename)) {
|
||||
guessOutputFilename();
|
||||
}
|
||||
|
||||
if (mInputFilename.equals("")) {
|
||||
Toast.makeText(this, R.string.noFileSelected, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mInputFilename.startsWith("file")) {
|
||||
File file = new File(mInputFilename);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(R.string.errorMessage, getString(R.string.error_fileNotFound)),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mDecryptTarget == Id.target.message) {
|
||||
String messageData = mMessage.getText().toString();
|
||||
Matcher matcher = Apg.PGP_SIGNED_MESSAGE.matcher(messageData);
|
||||
if (matcher.matches()) {
|
||||
mSignedOnly = true;
|
||||
decryptStart();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// else treat it as an decrypted message/file
|
||||
mSignedOnly = false;
|
||||
String error = null;
|
||||
fillDataSource();
|
||||
try {
|
||||
InputData in = mDataSource.getInputData(this, false);
|
||||
try {
|
||||
setSecretKeyId(Apg.getDecryptionKeyId(this, in));
|
||||
if (getSecretKeyId() == Id.key.none) {
|
||||
throw new Apg.GeneralException(getString(R.string.error_noSecretKeyFound));
|
||||
}
|
||||
mAssumeSymmetricEncryption = false;
|
||||
} catch (Apg.NoAsymmetricEncryptionException e) {
|
||||
setSecretKeyId(Id.key.symmetric);
|
||||
in = mDataSource.getInputData(this, false);
|
||||
if (!Apg.hasSymmetricEncryption(this, in)) {
|
||||
throw new Apg.GeneralException(getString(R.string.error_noKnownEncryptionFound));
|
||||
}
|
||||
mAssumeSymmetricEncryption = true;
|
||||
}
|
||||
|
||||
if (getSecretKeyId() == Id.key.symmetric
|
||||
|| Apg.getCachedPassPhrase(getSecretKeyId()) == null) {
|
||||
showDialog(Id.dialog.pass_phrase);
|
||||
} else {
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
askForOutputFilename();
|
||||
} else {
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
error = getString(R.string.error_fileNotFound);
|
||||
} catch (IOException e) {
|
||||
error = "" + e;
|
||||
} catch (Apg.GeneralException e) {
|
||||
error = "" + e;
|
||||
}
|
||||
if (error != null) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
private void replyClicked() {
|
||||
Intent intent = new Intent(this, EncryptActivity.class);
|
||||
intent.setAction(Apg.Intent.ENCRYPT);
|
||||
String data = mMessage.getText().toString();
|
||||
data = data.replaceAll("(?m)^", "> ");
|
||||
data = "\n\n" + data;
|
||||
intent.putExtra(Apg.EXTRA_TEXT, data);
|
||||
intent.putExtra(Apg.EXTRA_SUBJECT, "Re: " + mSubject);
|
||||
intent.putExtra(Apg.EXTRA_SEND_TO, mReplyTo);
|
||||
intent.putExtra(Apg.EXTRA_SIGNATURE_KEY_ID, getSecretKeyId());
|
||||
intent.putExtra(Apg.EXTRA_ENCRYPTION_KEY_IDS, new long[] { mSignatureKeyId });
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void askForOutputFilename() {
|
||||
showDialog(Id.dialog.output_filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passPhraseCallback(long keyId, String passPhrase) {
|
||||
super.passPhraseCallback(keyId, passPhrase);
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
askForOutputFilename();
|
||||
} else {
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
|
||||
private void decryptStart() {
|
||||
showDialog(Id.dialog.decrypting);
|
||||
startThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String error = null;
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
|
||||
Bundle data = new Bundle();
|
||||
Message msg = new Message();
|
||||
fillDataSource();
|
||||
fillDataDestination();
|
||||
try {
|
||||
InputData in = mDataSource.getInputData(this, true);
|
||||
OutputStream out = mDataDestination.getOutputStream(this);
|
||||
|
||||
if (mSignedOnly) {
|
||||
data = Apg.verifyText(this, in, out, this);
|
||||
} else {
|
||||
data = Apg.decrypt(this, in, out, Apg.getCachedPassPhrase(getSecretKeyId()), this,
|
||||
mAssumeSymmetricEncryption);
|
||||
}
|
||||
|
||||
out.close();
|
||||
|
||||
if (mDataDestination.getStreamFilename() != null) {
|
||||
data.putString(Apg.EXTRA_RESULT_URI, "content://" + DataProvider.AUTHORITY
|
||||
+ "/data/" + mDataDestination.getStreamFilename());
|
||||
} else if (mDecryptTarget == Id.target.message) {
|
||||
if (mReturnBinary) {
|
||||
data.putByteArray(Apg.EXTRA_DECRYPTED_DATA,
|
||||
((ByteArrayOutputStream) out).toByteArray());
|
||||
} else {
|
||||
data.putString(Apg.EXTRA_DECRYPTED_MESSAGE, new String(
|
||||
((ByteArrayOutputStream) out).toByteArray()));
|
||||
}
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
error = "" + e;
|
||||
} catch (IOException e) {
|
||||
error = "" + e;
|
||||
} catch (SignatureException e) {
|
||||
error = "" + e;
|
||||
} catch (Apg.GeneralException e) {
|
||||
error = "" + e;
|
||||
}
|
||||
|
||||
data.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
|
||||
if (error != null) {
|
||||
data.putString(Apg.EXTRA_ERROR, error);
|
||||
}
|
||||
|
||||
msg.setData(data);
|
||||
sendMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerCallback(Message msg) {
|
||||
Bundle data = msg.getData();
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.getInt(Constants.extras.STATUS) == Id.message.unknown_signature_key) {
|
||||
mUnknownSignatureKeyId = data.getLong(Constants.extras.KEY_ID);
|
||||
showDialog(Id.dialog.lookup_unknown_key);
|
||||
return;
|
||||
}
|
||||
|
||||
super.handlerCallback(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneCallback(Message msg) {
|
||||
super.doneCallback(msg);
|
||||
|
||||
Bundle data = msg.getData();
|
||||
removeDialog(Id.dialog.decrypting);
|
||||
mSignatureKeyId = 0;
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
mReplyButton.setVisibility(View.INVISIBLE);
|
||||
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(this, R.string.decryptionSuccessful, Toast.LENGTH_SHORT).show();
|
||||
if (mReturnResult) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtras(data);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mDecryptTarget) {
|
||||
case Id.target.message: {
|
||||
String decryptedMessage = data.getString(Apg.EXTRA_DECRYPTED_MESSAGE);
|
||||
mMessage.setText(decryptedMessage);
|
||||
mMessage.setHorizontallyScrolling(false);
|
||||
mReplyButton.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.target.file: {
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
setDeleteFile(mInputFilename);
|
||||
showDialog(Id.dialog.delete_file);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// shouldn't happen
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.getBoolean(Apg.EXTRA_SIGNATURE)) {
|
||||
String userId = data.getString(Apg.EXTRA_SIGNATURE_USER_ID);
|
||||
mSignatureKeyId = data.getLong(Apg.EXTRA_SIGNATURE_KEY_ID);
|
||||
mUserIdRest.setText("id: " + Apg.getSmallFingerPrint(mSignatureKeyId));
|
||||
if (userId == null) {
|
||||
userId = getResources().getString(R.string.unknownUserId);
|
||||
}
|
||||
String chunks[] = userId.split(" <", 2);
|
||||
userId = chunks[0];
|
||||
if (chunks.length > 1) {
|
||||
mUserIdRest.setText("<" + chunks[1]);
|
||||
}
|
||||
mUserId.setText(userId);
|
||||
|
||||
if (data.getBoolean(Apg.EXTRA_SIGNATURE_SUCCESS)) {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
|
||||
} else if (data.getBoolean(Apg.EXTRA_SIGNATURE_UNKNOWN)) {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
Toast.makeText(this, R.string.unknownSignatureKeyTouchToLookUp, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
} else {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
}
|
||||
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.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);
|
||||
|
||||
mFilename.setText(filename);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case Id.request.look_up_key_id: {
|
||||
PausableThread thread = getRunningThread();
|
||||
if (thread != null && thread.isPaused()) {
|
||||
thread.unpause();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case Id.dialog.output_filename: {
|
||||
return FileDialog.build(this, getString(R.string.title_decryptToFile),
|
||||
getString(R.string.specifyFileToDecryptTo), mOutputFilename,
|
||||
new FileDialog.OnClickListener() {
|
||||
public void onOkClick(String filename, boolean checked) {
|
||||
removeDialog(Id.dialog.output_filename);
|
||||
mOutputFilename = filename;
|
||||
decryptStart();
|
||||
}
|
||||
|
||||
public void onCancelClick() {
|
||||
removeDialog(Id.dialog.output_filename);
|
||||
}
|
||||
}, getString(R.string.filemanager_titleSave),
|
||||
getString(R.string.filemanager_btnSave), null, Id.request.output_filename);
|
||||
}
|
||||
|
||||
case Id.dialog.lookup_unknown_key: {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.title_unknownSignatureKey);
|
||||
alert.setMessage(getString(R.string.lookupUnknownKey,
|
||||
Apg.getSmallFingerPrint(mUnknownSignatureKeyId)));
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.lookup_unknown_key);
|
||||
Intent intent = new Intent(DecryptActivity.this, KeyServerQueryActivity.class);
|
||||
intent.setAction(Apg.Intent.LOOK_UP_KEY_ID);
|
||||
intent.putExtra(Apg.EXTRA_KEY_ID, mUnknownSignatureKeyId);
|
||||
startActivityForResult(intent, Id.request.look_up_key_id);
|
||||
}
|
||||
});
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.lookup_unknown_key);
|
||||
PausableThread thread = getRunningThread();
|
||||
if (thread != null && thread.isPaused()) {
|
||||
thread.unpause();
|
||||
}
|
||||
}
|
||||
});
|
||||
alert.setCancelable(true);
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
|
||||
protected void fillDataSource() {
|
||||
mDataSource = new DataSource();
|
||||
if (mContentUri != null) {
|
||||
mDataSource.setUri(mContentUri);
|
||||
} else if (mDecryptTarget == Id.target.file) {
|
||||
mDataSource.setUri(mInputFilename);
|
||||
} else {
|
||||
if (mData != null) {
|
||||
mDataSource.setData(mData);
|
||||
} else {
|
||||
mDataSource.setText(mMessage.getText().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void fillDataDestination() {
|
||||
mDataDestination = new DataDestination();
|
||||
if (mContentUri != null) {
|
||||
mDataDestination.setMode(Id.mode.stream);
|
||||
} else if (mDecryptTarget == Id.target.file) {
|
||||
mDataDestination.setFilename(mOutputFilename);
|
||||
mDataDestination.setMode(Id.mode.file);
|
||||
} else {
|
||||
mDataDestination.setMode(Id.mode.byte_array);
|
||||
}
|
||||
}
|
||||
}
|
||||
292
org_apg/src/org/apg/ui/EditKeyActivity.java
Normal file
292
org_apg/src/org/apg/ui/EditKeyActivity.java
Normal file
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Constants;
|
||||
import org.apg.Id;
|
||||
import org.apg.provider.Database;
|
||||
import org.apg.ui.widget.KeyEditor;
|
||||
import org.apg.ui.widget.SectionView;
|
||||
import org.apg.util.IterableIterator;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Vector;
|
||||
|
||||
public class EditKeyActivity extends BaseActivity implements OnClickListener {
|
||||
|
||||
private PGPSecretKeyRing mKeyRing = null;
|
||||
|
||||
private SectionView mUserIds;
|
||||
private SectionView mKeys;
|
||||
|
||||
private Button mSaveButton;
|
||||
private Button mDiscardButton;
|
||||
|
||||
private String mCurrentPassPhrase = null;
|
||||
private String mNewPassPhrase = null;
|
||||
|
||||
private Button mChangePassPhrase;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.edit_key);
|
||||
|
||||
Vector<String> userIds = new Vector<String>();
|
||||
Vector<PGPSecretKey> keys = new Vector<PGPSecretKey>();
|
||||
|
||||
Intent intent = getIntent();
|
||||
long keyId = 0;
|
||||
if (intent.getExtras() != null) {
|
||||
keyId = intent.getExtras().getLong(Apg.EXTRA_KEY_ID);
|
||||
}
|
||||
|
||||
if (keyId != 0) {
|
||||
PGPSecretKey masterKey = null;
|
||||
mKeyRing = Apg.getSecretKeyRing(keyId);
|
||||
if (mKeyRing != null) {
|
||||
masterKey = Apg.getMasterKey(mKeyRing);
|
||||
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(mKeyRing.getSecretKeys())) {
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
if (masterKey != null) {
|
||||
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
||||
userIds.add(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mChangePassPhrase = (Button) findViewById(R.id.btn_change_pass_phrase);
|
||||
mChangePassPhrase.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
showDialog(Id.dialog.new_pass_phrase);
|
||||
}
|
||||
});
|
||||
|
||||
mSaveButton = (Button) findViewById(R.id.btn_save);
|
||||
mDiscardButton = (Button) findViewById(R.id.btn_discard);
|
||||
|
||||
mSaveButton.setOnClickListener(this);
|
||||
mDiscardButton.setOnClickListener(this);
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
LinearLayout container = (LinearLayout) findViewById(R.id.container);
|
||||
mUserIds = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||
mUserIds.setType(Id.type.user_id);
|
||||
mUserIds.setUserIds(userIds);
|
||||
container.addView(mUserIds);
|
||||
mKeys = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||
mKeys.setType(Id.type.key);
|
||||
mKeys.setKeys(keys);
|
||||
container.addView(mKeys);
|
||||
|
||||
mCurrentPassPhrase = Apg.getEditPassPhrase();
|
||||
if (mCurrentPassPhrase == null) {
|
||||
mCurrentPassPhrase = "";
|
||||
}
|
||||
|
||||
updatePassPhraseButtonText();
|
||||
|
||||
Toast.makeText(this,
|
||||
getString(R.string.warningMessage, getString(R.string.keyEditingIsBeta)),
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private long getMasterKeyId() {
|
||||
if (mKeys.getEditors().getChildCount() == 0) {
|
||||
return 0;
|
||||
}
|
||||
return ((KeyEditor) mKeys.getEditors().getChildAt(0)).getValue().getKeyID();
|
||||
}
|
||||
|
||||
public boolean havePassPhrase() {
|
||||
return (!mCurrentPassPhrase.equals(""))
|
||||
|| (mNewPassPhrase != null && !mNewPassPhrase.equals(""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences).setIcon(
|
||||
android.R.drawable.ic_menu_preferences);
|
||||
menu.add(0, Id.menu.option.about, 1, R.string.menu_about).setIcon(
|
||||
android.R.drawable.ic_menu_info_details);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case Id.dialog.new_pass_phrase: {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
if (havePassPhrase()) {
|
||||
alert.setTitle(R.string.title_changePassPhrase);
|
||||
} else {
|
||||
alert.setTitle(R.string.title_setPassPhrase);
|
||||
}
|
||||
alert.setMessage(R.string.enterPassPhraseTwice);
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View view = inflater.inflate(R.layout.pass_phrase, null);
|
||||
final EditText input1 = (EditText) view.findViewById(R.id.passPhrase);
|
||||
final EditText input2 = (EditText) view.findViewById(R.id.passPhraseAgain);
|
||||
|
||||
alert.setView(view);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.new_pass_phrase);
|
||||
|
||||
String passPhrase1 = "" + input1.getText();
|
||||
String passPhrase2 = "" + input2.getText();
|
||||
if (!passPhrase1.equals(passPhrase2)) {
|
||||
showDialog(Id.dialog.pass_phrases_do_not_match);
|
||||
return;
|
||||
}
|
||||
|
||||
if (passPhrase1.equals("")) {
|
||||
showDialog(Id.dialog.no_pass_phrase);
|
||||
return;
|
||||
}
|
||||
|
||||
mNewPassPhrase = passPhrase1;
|
||||
updatePassPhraseButtonText();
|
||||
}
|
||||
});
|
||||
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.new_pass_phrase);
|
||||
}
|
||||
});
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
if (v == mSaveButton) {
|
||||
// TODO: some warning
|
||||
saveClicked();
|
||||
} else if (v == mDiscardButton) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveClicked() {
|
||||
if (!havePassPhrase()) {
|
||||
Toast.makeText(this, R.string.setAPassPhrase, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
showDialog(Id.dialog.saving);
|
||||
startThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String error = null;
|
||||
Bundle data = new Bundle();
|
||||
Message msg = new Message();
|
||||
|
||||
try {
|
||||
String oldPassPhrase = mCurrentPassPhrase;
|
||||
String newPassPhrase = mNewPassPhrase;
|
||||
if (newPassPhrase == null) {
|
||||
newPassPhrase = oldPassPhrase;
|
||||
}
|
||||
Apg.buildSecretKey(this, mUserIds, mKeys, oldPassPhrase, newPassPhrase, this);
|
||||
Apg.setCachedPassPhrase(getMasterKeyId(), newPassPhrase);
|
||||
} catch (NoSuchProviderException e) {
|
||||
error = "" + e;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
error = "" + e;
|
||||
} catch (PGPException e) {
|
||||
error = "" + e;
|
||||
} catch (SignatureException e) {
|
||||
error = "" + e;
|
||||
} catch (Apg.GeneralException e) {
|
||||
error = "" + e;
|
||||
} catch (Database.GeneralException e) {
|
||||
error = "" + e;
|
||||
} catch (IOException e) {
|
||||
error = "" + e;
|
||||
}
|
||||
|
||||
data.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
|
||||
if (error != null) {
|
||||
data.putString(Apg.EXTRA_ERROR, error);
|
||||
}
|
||||
|
||||
msg.setData(data);
|
||||
sendMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneCallback(Message msg) {
|
||||
super.doneCallback(msg);
|
||||
|
||||
Bundle data = msg.getData();
|
||||
removeDialog(Id.dialog.saving);
|
||||
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(EditKeyActivity.this, getString(R.string.errorMessage, error),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(EditKeyActivity.this, R.string.keySaved, Toast.LENGTH_SHORT).show();
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePassPhraseButtonText() {
|
||||
mChangePassPhrase.setText(havePassPhrase() ? R.string.btn_changePassPhrase
|
||||
: R.string.btn_setPassPhrase);
|
||||
}
|
||||
}
|
||||
998
org_apg/src/org/apg/ui/EncryptActivity.java
Normal file
998
org_apg/src/org/apg/ui/EncryptActivity.java
Normal file
@@ -0,0 +1,998 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Constants;
|
||||
import org.apg.DataDestination;
|
||||
import org.apg.DataSource;
|
||||
import org.apg.FileDialog;
|
||||
import org.apg.Id;
|
||||
import org.apg.InputData;
|
||||
import org.apg.provider.DataProvider;
|
||||
import org.apg.util.Choice;
|
||||
import org.apg.util.Compatibility;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ViewFlipper;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Vector;
|
||||
|
||||
public class EncryptActivity extends BaseActivity {
|
||||
private Intent mIntent = null;
|
||||
private String mSubject = null;
|
||||
private String mSendTo = null;
|
||||
|
||||
private long mEncryptionKeyIds[] = null;
|
||||
|
||||
private boolean mReturnResult = false;
|
||||
private EditText mMessage = null;
|
||||
private Button mSelectKeysButton = null;
|
||||
private Button mEncryptButton = null;
|
||||
private Button mEncryptToClipboardButton = null;
|
||||
private CheckBox mSign = null;
|
||||
private TextView mMainUserId = null;
|
||||
private TextView mMainUserIdRest = null;
|
||||
|
||||
private ViewFlipper mSource = null;
|
||||
private TextView mSourceLabel = null;
|
||||
private ImageView mSourcePrevious = null;
|
||||
private ImageView mSourceNext = null;
|
||||
|
||||
private ViewFlipper mMode = null;
|
||||
private TextView mModeLabel = null;
|
||||
private ImageView mModePrevious = null;
|
||||
private ImageView mModeNext = null;
|
||||
|
||||
private int mEncryptTarget;
|
||||
|
||||
private EditText mPassPhrase = null;
|
||||
private EditText mPassPhraseAgain = null;
|
||||
private CheckBox mAsciiArmour = null;
|
||||
private Spinner mFileCompression = null;
|
||||
|
||||
private EditText mFilename = null;
|
||||
private CheckBox mDeleteAfter = null;
|
||||
private ImageButton mBrowse = null;
|
||||
|
||||
private String mInputFilename = null;
|
||||
private String mOutputFilename = null;
|
||||
|
||||
private boolean mAsciiArmourDemand = false;
|
||||
private boolean mOverrideAsciiArmour = false;
|
||||
private Uri mContentUri = null;
|
||||
private byte[] mData = null;
|
||||
|
||||
private DataSource mDataSource = null;
|
||||
private DataDestination mDataDestination = null;
|
||||
|
||||
private boolean mGenerateSignature = false;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.encrypt);
|
||||
|
||||
mGenerateSignature = false;
|
||||
|
||||
mSource = (ViewFlipper) findViewById(R.id.source);
|
||||
mSourceLabel = (TextView) findViewById(R.id.sourceLabel);
|
||||
mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious);
|
||||
mSourceNext = (ImageView) findViewById(R.id.sourceNext);
|
||||
|
||||
mSourcePrevious.setClickable(true);
|
||||
mSourcePrevious.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
mSource.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
|
||||
R.anim.push_right_in));
|
||||
mSource.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
|
||||
R.anim.push_right_out));
|
||||
mSource.showPrevious();
|
||||
updateSource();
|
||||
}
|
||||
});
|
||||
|
||||
mSourceNext.setClickable(true);
|
||||
OnClickListener nextSourceClickListener = new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
mSource.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
|
||||
R.anim.push_left_in));
|
||||
mSource.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
|
||||
R.anim.push_left_out));
|
||||
mSource.showNext();
|
||||
updateSource();
|
||||
}
|
||||
};
|
||||
mSourceNext.setOnClickListener(nextSourceClickListener);
|
||||
|
||||
mSourceLabel.setClickable(true);
|
||||
mSourceLabel.setOnClickListener(nextSourceClickListener);
|
||||
|
||||
mMode = (ViewFlipper) findViewById(R.id.mode);
|
||||
mModeLabel = (TextView) findViewById(R.id.modeLabel);
|
||||
mModePrevious = (ImageView) findViewById(R.id.modePrevious);
|
||||
mModeNext = (ImageView) findViewById(R.id.modeNext);
|
||||
|
||||
mModePrevious.setClickable(true);
|
||||
mModePrevious.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
mMode.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
|
||||
R.anim.push_right_in));
|
||||
mMode.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
|
||||
R.anim.push_right_out));
|
||||
mMode.showPrevious();
|
||||
updateMode();
|
||||
}
|
||||
});
|
||||
|
||||
OnClickListener nextModeClickListener = new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
mMode.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
|
||||
R.anim.push_left_in));
|
||||
mMode.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this,
|
||||
R.anim.push_left_out));
|
||||
mMode.showNext();
|
||||
updateMode();
|
||||
}
|
||||
};
|
||||
mModeNext.setOnClickListener(nextModeClickListener);
|
||||
|
||||
mModeLabel.setClickable(true);
|
||||
mModeLabel.setOnClickListener(nextModeClickListener);
|
||||
|
||||
mMessage = (EditText) findViewById(R.id.message);
|
||||
mSelectKeysButton = (Button) findViewById(R.id.btn_selectEncryptKeys);
|
||||
mEncryptButton = (Button) findViewById(R.id.btn_encrypt);
|
||||
mEncryptToClipboardButton = (Button) findViewById(R.id.btn_encryptToClipboard);
|
||||
mSign = (CheckBox) findViewById(R.id.sign);
|
||||
mMainUserId = (TextView) findViewById(R.id.mainUserId);
|
||||
mMainUserIdRest = (TextView) findViewById(R.id.mainUserIdRest);
|
||||
|
||||
mPassPhrase = (EditText) findViewById(R.id.passPhrase);
|
||||
mPassPhraseAgain = (EditText) findViewById(R.id.passPhraseAgain);
|
||||
|
||||
// measure the height of the source_file view and set the message view's min height to that,
|
||||
// so it fills mSource fully... bit of a hack.
|
||||
View tmp = findViewById(R.id.sourceFile);
|
||||
tmp.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
|
||||
int height = tmp.getMeasuredHeight();
|
||||
mMessage.setMinimumHeight(height);
|
||||
|
||||
mFilename = (EditText) findViewById(R.id.filename);
|
||||
mBrowse = (ImageButton) findViewById(R.id.btn_browse);
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
openFile();
|
||||
}
|
||||
});
|
||||
|
||||
mFileCompression = (Spinner) findViewById(R.id.fileCompression);
|
||||
Choice[] choices = new Choice[] {
|
||||
new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " ("
|
||||
+ getString(R.string.fast) + ")"),
|
||||
new Choice(Id.choice.compression.zip, "ZIP (" + getString(R.string.fast) + ")"),
|
||||
new Choice(Id.choice.compression.zlib, "ZLIB (" + getString(R.string.fast) + ")"),
|
||||
new Choice(Id.choice.compression.bzip2, "BZIP2 (" + getString(R.string.very_slow)
|
||||
+ ")"), };
|
||||
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(this,
|
||||
android.R.layout.simple_spinner_item, choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mFileCompression.setAdapter(adapter);
|
||||
|
||||
int defaultFileCompression = mPreferences.getDefaultFileCompression();
|
||||
for (int i = 0; i < choices.length; ++i) {
|
||||
if (choices[i].getId() == defaultFileCompression) {
|
||||
mFileCompression.setSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterEncryption);
|
||||
|
||||
mAsciiArmour = (CheckBox) findViewById(R.id.asciiArmour);
|
||||
mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour());
|
||||
mAsciiArmour.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View view) {
|
||||
guessOutputFilename();
|
||||
}
|
||||
});
|
||||
|
||||
mEncryptToClipboardButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
encryptToClipboardClicked();
|
||||
}
|
||||
});
|
||||
|
||||
mEncryptButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
encryptClicked();
|
||||
}
|
||||
});
|
||||
|
||||
mSelectKeysButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
selectPublicKeys();
|
||||
}
|
||||
});
|
||||
|
||||
mSign.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
CheckBox checkBox = (CheckBox) v;
|
||||
if (checkBox.isChecked()) {
|
||||
selectSecretKey();
|
||||
} else {
|
||||
setSecretKeyId(Id.key.none);
|
||||
updateView();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mIntent = getIntent();
|
||||
if (Apg.Intent.ENCRYPT.equals(mIntent.getAction())
|
||||
|| Apg.Intent.ENCRYPT_FILE.equals(mIntent.getAction())
|
||||
|| Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction())
|
||||
|| Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) {
|
||||
mContentUri = mIntent.getData();
|
||||
Bundle extras = mIntent.getExtras();
|
||||
if (extras == null) {
|
||||
extras = new Bundle();
|
||||
}
|
||||
|
||||
if (Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction())
|
||||
|| Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) {
|
||||
mReturnResult = true;
|
||||
}
|
||||
|
||||
if (Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) {
|
||||
mGenerateSignature = true;
|
||||
mOverrideAsciiArmour = true;
|
||||
mAsciiArmourDemand = false;
|
||||
}
|
||||
|
||||
if (extras.containsKey(Apg.EXTRA_ASCII_ARMOUR)) {
|
||||
mAsciiArmourDemand = extras.getBoolean(Apg.EXTRA_ASCII_ARMOUR, true);
|
||||
mOverrideAsciiArmour = true;
|
||||
mAsciiArmour.setChecked(mAsciiArmourDemand);
|
||||
}
|
||||
|
||||
mData = extras.getByteArray(Apg.EXTRA_DATA);
|
||||
String textData = null;
|
||||
if (mData == null) {
|
||||
textData = extras.getString(Apg.EXTRA_TEXT);
|
||||
}
|
||||
mSendTo = extras.getString(Apg.EXTRA_SEND_TO);
|
||||
mSubject = extras.getString(Apg.EXTRA_SUBJECT);
|
||||
long signatureKeyId = extras.getLong(Apg.EXTRA_SIGNATURE_KEY_ID);
|
||||
long encryptionKeyIds[] = extras.getLongArray(Apg.EXTRA_ENCRYPTION_KEY_IDS);
|
||||
if (signatureKeyId != 0) {
|
||||
PGPSecretKeyRing keyRing = Apg.getSecretKeyRing(signatureKeyId);
|
||||
PGPSecretKey masterKey = null;
|
||||
if (keyRing != null) {
|
||||
masterKey = Apg.getMasterKey(keyRing);
|
||||
if (masterKey != null) {
|
||||
Vector<PGPSecretKey> signKeys = Apg.getUsableSigningKeys(keyRing);
|
||||
if (signKeys.size() > 0) {
|
||||
setSecretKeyId(masterKey.getKeyID());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (encryptionKeyIds != null) {
|
||||
Vector<Long> goodIds = new Vector<Long>();
|
||||
for (int i = 0; i < encryptionKeyIds.length; ++i) {
|
||||
PGPPublicKeyRing keyRing = Apg.getPublicKeyRing(encryptionKeyIds[i]);
|
||||
PGPPublicKey masterKey = null;
|
||||
if (keyRing == null) {
|
||||
continue;
|
||||
}
|
||||
masterKey = Apg.getMasterKey(keyRing);
|
||||
if (masterKey == null) {
|
||||
continue;
|
||||
}
|
||||
Vector<PGPPublicKey> encryptKeys = Apg.getUsableEncryptKeys(keyRing);
|
||||
if (encryptKeys.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
goodIds.add(masterKey.getKeyID());
|
||||
}
|
||||
if (goodIds.size() > 0) {
|
||||
mEncryptionKeyIds = new long[goodIds.size()];
|
||||
for (int i = 0; i < goodIds.size(); ++i) {
|
||||
mEncryptionKeyIds[i] = goodIds.get(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Apg.Intent.ENCRYPT.equals(mIntent.getAction())
|
||||
|| Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction())
|
||||
|| Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) {
|
||||
if (textData != null) {
|
||||
mMessage.setText(textData);
|
||||
}
|
||||
mSource.setInAnimation(null);
|
||||
mSource.setOutAnimation(null);
|
||||
while (mSource.getCurrentView().getId() != R.id.sourceMessage) {
|
||||
mSource.showNext();
|
||||
}
|
||||
} else if (Apg.Intent.ENCRYPT_FILE.equals(mIntent.getAction())) {
|
||||
if ("file".equals(mIntent.getScheme())) {
|
||||
mInputFilename = Uri.decode(mIntent.getDataString().replace("file://", ""));
|
||||
mFilename.setText(mInputFilename);
|
||||
guessOutputFilename();
|
||||
}
|
||||
mSource.setInAnimation(null);
|
||||
mSource.setOutAnimation(null);
|
||||
while (mSource.getCurrentView().getId() != R.id.sourceFile) {
|
||||
mSource.showNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateView();
|
||||
updateSource();
|
||||
updateMode();
|
||||
|
||||
if (mReturnResult) {
|
||||
mSourcePrevious.setClickable(false);
|
||||
mSourcePrevious.setEnabled(false);
|
||||
mSourcePrevious.setVisibility(View.INVISIBLE);
|
||||
|
||||
mSourceNext.setClickable(false);
|
||||
mSourceNext.setEnabled(false);
|
||||
mSourceNext.setVisibility(View.INVISIBLE);
|
||||
|
||||
mSourceLabel.setClickable(false);
|
||||
mSourceLabel.setEnabled(false);
|
||||
}
|
||||
|
||||
updateButtons();
|
||||
|
||||
if (mReturnResult
|
||||
&& (mMessage.getText().length() > 0 || mData != null || mContentUri != null)
|
||||
&& ((mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) || getSecretKeyId() != 0)) {
|
||||
encryptClicked();
|
||||
}
|
||||
}
|
||||
|
||||
private void openFile() {
|
||||
String filename = mFilename.getText().toString();
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
|
||||
intent.setData(Uri.parse("file://" + filename));
|
||||
intent.setType("*/*");
|
||||
|
||||
try {
|
||||
startActivityForResult(intent, Id.request.filename);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// No compatible file manager was found.
|
||||
Toast.makeText(this, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void guessOutputFilename() {
|
||||
mInputFilename = mFilename.getText().toString();
|
||||
File file = new File(mInputFilename);
|
||||
String ending = (mAsciiArmour.isChecked() ? ".asc" : ".gpg");
|
||||
mOutputFilename = Constants.path.APP_DIR + "/" + file.getName() + ending;
|
||||
}
|
||||
|
||||
private void updateSource() {
|
||||
switch (mSource.getCurrentView().getId()) {
|
||||
case R.id.sourceFile: {
|
||||
mSourceLabel.setText(R.string.label_file);
|
||||
break;
|
||||
}
|
||||
|
||||
case R.id.sourceMessage: {
|
||||
mSourceLabel.setText(R.string.label_message);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
private void updateButtons() {
|
||||
switch (mSource.getCurrentView().getId()) {
|
||||
case R.id.sourceFile: {
|
||||
mEncryptToClipboardButton.setVisibility(View.INVISIBLE);
|
||||
mEncryptButton.setText(R.string.btn_encrypt);
|
||||
break;
|
||||
}
|
||||
|
||||
case R.id.sourceMessage: {
|
||||
mSourceLabel.setText(R.string.label_message);
|
||||
if (mReturnResult) {
|
||||
mEncryptToClipboardButton.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
mEncryptToClipboardButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (mMode.getCurrentView().getId() == R.id.modeSymmetric) {
|
||||
if (mReturnResult) {
|
||||
mEncryptButton.setText(R.string.btn_encrypt);
|
||||
} else {
|
||||
mEncryptButton.setText(R.string.btn_encryptAndEmail);
|
||||
}
|
||||
mEncryptButton.setEnabled(true);
|
||||
mEncryptToClipboardButton.setText(R.string.btn_encryptToClipboard);
|
||||
mEncryptToClipboardButton.setEnabled(true);
|
||||
} else {
|
||||
if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) {
|
||||
if (getSecretKeyId() == 0) {
|
||||
if (mReturnResult) {
|
||||
mEncryptButton.setText(R.string.btn_encrypt);
|
||||
} else {
|
||||
mEncryptButton.setText(R.string.btn_encryptAndEmail);
|
||||
}
|
||||
mEncryptButton.setEnabled(false);
|
||||
mEncryptToClipboardButton.setText(R.string.btn_encryptToClipboard);
|
||||
mEncryptToClipboardButton.setEnabled(false);
|
||||
} else {
|
||||
if (mReturnResult) {
|
||||
mEncryptButton.setText(R.string.btn_sign);
|
||||
} else {
|
||||
mEncryptButton.setText(R.string.btn_signAndEmail);
|
||||
}
|
||||
mEncryptButton.setEnabled(true);
|
||||
mEncryptToClipboardButton.setText(R.string.btn_signToClipboard);
|
||||
mEncryptToClipboardButton.setEnabled(true);
|
||||
}
|
||||
} else {
|
||||
if (mReturnResult) {
|
||||
mEncryptButton.setText(R.string.btn_encrypt);
|
||||
} else {
|
||||
mEncryptButton.setText(R.string.btn_encryptAndEmail);
|
||||
}
|
||||
mEncryptButton.setEnabled(true);
|
||||
mEncryptToClipboardButton.setText(R.string.btn_encryptToClipboard);
|
||||
mEncryptToClipboardButton.setEnabled(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMode() {
|
||||
switch (mMode.getCurrentView().getId()) {
|
||||
case R.id.modeAsymmetric: {
|
||||
mModeLabel.setText(R.string.label_asymmetric);
|
||||
break;
|
||||
}
|
||||
|
||||
case R.id.modeSymmetric: {
|
||||
mModeLabel.setText(R.string.label_symmetric);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
private void encryptToClipboardClicked() {
|
||||
mEncryptTarget = Id.target.clipboard;
|
||||
initiateEncryption();
|
||||
}
|
||||
|
||||
private void encryptClicked() {
|
||||
if (mSource.getCurrentView().getId() == R.id.sourceFile) {
|
||||
mEncryptTarget = Id.target.file;
|
||||
} else {
|
||||
mEncryptTarget = Id.target.email;
|
||||
}
|
||||
initiateEncryption();
|
||||
}
|
||||
|
||||
private void initiateEncryption() {
|
||||
if (mEncryptTarget == Id.target.file) {
|
||||
String currentFilename = mFilename.getText().toString();
|
||||
if (mInputFilename == null || !mInputFilename.equals(currentFilename)) {
|
||||
guessOutputFilename();
|
||||
}
|
||||
|
||||
if (mInputFilename.equals("")) {
|
||||
Toast.makeText(this, R.string.noFileSelected, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mInputFilename.startsWith("content")) {
|
||||
File file = new File(mInputFilename);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(R.string.errorMessage, getString(R.string.error_fileNotFound)),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// symmetric encryption
|
||||
if (mMode.getCurrentView().getId() == R.id.modeSymmetric) {
|
||||
boolean gotPassPhrase = false;
|
||||
String passPhrase = mPassPhrase.getText().toString();
|
||||
String passPhraseAgain = mPassPhraseAgain.getText().toString();
|
||||
if (!passPhrase.equals(passPhraseAgain)) {
|
||||
Toast.makeText(this, R.string.passPhrasesDoNotMatch, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
gotPassPhrase = (passPhrase.length() != 0);
|
||||
if (!gotPassPhrase) {
|
||||
Toast.makeText(this, R.string.passPhraseMustNotBeEmpty, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
boolean encryptIt = (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0);
|
||||
// for now require at least one form of encryption for files
|
||||
if (!encryptIt && mEncryptTarget == Id.target.file) {
|
||||
Toast.makeText(this, R.string.selectEncryptionKey, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!encryptIt && getSecretKeyId() == 0) {
|
||||
Toast.makeText(this, R.string.selectEncryptionOrSignatureKey, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (getSecretKeyId() != 0 && Apg.getCachedPassPhrase(getSecretKeyId()) == null) {
|
||||
showDialog(Id.dialog.pass_phrase);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mEncryptTarget == Id.target.file) {
|
||||
askForOutputFilename();
|
||||
} else {
|
||||
encryptStart();
|
||||
}
|
||||
}
|
||||
|
||||
private void askForOutputFilename() {
|
||||
showDialog(Id.dialog.output_filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passPhraseCallback(long keyId, String passPhrase) {
|
||||
super.passPhraseCallback(keyId, passPhrase);
|
||||
if (mEncryptTarget == Id.target.file) {
|
||||
askForOutputFilename();
|
||||
} else {
|
||||
encryptStart();
|
||||
}
|
||||
}
|
||||
|
||||
private void encryptStart() {
|
||||
showDialog(Id.dialog.encrypting);
|
||||
startThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String error = null;
|
||||
Bundle data = new Bundle();
|
||||
Message msg = new Message();
|
||||
|
||||
try {
|
||||
InputData in;
|
||||
OutputStream out;
|
||||
boolean useAsciiArmour = true;
|
||||
long encryptionKeyIds[] = null;
|
||||
long signatureKeyId = 0;
|
||||
int compressionId = 0;
|
||||
boolean signOnly = false;
|
||||
|
||||
String passPhrase = null;
|
||||
if (mMode.getCurrentView().getId() == R.id.modeSymmetric) {
|
||||
passPhrase = mPassPhrase.getText().toString();
|
||||
if (passPhrase.length() == 0) {
|
||||
passPhrase = null;
|
||||
}
|
||||
} else {
|
||||
encryptionKeyIds = mEncryptionKeyIds;
|
||||
signatureKeyId = getSecretKeyId();
|
||||
signOnly = (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0);
|
||||
}
|
||||
|
||||
fillDataSource(signOnly && !mReturnResult);
|
||||
fillDataDestination();
|
||||
|
||||
// streams
|
||||
in = mDataSource.getInputData(this, true);
|
||||
out = mDataDestination.getOutputStream(this);
|
||||
|
||||
if (mEncryptTarget == Id.target.file) {
|
||||
useAsciiArmour = mAsciiArmour.isChecked();
|
||||
compressionId = ((Choice) mFileCompression.getSelectedItem()).getId();
|
||||
} else {
|
||||
useAsciiArmour = true;
|
||||
compressionId = mPreferences.getDefaultMessageCompression();
|
||||
}
|
||||
|
||||
if (mOverrideAsciiArmour) {
|
||||
useAsciiArmour = mAsciiArmourDemand;
|
||||
}
|
||||
|
||||
if (mGenerateSignature) {
|
||||
Apg.generateSignature(this, in, out, useAsciiArmour, mDataSource.isBinary(),
|
||||
getSecretKeyId(), Apg.getCachedPassPhrase(getSecretKeyId()),
|
||||
mPreferences.getDefaultHashAlgorithm(),
|
||||
mPreferences.getForceV3Signatures(), this);
|
||||
} else if (signOnly) {
|
||||
Apg.signText(this, in, out, getSecretKeyId(),
|
||||
Apg.getCachedPassPhrase(getSecretKeyId()),
|
||||
mPreferences.getDefaultHashAlgorithm(),
|
||||
mPreferences.getForceV3Signatures(), this);
|
||||
} else {
|
||||
Apg.encrypt(this, in, out, useAsciiArmour, encryptionKeyIds, signatureKeyId,
|
||||
Apg.getCachedPassPhrase(signatureKeyId), this,
|
||||
mPreferences.getDefaultEncryptionAlgorithm(),
|
||||
mPreferences.getDefaultHashAlgorithm(), compressionId,
|
||||
mPreferences.getForceV3Signatures(), passPhrase);
|
||||
}
|
||||
|
||||
out.close();
|
||||
if (mEncryptTarget != Id.target.file) {
|
||||
|
||||
if (out instanceof ByteArrayOutputStream) {
|
||||
if (useAsciiArmour) {
|
||||
String extraData = new String(((ByteArrayOutputStream) out).toByteArray());
|
||||
if (mGenerateSignature) {
|
||||
data.putString(Apg.EXTRA_SIGNATURE_TEXT, extraData);
|
||||
} else {
|
||||
data.putString(Apg.EXTRA_ENCRYPTED_MESSAGE, extraData);
|
||||
}
|
||||
} else {
|
||||
byte extraData[] = ((ByteArrayOutputStream) out).toByteArray();
|
||||
if (mGenerateSignature) {
|
||||
data.putByteArray(Apg.EXTRA_SIGNATURE_DATA, extraData);
|
||||
} else {
|
||||
data.putByteArray(Apg.EXTRA_ENCRYPTED_DATA, extraData);
|
||||
}
|
||||
}
|
||||
} else if (out instanceof FileOutputStream) {
|
||||
String fileName = mDataDestination.getStreamFilename();
|
||||
String uri = "content://" + DataProvider.AUTHORITY + "/data/" + fileName;
|
||||
data.putString(Apg.EXTRA_RESULT_URI, uri);
|
||||
} else {
|
||||
throw new Apg.GeneralException("No output-data found.");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
error = "" + e;
|
||||
} catch (PGPException e) {
|
||||
error = "" + e;
|
||||
} catch (NoSuchProviderException e) {
|
||||
error = "" + e;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
error = "" + e;
|
||||
} catch (SignatureException e) {
|
||||
error = "" + e;
|
||||
} catch (Apg.GeneralException e) {
|
||||
error = "" + e;
|
||||
}
|
||||
|
||||
data.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
|
||||
if (error != null) {
|
||||
data.putString(Apg.EXTRA_ERROR, error);
|
||||
}
|
||||
|
||||
msg.setData(data);
|
||||
sendMessage(msg);
|
||||
}
|
||||
|
||||
private void updateView() {
|
||||
if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) {
|
||||
mSelectKeysButton.setText(R.string.noKeysSelected);
|
||||
} else if (mEncryptionKeyIds.length == 1) {
|
||||
mSelectKeysButton.setText(R.string.oneKeySelected);
|
||||
} else {
|
||||
mSelectKeysButton.setText("" + mEncryptionKeyIds.length + " "
|
||||
+ getResources().getString(R.string.nKeysSelected));
|
||||
}
|
||||
|
||||
if (getSecretKeyId() == 0) {
|
||||
mSign.setChecked(false);
|
||||
mMainUserId.setText("");
|
||||
mMainUserIdRest.setText("");
|
||||
} else {
|
||||
String uid = getResources().getString(R.string.unknownUserId);
|
||||
String uidExtra = "";
|
||||
PGPSecretKeyRing keyRing = Apg.getSecretKeyRing(getSecretKeyId());
|
||||
if (keyRing != null) {
|
||||
PGPSecretKey key = Apg.getMasterKey(keyRing);
|
||||
if (key != null) {
|
||||
String userId = Apg.getMainUserIdSafe(this, key);
|
||||
String chunks[] = userId.split(" <", 2);
|
||||
uid = chunks[0];
|
||||
if (chunks.length > 1) {
|
||||
uidExtra = "<" + chunks[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
mMainUserId.setText(uid);
|
||||
mMainUserIdRest.setText(uidExtra);
|
||||
mSign.setChecked(true);
|
||||
}
|
||||
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
private void selectPublicKeys() {
|
||||
Intent intent = new Intent(this, SelectPublicKeyListActivity.class);
|
||||
Vector<Long> keyIds = new Vector<Long>();
|
||||
if (getSecretKeyId() != 0) {
|
||||
keyIds.add(getSecretKeyId());
|
||||
}
|
||||
if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) {
|
||||
for (int i = 0; i < mEncryptionKeyIds.length; ++i) {
|
||||
keyIds.add(mEncryptionKeyIds[i]);
|
||||
}
|
||||
}
|
||||
long[] initialKeyIds = null;
|
||||
if (keyIds.size() > 0) {
|
||||
initialKeyIds = new long[keyIds.size()];
|
||||
for (int i = 0; i < keyIds.size(); ++i) {
|
||||
initialKeyIds[i] = keyIds.get(i);
|
||||
}
|
||||
}
|
||||
intent.putExtra(Apg.EXTRA_SELECTION, initialKeyIds);
|
||||
startActivityForResult(intent, Id.request.public_keys);
|
||||
}
|
||||
|
||||
private void selectSecretKey() {
|
||||
Intent intent = new Intent(this, SelectSecretKeyListActivity.class);
|
||||
startActivityForResult(intent, Id.request.secret_keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.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);
|
||||
|
||||
mFilename.setText(filename);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case Id.request.secret_keys: {
|
||||
if (resultCode == RESULT_OK) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
updateView();
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.request.public_keys: {
|
||||
if (resultCode == RESULT_OK) {
|
||||
Bundle bundle = data.getExtras();
|
||||
mEncryptionKeyIds = bundle.getLongArray(Apg.EXTRA_SELECTION);
|
||||
}
|
||||
updateView();
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneCallback(Message msg) {
|
||||
super.doneCallback(msg);
|
||||
|
||||
removeDialog(Id.dialog.encrypting);
|
||||
|
||||
Bundle data = msg.getData();
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
switch (mEncryptTarget) {
|
||||
case Id.target.clipboard: {
|
||||
String message = data.getString(Apg.EXTRA_ENCRYPTED_MESSAGE);
|
||||
Compatibility.copyToClipboard(this, message);
|
||||
Toast.makeText(this, R.string.encryptionToClipboardSuccessful, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.target.email: {
|
||||
if (mReturnResult) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtras(data);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
String message = data.getString(Apg.EXTRA_ENCRYPTED_MESSAGE);
|
||||
Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
|
||||
emailIntent.setType("text/plain; charset=utf-8");
|
||||
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, message);
|
||||
if (mSubject != null) {
|
||||
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, mSubject);
|
||||
}
|
||||
if (mSendTo != null) {
|
||||
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] { mSendTo });
|
||||
}
|
||||
EncryptActivity.this.startActivity(Intent.createChooser(emailIntent,
|
||||
getString(R.string.title_sendEmail)));
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.target.file: {
|
||||
Toast.makeText(this, R.string.encryptionSuccessful, Toast.LENGTH_SHORT).show();
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
setDeleteFile(mInputFilename);
|
||||
showDialog(Id.dialog.delete_file);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// shouldn't happen
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case Id.dialog.output_filename: {
|
||||
return FileDialog.build(this, getString(R.string.title_encryptToFile),
|
||||
getString(R.string.specifyFileToEncryptTo), mOutputFilename,
|
||||
new FileDialog.OnClickListener() {
|
||||
public void onOkClick(String filename, boolean checked) {
|
||||
removeDialog(Id.dialog.output_filename);
|
||||
mOutputFilename = filename;
|
||||
encryptStart();
|
||||
}
|
||||
|
||||
public void onCancelClick() {
|
||||
removeDialog(Id.dialog.output_filename);
|
||||
}
|
||||
}, getString(R.string.filemanager_titleSave),
|
||||
getString(R.string.filemanager_btnSave), null, Id.request.output_filename);
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
|
||||
protected void fillDataSource(boolean fixContent) {
|
||||
mDataSource = new DataSource();
|
||||
if (mContentUri != null) {
|
||||
mDataSource.setUri(mContentUri);
|
||||
} else if (mEncryptTarget == Id.target.file) {
|
||||
mDataSource.setUri(mInputFilename);
|
||||
} else {
|
||||
if (mData != null) {
|
||||
mDataSource.setData(mData);
|
||||
} else {
|
||||
String message = mMessage.getText().toString();
|
||||
if (fixContent) {
|
||||
// fix the message a bit, trailing spaces and newlines break stuff,
|
||||
// because GMail sends as HTML and such things fuck up the
|
||||
// signature,
|
||||
// TODO: things like "<" and ">" also fuck up the signature
|
||||
message = message.replaceAll(" +\n", "\n");
|
||||
message = message.replaceAll("\n\n+", "\n\n");
|
||||
message = message.replaceFirst("^\n+", "");
|
||||
// make sure there'll be exactly one newline at the end
|
||||
message = message.replaceFirst("\n*$", "\n");
|
||||
}
|
||||
mDataSource.setText(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void fillDataDestination() {
|
||||
mDataDestination = new DataDestination();
|
||||
if (mContentUri != null) {
|
||||
mDataDestination.setMode(Id.mode.stream);
|
||||
} else if (mEncryptTarget == Id.target.file) {
|
||||
mDataDestination.setFilename(mOutputFilename);
|
||||
mDataDestination.setMode(Id.mode.file);
|
||||
} else {
|
||||
mDataDestination.setMode(Id.mode.byte_array);
|
||||
}
|
||||
}
|
||||
}
|
||||
177
org_apg/src/org/apg/ui/GeneralActivity.java
Normal file
177
org_apg/src/org/apg/ui/GeneralActivity.java
Normal file
@@ -0,0 +1,177 @@
|
||||
package org.apg.ui;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Id;
|
||||
import org.apg.util.Choice;
|
||||
import org.apg.R;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class GeneralActivity extends BaseActivity {
|
||||
private Intent mIntent;
|
||||
private ArrayAdapter<Choice> mAdapter;
|
||||
private ListView mList;
|
||||
private Button mCancelButton;
|
||||
private String mDataString;
|
||||
private Uri mDataUri;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.general);
|
||||
|
||||
mIntent = getIntent();
|
||||
|
||||
InputStream inStream = null;
|
||||
{
|
||||
String data = mIntent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
if (data != null) {
|
||||
mDataString = data;
|
||||
inStream = new ByteArrayInputStream(data.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
if (inStream == null) {
|
||||
Uri data = mIntent.getData();
|
||||
if (data != null) {
|
||||
mDataUri = data;
|
||||
try {
|
||||
inStream = getContentResolver().openInputStream(data);
|
||||
} catch (FileNotFoundException e) {
|
||||
// didn't work
|
||||
Toast.makeText(this, "failed to open stream", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inStream == null) {
|
||||
Toast.makeText(this, "no data found", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
int contentType = Id.content.unknown;
|
||||
try {
|
||||
contentType = Apg.getStreamContent(this, inStream);
|
||||
inStream.close();
|
||||
} catch (IOException e) {
|
||||
// just means that there's no PGP data in there
|
||||
}
|
||||
|
||||
mList = (ListView) findViewById(R.id.options);
|
||||
Vector<Choice> choices = new Vector<Choice>();
|
||||
|
||||
if (contentType == Id.content.keys) {
|
||||
choices.add(new Choice(Id.choice.action.import_public,
|
||||
getString(R.string.action_importPublic)));
|
||||
choices.add(new Choice(Id.choice.action.import_secret,
|
||||
getString(R.string.action_importSecret)));
|
||||
}
|
||||
|
||||
if (contentType == Id.content.encrypted_data) {
|
||||
choices.add(new Choice(Id.choice.action.decrypt, getString(R.string.action_decrypt)));
|
||||
}
|
||||
|
||||
if (contentType == Id.content.unknown) {
|
||||
choices.add(new Choice(Id.choice.action.encrypt, getString(R.string.action_encrypt)));
|
||||
}
|
||||
|
||||
mAdapter = new ArrayAdapter<Choice>(this, android.R.layout.simple_list_item_1, choices);
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
mList.setOnItemClickListener(new OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
|
||||
clicked(mAdapter.getItem(arg2).getId());
|
||||
}
|
||||
});
|
||||
|
||||
mCancelButton = (Button) findViewById(R.id.btn_cancel);
|
||||
mCancelButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
GeneralActivity.this.finish();
|
||||
}
|
||||
});
|
||||
|
||||
if (choices.size() == 1) {
|
||||
clicked(choices.get(0).getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void clicked(int id) {
|
||||
Intent intent = new Intent();
|
||||
switch (id) {
|
||||
case Id.choice.action.encrypt: {
|
||||
intent.setClass(this, EncryptActivity.class);
|
||||
if (mDataString != null) {
|
||||
intent.setAction(Apg.Intent.ENCRYPT);
|
||||
intent.putExtra(Apg.EXTRA_TEXT, mDataString);
|
||||
} else if (mDataUri != null) {
|
||||
intent.setAction(Apg.Intent.ENCRYPT_FILE);
|
||||
intent.setDataAndType(mDataUri, mIntent.getType());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.choice.action.decrypt: {
|
||||
intent.setClass(this, DecryptActivity.class);
|
||||
if (mDataString != null) {
|
||||
intent.setAction(Apg.Intent.DECRYPT);
|
||||
intent.putExtra(Apg.EXTRA_TEXT, mDataString);
|
||||
} else if (mDataUri != null) {
|
||||
intent.setAction(Apg.Intent.DECRYPT_FILE);
|
||||
intent.setDataAndType(mDataUri, mIntent.getType());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.choice.action.import_public: {
|
||||
intent.setClass(this, PublicKeyListActivity.class);
|
||||
intent.setAction(Apg.Intent.IMPORT);
|
||||
if (mDataString != null) {
|
||||
intent.putExtra(Apg.EXTRA_TEXT, mDataString);
|
||||
} else if (mDataUri != null) {
|
||||
intent.setDataAndType(mDataUri, mIntent.getType());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.choice.action.import_secret: {
|
||||
intent.setClass(this, SecretKeyListActivity.class);
|
||||
intent.setAction(Apg.Intent.IMPORT);
|
||||
if (mDataString != null) {
|
||||
intent.putExtra(Apg.EXTRA_TEXT, mDataString);
|
||||
} else if (mDataUri != null) {
|
||||
intent.setDataAndType(mDataUri, mIntent.getType());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// shouldn't happen
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
138
org_apg/src/org/apg/ui/ImportFromQRCodeActivity.java
Normal file
138
org_apg/src/org/apg/ui/ImportFromQRCodeActivity.java
Normal file
@@ -0,0 +1,138 @@
|
||||
package org.apg.ui;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Constants;
|
||||
import org.apg.HkpKeyServer;
|
||||
import org.apg.Id;
|
||||
import org.apg.KeyServer.QueryException;
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.apg.R;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
public class ImportFromQRCodeActivity extends BaseActivity {
|
||||
private static final String TAG = "ImportFromQRCodeActivity";
|
||||
|
||||
private final Bundle status = new Bundle();
|
||||
private final Message msg = new Message();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
new IntentIntegrator(this).initiateScan();
|
||||
}
|
||||
|
||||
private void importAndSign(final long keyId, final String expectedFingerprint) {
|
||||
if (expectedFingerprint != null && expectedFingerprint.length() > 0) {
|
||||
|
||||
Thread t = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// TODO: display some sort of spinner here while the user waits
|
||||
|
||||
HkpKeyServer server = new HkpKeyServer(mPreferences.getKeyServers()[0]); // TODO: there should be only 1
|
||||
String encodedKey = server.get(keyId);
|
||||
|
||||
PGPKeyRing keyring = Apg.decodeKeyRing(new ByteArrayInputStream(encodedKey.getBytes()));
|
||||
if (keyring != null && keyring instanceof PGPPublicKeyRing) {
|
||||
PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring;
|
||||
|
||||
// make sure the fingerprints match before we cache this thing
|
||||
String actualFingerprint = Apg.convertToHex(publicKeyRing.getPublicKey().getFingerprint());
|
||||
if (expectedFingerprint.equals(actualFingerprint)) {
|
||||
// store the signed key in our local cache
|
||||
int retval = Apg.storeKeyRingInCache(publicKeyRing);
|
||||
if (retval != Id.return_value.ok && retval != Id.return_value.updated) {
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to store signed key in local cache");
|
||||
} else {
|
||||
Intent intent = new Intent(ImportFromQRCodeActivity.this, SignKeyActivity.class);
|
||||
intent.putExtra(Apg.EXTRA_KEY_ID, keyId);
|
||||
startActivityForResult(intent, Id.request.sign_key);
|
||||
}
|
||||
} else {
|
||||
status.putString(Apg.EXTRA_ERROR, "Scanned fingerprint does NOT match the fingerprint of the received key. You shouldnt trust this key.");
|
||||
}
|
||||
}
|
||||
} catch (QueryException e) {
|
||||
Log.e(TAG, "Failed to query KeyServer", e);
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to query KeyServer");
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to query KeyServer", e);
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to query KeyServer");
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
t.setName("KeyExchange Download Thread");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case IntentIntegrator.REQUEST_CODE: {
|
||||
boolean debug = true; // TODO: remove this!!!
|
||||
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
|
||||
if (debug || (scanResult != null && scanResult.getFormatName() != null)) {
|
||||
String[] bits = debug ? new String[] { "5993515643896327656", "0816 F68A 6816 68FB 01BF 2CA5 532D 3EB9 1E2F EDE8" } : scanResult.getContents().split(",");
|
||||
if (bits.length != 2) {
|
||||
return; // dont know how to handle this. Not a valid code
|
||||
}
|
||||
|
||||
long keyId = Long.parseLong(bits[0]);
|
||||
String expectedFingerprint = bits[1];
|
||||
|
||||
importAndSign(keyId, expectedFingerprint);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case Id.request.sign_key: {
|
||||
// signals the end of processing. Signature was either applied, or it wasnt
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
|
||||
msg.setData(status);
|
||||
sendMessage(msg);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneCallback(Message msg) {
|
||||
super.doneCallback(msg);
|
||||
|
||||
Bundle data = msg.getData();
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(this, R.string.keySignSuccess, Toast.LENGTH_SHORT).show(); // TODO
|
||||
finish();
|
||||
}
|
||||
}
|
||||
768
org_apg/src/org/apg/ui/KeyListActivity.java
Normal file
768
org_apg/src/org/apg/ui/KeyListActivity.java
Normal file
@@ -0,0 +1,768 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Constants;
|
||||
import org.apg.FileDialog;
|
||||
import org.apg.Id;
|
||||
import org.apg.InputData;
|
||||
import org.apg.provider.KeyRings;
|
||||
import org.apg.provider.Keys;
|
||||
import org.apg.provider.UserIds;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseExpandableListAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ExpandableListView;
|
||||
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
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.util.Vector;
|
||||
|
||||
public class KeyListActivity extends BaseActivity {
|
||||
protected ExpandableListView mList;
|
||||
protected KeyListAdapter mListAdapter;
|
||||
protected View mFilterLayout;
|
||||
protected Button mClearFilterButton;
|
||||
protected TextView mFilterInfo;
|
||||
|
||||
protected int mSelectedItem = -1;
|
||||
protected int mTask = 0;
|
||||
|
||||
protected String mImportFilename = Constants.path.APP_DIR + "/";
|
||||
protected String mExportFilename = Constants.path.APP_DIR + "/";
|
||||
|
||||
protected String mImportData;
|
||||
protected boolean mDeleteAfterImport = false;
|
||||
|
||||
protected int mKeyType = Id.type.public_key;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.key_list);
|
||||
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
mList = (ExpandableListView) findViewById(R.id.list);
|
||||
registerForContextMenu(mList);
|
||||
|
||||
mFilterLayout = findViewById(R.id.layout_filter);
|
||||
mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo);
|
||||
mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear);
|
||||
|
||||
mClearFilterButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
handleIntent(new Intent());
|
||||
}
|
||||
});
|
||||
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
protected void handleIntent(Intent intent) {
|
||||
String searchString = null;
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
searchString = intent.getStringExtra(SearchManager.QUERY);
|
||||
if (searchString != null && searchString.trim().length() == 0) {
|
||||
searchString = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (searchString == null) {
|
||||
mFilterLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
mFilterLayout.setVisibility(View.VISIBLE);
|
||||
mFilterInfo.setText(getString(R.string.filterInfo, searchString));
|
||||
}
|
||||
|
||||
if (mListAdapter != null) {
|
||||
mListAdapter.cleanup();
|
||||
}
|
||||
mListAdapter = new KeyListAdapter(this, searchString);
|
||||
mList.setAdapter(mListAdapter);
|
||||
|
||||
if (Apg.Intent.IMPORT.equals(intent.getAction())) {
|
||||
if ("file".equals(intent.getScheme()) && intent.getDataString() != null) {
|
||||
mImportFilename = Uri.decode(intent.getDataString().replace("file://", ""));
|
||||
} else {
|
||||
mImportData = intent.getStringExtra(Apg.EXTRA_TEXT);
|
||||
}
|
||||
importKeys();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case Id.menu.option.import_keys: {
|
||||
showDialog(Id.dialog.import_keys);
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.option.export_keys: {
|
||||
showDialog(Id.dialog.export_keys);
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem) {
|
||||
ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo();
|
||||
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
|
||||
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
|
||||
|
||||
if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
|
||||
switch (menuItem.getItemId()) {
|
||||
case Id.menu.export: {
|
||||
mSelectedItem = groupPosition;
|
||||
showDialog(Id.dialog.export_key);
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.delete: {
|
||||
mSelectedItem = groupPosition;
|
||||
showDialog(Id.dialog.delete_key);
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
boolean singleKeyExport = false;
|
||||
|
||||
switch (id) {
|
||||
case Id.dialog.delete_key: {
|
||||
final int keyRingId = mListAdapter.getKeyRingId(mSelectedItem);
|
||||
mSelectedItem = -1;
|
||||
// TODO: better way to do this?
|
||||
String userId = "<unknown>";
|
||||
Object keyRing = Apg.getKeyRing(keyRingId);
|
||||
if (keyRing != null) {
|
||||
if (keyRing instanceof PGPPublicKeyRing) {
|
||||
userId = Apg.getMainUserIdSafe(this,
|
||||
Apg.getMasterKey((PGPPublicKeyRing) keyRing));
|
||||
} else {
|
||||
userId = Apg.getMainUserIdSafe(this,
|
||||
Apg.getMasterKey((PGPSecretKeyRing) keyRing));
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.warning);
|
||||
builder.setMessage(getString(
|
||||
mKeyType == Id.type.public_key ? R.string.keyDeletionConfirmation
|
||||
: R.string.secretKeyDeletionConfirmation, userId));
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
deleteKey(keyRingId);
|
||||
removeDialog(Id.dialog.delete_key);
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
removeDialog(Id.dialog.delete_key);
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
case Id.dialog.import_keys: {
|
||||
return FileDialog.build(this, getString(R.string.title_importKeys),
|
||||
getString(R.string.specifyFileToImportFrom), mImportFilename,
|
||||
new FileDialog.OnClickListener() {
|
||||
public void onOkClick(String filename, boolean checked) {
|
||||
removeDialog(Id.dialog.import_keys);
|
||||
mDeleteAfterImport = checked;
|
||||
mImportFilename = filename;
|
||||
importKeys();
|
||||
}
|
||||
|
||||
public void onCancelClick() {
|
||||
removeDialog(Id.dialog.import_keys);
|
||||
}
|
||||
}, getString(R.string.filemanager_titleOpen),
|
||||
getString(R.string.filemanager_btnOpen),
|
||||
getString(R.string.label_deleteAfterImport), Id.request.filename);
|
||||
}
|
||||
|
||||
case Id.dialog.export_key: {
|
||||
singleKeyExport = true;
|
||||
// break intentionally omitted, to use the Id.dialog.export_keys dialog
|
||||
}
|
||||
|
||||
case Id.dialog.export_keys: {
|
||||
String title = (singleKeyExport ? getString(R.string.title_exportKey)
|
||||
: getString(R.string.title_exportKeys));
|
||||
|
||||
final int thisDialogId = (singleKeyExport ? Id.dialog.export_key
|
||||
: Id.dialog.export_keys);
|
||||
|
||||
return FileDialog.build(this, title,
|
||||
getString(mKeyType == Id.type.public_key ? R.string.specifyFileToExportTo
|
||||
: R.string.specifyFileToExportSecretKeysTo), mExportFilename,
|
||||
new FileDialog.OnClickListener() {
|
||||
public void onOkClick(String filename, boolean checked) {
|
||||
removeDialog(thisDialogId);
|
||||
mExportFilename = filename;
|
||||
exportKeys();
|
||||
}
|
||||
|
||||
public void onCancelClick() {
|
||||
removeDialog(thisDialogId);
|
||||
}
|
||||
}, getString(R.string.filemanager_titleSave),
|
||||
getString(R.string.filemanager_btnSave), null, Id.request.filename);
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void importKeys() {
|
||||
showDialog(Id.dialog.importing);
|
||||
mTask = Id.task.import_keys;
|
||||
startThread();
|
||||
}
|
||||
|
||||
public void exportKeys() {
|
||||
showDialog(Id.dialog.exporting);
|
||||
mTask = Id.task.export_keys;
|
||||
startThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String error = null;
|
||||
Bundle data = new Bundle();
|
||||
Message msg = new Message();
|
||||
|
||||
try {
|
||||
InputStream importInputStream = null;
|
||||
OutputStream exportOutputStream = null;
|
||||
long size = 0;
|
||||
if (mTask == Id.task.import_keys) {
|
||||
if (mImportData != null) {
|
||||
byte[] bytes = mImportData.getBytes();
|
||||
size = bytes.length;
|
||||
importInputStream = new ByteArrayInputStream(bytes);
|
||||
} else {
|
||||
File file = new File(mImportFilename);
|
||||
size = file.length();
|
||||
importInputStream = new FileInputStream(file);
|
||||
}
|
||||
} else {
|
||||
exportOutputStream = new FileOutputStream(mExportFilename);
|
||||
}
|
||||
|
||||
if (mTask == Id.task.import_keys) {
|
||||
data = Apg.importKeyRings(this, mKeyType, new InputData(importInputStream, size),
|
||||
this);
|
||||
} else {
|
||||
Vector<Integer> keyRingIds = new Vector<Integer>();
|
||||
if (mSelectedItem == -1) {
|
||||
keyRingIds = Apg
|
||||
.getKeyRingIds(mKeyType == Id.type.public_key ? Id.database.type_public
|
||||
: Id.database.type_secret);
|
||||
} else {
|
||||
int keyRingId = mListAdapter.getKeyRingId(mSelectedItem);
|
||||
keyRingIds.add(keyRingId);
|
||||
mSelectedItem = -1;
|
||||
}
|
||||
data = Apg.exportKeyRings(this, keyRingIds, exportOutputStream, this);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
error = getString(R.string.error_fileNotFound);
|
||||
} catch (IOException e) {
|
||||
error = "" + e;
|
||||
} catch (PGPException e) {
|
||||
error = "" + e;
|
||||
} catch (Apg.GeneralException e) {
|
||||
error = "" + e;
|
||||
}
|
||||
|
||||
mImportData = null;
|
||||
|
||||
if (mTask == Id.task.import_keys) {
|
||||
data.putInt(Constants.extras.STATUS, Id.message.import_done);
|
||||
} else {
|
||||
data.putInt(Constants.extras.STATUS, Id.message.export_done);
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
data.putString(Apg.EXTRA_ERROR, error);
|
||||
}
|
||||
|
||||
msg.setData(data);
|
||||
sendMessage(msg);
|
||||
}
|
||||
|
||||
protected void deleteKey(int keyRingId) {
|
||||
Apg.deleteKey(keyRingId);
|
||||
refreshList();
|
||||
}
|
||||
|
||||
protected void refreshList() {
|
||||
mListAdapter.rebuild(true);
|
||||
mListAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneCallback(Message msg) {
|
||||
super.doneCallback(msg);
|
||||
|
||||
Bundle data = msg.getData();
|
||||
if (data != null) {
|
||||
int type = data.getInt(Constants.extras.STATUS);
|
||||
switch (type) {
|
||||
case Id.message.import_done: {
|
||||
removeDialog(Id.dialog.importing);
|
||||
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(KeyListActivity.this, getString(R.string.errorMessage, error),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
int added = data.getInt("added");
|
||||
int updated = data.getInt("updated");
|
||||
int bad = data.getInt("bad");
|
||||
String message;
|
||||
if (added > 0 && updated > 0) {
|
||||
message = getString(R.string.keysAddedAndUpdated, added, updated);
|
||||
} else if (added > 0) {
|
||||
message = getString(R.string.keysAdded, added);
|
||||
} else if (updated > 0) {
|
||||
message = getString(R.string.keysUpdated, updated);
|
||||
} else {
|
||||
message = getString(R.string.noKeysAddedOrUpdated);
|
||||
}
|
||||
Toast.makeText(KeyListActivity.this, message, Toast.LENGTH_SHORT).show();
|
||||
if (bad > 0) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.warning);
|
||||
alert.setMessage(this.getString(R.string.badKeysEncountered, bad));
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
alert.setCancelable(true);
|
||||
alert.create().show();
|
||||
} else if (mDeleteAfterImport) {
|
||||
// everything went well, so now delete, if that was turned on
|
||||
setDeleteFile(mImportFilename);
|
||||
showDialog(Id.dialog.delete_file);
|
||||
}
|
||||
}
|
||||
refreshList();
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.message.export_done: {
|
||||
removeDialog(Id.dialog.exporting);
|
||||
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(KeyListActivity.this, getString(R.string.errorMessage, error),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
int exported = data.getInt("exported");
|
||||
String message;
|
||||
if (exported == 1) {
|
||||
message = getString(R.string.keyExported);
|
||||
} else if (exported > 0) {
|
||||
message = getString(R.string.keysExported, exported);
|
||||
} else {
|
||||
message = getString(R.string.noKeysExported);
|
||||
}
|
||||
Toast.makeText(KeyListActivity.this, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected class KeyListAdapter extends BaseExpandableListAdapter {
|
||||
private LayoutInflater mInflater;
|
||||
private Vector<Vector<KeyChild>> mChildren;
|
||||
private SQLiteDatabase mDatabase;
|
||||
private Cursor mCursor;
|
||||
private String mSearchString;
|
||||
|
||||
private class KeyChild {
|
||||
public static final int KEY = 0;
|
||||
public static final int USER_ID = 1;
|
||||
public static final int FINGER_PRINT = 2;
|
||||
|
||||
public int type;
|
||||
public String userId;
|
||||
public long keyId;
|
||||
public boolean isMasterKey;
|
||||
public int algorithm;
|
||||
public int keySize;
|
||||
public boolean canSign;
|
||||
public boolean canEncrypt;
|
||||
public String fingerPrint;
|
||||
|
||||
public KeyChild(long keyId, boolean isMasterKey, int algorithm, int keySize,
|
||||
boolean canSign, boolean canEncrypt) {
|
||||
this.type = KEY;
|
||||
this.keyId = keyId;
|
||||
this.isMasterKey = isMasterKey;
|
||||
this.algorithm = algorithm;
|
||||
this.keySize = keySize;
|
||||
this.canSign = canSign;
|
||||
this.canEncrypt = canEncrypt;
|
||||
}
|
||||
|
||||
public KeyChild(String userId) {
|
||||
type = USER_ID;
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public KeyChild(String fingerPrint, boolean isFingerPrint) {
|
||||
type = FINGER_PRINT;
|
||||
this.fingerPrint = fingerPrint;
|
||||
}
|
||||
}
|
||||
|
||||
public KeyListAdapter(Context context, String searchString) {
|
||||
mSearchString = searchString;
|
||||
|
||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mDatabase = Apg.getDatabase().db();
|
||||
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
|
||||
qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + "("
|
||||
+ KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "."
|
||||
+ Keys.KEY_RING_ID + " AND " + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY
|
||||
+ " = '1'" + ") " + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + "("
|
||||
+ Keys.TABLE_NAME + "." + Keys._ID + " = " + UserIds.TABLE_NAME + "."
|
||||
+ UserIds.KEY_ID + " AND " + UserIds.TABLE_NAME + "." + UserIds.RANK
|
||||
+ " = '0')");
|
||||
|
||||
if (searchString != null && searchString.trim().length() > 0) {
|
||||
String[] chunks = searchString.trim().split(" +");
|
||||
qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " + UserIds.TABLE_NAME
|
||||
+ " AS tmp WHERE " + "tmp." + UserIds.KEY_ID + " = " + Keys.TABLE_NAME
|
||||
+ "." + Keys._ID);
|
||||
for (int i = 0; i < chunks.length; ++i) {
|
||||
qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE ");
|
||||
qb.appendWhereEscapeString("%" + chunks[i] + "%");
|
||||
}
|
||||
qb.appendWhere(")");
|
||||
}
|
||||
|
||||
mCursor = qb.query(mDatabase, new String[] { KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0
|
||||
KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1
|
||||
UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2
|
||||
}, KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", new String[] { ""
|
||||
+ (mKeyType == Id.type.public_key ? Id.database.type_public
|
||||
: Id.database.type_secret) }, null, null, UserIds.TABLE_NAME + "."
|
||||
+ UserIds.USER_ID + " ASC");
|
||||
|
||||
// content provider way for reference, might have to go back to it sometime:
|
||||
/*
|
||||
* Uri contentUri = null; if (mKeyType == Id.type.secret_key) { contentUri =
|
||||
* Apg.CONTENT_URI_SECRET_KEY_RINGS; } else { contentUri =
|
||||
* Apg.CONTENT_URI_PUBLIC_KEY_RINGS; } mCursor = getContentResolver().query( contentUri,
|
||||
* new String[] { DataProvider._ID, // 0 DataProvider.MASTER_KEY_ID, // 1
|
||||
* DataProvider.USER_ID, // 2 }, null, null, null);
|
||||
*/
|
||||
|
||||
startManagingCursor(mCursor);
|
||||
rebuild(false);
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
if (mCursor != null) {
|
||||
stopManagingCursor(mCursor);
|
||||
mCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void rebuild(boolean requery) {
|
||||
if (requery) {
|
||||
mCursor.requery();
|
||||
}
|
||||
mChildren = new Vector<Vector<KeyChild>>();
|
||||
for (int i = 0; i < mCursor.getCount(); ++i) {
|
||||
mChildren.add(null);
|
||||
}
|
||||
}
|
||||
|
||||
protected Vector<KeyChild> getChildrenOfGroup(int groupPosition) {
|
||||
Vector<KeyChild> children = mChildren.get(groupPosition);
|
||||
if (children != null) {
|
||||
return children;
|
||||
}
|
||||
|
||||
mCursor.moveToPosition(groupPosition);
|
||||
children = new Vector<KeyChild>();
|
||||
Cursor c = mDatabase.query(Keys.TABLE_NAME, new String[] { Keys._ID, // 0
|
||||
Keys.KEY_ID, // 1
|
||||
Keys.IS_MASTER_KEY, // 2
|
||||
Keys.ALGORITHM, // 3
|
||||
Keys.KEY_SIZE, // 4
|
||||
Keys.CAN_SIGN, // 5
|
||||
Keys.CAN_ENCRYPT, // 6
|
||||
}, Keys.KEY_RING_ID + " = ?", new String[] { mCursor.getString(0) }, null, null,
|
||||
Keys.RANK + " ASC");
|
||||
|
||||
int masterKeyId = -1;
|
||||
long fingerPrintId = -1;
|
||||
for (int i = 0; i < c.getCount(); ++i) {
|
||||
c.moveToPosition(i);
|
||||
children.add(new KeyChild(c.getLong(1), c.getInt(2) == 1, c.getInt(3), c.getInt(4),
|
||||
c.getInt(5) == 1, c.getInt(6) == 1));
|
||||
if (i == 0) {
|
||||
masterKeyId = c.getInt(0);
|
||||
fingerPrintId = c.getLong(1);
|
||||
}
|
||||
}
|
||||
c.close();
|
||||
|
||||
if (masterKeyId != -1) {
|
||||
children.insertElementAt(new KeyChild(Apg.getFingerPrint(fingerPrintId), true), 0);
|
||||
c = mDatabase.query(UserIds.TABLE_NAME, new String[] { UserIds.USER_ID, // 0
|
||||
}, UserIds.KEY_ID + " = ? AND " + UserIds.RANK + " > 0", new String[] { ""
|
||||
+ masterKeyId }, null, null, UserIds.RANK + " ASC");
|
||||
|
||||
for (int i = 0; i < c.getCount(); ++i) {
|
||||
c.moveToPosition(i);
|
||||
children.add(new KeyChild(c.getString(0)));
|
||||
}
|
||||
c.close();
|
||||
}
|
||||
|
||||
mChildren.set(groupPosition, children);
|
||||
return children;
|
||||
}
|
||||
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isChildSelectable(int groupPosition, int childPosition) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getGroupCount() {
|
||||
return mCursor.getCount();
|
||||
}
|
||||
|
||||
public Object getChild(int groupPosition, int childPosition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getChildId(int groupPosition, int childPosition) {
|
||||
return childPosition;
|
||||
}
|
||||
|
||||
public int getChildrenCount(int groupPosition) {
|
||||
return getChildrenOfGroup(groupPosition).size();
|
||||
}
|
||||
|
||||
public Object getGroup(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
public long getGroupId(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getLong(1); // MASTER_KEY_ID
|
||||
}
|
||||
|
||||
public int getKeyRingId(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getInt(0); // _ID
|
||||
}
|
||||
|
||||
public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
|
||||
ViewGroup parent) {
|
||||
mCursor.moveToPosition(groupPosition);
|
||||
|
||||
View view = mInflater.inflate(R.layout.key_list_group_item, null);
|
||||
view.setBackgroundResource(android.R.drawable.list_selector_background);
|
||||
|
||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mainUserId.setText("");
|
||||
TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
mainUserIdRest.setText("");
|
||||
|
||||
String userId = mCursor.getString(2); // USER_ID
|
||||
if (userId != null) {
|
||||
String chunks[] = userId.split(" <", 2);
|
||||
userId = chunks[0];
|
||||
if (chunks.length > 1) {
|
||||
mainUserIdRest.setText("<" + chunks[1]);
|
||||
}
|
||||
mainUserId.setText(userId);
|
||||
}
|
||||
|
||||
if (mainUserId.getText().length() == 0) {
|
||||
mainUserId.setText(R.string.unknownUserId);
|
||||
}
|
||||
|
||||
if (mainUserIdRest.getText().length() == 0) {
|
||||
mainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
|
||||
View convertView, ViewGroup parent) {
|
||||
mCursor.moveToPosition(groupPosition);
|
||||
|
||||
Vector<KeyChild> children = getChildrenOfGroup(groupPosition);
|
||||
|
||||
KeyChild child = children.get(childPosition);
|
||||
View view = null;
|
||||
switch (child.type) {
|
||||
case KeyChild.KEY: {
|
||||
if (child.isMasterKey) {
|
||||
view = mInflater.inflate(R.layout.key_list_child_item_master_key, null);
|
||||
} else {
|
||||
view = mInflater.inflate(R.layout.key_list_child_item_sub_key, null);
|
||||
}
|
||||
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
String keyIdStr = Apg.getSmallFingerPrint(child.keyId);
|
||||
keyId.setText(keyIdStr);
|
||||
TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails);
|
||||
String algorithmStr = Apg.getAlgorithmInfo(child.algorithm, child.keySize);
|
||||
keyDetails.setText("(" + algorithmStr + ")");
|
||||
|
||||
ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
|
||||
if (!child.canEncrypt) {
|
||||
encryptIcon.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
|
||||
if (!child.canSign) {
|
||||
signIcon.setVisibility(View.GONE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case KeyChild.USER_ID: {
|
||||
view = mInflater.inflate(R.layout.key_list_child_item_user_id, null);
|
||||
TextView userId = (TextView) view.findViewById(R.id.userId);
|
||||
userId.setText(child.userId);
|
||||
break;
|
||||
}
|
||||
|
||||
case KeyChild.FINGER_PRINT: {
|
||||
view = mInflater.inflate(R.layout.key_list_child_item_user_id, null);
|
||||
TextView userId = (TextView) view.findViewById(R.id.userId);
|
||||
userId.setText(getString(R.string.fingerprint) + ":\n"
|
||||
+ child.fingerPrint.replace(" ", "\n"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.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);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
125
org_apg/src/org/apg/ui/KeyServerPreferenceActivity.java
Normal file
125
org_apg/src/org/apg/ui/KeyServerPreferenceActivity.java
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.ui.widget.Editor;
|
||||
import org.apg.ui.widget.KeyServerEditor;
|
||||
import org.apg.ui.widget.Editor.EditorListener;
|
||||
import org.apg.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class KeyServerPreferenceActivity extends BaseActivity
|
||||
implements OnClickListener, EditorListener {
|
||||
private LayoutInflater mInflater;
|
||||
private ViewGroup mEditors;
|
||||
private View mAdd;
|
||||
private TextView mTitle;
|
||||
private TextView mSummary;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.key_server_preference);
|
||||
|
||||
mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
mTitle = (TextView) findViewById(R.id.title);
|
||||
mSummary = (TextView) findViewById(R.id.summary);
|
||||
|
||||
mTitle.setText(R.string.label_keyServers);
|
||||
|
||||
mEditors = (ViewGroup) findViewById(R.id.editors);
|
||||
mAdd = findViewById(R.id.add);
|
||||
mAdd.setOnClickListener(this);
|
||||
|
||||
Intent intent = getIntent();
|
||||
String servers[] = intent.getStringArrayExtra(Apg.EXTRA_KEY_SERVERS);
|
||||
if (servers != null) {
|
||||
for (int i = 0; i < servers.length; ++i) {
|
||||
KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor, mEditors, false);
|
||||
view.setEditorListener(this);
|
||||
view.setValue(servers[i]);
|
||||
mEditors.addView(view);
|
||||
}
|
||||
}
|
||||
|
||||
Button okButton = (Button) findViewById(R.id.btn_ok);
|
||||
okButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
okClicked();
|
||||
}
|
||||
});
|
||||
|
||||
Button cancelButton = (Button) findViewById(R.id.btn_cancel);
|
||||
cancelButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
cancelClicked();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onDeleted(Editor editor) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor, mEditors, false);
|
||||
view.setEditorListener(this);
|
||||
mEditors.addView(view);
|
||||
}
|
||||
|
||||
private void cancelClicked() {
|
||||
setResult(RESULT_CANCELED, null);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void okClicked() {
|
||||
Intent data = new Intent();
|
||||
Vector<String> servers = new Vector<String>();
|
||||
for (int i = 0; i < mEditors.getChildCount(); ++i) {
|
||||
KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i);
|
||||
String tmp = editor.getValue();
|
||||
if (tmp.length() > 0) {
|
||||
servers.add(tmp);
|
||||
}
|
||||
}
|
||||
String[] dummy = new String[0];
|
||||
data.putExtra(Apg.EXTRA_KEY_SERVERS, servers.toArray(dummy));
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// override this, so no option menu is added (as would be in BaseActivity), since
|
||||
// we're still in preferences
|
||||
return true;
|
||||
}
|
||||
}
|
||||
297
org_apg/src/org/apg/ui/KeyServerQueryActivity.java
Normal file
297
org_apg/src/org/apg/ui/KeyServerQueryActivity.java
Normal file
@@ -0,0 +1,297 @@
|
||||
package org.apg.ui;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Constants;
|
||||
import org.apg.HkpKeyServer;
|
||||
import org.apg.Id;
|
||||
import org.apg.KeyServer.InsufficientQuery;
|
||||
import org.apg.KeyServer.KeyInfo;
|
||||
import org.apg.KeyServer.QueryException;
|
||||
import org.apg.KeyServer.TooManyResponses;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class KeyServerQueryActivity extends BaseActivity {
|
||||
private ListView mList;
|
||||
private EditText mQuery;
|
||||
private Button mSearch;
|
||||
private KeyInfoListAdapter mAdapter;
|
||||
private Spinner mKeyServer;
|
||||
|
||||
private int mQueryType;
|
||||
private String mQueryString;
|
||||
private long mQueryId;
|
||||
private volatile List<KeyInfo> mSearchResult;
|
||||
private volatile String mKeyData;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.key_server_query_layout);
|
||||
|
||||
mQuery = (EditText) findViewById(R.id.query);
|
||||
mSearch = (Button) findViewById(R.id.btn_search);
|
||||
mList = (ListView) findViewById(R.id.list);
|
||||
mAdapter = new KeyInfoListAdapter(this);
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
mKeyServer = (Spinner) findViewById(R.id.keyServer);
|
||||
ArrayAdapter<String> adapter =
|
||||
new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item,
|
||||
mPreferences.getKeyServers());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mKeyServer.setAdapter(adapter);
|
||||
if (adapter.getCount() > 0) {
|
||||
mKeyServer.setSelection(0);
|
||||
} else {
|
||||
mSearch.setEnabled(false);
|
||||
}
|
||||
|
||||
mList.setOnItemClickListener(new OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> adapter, View view, int position, long keyId) {
|
||||
get(keyId);
|
||||
}
|
||||
});
|
||||
|
||||
mSearch.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
String query = mQuery.getText().toString();
|
||||
search(query);
|
||||
}
|
||||
});
|
||||
|
||||
Intent intent = getIntent();
|
||||
if (Apg.Intent.LOOK_UP_KEY_ID.equals(intent.getAction()) ||
|
||||
Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN.equals(intent.getAction())) {
|
||||
long keyId = intent.getLongExtra(Apg.EXTRA_KEY_ID, 0);
|
||||
if (keyId != 0) {
|
||||
String query = "0x" + Apg.keyToHex(keyId);
|
||||
mQuery.setText(query);
|
||||
search(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void search(String query) {
|
||||
showDialog(Id.dialog.querying);
|
||||
mQueryType = Id.keyserver.search;
|
||||
mQueryString = query;
|
||||
mAdapter.setKeys(new Vector<KeyInfo>());
|
||||
startThread();
|
||||
}
|
||||
|
||||
private void get(long keyId) {
|
||||
showDialog(Id.dialog.querying);
|
||||
mQueryType = Id.keyserver.get;
|
||||
mQueryId = keyId;
|
||||
startThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
ProgressDialog progress = (ProgressDialog) super.onCreateDialog(id);
|
||||
progress.setMessage(this.getString(R.string.progress_queryingServer,
|
||||
(String)mKeyServer.getSelectedItem()));
|
||||
return progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String error = null;
|
||||
Bundle data = new Bundle();
|
||||
Message msg = new Message();
|
||||
|
||||
try {
|
||||
HkpKeyServer server = new HkpKeyServer((String)mKeyServer.getSelectedItem());
|
||||
if (mQueryType == Id.keyserver.search) {
|
||||
mSearchResult = server.search(mQueryString);
|
||||
} else if (mQueryType == Id.keyserver.get) {
|
||||
mKeyData = server.get(mQueryId);
|
||||
}
|
||||
} catch (QueryException e) {
|
||||
error = "" + e;
|
||||
} catch (InsufficientQuery e) {
|
||||
error = "Insufficient query.";
|
||||
} catch (TooManyResponses e) {
|
||||
error = "Too many responses.";
|
||||
}
|
||||
|
||||
data.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
|
||||
if (error != null) {
|
||||
data.putString(Apg.EXTRA_ERROR, error);
|
||||
}
|
||||
|
||||
msg.setData(data);
|
||||
sendMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneCallback(Message msg) {
|
||||
super.doneCallback(msg);
|
||||
|
||||
removeDialog(Id.dialog.querying);
|
||||
|
||||
Bundle data = msg.getData();
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mQueryType == Id.keyserver.search) {
|
||||
if (mSearchResult != null) {
|
||||
Toast.makeText(this, getString(R.string.keysFound, mSearchResult.size()), Toast.LENGTH_SHORT).show();
|
||||
mAdapter.setKeys(mSearchResult);
|
||||
}
|
||||
} else if (mQueryType == Id.keyserver.get) {
|
||||
Intent orgIntent = getIntent();
|
||||
if (Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN.equals(orgIntent.getAction())) {
|
||||
if (mKeyData != null) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(Apg.EXTRA_TEXT, mKeyData);
|
||||
setResult(RESULT_OK, intent);
|
||||
} else {
|
||||
setResult(RESULT_CANCELED);
|
||||
}
|
||||
finish();
|
||||
} else {
|
||||
if (mKeyData != null) {
|
||||
Intent intent = new Intent(this, PublicKeyListActivity.class);
|
||||
intent.setAction(Apg.Intent.IMPORT);
|
||||
intent.putExtra(Apg.EXTRA_TEXT, mKeyData);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class KeyInfoListAdapter extends BaseAdapter {
|
||||
protected LayoutInflater mInflater;
|
||||
protected Activity mActivity;
|
||||
protected List<KeyInfo> mKeys;
|
||||
|
||||
public KeyInfoListAdapter(Activity activity) {
|
||||
mActivity = activity;
|
||||
mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mKeys = new Vector<KeyInfo>();
|
||||
}
|
||||
|
||||
public void setKeys(List<KeyInfo> keys) {
|
||||
mKeys = keys;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return mKeys.size();
|
||||
}
|
||||
|
||||
public Object getItem(int position) {
|
||||
return mKeys.get(position);
|
||||
}
|
||||
|
||||
public long getItemId(int position) {
|
||||
return mKeys.get(position).keyId;
|
||||
}
|
||||
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
KeyInfo keyInfo = mKeys.get(position);
|
||||
|
||||
View view = mInflater.inflate(R.layout.key_server_query_result_item, null);
|
||||
|
||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mainUserId.setText(R.string.unknownUserId);
|
||||
TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
mainUserIdRest.setText("");
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
keyId.setText(R.string.noKey);
|
||||
TextView algorithm = (TextView) view.findViewById(R.id.algorithm);
|
||||
algorithm.setText("");
|
||||
TextView status = (TextView) view.findViewById(R.id.status);
|
||||
status.setText("");
|
||||
|
||||
String userId = keyInfo.userIds.get(0);
|
||||
if (userId != null) {
|
||||
String chunks[] = userId.split(" <", 2);
|
||||
userId = chunks[0];
|
||||
if (chunks.length > 1) {
|
||||
mainUserIdRest.setText("<" + chunks[1]);
|
||||
}
|
||||
mainUserId.setText(userId);
|
||||
}
|
||||
|
||||
keyId.setText(Apg.getSmallFingerPrint(keyInfo.keyId));
|
||||
|
||||
if (mainUserIdRest.getText().length() == 0) {
|
||||
mainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
algorithm.setText("" + keyInfo.size + "/" + keyInfo.algorithm);
|
||||
|
||||
if (keyInfo.revoked != null) {
|
||||
status.setText("revoked");
|
||||
} else {
|
||||
status.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
LinearLayout ll = (LinearLayout) view.findViewById(R.id.list);
|
||||
if (keyInfo.userIds.size() == 1) {
|
||||
ll.setVisibility(View.GONE);
|
||||
} else {
|
||||
boolean first = true;
|
||||
boolean second = true;
|
||||
for (String uid : keyInfo.userIds) {
|
||||
if (first) {
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
if (!second) {
|
||||
View sep = new View(mActivity);
|
||||
sep.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, 1));
|
||||
sep.setBackgroundResource(android.R.drawable.divider_horizontal_dark);
|
||||
ll.addView(sep);
|
||||
}
|
||||
TextView uidView = (TextView) mInflater.inflate(R.layout.key_server_query_result_user_id, null);
|
||||
uidView.setText(uid);
|
||||
ll.addView(uidView);
|
||||
second = false;
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
222
org_apg/src/org/apg/ui/MailListActivity.java
Normal file
222
org_apg/src/org/apg/ui/MailListActivity.java
Normal file
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui;
|
||||
|
||||
import java.util.Vector;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Preferences;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.ListActivity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class MailListActivity extends ListActivity {
|
||||
LayoutInflater mInflater = null;
|
||||
|
||||
public static final String EXTRA_ACCOUNT = "account";
|
||||
|
||||
private static class Conversation {
|
||||
public long id;
|
||||
public String subject;
|
||||
public Vector<Message> messages;
|
||||
|
||||
public Conversation(long id, String subject) {
|
||||
this.id = id;
|
||||
this.subject = subject;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Message {
|
||||
public Conversation parent;
|
||||
public long id;
|
||||
public String subject;
|
||||
public String fromAddress;
|
||||
public String data;
|
||||
public String replyTo;
|
||||
public boolean signedOnly;
|
||||
|
||||
public Message(Conversation parent, long id, String subject,
|
||||
String fromAddress, String replyTo,
|
||||
String data, boolean signedOnly) {
|
||||
this.parent = parent;
|
||||
this.id = id;
|
||||
this.subject = subject;
|
||||
this.fromAddress = fromAddress;
|
||||
this.replyTo = replyTo;
|
||||
this.data = data;
|
||||
if (this.replyTo == null || this.replyTo.equals("")) {
|
||||
this.replyTo = this.fromAddress;
|
||||
}
|
||||
this.signedOnly = signedOnly;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector<Conversation> mConversations;
|
||||
private Vector<Message> mMessages;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
BaseActivity.setLanguage(this, prefs.getLanguage());
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
mConversations = new Vector<Conversation>();
|
||||
mMessages = new Vector<Message>();
|
||||
|
||||
String account = getIntent().getExtras().getString(EXTRA_ACCOUNT);
|
||||
// TODO: what if account is null?
|
||||
Uri uri = Uri.parse("content://gmail-ls/conversations/" + account);
|
||||
Cursor cursor =
|
||||
managedQuery(uri, new String[] { "conversation_id", "subject" }, null, null, null);
|
||||
for (int i = 0; i < cursor.getCount(); ++i) {
|
||||
cursor.moveToPosition(i);
|
||||
|
||||
int idIndex = cursor.getColumnIndex("conversation_id");
|
||||
int subjectIndex = cursor.getColumnIndex("subject");
|
||||
long conversationId = cursor.getLong(idIndex);
|
||||
Conversation conversation =
|
||||
new Conversation(conversationId, cursor.getString(subjectIndex));
|
||||
Uri messageUri = Uri.withAppendedPath(uri, "" + conversationId + "/messages");
|
||||
Cursor messageCursor =
|
||||
managedQuery(messageUri, new String[] {
|
||||
"messageId",
|
||||
"subject",
|
||||
"fromAddress",
|
||||
"replyToAddresses",
|
||||
"body" }, null, null, null);
|
||||
Vector<Message> messages = new Vector<Message>();
|
||||
for (int j = 0; j < messageCursor.getCount(); ++j) {
|
||||
messageCursor.moveToPosition(j);
|
||||
idIndex = messageCursor.getColumnIndex("messageId");
|
||||
subjectIndex = messageCursor.getColumnIndex("subject");
|
||||
int fromAddressIndex = messageCursor.getColumnIndex("fromAddress");
|
||||
int replyToIndex = messageCursor.getColumnIndex("replyToAddresses");
|
||||
int bodyIndex = messageCursor.getColumnIndex("body");
|
||||
String data = messageCursor.getString(bodyIndex);
|
||||
data = Html.fromHtml(data).toString();
|
||||
boolean signedOnly = false;
|
||||
Matcher matcher = Apg.PGP_MESSAGE.matcher(data);
|
||||
if (matcher.matches()) {
|
||||
data = matcher.group(1);
|
||||
} else {
|
||||
matcher = Apg.PGP_SIGNED_MESSAGE.matcher(data);
|
||||
if (matcher.matches()) {
|
||||
data = matcher.group(1);
|
||||
signedOnly = true;
|
||||
} else {
|
||||
data = null;
|
||||
}
|
||||
}
|
||||
Message message =
|
||||
new Message(conversation,
|
||||
messageCursor.getLong(idIndex),
|
||||
messageCursor.getString(subjectIndex),
|
||||
messageCursor.getString(fromAddressIndex),
|
||||
messageCursor.getString(replyToIndex),
|
||||
data, signedOnly);
|
||||
|
||||
messages.add(message);
|
||||
mMessages.add(message);
|
||||
}
|
||||
conversation.messages = messages;
|
||||
mConversations.add(conversation);
|
||||
}
|
||||
|
||||
setListAdapter(new MailboxAdapter());
|
||||
getListView().setOnItemClickListener(new OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> arg0, View v, int position, long id) {
|
||||
Intent intent = new Intent(MailListActivity.this, DecryptActivity.class);
|
||||
intent.setAction(Apg.Intent.DECRYPT);
|
||||
Message message = (Message) ((MailboxAdapter) getListAdapter()).getItem(position);
|
||||
intent.putExtra(Apg.EXTRA_TEXT, message.data);
|
||||
intent.putExtra(Apg.EXTRA_SUBJECT, message.subject);
|
||||
intent.putExtra(Apg.EXTRA_REPLY_TO, message.replyTo);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class MailboxAdapter extends BaseAdapter implements ListAdapter {
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
Message message = (Message) getItem(position);
|
||||
return message.data != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return mMessages.size();
|
||||
}
|
||||
|
||||
public Object getItem(int position) {
|
||||
return mMessages.get(position);
|
||||
}
|
||||
|
||||
public long getItemId(int position) {
|
||||
return mMessages.get(position).id;
|
||||
}
|
||||
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View view = mInflater.inflate(R.layout.mailbox_message_item, null);
|
||||
|
||||
Message message = (Message) getItem(position);
|
||||
|
||||
TextView subject = (TextView) view.findViewById(R.id.subject);
|
||||
TextView email = (TextView) view.findViewById(R.id.emailAddress);
|
||||
ImageView status = (ImageView) view.findViewById(R.id.ic_status);
|
||||
|
||||
subject.setText(message.subject);
|
||||
email.setText(message.fromAddress);
|
||||
if (message.data != null) {
|
||||
if (message.signedOnly) {
|
||||
status.setImageResource(R.drawable.signed);
|
||||
} else {
|
||||
status.setImageResource(R.drawable.encrypted);
|
||||
}
|
||||
status.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
status.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
419
org_apg/src/org/apg/ui/MainActivity.java
Normal file
419
org_apg/src/org/apg/ui/MainActivity.java
Normal file
@@ -0,0 +1,419 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Id;
|
||||
import org.apg.Id.dialog;
|
||||
import org.apg.Id.menu;
|
||||
import org.apg.Id.menu.option;
|
||||
import org.apg.provider.Accounts;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.SQLException;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.util.Linkify;
|
||||
import android.text.util.Linkify.TransformFilter;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class MainActivity extends BaseActivity {
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
private ListView mAccounts = null;
|
||||
private AccountListAdapter mListAdapter = null;
|
||||
private Cursor mAccountCursor;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main);
|
||||
|
||||
Button encryptMessageButton = (Button) findViewById(R.id.btn_encryptMessage);
|
||||
Button decryptMessageButton = (Button) findViewById(R.id.btn_decryptMessage);
|
||||
Button encryptFileButton = (Button) findViewById(R.id.btn_encryptFile);
|
||||
Button decryptFileButton = (Button) findViewById(R.id.btn_decryptFile);
|
||||
mAccounts = (ListView) findViewById(R.id.accounts);
|
||||
|
||||
encryptMessageButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(MainActivity.this, EncryptActivity.class);
|
||||
intent.setAction(Apg.Intent.ENCRYPT);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
decryptMessageButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(MainActivity.this, DecryptActivity.class);
|
||||
intent.setAction(Apg.Intent.DECRYPT);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
encryptFileButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(MainActivity.this, EncryptActivity.class);
|
||||
intent.setAction(Apg.Intent.ENCRYPT_FILE);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
decryptFileButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(MainActivity.this, DecryptActivity.class);
|
||||
intent.setAction(Apg.Intent.DECRYPT_FILE);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
mAccountCursor =
|
||||
Apg.getDatabase().db().query(Accounts.TABLE_NAME,
|
||||
new String[] {
|
||||
Accounts._ID,
|
||||
Accounts.NAME,
|
||||
}, null, null, null, null, Accounts.NAME + " ASC");
|
||||
startManagingCursor(mAccountCursor);
|
||||
|
||||
mListAdapter = new AccountListAdapter(this, mAccountCursor);
|
||||
mAccounts.setAdapter(mListAdapter);
|
||||
mAccounts.setOnItemClickListener(new OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> arg0, View view, int index, long id) {
|
||||
String accountName = (String) mAccounts.getItemAtPosition(index);
|
||||
startActivity(new Intent(MainActivity.this, MailListActivity.class)
|
||||
.putExtra(MailListActivity.EXTRA_ACCOUNT, accountName));
|
||||
}
|
||||
});
|
||||
registerForContextMenu(mAccounts);
|
||||
|
||||
if (!mPreferences.hasSeenHelp()) {
|
||||
showDialog(Id.dialog.help);
|
||||
}
|
||||
|
||||
if (Apg.isReleaseVersion(this) && !mPreferences.hasSeenChangeLog(Apg.getVersion(this))) {
|
||||
showDialog(Id.dialog.change_log);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case Id.dialog.new_account: {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
alert.setTitle(R.string.title_addAccount);
|
||||
alert.setMessage(R.string.specifyGoogleMailAccount);
|
||||
|
||||
LayoutInflater inflater =
|
||||
(LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View view = inflater.inflate(R.layout.add_account_dialog, null);
|
||||
|
||||
final EditText input = (EditText) view.findViewById(R.id.input);
|
||||
alert.setView(view);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
MainActivity.this.removeDialog(Id.dialog.new_account);
|
||||
String accountName = "" + input.getText();
|
||||
|
||||
try {
|
||||
Cursor testCursor =
|
||||
managedQuery(Uri.parse("content://gmail-ls/conversations/" +
|
||||
accountName),
|
||||
null, null, null, null);
|
||||
if (testCursor == null) {
|
||||
Toast.makeText(MainActivity.this,
|
||||
getString(R.string.errorMessage,
|
||||
getString(R.string.error_accountNotFound,
|
||||
accountName)),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
Toast.makeText(MainActivity.this,
|
||||
getString(R.string.errorMessage,
|
||||
getString(R.string.error_accountReadingNotAllowed)),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Accounts.NAME, accountName);
|
||||
try {
|
||||
Apg.getDatabase().db().insert(Accounts.TABLE_NAME,
|
||||
Accounts.NAME, values);
|
||||
mAccountCursor.requery();
|
||||
mListAdapter.notifyDataSetChanged();
|
||||
} catch (SQLException e) {
|
||||
Toast.makeText(MainActivity.this,
|
||||
getString(R.string.errorMessage,
|
||||
getString(R.string.error_addingAccountFailed,
|
||||
accountName)),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
alert.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
MainActivity.this.removeDialog(Id.dialog.new_account);
|
||||
}
|
||||
});
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
case Id.dialog.change_log: {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
alert.setTitle("Changes " + Apg.getFullVersion(this));
|
||||
LayoutInflater inflater =
|
||||
(LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View layout = inflater.inflate(R.layout.info, null);
|
||||
TextView message = (TextView) layout.findViewById(R.id.message);
|
||||
|
||||
message.setText("Changes:\n" +
|
||||
"* \n" +
|
||||
"\n" +
|
||||
"WARNING: be careful editing your existing keys, as they " +
|
||||
"WILL be stripped of certificates right now.\n" +
|
||||
"\n" +
|
||||
"Also: key cross-certification is NOT supported, so signing " +
|
||||
"with those keys will get a warning when the signature is " +
|
||||
"checked.\n" +
|
||||
"\n" +
|
||||
"I hope APG continues to be useful to you, please send " +
|
||||
"bug reports, feature wishes, feedback.");
|
||||
alert.setView(layout);
|
||||
|
||||
alert.setCancelable(false);
|
||||
alert.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
MainActivity.this.removeDialog(Id.dialog.change_log);
|
||||
mPreferences.setHasSeenChangeLog(
|
||||
Apg.getVersion(MainActivity.this), true);
|
||||
}
|
||||
});
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
case Id.dialog.help: {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
||||
alert.setTitle(R.string.title_help);
|
||||
|
||||
LayoutInflater inflater =
|
||||
(LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View layout = inflater.inflate(R.layout.info, null);
|
||||
TextView message = (TextView) layout.findViewById(R.id.message);
|
||||
message.setText(R.string.text_help);
|
||||
|
||||
TransformFilter packageNames = new TransformFilter() {
|
||||
public final String transformUrl(final Matcher match, String url) {
|
||||
String name = match.group(1).toLowerCase();
|
||||
if (name.equals("astro")) {
|
||||
return "com.metago.astro";
|
||||
} else if (name.equals("k-9 mail")) {
|
||||
return "com.fsck.k9";
|
||||
} else {
|
||||
return "org.openintents.filemanager";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Pattern pattern = Pattern.compile("(OI File Manager|ASTRO|K-9 Mail)");
|
||||
String scheme = "market://search?q=pname:";
|
||||
message.setAutoLinkMask(0);
|
||||
Linkify.addLinks(message, pattern, scheme, null, packageNames);
|
||||
|
||||
alert.setView(layout);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
MainActivity.this.removeDialog(Id.dialog.help);
|
||||
mPreferences.setHasSeenHelp(true);
|
||||
}
|
||||
});
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, Id.menu.option.manage_public_keys, 0, R.string.menu_managePublicKeys)
|
||||
.setIcon(android.R.drawable.ic_menu_manage);
|
||||
menu.add(0, Id.menu.option.manage_secret_keys, 1, R.string.menu_manageSecretKeys)
|
||||
.setIcon(android.R.drawable.ic_menu_manage);
|
||||
menu.add(1, Id.menu.option.create, 2, R.string.menu_addAccount)
|
||||
.setIcon(android.R.drawable.ic_menu_add);
|
||||
menu.add(2, Id.menu.option.preferences, 3, R.string.menu_preferences)
|
||||
.setIcon(android.R.drawable.ic_menu_preferences);
|
||||
menu.add(2, Id.menu.option.key_server, 4, R.string.menu_keyServer)
|
||||
.setIcon(android.R.drawable.ic_menu_search);
|
||||
menu.add(3, Id.menu.option.about, 5, R.string.menu_about)
|
||||
.setIcon(android.R.drawable.ic_menu_info_details);
|
||||
menu.add(3, Id.menu.option.help, 6, R.string.menu_help)
|
||||
.setIcon(android.R.drawable.ic_menu_help);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case Id.menu.option.create: {
|
||||
showDialog(Id.dialog.new_account);
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.option.manage_public_keys: {
|
||||
startActivity(new Intent(this, PublicKeyListActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.option.manage_secret_keys: {
|
||||
startActivity(new Intent(this, SecretKeyListActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.option.help: {
|
||||
showDialog(Id.dialog.help);
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.option.key_server: {
|
||||
startActivity(new Intent(this, KeyServerQueryActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
|
||||
TextView nameTextView = (TextView) v.findViewById(R.id.accountName);
|
||||
if (nameTextView != null) {
|
||||
menu.setHeaderTitle(nameTextView.getText());
|
||||
menu.add(0, Id.menu.delete, 0, R.string.menu_deleteAccount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem) {
|
||||
AdapterView.AdapterContextMenuInfo info =
|
||||
(AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
|
||||
|
||||
switch (menuItem.getItemId()) {
|
||||
case Id.menu.delete: {
|
||||
Apg.getDatabase().db().delete(Accounts.TABLE_NAME,
|
||||
Accounts._ID + " = ?",
|
||||
new String[] { "" + info.id });
|
||||
mAccountCursor.requery();
|
||||
mListAdapter.notifyDataSetChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class AccountListAdapter extends CursorAdapter {
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
public AccountListAdapter(Context context, Cursor cursor) {
|
||||
super(context, cursor);
|
||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
Cursor c = getCursor();
|
||||
c.moveToPosition(position);
|
||||
return c.getString(c.getColumnIndex(Accounts.NAME));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return super.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.account_item, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
TextView nameTextView = (TextView) view.findViewById(R.id.accountName);
|
||||
int nameIndex = cursor.getColumnIndex(Accounts.NAME);
|
||||
final String account = cursor.getString(nameIndex);
|
||||
nameTextView.setText(account);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
274
org_apg/src/org/apg/ui/PreferencesActivity.java
Normal file
274
org_apg/src/org/apg/ui/PreferencesActivity.java
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Constants;
|
||||
import org.apg.Id;
|
||||
import org.apg.Preferences;
|
||||
import org.apg.Constants.pref;
|
||||
import org.apg.Id.choice;
|
||||
import org.apg.Id.request;
|
||||
import org.apg.Id.choice.compression;
|
||||
import org.apg.ui.widget.IntegerListPreference;
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.apg.R;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceScreen;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Vector;
|
||||
|
||||
public class PreferencesActivity extends PreferenceActivity {
|
||||
private ListPreference mLanguage = null;
|
||||
private IntegerListPreference mPassPhraseCacheTtl = null;
|
||||
private IntegerListPreference mEncryptionAlgorithm = null;
|
||||
private IntegerListPreference mHashAlgorithm = null;
|
||||
private IntegerListPreference mMessageCompression = null;
|
||||
private IntegerListPreference mFileCompression = null;
|
||||
private CheckBoxPreference mAsciiArmour = null;
|
||||
private CheckBoxPreference mForceV3Signatures = null;
|
||||
private PreferenceScreen mKeyServerPreference = null;
|
||||
private Preferences mPreferences;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
mPreferences = Preferences.getPreferences(this);
|
||||
BaseActivity.setLanguage(this, mPreferences.getLanguage());
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
addPreferencesFromResource(R.xml.apg_preferences);
|
||||
|
||||
mLanguage = (ListPreference) findPreference(Constants.pref.LANGUAGE);
|
||||
Vector<CharSequence> entryVector = new Vector<CharSequence>(Arrays.asList(mLanguage.getEntries()));
|
||||
Vector<CharSequence> entryValueVector = new Vector<CharSequence>(Arrays.asList(mLanguage.getEntryValues()));
|
||||
String supportedLanguages[] = getResources().getStringArray(R.array.supported_languages);
|
||||
HashSet<String> supportedLanguageSet = new HashSet<String>(Arrays.asList(supportedLanguages));
|
||||
for (int i = entryVector.size() - 1; i > -1; --i)
|
||||
{
|
||||
if (!supportedLanguageSet.contains(entryValueVector.get(i)))
|
||||
{
|
||||
entryVector.remove(i);
|
||||
entryValueVector.remove(i);
|
||||
}
|
||||
}
|
||||
CharSequence dummy[] = new CharSequence[0];
|
||||
mLanguage.setEntries(entryVector.toArray(dummy));
|
||||
mLanguage.setEntryValues(entryValueVector.toArray(dummy));
|
||||
mLanguage.setValue(mPreferences.getLanguage());
|
||||
mLanguage.setSummary(mLanguage.getEntry());
|
||||
mLanguage.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
|
||||
{
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue)
|
||||
{
|
||||
mLanguage.setValue(newValue.toString());
|
||||
mLanguage.setSummary(mLanguage.getEntry());
|
||||
mPreferences.setLanguage(newValue.toString());
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mPassPhraseCacheTtl = (IntegerListPreference) findPreference(Constants.pref.PASS_PHRASE_CACHE_TTL);
|
||||
mPassPhraseCacheTtl.setValue("" + mPreferences.getPassPhraseCacheTtl());
|
||||
mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry());
|
||||
mPassPhraseCacheTtl.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
|
||||
{
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue)
|
||||
{
|
||||
mPassPhraseCacheTtl.setValue(newValue.toString());
|
||||
mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry());
|
||||
mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString()));
|
||||
BaseActivity.startCacheService(PreferencesActivity.this, mPreferences);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mEncryptionAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM);
|
||||
int valueIds[] = {
|
||||
PGPEncryptedData.AES_128, PGPEncryptedData.AES_192, PGPEncryptedData.AES_256,
|
||||
PGPEncryptedData.BLOWFISH, PGPEncryptedData.TWOFISH, PGPEncryptedData.CAST5,
|
||||
PGPEncryptedData.DES, PGPEncryptedData.TRIPLE_DES, PGPEncryptedData.IDEA,
|
||||
};
|
||||
String entries[] = {
|
||||
"AES-128", "AES-192", "AES-256",
|
||||
"Blowfish", "Twofish", "CAST5",
|
||||
"DES", "Triple DES", "IDEA",
|
||||
};
|
||||
String values[] = new String[valueIds.length];
|
||||
for (int i = 0; i < values.length; ++i) {
|
||||
values[i] = "" + valueIds[i];
|
||||
}
|
||||
mEncryptionAlgorithm.setEntries(entries);
|
||||
mEncryptionAlgorithm.setEntryValues(values);
|
||||
mEncryptionAlgorithm.setValue("" + mPreferences.getDefaultEncryptionAlgorithm());
|
||||
mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry());
|
||||
mEncryptionAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
|
||||
{
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue)
|
||||
{
|
||||
mEncryptionAlgorithm.setValue(newValue.toString());
|
||||
mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry());
|
||||
mPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mHashAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_HASH_ALGORITHM);
|
||||
valueIds = new int[] {
|
||||
HashAlgorithmTags.MD5, HashAlgorithmTags.RIPEMD160, HashAlgorithmTags.SHA1,
|
||||
HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256, HashAlgorithmTags.SHA384,
|
||||
HashAlgorithmTags.SHA512,
|
||||
};
|
||||
entries = new String[] {
|
||||
"MD5", "RIPEMD-160", "SHA-1",
|
||||
"SHA-224", "SHA-256", "SHA-384",
|
||||
"SHA-512",
|
||||
};
|
||||
values = new String[valueIds.length];
|
||||
for (int i = 0; i < values.length; ++i) {
|
||||
values[i] = "" + valueIds[i];
|
||||
}
|
||||
mHashAlgorithm.setEntries(entries);
|
||||
mHashAlgorithm.setEntryValues(values);
|
||||
mHashAlgorithm.setValue("" + mPreferences.getDefaultHashAlgorithm());
|
||||
mHashAlgorithm.setSummary(mHashAlgorithm.getEntry());
|
||||
mHashAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
|
||||
{
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue)
|
||||
{
|
||||
mHashAlgorithm.setValue(newValue.toString());
|
||||
mHashAlgorithm.setSummary(mHashAlgorithm.getEntry());
|
||||
mPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mMessageCompression = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_MESSAGE_COMPRESSION);
|
||||
valueIds = new int[] {
|
||||
Id.choice.compression.none,
|
||||
Id.choice.compression.zip,
|
||||
Id.choice.compression.zlib,
|
||||
Id.choice.compression.bzip2,
|
||||
};
|
||||
entries = new String[] {
|
||||
getString(R.string.choice_none) + " (" + getString(R.string.fast) + ")",
|
||||
"ZIP (" + getString(R.string.fast) + ")",
|
||||
"ZLIB (" + getString(R.string.fast) + ")",
|
||||
"BZIP2 (" + getString(R.string.very_slow) + ")",
|
||||
};
|
||||
values = new String[valueIds.length];
|
||||
for (int i = 0; i < values.length; ++i) {
|
||||
values[i] = "" + valueIds[i];
|
||||
}
|
||||
mMessageCompression.setEntries(entries);
|
||||
mMessageCompression.setEntryValues(values);
|
||||
mMessageCompression.setValue("" + mPreferences.getDefaultMessageCompression());
|
||||
mMessageCompression.setSummary(mMessageCompression.getEntry());
|
||||
mMessageCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
|
||||
{
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue)
|
||||
{
|
||||
mMessageCompression.setValue(newValue.toString());
|
||||
mMessageCompression.setSummary(mMessageCompression.getEntry());
|
||||
mPreferences.setDefaultMessageCompression(Integer.parseInt(newValue.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mFileCompression = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_FILE_COMPRESSION);
|
||||
mFileCompression.setEntries(entries);
|
||||
mFileCompression.setEntryValues(values);
|
||||
mFileCompression.setValue("" + mPreferences.getDefaultFileCompression());
|
||||
mFileCompression.setSummary(mFileCompression.getEntry());
|
||||
mFileCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
|
||||
{
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue)
|
||||
{
|
||||
mFileCompression.setValue(newValue.toString());
|
||||
mFileCompression.setSummary(mFileCompression.getEntry());
|
||||
mPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mAsciiArmour = (CheckBoxPreference) findPreference(Constants.pref.DEFAULT_ASCII_ARMOUR);
|
||||
mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour());
|
||||
mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
|
||||
{
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue)
|
||||
{
|
||||
mAsciiArmour.setChecked((Boolean)newValue);
|
||||
mPreferences.setDefaultAsciiArmour((Boolean)newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mForceV3Signatures = (CheckBoxPreference) findPreference(Constants.pref.FORCE_V3_SIGNATURES);
|
||||
mForceV3Signatures.setChecked(mPreferences.getForceV3Signatures());
|
||||
mForceV3Signatures.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
|
||||
{
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue)
|
||||
{
|
||||
mForceV3Signatures.setChecked((Boolean)newValue);
|
||||
mPreferences.setForceV3Signatures((Boolean)newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mKeyServerPreference = (PreferenceScreen) findPreference(Constants.pref.KEY_SERVERS);
|
||||
String servers[] = mPreferences.getKeyServers();
|
||||
mKeyServerPreference.setSummary(getResources().getString(R.string.nKeyServers, servers.length));
|
||||
mKeyServerPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent intent = new Intent(PreferencesActivity.this,
|
||||
KeyServerPreferenceActivity.class);
|
||||
intent.putExtra(Apg.EXTRA_KEY_SERVERS, mPreferences.getKeyServers());
|
||||
startActivityForResult(intent, Id.request.key_server_preference);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.key_server_preference: {
|
||||
if (resultCode == RESULT_CANCELED || data == null) {
|
||||
return;
|
||||
}
|
||||
String servers[] = data.getStringArrayExtra(Apg.EXTRA_KEY_SERVERS);
|
||||
mPreferences.setKeyServers(servers);
|
||||
mKeyServerPreference.setSummary(getResources().getString(R.string.nKeyServers, servers.length));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
191
org_apg/src/org/apg/ui/PublicKeyListActivity.java
Normal file
191
org_apg/src/org/apg/ui/PublicKeyListActivity.java
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Constants;
|
||||
import org.apg.Id;
|
||||
import org.apg.Constants.path;
|
||||
import org.apg.Id.menu;
|
||||
import org.apg.Id.request;
|
||||
import org.apg.Id.type;
|
||||
import org.apg.Id.menu.option;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.apg.R;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ExpandableListView;
|
||||
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
|
||||
|
||||
public class PublicKeyListActivity extends KeyListActivity {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
mExportFilename = Constants.path.APP_DIR + "/pubexport.asc";
|
||||
mKeyType = Id.type.public_key;
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, Id.menu.option.import_keys, 0, R.string.menu_importKeys).setIcon(
|
||||
android.R.drawable.ic_menu_add);
|
||||
menu.add(0, Id.menu.option.export_keys, 1, R.string.menu_exportKeys).setIcon(
|
||||
android.R.drawable.ic_menu_save);
|
||||
menu.add(1, Id.menu.option.search, 2, R.string.menu_search).setIcon(
|
||||
android.R.drawable.ic_menu_search);
|
||||
menu.add(1, Id.menu.option.preferences, 3, R.string.menu_preferences).setIcon(
|
||||
android.R.drawable.ic_menu_preferences);
|
||||
menu.add(1, Id.menu.option.about, 4, R.string.menu_about).setIcon(
|
||||
android.R.drawable.ic_menu_info_details);
|
||||
menu.add(1, Id.menu.option.scanQRCode, 5, R.string.menu_scanQRCode).setIcon(
|
||||
android.R.drawable.ic_menu_add);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
|
||||
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
|
||||
|
||||
if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
|
||||
// TODO: user id? menu.setHeaderTitle("Key");
|
||||
menu.add(0, Id.menu.export, 0, R.string.menu_exportKey);
|
||||
menu.add(0, Id.menu.delete, 1, R.string.menu_deleteKey);
|
||||
menu.add(0, Id.menu.update, 1, R.string.menu_updateKey);
|
||||
menu.add(0, Id.menu.exportToServer, 1, R.string.menu_exportKeyToServer);
|
||||
menu.add(0, Id.menu.signKey, 1, R.string.menu_signKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem) {
|
||||
ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo();
|
||||
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
|
||||
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
|
||||
|
||||
if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
|
||||
switch (menuItem.getItemId()) {
|
||||
case Id.menu.update: {
|
||||
mSelectedItem = groupPosition;
|
||||
final int keyRingId = mListAdapter.getKeyRingId(groupPosition);
|
||||
long keyId = 0;
|
||||
Object keyRing = Apg.getKeyRing(keyRingId);
|
||||
if (keyRing != null && keyRing instanceof PGPPublicKeyRing) {
|
||||
keyId = Apg.getMasterKey((PGPPublicKeyRing) keyRing).getKeyID();
|
||||
}
|
||||
if (keyId == 0) {
|
||||
// this shouldn't happen
|
||||
return true;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, KeyServerQueryActivity.class);
|
||||
intent.setAction(Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN);
|
||||
intent.putExtra(Apg.EXTRA_KEY_ID, keyId);
|
||||
startActivityForResult(intent, Id.request.look_up_key_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.exportToServer: {
|
||||
mSelectedItem = groupPosition;
|
||||
final int keyRingId = mListAdapter.getKeyRingId(groupPosition);
|
||||
|
||||
Intent intent = new Intent(this, SendKeyActivity.class);
|
||||
intent.setAction(Apg.Intent.EXPORT_KEY_TO_SERVER);
|
||||
intent.putExtra(Apg.EXTRA_KEY_ID, keyRingId);
|
||||
startActivityForResult(intent, Id.request.export_to_server);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.signKey: {
|
||||
mSelectedItem = groupPosition;
|
||||
final int keyRingId = mListAdapter.getKeyRingId(groupPosition);
|
||||
long keyId = 0;
|
||||
Object keyRing = Apg.getKeyRing(keyRingId);
|
||||
if (keyRing != null && keyRing instanceof PGPPublicKeyRing) {
|
||||
keyId = Apg.getMasterKey((PGPPublicKeyRing) keyRing).getKeyID();
|
||||
}
|
||||
|
||||
if (keyId == 0) {
|
||||
// this shouldn't happen
|
||||
return true;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, SignKeyActivity.class);
|
||||
intent.putExtra(Apg.EXTRA_KEY_ID, keyId);
|
||||
startActivity(intent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case Id.menu.option.scanQRCode: {
|
||||
Intent intent = new Intent(this, ImportFromQRCodeActivity.class);
|
||||
intent.setAction(Apg.Intent.IMPORT_FROM_QR_CODE);
|
||||
startActivityForResult(intent, Id.request.import_from_qr_code);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.look_up_key_id: {
|
||||
if (resultCode == RESULT_CANCELED || data == null
|
||||
|| data.getStringExtra(Apg.EXTRA_TEXT) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, PublicKeyListActivity.class);
|
||||
intent.setAction(Apg.Intent.IMPORT);
|
||||
intent.putExtra(Apg.EXTRA_TEXT, data.getStringExtra(Apg.EXTRA_TEXT));
|
||||
handleIntent(intent);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
203
org_apg/src/org/apg/ui/SecretKeyListActivity.java
Normal file
203
org_apg/src/org/apg/ui/SecretKeyListActivity.java
Normal file
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.AskForSecretKeyPassPhrase;
|
||||
import org.apg.Constants;
|
||||
import org.apg.Id;
|
||||
import org.apg.Constants.path;
|
||||
import org.apg.Id.dialog;
|
||||
import org.apg.Id.menu;
|
||||
import org.apg.Id.message;
|
||||
import org.apg.Id.type;
|
||||
import org.apg.Id.menu.option;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ExpandableListView;
|
||||
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
|
||||
import android.widget.ExpandableListView.OnChildClickListener;
|
||||
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
|
||||
public class SecretKeyListActivity extends KeyListActivity implements OnChildClickListener {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
mExportFilename = Constants.path.APP_DIR + "/secexport.asc";
|
||||
mKeyType = Id.type.secret_key;
|
||||
super.onCreate(savedInstanceState);
|
||||
mList.setOnChildClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, Id.menu.option.import_keys, 0, R.string.menu_importKeys)
|
||||
.setIcon(android.R.drawable.ic_menu_add);
|
||||
menu.add(0, Id.menu.option.export_keys, 1, R.string.menu_exportKeys)
|
||||
.setIcon(android.R.drawable.ic_menu_save);
|
||||
menu.add(1, Id.menu.option.create, 2, R.string.menu_createKey)
|
||||
.setIcon(android.R.drawable.ic_menu_add);
|
||||
menu.add(3, Id.menu.option.search, 3, R.string.menu_search)
|
||||
.setIcon(android.R.drawable.ic_menu_search);
|
||||
menu.add(3, Id.menu.option.preferences, 4, R.string.menu_preferences)
|
||||
.setIcon(android.R.drawable.ic_menu_preferences);
|
||||
menu.add(3, Id.menu.option.about, 5, R.string.menu_about)
|
||||
.setIcon(android.R.drawable.ic_menu_info_details);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case Id.menu.option.create: {
|
||||
createKey();
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
ExpandableListView.ExpandableListContextMenuInfo info =
|
||||
(ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
|
||||
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
|
||||
|
||||
if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
|
||||
// TODO: user id? menu.setHeaderTitle("Key");
|
||||
menu.add(0, Id.menu.edit, 0, R.string.menu_editKey);
|
||||
menu.add(0, Id.menu.export, 1, R.string.menu_exportKey);
|
||||
menu.add(0, Id.menu.delete, 2, R.string.menu_deleteKey);
|
||||
menu.add(0, Id.menu.share, 2, R.string.menu_share);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem) {
|
||||
ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo();
|
||||
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
|
||||
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
|
||||
|
||||
if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
|
||||
switch (menuItem.getItemId()) {
|
||||
case Id.menu.edit: {
|
||||
mSelectedItem = groupPosition;
|
||||
checkPassPhraseAndEdit();
|
||||
return true;
|
||||
}
|
||||
|
||||
case Id.menu.share: {
|
||||
mSelectedItem = groupPosition;
|
||||
|
||||
long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem);
|
||||
String msg = keyId + "," + Apg.getFingerPrint(keyId);;
|
||||
|
||||
new IntentIntegrator(this).shareText(msg);
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
|
||||
int childPosition, long id) {
|
||||
mSelectedItem = groupPosition;
|
||||
checkPassPhraseAndEdit();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case Id.dialog.pass_phrase: {
|
||||
long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem);
|
||||
return AskForSecretKeyPassPhrase.createDialog(this, keyId, this);
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void checkPassPhraseAndEdit() {
|
||||
long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem);
|
||||
String passPhrase = Apg.getCachedPassPhrase(keyId);
|
||||
if (passPhrase == null) {
|
||||
showDialog(Id.dialog.pass_phrase);
|
||||
} else {
|
||||
Apg.setEditPassPhrase(passPhrase);
|
||||
editKey();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passPhraseCallback(long keyId, String passPhrase) {
|
||||
super.passPhraseCallback(keyId, passPhrase);
|
||||
Apg.setEditPassPhrase(passPhrase);
|
||||
editKey();
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
Apg.setEditPassPhrase("");
|
||||
Intent intent = new Intent(this, EditKeyActivity.class);
|
||||
startActivityForResult(intent, Id.message.create_key);
|
||||
}
|
||||
|
||||
private void editKey() {
|
||||
long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem);
|
||||
Intent intent = new Intent(this, EditKeyActivity.class);
|
||||
intent.putExtra(Apg.EXTRA_KEY_ID, keyId);
|
||||
startActivityForResult(intent, Id.message.edit_key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.message.create_key: // intentionally no break
|
||||
case Id.message.edit_key: {
|
||||
if (resultCode == RESULT_OK) {
|
||||
refreshList();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
172
org_apg/src/org/apg/ui/SelectPublicKeyListActivity.java
Normal file
172
org_apg/src/org/apg/ui/SelectPublicKeyListActivity.java
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Id;
|
||||
import org.apg.Id.menu;
|
||||
import org.apg.Id.menu.option;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class SelectPublicKeyListActivity extends BaseActivity {
|
||||
protected ListView mList;
|
||||
protected SelectPublicKeyListAdapter mListAdapter;
|
||||
protected View mFilterLayout;
|
||||
protected Button mClearFilterButton;
|
||||
protected TextView mFilterInfo;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.select_public_key);
|
||||
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
mList = (ListView) findViewById(R.id.list);
|
||||
// needed in Android 1.5, where the XML attribute gets ignored
|
||||
mList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||
|
||||
Button okButton = (Button) findViewById(R.id.btn_ok);
|
||||
okButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
okClicked();
|
||||
}
|
||||
});
|
||||
|
||||
Button cancelButton = (Button) findViewById(R.id.btn_cancel);
|
||||
cancelButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
cancelClicked();
|
||||
}
|
||||
});
|
||||
|
||||
mFilterLayout = findViewById(R.id.layout_filter);
|
||||
mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo);
|
||||
mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear);
|
||||
|
||||
mClearFilterButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
handleIntent(new Intent());
|
||||
}
|
||||
});
|
||||
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
String searchString = null;
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
searchString = intent.getStringExtra(SearchManager.QUERY);
|
||||
if (searchString != null && searchString.trim().length() == 0) {
|
||||
searchString = null;
|
||||
}
|
||||
}
|
||||
|
||||
long selectedKeyIds[] = null;
|
||||
selectedKeyIds = intent.getLongArrayExtra(Apg.EXTRA_SELECTION);
|
||||
|
||||
if (selectedKeyIds == null) {
|
||||
Vector<Long> vector = new Vector<Long>();
|
||||
for (int i = 0; i < mList.getCount(); ++i) {
|
||||
if (mList.isItemChecked(i)) {
|
||||
vector.add(mList.getItemIdAtPosition(i));
|
||||
}
|
||||
}
|
||||
selectedKeyIds = new long[vector.size()];
|
||||
for (int i = 0; i < vector.size(); ++i) {
|
||||
selectedKeyIds[i] = vector.get(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (searchString == null) {
|
||||
mFilterLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
mFilterLayout.setVisibility(View.VISIBLE);
|
||||
mFilterInfo.setText(getString(R.string.filterInfo, searchString));
|
||||
}
|
||||
|
||||
if (mListAdapter != null) {
|
||||
mListAdapter.cleanup();
|
||||
}
|
||||
|
||||
mListAdapter = new SelectPublicKeyListAdapter(this, mList, searchString, selectedKeyIds);
|
||||
mList.setAdapter(mListAdapter);
|
||||
|
||||
if (selectedKeyIds != null) {
|
||||
for (int i = 0; i < mListAdapter.getCount(); ++i) {
|
||||
long keyId = mListAdapter.getItemId(i);
|
||||
for (int j = 0; j < selectedKeyIds.length; ++j) {
|
||||
if (keyId == selectedKeyIds[j]) {
|
||||
mList.setItemChecked(i, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelClicked() {
|
||||
setResult(RESULT_CANCELED, null);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void okClicked() {
|
||||
Intent data = new Intent();
|
||||
Vector<Long> keys = new Vector<Long>();
|
||||
Vector<String> userIds = new Vector<String>();
|
||||
for (int i = 0; i < mList.getCount(); ++i) {
|
||||
if (mList.isItemChecked(i)) {
|
||||
keys.add(mList.getItemIdAtPosition(i));
|
||||
userIds.add((String) mList.getItemAtPosition(i));
|
||||
}
|
||||
}
|
||||
long selectedKeyIds[] = new long[keys.size()];
|
||||
for (int i = 0; i < keys.size(); ++i) {
|
||||
selectedKeyIds[i] = keys.get(i);
|
||||
}
|
||||
String userIdArray[] = new String[0];
|
||||
data.putExtra(Apg.EXTRA_SELECTION, selectedKeyIds);
|
||||
data.putExtra(Apg.EXTRA_USER_IDS, userIds.toArray(userIdArray));
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, Id.menu.option.search, 0, R.string.menu_search)
|
||||
.setIcon(android.R.drawable.ic_menu_search);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
226
org_apg/src/org/apg/ui/SelectPublicKeyListAdapter.java
Normal file
226
org_apg/src/org/apg/ui/SelectPublicKeyListAdapter.java
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Id;
|
||||
import org.apg.Id.database;
|
||||
import org.apg.provider.KeyRings;
|
||||
import org.apg.provider.Keys;
|
||||
import org.apg.provider.UserIds;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class SelectPublicKeyListAdapter extends BaseAdapter {
|
||||
protected LayoutInflater mInflater;
|
||||
protected ListView mParent;
|
||||
protected SQLiteDatabase mDatabase;
|
||||
protected Cursor mCursor;
|
||||
protected String mSearchString;
|
||||
protected Activity mActivity;
|
||||
|
||||
public SelectPublicKeyListAdapter(Activity activity, ListView parent,
|
||||
String searchString, long selectedKeyIds[]) {
|
||||
mSearchString = searchString;
|
||||
|
||||
mActivity = activity;
|
||||
mParent = parent;
|
||||
mDatabase = Apg.getDatabase().db();
|
||||
mInflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
long now = new Date().getTime() / 1000;
|
||||
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
|
||||
qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " +
|
||||
"(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
|
||||
Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " +
|
||||
Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" +
|
||||
") " +
|
||||
" INNER JOIN " + UserIds.TABLE_NAME + " ON " +
|
||||
"(" + Keys.TABLE_NAME + "." + Keys._ID + " = " +
|
||||
UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " +
|
||||
UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') ");
|
||||
|
||||
String inIdList = null;
|
||||
|
||||
if (selectedKeyIds != null && selectedKeyIds.length > 0) {
|
||||
inIdList = KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " IN (";
|
||||
for (int i = 0; i < selectedKeyIds.length; ++i) {
|
||||
if (i != 0) {
|
||||
inIdList += ", ";
|
||||
}
|
||||
inIdList += DatabaseUtils.sqlEscapeString("" + selectedKeyIds[i]);
|
||||
}
|
||||
inIdList += ")";
|
||||
}
|
||||
|
||||
if (searchString != null && searchString.trim().length() > 0) {
|
||||
String[] chunks = searchString.trim().split(" +");
|
||||
qb.appendWhere("(EXISTS (SELECT tmp." + UserIds._ID + " FROM " +
|
||||
UserIds.TABLE_NAME + " AS tmp WHERE " +
|
||||
"tmp." + UserIds.KEY_ID + " = " +
|
||||
Keys.TABLE_NAME + "." + Keys._ID);
|
||||
for (int i = 0; i < chunks.length; ++i) {
|
||||
qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE ");
|
||||
qb.appendWhereEscapeString("%" + chunks[i] + "%");
|
||||
}
|
||||
qb.appendWhere("))");
|
||||
|
||||
if (inIdList != null) {
|
||||
qb.appendWhere(" OR (" + inIdList + ")");
|
||||
}
|
||||
}
|
||||
|
||||
String orderBy = UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC";
|
||||
if (inIdList != null) {
|
||||
orderBy = inIdList + " DESC, " + orderBy;
|
||||
}
|
||||
|
||||
mCursor = qb.query(mDatabase,
|
||||
new String[] {
|
||||
KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0
|
||||
KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1
|
||||
UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2
|
||||
"(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " +
|
||||
"tmp." + Keys.KEY_RING_ID + " = " +
|
||||
KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " +
|
||||
"tmp." + Keys.IS_REVOKED + " = '0' AND " +
|
||||
"tmp." + Keys.CAN_ENCRYPT + " = '1')", // 3
|
||||
"(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " +
|
||||
"tmp." + Keys.KEY_RING_ID + " = " +
|
||||
KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " +
|
||||
"tmp." + Keys.IS_REVOKED + " = '0' AND " +
|
||||
"tmp." + Keys.CAN_ENCRYPT + " = '1' AND " +
|
||||
"tmp." + Keys.CREATION + " <= '" + now + "' AND " +
|
||||
"(tmp." + Keys.EXPIRY + " IS NULL OR " +
|
||||
"tmp." + Keys.EXPIRY + " >= '" + now + "'))", // 4
|
||||
},
|
||||
KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?",
|
||||
new String[] { "" + Id.database.type_public },
|
||||
null, null, orderBy);
|
||||
|
||||
activity.startManagingCursor(mCursor);
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
if (mCursor != null) {
|
||||
mActivity.stopManagingCursor(mCursor);
|
||||
mCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getInt(4) > 0; // valid CAN_ENCRYPT
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return mCursor.getCount();
|
||||
}
|
||||
|
||||
public Object getItem(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getString(2); // USER_ID
|
||||
}
|
||||
|
||||
public long getItemId(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getLong(1); // MASTER_KEY_ID
|
||||
}
|
||||
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
mCursor.moveToPosition(position);
|
||||
|
||||
View view = mInflater.inflate(R.layout.select_public_key_item, null);
|
||||
boolean enabled = isEnabled(position);
|
||||
|
||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mainUserId.setText(R.string.unknownUserId);
|
||||
TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
mainUserIdRest.setText("");
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
keyId.setText(R.string.noKey);
|
||||
TextView status = (TextView) view.findViewById(R.id.status);
|
||||
status.setText(R.string.unknownStatus);
|
||||
|
||||
String userId = mCursor.getString(2); // USER_ID
|
||||
if (userId != null) {
|
||||
String chunks[] = userId.split(" <", 2);
|
||||
userId = chunks[0];
|
||||
if (chunks.length > 1) {
|
||||
mainUserIdRest.setText("<" + chunks[1]);
|
||||
}
|
||||
mainUserId.setText(userId);
|
||||
}
|
||||
|
||||
long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID
|
||||
keyId.setText(Apg.getSmallFingerPrint(masterKeyId));
|
||||
|
||||
if (mainUserIdRest.getText().length() == 0) {
|
||||
mainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
status.setText(R.string.canEncrypt);
|
||||
} else {
|
||||
if (mCursor.getInt(3) > 0) {
|
||||
// has some CAN_ENCRYPT keys, but col(4) = 0, so must be revoked or expired
|
||||
status.setText(R.string.expired);
|
||||
} else {
|
||||
status.setText(R.string.noKey);
|
||||
}
|
||||
}
|
||||
|
||||
status.setText(status.getText() + " ");
|
||||
|
||||
CheckBox selected = (CheckBox) view.findViewById(R.id.selected);
|
||||
|
||||
if (!enabled) {
|
||||
mParent.setItemChecked(position, false);
|
||||
}
|
||||
|
||||
selected.setChecked(mParent.isItemChecked(position));
|
||||
|
||||
view.setEnabled(enabled);
|
||||
mainUserId.setEnabled(enabled);
|
||||
mainUserIdRest.setEnabled(enabled);
|
||||
keyId.setEnabled(enabled);
|
||||
selected.setEnabled(enabled);
|
||||
status.setEnabled(enabled);
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
115
org_apg/src/org/apg/ui/SelectSecretKeyListActivity.java
Normal file
115
org_apg/src/org/apg/ui/SelectSecretKeyListActivity.java
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Id;
|
||||
import org.apg.Id.menu;
|
||||
import org.apg.Id.menu.option;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class SelectSecretKeyListActivity extends BaseActivity {
|
||||
protected ListView mList;
|
||||
protected SelectSecretKeyListAdapter mListAdapter;
|
||||
protected View mFilterLayout;
|
||||
protected Button mClearFilterButton;
|
||||
protected TextView mFilterInfo;
|
||||
|
||||
protected long mSelectedKeyId = 0;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
setContentView(R.layout.select_secret_key);
|
||||
|
||||
mList = (ListView) findViewById(R.id.list);
|
||||
|
||||
mList.setOnItemClickListener(new OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
Intent data = new Intent();
|
||||
data.putExtra(Apg.EXTRA_KEY_ID, id);
|
||||
data.putExtra(Apg.EXTRA_USER_ID, (String)mList.getItemAtPosition(position));
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
mFilterLayout = findViewById(R.id.layout_filter);
|
||||
mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo);
|
||||
mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear);
|
||||
|
||||
mClearFilterButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
handleIntent(new Intent());
|
||||
}
|
||||
});
|
||||
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
String searchString = null;
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
searchString = intent.getStringExtra(SearchManager.QUERY);
|
||||
if (searchString != null && searchString.trim().length() == 0) {
|
||||
searchString = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (searchString == null) {
|
||||
mFilterLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
mFilterLayout.setVisibility(View.VISIBLE);
|
||||
mFilterInfo.setText(getString(R.string.filterInfo, searchString));
|
||||
}
|
||||
|
||||
if (mListAdapter != null) {
|
||||
mListAdapter.cleanup();
|
||||
}
|
||||
|
||||
mListAdapter = new SelectSecretKeyListAdapter(this, mList, searchString);
|
||||
mList.setAdapter(mListAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, Id.menu.option.search, 0, R.string.menu_search)
|
||||
.setIcon(android.R.drawable.ic_menu_search);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
176
org_apg/src/org/apg/ui/SelectSecretKeyListAdapter.java
Normal file
176
org_apg/src/org/apg/ui/SelectSecretKeyListAdapter.java
Normal file
@@ -0,0 +1,176 @@
|
||||
package org.apg.ui;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Id;
|
||||
import org.apg.Id.database;
|
||||
import org.apg.provider.KeyRings;
|
||||
import org.apg.provider.Keys;
|
||||
import org.apg.provider.UserIds;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class SelectSecretKeyListAdapter extends BaseAdapter {
|
||||
protected LayoutInflater mInflater;
|
||||
protected ListView mParent;
|
||||
protected SQLiteDatabase mDatabase;
|
||||
protected Cursor mCursor;
|
||||
protected String mSearchString;
|
||||
protected Activity mActivity;
|
||||
|
||||
public SelectSecretKeyListAdapter(Activity activity, ListView parent, String searchString) {
|
||||
mSearchString = searchString;
|
||||
|
||||
mActivity = activity;
|
||||
mParent = parent;
|
||||
mDatabase = Apg.getDatabase().db();
|
||||
mInflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
long now = new Date().getTime() / 1000;
|
||||
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
|
||||
qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " +
|
||||
"(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
|
||||
Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " +
|
||||
Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" +
|
||||
") " +
|
||||
" INNER JOIN " + UserIds.TABLE_NAME + " ON " +
|
||||
"(" + Keys.TABLE_NAME + "." + Keys._ID + " = " +
|
||||
UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " +
|
||||
UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') ");
|
||||
|
||||
if (searchString != null && searchString.trim().length() > 0) {
|
||||
String[] chunks = searchString.trim().split(" +");
|
||||
qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " +
|
||||
UserIds.TABLE_NAME + " AS tmp WHERE " +
|
||||
"tmp." + UserIds.KEY_ID + " = " +
|
||||
Keys.TABLE_NAME + "." + Keys._ID);
|
||||
for (int i = 0; i < chunks.length; ++i) {
|
||||
qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE ");
|
||||
qb.appendWhereEscapeString("%" + chunks[i] + "%");
|
||||
}
|
||||
qb.appendWhere(")");
|
||||
}
|
||||
|
||||
mCursor = qb.query(mDatabase,
|
||||
new String[] {
|
||||
KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0
|
||||
KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1
|
||||
UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2
|
||||
"(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " +
|
||||
"tmp." + Keys.KEY_RING_ID + " = " +
|
||||
KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " +
|
||||
"tmp." + Keys.IS_REVOKED + " = '0' AND " +
|
||||
"tmp." + Keys.CAN_SIGN + " = '1')", // 3,
|
||||
"(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " +
|
||||
"tmp." + Keys.KEY_RING_ID + " = " +
|
||||
KeyRings.TABLE_NAME + "." + KeyRings._ID + " AND " +
|
||||
"tmp." + Keys.IS_REVOKED + " = '0' AND " +
|
||||
"tmp." + Keys.CAN_SIGN + " = '1' AND " +
|
||||
"tmp." + Keys.CREATION + " <= '" + now + "' AND " +
|
||||
"(tmp." + Keys.EXPIRY + " IS NULL OR " +
|
||||
"tmp." + Keys.EXPIRY + " >= '" + now + "'))", // 4
|
||||
},
|
||||
KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?",
|
||||
new String[] { "" + Id.database.type_secret },
|
||||
null, null, UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC");
|
||||
|
||||
activity.startManagingCursor(mCursor);
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
if (mCursor != null) {
|
||||
mActivity.stopManagingCursor(mCursor);
|
||||
mCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getInt(4) > 0; // valid CAN_SIGN
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return mCursor.getCount();
|
||||
}
|
||||
|
||||
public Object getItem(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getString(2); // USER_ID
|
||||
}
|
||||
|
||||
public long getItemId(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getLong(1); // MASTER_KEY_ID
|
||||
}
|
||||
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
mCursor.moveToPosition(position);
|
||||
|
||||
View view = mInflater.inflate(R.layout.select_secret_key_item, null);
|
||||
boolean enabled = isEnabled(position);
|
||||
|
||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mainUserId.setText(R.string.unknownUserId);
|
||||
TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
mainUserIdRest.setText("");
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
keyId.setText(R.string.noKey);
|
||||
TextView status = (TextView) view.findViewById(R.id.status);
|
||||
status.setText(R.string.unknownStatus);
|
||||
|
||||
String userId = mCursor.getString(2); // USER_ID
|
||||
if (userId != null) {
|
||||
String chunks[] = userId.split(" <", 2);
|
||||
userId = chunks[0];
|
||||
if (chunks.length > 1) {
|
||||
mainUserIdRest.setText("<" + chunks[1]);
|
||||
}
|
||||
mainUserId.setText(userId);
|
||||
}
|
||||
|
||||
long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID
|
||||
keyId.setText(Apg.getSmallFingerPrint(masterKeyId));
|
||||
|
||||
if (mainUserIdRest.getText().length() == 0) {
|
||||
mainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
status.setText(R.string.canSign);
|
||||
} else {
|
||||
if (mCursor.getInt(3) > 0) {
|
||||
// has some CAN_SIGN keys, but col(4) = 0, so must be revoked or expired
|
||||
status.setText(R.string.expired);
|
||||
} else {
|
||||
status.setText(R.string.noKey);
|
||||
}
|
||||
}
|
||||
|
||||
status.setText(status.getText() + " ");
|
||||
|
||||
view.setEnabled(enabled);
|
||||
mainUserId.setEnabled(enabled);
|
||||
mainUserIdRest.setEnabled(enabled);
|
||||
keyId.setEnabled(enabled);
|
||||
status.setEnabled(enabled);
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
100
org_apg/src/org/apg/ui/SendKeyActivity.java
Normal file
100
org_apg/src/org/apg/ui/SendKeyActivity.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package org.apg.ui;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Constants;
|
||||
import org.apg.HkpKeyServer;
|
||||
import org.apg.Id;
|
||||
import org.apg.Constants.extras;
|
||||
import org.apg.Id.message;
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.apg.R;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* gpg --send-key activity
|
||||
*
|
||||
* Sends the selected public key to a key server
|
||||
*/
|
||||
public class SendKeyActivity extends BaseActivity {
|
||||
|
||||
private Button export;
|
||||
private Spinner keyServer;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.key_server_export_layout);
|
||||
|
||||
export = (Button) findViewById(R.id.btn_export_to_server);
|
||||
keyServer = (Spinner) findViewById(R.id.keyServer);
|
||||
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mPreferences.getKeyServers());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
keyServer.setAdapter(adapter);
|
||||
if (adapter.getCount() > 0) {
|
||||
keyServer.setSelection(0);
|
||||
} else {
|
||||
export.setEnabled(false);
|
||||
}
|
||||
|
||||
export.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startThread();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String error = null;
|
||||
Bundle data = new Bundle();
|
||||
Message msg = new Message();
|
||||
|
||||
HkpKeyServer server = new HkpKeyServer((String) keyServer.getSelectedItem());
|
||||
|
||||
int keyRingId = getIntent().getIntExtra(Apg.EXTRA_KEY_ID, -1);
|
||||
|
||||
PGPKeyRing keyring = Apg.getKeyRing(keyRingId);
|
||||
if (keyring != null && keyring instanceof PGPPublicKeyRing) {
|
||||
boolean uploaded = Apg.uploadKeyRingToServer(server, (PGPPublicKeyRing) keyring);
|
||||
if (!uploaded) {
|
||||
error = "Unable to export key to selected server";
|
||||
}
|
||||
}
|
||||
|
||||
data.putInt(Constants.extras.STATUS, Id.message.export_done);
|
||||
|
||||
if (error != null) {
|
||||
data.putString(Apg.EXTRA_ERROR, error);
|
||||
}
|
||||
|
||||
msg.setData(data);
|
||||
sendMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneCallback(Message msg) {
|
||||
super.doneCallback(msg);
|
||||
|
||||
Bundle data = msg.getData();
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(this, R.string.keySendSuccess, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
294
org_apg/src/org/apg/ui/SignKeyActivity.java
Normal file
294
org_apg/src/org/apg/ui/SignKeyActivity.java
Normal file
@@ -0,0 +1,294 @@
|
||||
package org.apg.ui;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Constants;
|
||||
import org.apg.HkpKeyServer;
|
||||
import org.apg.Id;
|
||||
import org.apg.Constants.extras;
|
||||
import org.apg.Id.dialog;
|
||||
import org.apg.Id.message;
|
||||
import org.apg.Id.request;
|
||||
import org.apg.Id.return_value;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.apg.R;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* gpg --sign-key
|
||||
*
|
||||
* signs the specified public key with the specified secret master key
|
||||
*/
|
||||
public class SignKeyActivity extends BaseActivity {
|
||||
private static final String TAG = "SignKeyActivity";
|
||||
|
||||
private long pubKeyId = 0;
|
||||
private long masterKeyId = 0;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// check we havent already signed it
|
||||
setContentView(R.layout.sign_key_layout);
|
||||
|
||||
final Spinner keyServer = (Spinner) findViewById(R.id.keyServer);
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mPreferences.getKeyServers());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
keyServer.setAdapter(adapter);
|
||||
|
||||
final CheckBox sendKey = (CheckBox) findViewById(R.id.sendKey);
|
||||
if (!sendKey.isChecked()) {
|
||||
keyServer.setEnabled(false);
|
||||
} else {
|
||||
keyServer.setEnabled(true);
|
||||
}
|
||||
|
||||
sendKey.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (!isChecked) {
|
||||
keyServer.setEnabled(false);
|
||||
} else {
|
||||
keyServer.setEnabled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Button sign = (Button) findViewById(R.id.sign);
|
||||
sign.setEnabled(false); // disabled until the user selects a key to sign with
|
||||
sign.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (pubKeyId != 0) {
|
||||
initiateSigning();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pubKeyId = getIntent().getLongExtra(Apg.EXTRA_KEY_ID, 0);
|
||||
if (pubKeyId == 0) {
|
||||
finish(); // nothing to do if we dont know what key to sign
|
||||
} else {
|
||||
// kick off the SecretKey selection activity so the user chooses which key to sign with first
|
||||
Intent intent = new Intent(this, SelectSecretKeyListActivity.class);
|
||||
startActivityForResult(intent, Id.request.secret_keys);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handles the UI bits of the signing process on the UI thread
|
||||
*/
|
||||
private void initiateSigning() {
|
||||
PGPPublicKeyRing pubring = Apg.getPublicKeyRing(pubKeyId);
|
||||
if (pubring != null) {
|
||||
// if we have already signed this key, dont bother doing it again
|
||||
boolean alreadySigned = false;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSignature> itr = pubring.getPublicKey(pubKeyId).getSignatures();
|
||||
while (itr.hasNext()) {
|
||||
PGPSignature sig = itr.next();
|
||||
if (sig.getKeyID() == masterKeyId) {
|
||||
alreadySigned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!alreadySigned) {
|
||||
/*
|
||||
* get the user's passphrase for this key (if required)
|
||||
*/
|
||||
String passphrase = Apg.getCachedPassPhrase(masterKeyId);
|
||||
if (passphrase == null) {
|
||||
showDialog(Id.dialog.pass_phrase);
|
||||
return; // bail out; need to wait until the user has entered the passphrase before trying again
|
||||
} else {
|
||||
startSigning();
|
||||
}
|
||||
} else {
|
||||
final Bundle status = new Bundle();
|
||||
Message msg = new Message();
|
||||
|
||||
status.putString(Apg.EXTRA_ERROR, "Key has already been signed");
|
||||
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
|
||||
msg.setData(status);
|
||||
sendMessage(msg);
|
||||
|
||||
setResult(Id.return_value.error);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSecretKeyId() {
|
||||
return masterKeyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passPhraseCallback(long keyId, String passPhrase) {
|
||||
super.passPhraseCallback(keyId, passPhrase);
|
||||
startSigning();
|
||||
}
|
||||
|
||||
/**
|
||||
* kicks off the actual signing process on a background thread
|
||||
*/
|
||||
private void startSigning() {
|
||||
showDialog(Id.dialog.signing);
|
||||
startThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final Bundle status = new Bundle();
|
||||
Message msg = new Message();
|
||||
|
||||
try {
|
||||
String passphrase = Apg.getCachedPassPhrase(masterKeyId);
|
||||
if (passphrase == null || passphrase.length() <= 0) {
|
||||
status.putString(Apg.EXTRA_ERROR, "Unable to obtain passphrase");
|
||||
} else {
|
||||
PGPPublicKeyRing pubring = Apg.getPublicKeyRing(pubKeyId);
|
||||
|
||||
/*
|
||||
* sign the incoming key
|
||||
*/
|
||||
PGPSecretKey secretKey = Apg.getSecretKey(masterKeyId);
|
||||
PGPPrivateKey signingKey = secretKey.extractPrivateKey(passphrase.toCharArray(), BouncyCastleProvider.PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(secretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256, BouncyCastleProvider.PROVIDER_NAME);
|
||||
sGen.initSign(PGPSignature.DIRECT_KEY, signingKey);
|
||||
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||
sGen.setHashedSubpackets(packetVector);
|
||||
|
||||
PGPPublicKey signedKey = PGPPublicKey.addCertification(pubring.getPublicKey(pubKeyId), sGen.generate());
|
||||
pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey);
|
||||
|
||||
// check if we need to send the key to the server or not
|
||||
CheckBox sendKey = (CheckBox) findViewById(R.id.sendKey);
|
||||
if (sendKey.isChecked()) {
|
||||
Spinner keyServer = (Spinner) findViewById(R.id.keyServer);
|
||||
HkpKeyServer server = new HkpKeyServer((String) keyServer.getSelectedItem());
|
||||
|
||||
/*
|
||||
* upload the newly signed key to the key server
|
||||
*/
|
||||
|
||||
Apg.uploadKeyRingToServer(server, pubring);
|
||||
}
|
||||
|
||||
// store the signed key in our local cache
|
||||
int retval = Apg.storeKeyRingInCache(pubring);
|
||||
if (retval != Id.return_value.ok && retval != Id.return_value.updated) {
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to store signed key in local cache");
|
||||
}
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
Log.e(TAG, "Failed to sign key", e);
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to sign key");
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
return;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(TAG, "Failed to sign key", e);
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to sign key");
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
return;
|
||||
} catch (NoSuchProviderException e) {
|
||||
Log.e(TAG, "Failed to sign key", e);
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to sign key");
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
return;
|
||||
} catch (SignatureException e) {
|
||||
Log.e(TAG, "Failed to sign key", e);
|
||||
status.putString(Apg.EXTRA_ERROR, "Failed to sign key");
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
return;
|
||||
}
|
||||
|
||||
status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
|
||||
msg.setData(status);
|
||||
sendMessage(msg);
|
||||
|
||||
if (status.containsKey(Apg.EXTRA_ERROR)) {
|
||||
setResult(Id.return_value.error);
|
||||
} else {
|
||||
setResult(Id.return_value.ok);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.secret_keys: {
|
||||
if (resultCode == RESULT_OK) {
|
||||
masterKeyId = data.getLongExtra(Apg.EXTRA_KEY_ID, 0);
|
||||
|
||||
// re-enable the sign button so the user can initiate the sign process
|
||||
Button sign = (Button) findViewById(R.id.sign);
|
||||
sign.setEnabled(true);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneCallback(Message msg) {
|
||||
super.doneCallback(msg);
|
||||
|
||||
removeDialog(Id.dialog.signing);
|
||||
|
||||
Bundle data = msg.getData();
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(this, R.string.keySignSuccess, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
25
org_apg/src/org/apg/ui/widget/Editor.java
Normal file
25
org_apg/src/org/apg/ui/widget/Editor.java
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui.widget;
|
||||
|
||||
public interface Editor {
|
||||
public interface EditorListener {
|
||||
public void onDeleted(Editor editor);
|
||||
}
|
||||
|
||||
public void setEditorListener(EditorListener listener);
|
||||
}
|
||||
95
org_apg/src/org/apg/ui/widget/IntegerListPreference.java
Normal file
95
org_apg/src/org/apg/ui/widget/IntegerListPreference.java
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* 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.apg.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.ListPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
* A list preference which persists its values as integers instead of strings.
|
||||
* Code reading the values should use
|
||||
* {@link android.content.SharedPreferences#getInt}.
|
||||
* When using XML-declared arrays for entry values, the arrays should be regular
|
||||
* string arrays containing valid integer values.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class IntegerListPreference extends ListPreference {
|
||||
|
||||
public IntegerListPreference(Context context) {
|
||||
super(context);
|
||||
|
||||
verifyEntryValues(null);
|
||||
}
|
||||
|
||||
public IntegerListPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
verifyEntryValues(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntryValues(CharSequence[] entryValues) {
|
||||
CharSequence[] oldValues = getEntryValues();
|
||||
super.setEntryValues(entryValues);
|
||||
verifyEntryValues(oldValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntryValues(int entryValuesResId) {
|
||||
CharSequence[] oldValues = getEntryValues();
|
||||
super.setEntryValues(entryValuesResId);
|
||||
verifyEntryValues(oldValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPersistedString(String defaultReturnValue) {
|
||||
// During initial load, there's no known default value
|
||||
int defaultIntegerValue = Integer.MIN_VALUE;
|
||||
if (defaultReturnValue != null) {
|
||||
defaultIntegerValue = Integer.parseInt(defaultReturnValue);
|
||||
}
|
||||
|
||||
// When the list preference asks us to read a string, instead read an
|
||||
// integer.
|
||||
int value = getPersistedInt(defaultIntegerValue);
|
||||
return Integer.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean persistString(String value) {
|
||||
// When asked to save a string, instead save an integer
|
||||
return persistInt(Integer.parseInt(value));
|
||||
}
|
||||
|
||||
private void verifyEntryValues(CharSequence[] oldValues) {
|
||||
CharSequence[] entryValues = getEntryValues();
|
||||
if (entryValues == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (CharSequence entryValue : entryValues) {
|
||||
try {
|
||||
Integer.parseInt(entryValue.toString());
|
||||
} catch (NumberFormatException nfe) {
|
||||
super.setEntryValues(oldValues);
|
||||
throw nfe;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
233
org_apg/src/org/apg/ui/widget/KeyEditor.java
Normal file
233
org_apg/src/org/apg/ui/widget/KeyEditor.java
Normal file
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui.widget;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Id;
|
||||
import org.apg.util.Choice;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.DatePickerDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.DatePicker;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Vector;
|
||||
|
||||
public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
|
||||
private PGPSecretKey mKey;
|
||||
|
||||
private EditorListener mEditorListener = null;
|
||||
|
||||
private boolean mIsMasterKey;
|
||||
ImageButton mDeleteButton;
|
||||
TextView mAlgorithm;
|
||||
TextView mKeyId;
|
||||
Spinner mUsage;
|
||||
TextView mCreationDate;
|
||||
Button mExpiryDateButton;
|
||||
GregorianCalendar mExpiryDate;
|
||||
|
||||
private DatePickerDialog.OnDateSetListener mExpiryDateSetListener =
|
||||
new DatePickerDialog.OnDateSetListener() {
|
||||
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
|
||||
GregorianCalendar date = new GregorianCalendar(year, monthOfYear, dayOfMonth);
|
||||
setExpiryDate(date);
|
||||
}
|
||||
};
|
||||
|
||||
public KeyEditor(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public KeyEditor(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
setDrawingCacheEnabled(true);
|
||||
setAlwaysDrawnWithCacheEnabled(true);
|
||||
|
||||
mAlgorithm = (TextView) findViewById(R.id.algorithm);
|
||||
mKeyId = (TextView) findViewById(R.id.keyId);
|
||||
mCreationDate = (TextView) findViewById(R.id.creation);
|
||||
mExpiryDateButton = (Button) findViewById(R.id.expiry);
|
||||
mUsage = (Spinner) findViewById(R.id.usage);
|
||||
Choice choices[] = {
|
||||
new Choice(Id.choice.usage.sign_only,
|
||||
getResources().getString(R.string.choice_signOnly)),
|
||||
new Choice(Id.choice.usage.encrypt_only,
|
||||
getResources().getString(R.string.choice_encryptOnly)),
|
||||
new Choice(Id.choice.usage.sign_and_encrypt,
|
||||
getResources().getString(R.string.choice_signAndEncrypt)),
|
||||
};
|
||||
ArrayAdapter<Choice> adapter =
|
||||
new ArrayAdapter<Choice>(getContext(),
|
||||
android.R.layout.simple_spinner_item, choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mUsage.setAdapter(adapter);
|
||||
|
||||
mDeleteButton = (ImageButton) findViewById(R.id.delete);
|
||||
mDeleteButton.setOnClickListener(this);
|
||||
|
||||
setExpiryDate(null);
|
||||
|
||||
mExpiryDateButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
GregorianCalendar date = mExpiryDate;
|
||||
if (date == null) {
|
||||
date = new GregorianCalendar();
|
||||
}
|
||||
|
||||
DatePickerDialog dialog =
|
||||
new DatePickerDialog(getContext(), mExpiryDateSetListener,
|
||||
date.get(Calendar.YEAR),
|
||||
date.get(Calendar.MONTH),
|
||||
date.get(Calendar.DAY_OF_MONTH));
|
||||
dialog.setCancelable(true);
|
||||
dialog.setButton(Dialog.BUTTON_NEGATIVE,
|
||||
getContext().getString(R.string.btn_noDate),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
setExpiryDate(null);
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
public void setValue(PGPSecretKey key, boolean isMasterKey) {
|
||||
mKey = key;
|
||||
|
||||
mIsMasterKey = isMasterKey;
|
||||
if (mIsMasterKey) {
|
||||
mDeleteButton.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
mAlgorithm.setText(Apg.getAlgorithmInfo(key));
|
||||
String keyId1Str = Apg.getSmallFingerPrint(key.getKeyID());
|
||||
String keyId2Str = Apg.getSmallFingerPrint(key.getKeyID() >> 32);
|
||||
mKeyId.setText(keyId1Str + " " + keyId2Str);
|
||||
|
||||
Vector<Choice> choices = new Vector<Choice>();
|
||||
boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT);
|
||||
if (!isElGamalKey) {
|
||||
choices.add(new Choice(Id.choice.usage.sign_only,
|
||||
getResources().getString(R.string.choice_signOnly)));
|
||||
}
|
||||
if (!mIsMasterKey) {
|
||||
choices.add(new Choice(Id.choice.usage.encrypt_only,
|
||||
getResources().getString(R.string.choice_encryptOnly)));
|
||||
}
|
||||
if (!isElGamalKey) {
|
||||
choices.add(new Choice(Id.choice.usage.sign_and_encrypt,
|
||||
getResources().getString(R.string.choice_signAndEncrypt)));
|
||||
}
|
||||
|
||||
ArrayAdapter<Choice> adapter =
|
||||
new ArrayAdapter<Choice>(getContext(),
|
||||
android.R.layout.simple_spinner_item, choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mUsage.setAdapter(adapter);
|
||||
|
||||
int selectId = 0;
|
||||
if (Apg.isEncryptionKey(key)) {
|
||||
if (Apg.isSigningKey(key)) {
|
||||
selectId = Id.choice.usage.sign_and_encrypt;
|
||||
} else {
|
||||
selectId = Id.choice.usage.encrypt_only;
|
||||
}
|
||||
} else {
|
||||
selectId = Id.choice.usage.sign_only;
|
||||
}
|
||||
|
||||
for (int i = 0; i < choices.size(); ++i) {
|
||||
if (choices.get(i).getId() == selectId) {
|
||||
mUsage.setSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GregorianCalendar cal = new GregorianCalendar();
|
||||
cal.setTime(Apg.getCreationDate(key));
|
||||
mCreationDate.setText(DateFormat.getDateInstance().format(cal.getTime()));
|
||||
cal = new GregorianCalendar();
|
||||
Date date = Apg.getExpiryDate(key);
|
||||
if (date == null) {
|
||||
setExpiryDate(null);
|
||||
} else {
|
||||
cal.setTime(Apg.getExpiryDate(key));
|
||||
setExpiryDate(cal);
|
||||
}
|
||||
}
|
||||
|
||||
public PGPSecretKey getValue() {
|
||||
return mKey;
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
final ViewGroup parent = (ViewGroup)getParent();
|
||||
if (v == mDeleteButton) {
|
||||
parent.removeView(this);
|
||||
if (mEditorListener != null) {
|
||||
mEditorListener.onDeleted(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setEditorListener(EditorListener listener) {
|
||||
mEditorListener = listener;
|
||||
}
|
||||
|
||||
private void setExpiryDate(GregorianCalendar date) {
|
||||
mExpiryDate = date;
|
||||
if (date == null) {
|
||||
mExpiryDateButton.setText(R.string.none);
|
||||
} else {
|
||||
mExpiryDateButton.setText(DateFormat.getDateInstance().format(date.getTime()));
|
||||
}
|
||||
}
|
||||
|
||||
public GregorianCalendar getExpiryDate() {
|
||||
return mExpiryDate;
|
||||
}
|
||||
|
||||
public int getUsage() {
|
||||
return ((Choice) mUsage.getSelectedItem()).getId();
|
||||
}
|
||||
}
|
||||
78
org_apg/src/org/apg/ui/widget/KeyServerEditor.java
Normal file
78
org_apg/src/org/apg/ui/widget/KeyServerEditor.java
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui.widget;
|
||||
|
||||
import org.apg.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class KeyServerEditor extends LinearLayout implements Editor, OnClickListener {
|
||||
private EditorListener mEditorListener = null;
|
||||
|
||||
ImageButton mDeleteButton;
|
||||
TextView mServer;
|
||||
|
||||
public KeyServerEditor(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public KeyServerEditor(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
setDrawingCacheEnabled(true);
|
||||
setAlwaysDrawnWithCacheEnabled(true);
|
||||
|
||||
mServer = (TextView) findViewById(R.id.server);
|
||||
|
||||
mDeleteButton = (ImageButton) findViewById(R.id.delete);
|
||||
mDeleteButton.setOnClickListener(this);
|
||||
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
mServer.setText(value);
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return mServer.getText().toString().trim();
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
final ViewGroup parent = (ViewGroup)getParent();
|
||||
if (v == mDeleteButton) {
|
||||
parent.removeView(this);
|
||||
if (mEditorListener != null) {
|
||||
mEditorListener.onDeleted(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setEditorListener(EditorListener listener) {
|
||||
mEditorListener = listener;
|
||||
}
|
||||
}
|
||||
335
org_apg/src/org/apg/ui/widget/SectionView.java
Normal file
335
org_apg/src/org/apg/ui/widget/SectionView.java
Normal file
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui.widget;
|
||||
|
||||
import org.apg.Apg;
|
||||
import org.apg.Id;
|
||||
import org.apg.ui.widget.Editor.EditorListener;
|
||||
import org.apg.util.Choice;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.apg.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Vector;
|
||||
|
||||
public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Runnable {
|
||||
private LayoutInflater mInflater;
|
||||
private View mAdd;
|
||||
private ViewGroup mEditors;
|
||||
private TextView mTitle;
|
||||
private int mType = 0;
|
||||
|
||||
private Choice mNewKeyAlgorithmChoice;
|
||||
private int mNewKeySize;
|
||||
|
||||
volatile private PGPSecretKey mNewKey;
|
||||
private ProgressDialog mProgressDialog;
|
||||
private Thread mRunningThread = null;
|
||||
|
||||
private Handler mHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
Bundle data = msg.getData();
|
||||
if (data != null) {
|
||||
boolean closeProgressDialog = data.getBoolean("closeProgressDialog");
|
||||
if (closeProgressDialog) {
|
||||
if (mProgressDialog != null) {
|
||||
mProgressDialog.dismiss();
|
||||
mProgressDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
String error = data.getString(Apg.EXTRA_ERROR);
|
||||
if (error != null) {
|
||||
Toast.makeText(getContext(),
|
||||
getContext().getString(R.string.errorMessage, error),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
boolean gotNewKey = data.getBoolean("gotNewKey");
|
||||
if (gotNewKey) {
|
||||
KeyEditor view =
|
||||
(KeyEditor) mInflater.inflate(R.layout.edit_key_key_item,
|
||||
mEditors, false);
|
||||
view.setEditorListener(SectionView.this);
|
||||
boolean isMasterKey = (mEditors.getChildCount() == 0);
|
||||
view.setValue(mNewKey, isMasterKey);
|
||||
mEditors.addView(view);
|
||||
SectionView.this.updateEditorsVisible();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public SectionView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SectionView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ViewGroup getEditors() {
|
||||
return mEditors;
|
||||
}
|
||||
|
||||
public void setType(int type) {
|
||||
mType = type;
|
||||
switch (type) {
|
||||
case Id.type.user_id: {
|
||||
mTitle.setText(R.string.section_userIds);
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.type.key: {
|
||||
mTitle.setText(R.string.section_keys);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
setDrawingCacheEnabled(true);
|
||||
setAlwaysDrawnWithCacheEnabled(true);
|
||||
|
||||
mAdd = findViewById(R.id.header);
|
||||
mAdd.setOnClickListener(this);
|
||||
|
||||
mEditors = (ViewGroup) findViewById(R.id.editors);
|
||||
mTitle = (TextView) findViewById(R.id.title);
|
||||
|
||||
updateEditorsVisible();
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void onDeleted(Editor editor) {
|
||||
this.updateEditorsVisible();
|
||||
}
|
||||
|
||||
protected void updateEditorsVisible() {
|
||||
final boolean hasChildren = mEditors.getChildCount() > 0;
|
||||
mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void onClick(View v) {
|
||||
switch (mType) {
|
||||
case Id.type.user_id: {
|
||||
UserIdEditor view =
|
||||
(UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item,
|
||||
mEditors, false);
|
||||
view.setEditorListener(this);
|
||||
if (mEditors.getChildCount() == 0) {
|
||||
view.setIsMainUserId(true);
|
||||
}
|
||||
mEditors.addView(view);
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.type.key: {
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(getContext());
|
||||
|
||||
View view = mInflater.inflate(R.layout.create_key, null);
|
||||
dialog.setView(view);
|
||||
dialog.setTitle(R.string.title_createKey);
|
||||
dialog.setMessage(R.string.keyCreationElGamalInfo);
|
||||
|
||||
boolean wouldBeMasterKey = (mEditors.getChildCount() == 0);
|
||||
|
||||
final Spinner algorithm = (Spinner) view.findViewById(R.id.algorithm);
|
||||
Vector<Choice> choices = new Vector<Choice>();
|
||||
choices.add(new Choice(Id.choice.algorithm.dsa,
|
||||
getResources().getString(R.string.dsa)));
|
||||
if (!wouldBeMasterKey) {
|
||||
choices.add(new Choice(Id.choice.algorithm.elgamal,
|
||||
getResources().getString(R.string.elgamal)));
|
||||
}
|
||||
|
||||
choices.add(new Choice(Id.choice.algorithm.rsa,
|
||||
getResources().getString(R.string.rsa)));
|
||||
|
||||
ArrayAdapter<Choice> adapter =
|
||||
new ArrayAdapter<Choice>(getContext(),
|
||||
android.R.layout.simple_spinner_item,
|
||||
choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
algorithm.setAdapter(adapter);
|
||||
// make RSA the default
|
||||
for (int i = 0; i < choices.size(); ++i) {
|
||||
if (choices.get(i).getId() == Id.choice.algorithm.rsa) {
|
||||
algorithm.setSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final EditText keySize = (EditText) view.findViewById(R.id.size);
|
||||
|
||||
dialog.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface di, int id) {
|
||||
di.dismiss();
|
||||
try {
|
||||
mNewKeySize = Integer.parseInt("" + keySize.getText());
|
||||
} catch (NumberFormatException e) {
|
||||
mNewKeySize = 0;
|
||||
}
|
||||
|
||||
mNewKeyAlgorithmChoice = (Choice) algorithm.getSelectedItem();
|
||||
createKey();
|
||||
}
|
||||
});
|
||||
|
||||
dialog.setCancelable(true);
|
||||
dialog.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface di, int id) {
|
||||
di.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
dialog.create().show();
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.updateEditorsVisible();
|
||||
}
|
||||
|
||||
public void setUserIds(Vector<String> list) {
|
||||
if (mType != Id.type.user_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
mEditors.removeAllViews();
|
||||
for (String userId : list) {
|
||||
UserIdEditor view =
|
||||
(UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item, mEditors, false);
|
||||
view.setEditorListener(this);
|
||||
view.setValue(userId);
|
||||
if (mEditors.getChildCount() == 0) {
|
||||
view.setIsMainUserId(true);
|
||||
}
|
||||
mEditors.addView(view);
|
||||
}
|
||||
|
||||
this.updateEditorsVisible();
|
||||
}
|
||||
|
||||
public void setKeys(Vector<PGPSecretKey> list) {
|
||||
if (mType != Id.type.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
mEditors.removeAllViews();
|
||||
for (PGPSecretKey key : list) {
|
||||
KeyEditor view =
|
||||
(KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, mEditors, false);
|
||||
view.setEditorListener(this);
|
||||
boolean isMasterKey = (mEditors.getChildCount() == 0);
|
||||
view.setValue(key, isMasterKey);
|
||||
mEditors.addView(view);
|
||||
}
|
||||
|
||||
this.updateEditorsVisible();
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
mProgressDialog = new ProgressDialog(getContext());
|
||||
mProgressDialog.setMessage(getContext().getString(R.string.progress_generating));
|
||||
mProgressDialog.setCancelable(false);
|
||||
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
mProgressDialog.show();
|
||||
mRunningThread = new Thread(this);
|
||||
mRunningThread.start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
String error = null;
|
||||
try {
|
||||
PGPSecretKey masterKey = null;
|
||||
String passPhrase;
|
||||
if (mEditors.getChildCount() > 0) {
|
||||
masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue();
|
||||
passPhrase = Apg.getCachedPassPhrase(masterKey.getKeyID());
|
||||
} else {
|
||||
passPhrase = "";
|
||||
}
|
||||
mNewKey = Apg.createKey(getContext(),
|
||||
mNewKeyAlgorithmChoice.getId(),
|
||||
mNewKeySize, passPhrase,
|
||||
masterKey);
|
||||
} catch (NoSuchProviderException e) {
|
||||
error = "" + e;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
error = "" + e;
|
||||
} catch (PGPException e) {
|
||||
error = "" + e;
|
||||
} catch (InvalidParameterException e) {
|
||||
error = "" + e;
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
error = "" + e;
|
||||
} catch (Apg.GeneralException e) {
|
||||
error = "" + e;
|
||||
}
|
||||
|
||||
Message message = new Message();
|
||||
Bundle data = new Bundle();
|
||||
data.putBoolean("closeProgressDialog", true);
|
||||
if (error != null) {
|
||||
data.putString(Apg.EXTRA_ERROR, error);
|
||||
} else {
|
||||
data.putBoolean("gotNewKey", true);
|
||||
}
|
||||
message.setData(data);
|
||||
mHandler.sendMessage(message);
|
||||
}
|
||||
}
|
||||
192
org_apg/src/org/apg/ui/widget/UserIdEditor.java
Normal file
192
org_apg/src/org/apg/ui/widget/UserIdEditor.java
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.apg.ui.widget;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apg.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RadioButton;
|
||||
|
||||
public class UserIdEditor extends LinearLayout implements Editor, OnClickListener {
|
||||
private EditorListener mEditorListener = null;
|
||||
|
||||
private ImageButton mDeleteButton;
|
||||
private RadioButton mIsMainUserId;
|
||||
private EditText mName;
|
||||
private EditText mEmail;
|
||||
private EditText mComment;
|
||||
|
||||
private static final Pattern EMAIL_PATTERN =
|
||||
Pattern.compile("^([a-zA-Z0-9_.-])+@([a-zA-Z0-9_.-])+[.]([a-zA-Z])+([a-zA-Z])+",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
public static class NoNameException extends Exception {
|
||||
static final long serialVersionUID = 0xf812773343L;
|
||||
|
||||
public NoNameException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static class NoEmailException extends Exception {
|
||||
static final long serialVersionUID = 0xf812773344L;
|
||||
|
||||
public NoEmailException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidEmailException extends Exception {
|
||||
static final long serialVersionUID = 0xf812773345L;
|
||||
|
||||
public InvalidEmailException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public UserIdEditor(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public UserIdEditor(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
setDrawingCacheEnabled(true);
|
||||
setAlwaysDrawnWithCacheEnabled(true);
|
||||
|
||||
mDeleteButton = (ImageButton) findViewById(R.id.delete);
|
||||
mDeleteButton.setOnClickListener(this);
|
||||
mIsMainUserId = (RadioButton) findViewById(R.id.isMainUserId);
|
||||
mIsMainUserId.setOnClickListener(this);
|
||||
|
||||
mName = (EditText) findViewById(R.id.name);
|
||||
mEmail = (EditText) findViewById(R.id.email);
|
||||
mComment = (EditText) findViewById(R.id.comment);
|
||||
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
public void setValue(String userId) {
|
||||
mName.setText("");
|
||||
mComment.setText("");
|
||||
mEmail.setText("");
|
||||
|
||||
Pattern withComment = Pattern.compile("^(.*) [(](.*)[)] <(.*)>$");
|
||||
Matcher matcher = withComment.matcher(userId);
|
||||
if (matcher.matches()) {
|
||||
mName.setText(matcher.group(1));
|
||||
mComment.setText(matcher.group(2));
|
||||
mEmail.setText(matcher.group(3));
|
||||
return;
|
||||
}
|
||||
|
||||
Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$");
|
||||
matcher = withoutComment.matcher(userId);
|
||||
if (matcher.matches()) {
|
||||
mName.setText(matcher.group(1));
|
||||
mEmail.setText(matcher.group(2));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public String getValue() throws NoNameException, NoEmailException, InvalidEmailException {
|
||||
String name = ("" + mName.getText()).trim();
|
||||
String email = ("" + mEmail.getText()).trim();
|
||||
String comment = ("" + mComment.getText()).trim();
|
||||
|
||||
if (email.length() > 0) {
|
||||
Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
|
||||
if (!emailMatcher.matches()) {
|
||||
throw new InvalidEmailException(
|
||||
getContext().getString(R.string.error_invalidEmail, email));
|
||||
}
|
||||
}
|
||||
|
||||
String userId = name;
|
||||
if (comment.length() > 0) {
|
||||
userId += " (" + comment + ")";
|
||||
}
|
||||
if (email.length() > 0) {
|
||||
userId += " <" + email + ">";
|
||||
}
|
||||
|
||||
if (userId.equals("")) {
|
||||
// ok, empty one...
|
||||
return userId;
|
||||
}
|
||||
|
||||
// otherwise make sure that name and email exist
|
||||
if (name.equals("")) {
|
||||
throw new NoNameException("need a name");
|
||||
}
|
||||
|
||||
if (email.equals("")) {
|
||||
throw new NoEmailException("need an email");
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
final ViewGroup parent = (ViewGroup)getParent();
|
||||
if (v == mDeleteButton) {
|
||||
boolean wasMainUserId = mIsMainUserId.isChecked();
|
||||
parent.removeView(this);
|
||||
if (mEditorListener != null) {
|
||||
mEditorListener.onDeleted(this);
|
||||
}
|
||||
if (wasMainUserId && parent.getChildCount() > 0) {
|
||||
UserIdEditor editor = (UserIdEditor) parent.getChildAt(0);
|
||||
editor.setIsMainUserId(true);
|
||||
}
|
||||
} else if (v == mIsMainUserId) {
|
||||
for (int i = 0; i < parent.getChildCount(); ++i) {
|
||||
UserIdEditor editor = (UserIdEditor) parent.getChildAt(i);
|
||||
if (editor == this) {
|
||||
editor.setIsMainUserId(true);
|
||||
} else {
|
||||
editor.setIsMainUserId(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setIsMainUserId(boolean value) {
|
||||
mIsMainUserId.setChecked(value);
|
||||
}
|
||||
|
||||
public boolean isMainUserId() {
|
||||
return mIsMainUserId.isChecked();
|
||||
}
|
||||
|
||||
public void setEditorListener(EditorListener listener) {
|
||||
mEditorListener = listener;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user