package org.nick.passman;
import static org.nick.passman.Hex.toHex;
import java.util.List;
import org.simalliance.openmobileapi.Reader;
import org.simalliance.openmobileapi.SEService;
import org.simalliance.openmobileapi.Session;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity implements SEService.CallBack,
OnClickListener, OnItemLongClickListener {
private static final String TAG = "SIM Password Manager";
private SEService seService;
private TextView messageText;
private EditText nameText;
private EditText passwordText;
private Button initilizeButton;
private ListView passwordsList;
private Reader reader;
private Session session;
private PmAppletClient pm;
private PasswordDb db;
private boolean appletInitialized = false;
private ActionMode currentActionMode;
private Handler handler;
private static class PasswordAdapter extends ArrayAdapter<PasswordEntry> {
PasswordAdapter(Context context, PasswordEntry[] items) {
super(context, android.R.layout.select_dialog_item,
android.R.id.text1, items);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View result = super.getView(position, convertView, parent);
TextView tv = (TextView) result.findViewById(android.R.id.text1);
tv.setText(getItem(position).getName());
return result;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_main);
messageText = (TextView) findViewById(R.id.message_text);
nameText = (EditText) findViewById(R.id.name_text);
passwordText = (EditText) findViewById(R.id.password_text);
initilizeButton = (Button) findViewById(R.id.init_pm_button);
initilizeButton.setOnClickListener(this);
initilizeButton.setVisibility(View.GONE);
initilizeButton.setEnabled(false);
passwordsList = (ListView) findViewById(R.id.passwords_list);
passwordsList.setOnItemLongClickListener(this);
passwordsList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
nameText.setText(null);
passwordText.setText(null);
}
};
db = PasswordDb.getInstance(this);
}
private void connectToSeService() {
try {
Log.i(TAG, "creating SEService object");
seService = new SEService(this, this);
} catch (SecurityException e) {
Log.e(TAG,
"Binding not allowed, uses-permission org.simalliance.openmobileapi.SMARTCARD?");
Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_LONG)
.show();
finish();
} catch (Exception e) {
Log.e(TAG, "Exception: " + e.getMessage());
Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_LONG)
.show();
finish();
}
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.action_add).setEnabled(appletInitialized);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public void onResume() {
super.onResume();
if (seService == null || !seService.isConnected()) {
connectToSeService();
return;
}
checkAppletState();
}
@Override
public void onPause() {
super.onPause();
passwordText.setText(null);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_add:
addPassword();
break;
case R.id.action_clear:
clear();
break;
default:
// do nothing
}
return super.onOptionsItemSelected(item);
}
private void addPassword() {
if (TextUtils.isEmpty(nameText.getText())
|| TextUtils.isEmpty(passwordText.getText())) {
Toast.makeText(this, "Name and password must not be empty",
Toast.LENGTH_SHORT).show();
return;
}
new AsyncTask<Void, Void, Void>() {
Exception error;
@Override
protected void onPreExecute() {
setProgressBarIndeterminateVisibility(true);
messageText.setText("");
}
@Override
protected Void doInBackground(Void... arg) {
try {
String encrypted = pm.encryptStr(passwordText.getText()
.toString());
PasswordEntry entry = new PasswordEntry(nameText.getText()
.toString(), encrypted);
db.addPasswrod(entry);
} catch (Exception e) {
Log.e(TAG, "Error: " + e.getMessage(), e);
error = e;
} finally {
if (pm != null) {
pm.disconnect();
}
}
return null;
}
@Override
protected void onPostExecute(Void result) {
setProgressBarIndeterminateVisibility(false);
if (error != null) {
Toast.makeText(MainActivity.this, error.getMessage(),
Toast.LENGTH_LONG).show();
return;
}
nameText.setText(null);
passwordText.setText(null);
loadPasswords();
}
}.execute();
}
private void loadPasswords() {
new AsyncTask<Void, Void, List<PasswordEntry>>() {
Exception error;
@Override
protected void onPreExecute() {
setProgressBarIndeterminateVisibility(true);
}
@Override
protected List<PasswordEntry> doInBackground(Void... arg0) {
try {
return db.getAllPasswords();
} catch (Exception e) {
Log.e(TAG, "Error: " + e.getMessage(), e);
error = e;
}
return null;
}
@Override
protected void onPostExecute(List<PasswordEntry> result) {
setProgressBarIndeterminateVisibility(false);
if (error != null) {
Toast.makeText(MainActivity.this,
"Error loading passwords: " + error.getMessage(),
Toast.LENGTH_LONG).show();
return;
}
PasswordEntry[] passwords = new PasswordEntry[result.size()];
result.toArray(passwords);
PasswordAdapter adapter = new PasswordAdapter(
MainActivity.this, passwords);
passwordsList.setAdapter(adapter);
}
}.execute();
}
private void clear() {
new AsyncTask<Void, Void, Void>() {
Exception error;
@Override
protected void onPreExecute() {
setProgressBarIndeterminateVisibility(true);
}
@Override
protected Void doInBackground(Void... arg0) {
try {
if (session == null) {
session = reader.openSession();
}
try {
if (pm == null) {
pm = new PmAppletClient(session);
}
appletInitialized = pm.isInitialized();
Log.d(TAG, "initialized: " + appletInitialized);
pm.clear();
appletInitialized = pm.isInitialized();
db.deleteAll();
} finally {
if (pm != null) {
pm.disconnect();
}
}
} catch (Exception e) {
Log.e(TAG, "Error: " + e.getMessage(), e);
error = e;
}
return null;
}
@Override
protected void onPostExecute(Void result) {
setProgressBarIndeterminateVisibility(false);
if (error != null) {
Toast.makeText(
MainActivity.this,
"Error clearing applet state: "
+ error.getMessage(), Toast.LENGTH_LONG)
.show();
return;
}
toggleUi();
loadPasswords();
}
}.execute();
}
private void toggleUi() {
messageText.setText(appletInitialized ? "Connected"
: "Applet not initialized");
messageText.setVisibility(View.VISIBLE);
nameText.setVisibility(appletInitialized ? View.VISIBLE
: View.INVISIBLE);
passwordText.setVisibility(appletInitialized ? View.VISIBLE
: View.INVISIBLE);
initilizeButton.setVisibility(appletInitialized ? View.GONE
: View.VISIBLE);
initilizeButton.setEnabled(!appletInitialized);
invalidateOptionsMenu();
}
@Override
protected void onDestroy() {
if (reader != null) {
reader.closeSessions();
}
if (seService != null && seService.isConnected()) {
seService.shutdown();
}
super.onDestroy();
}
public void serviceConnected(SEService service) {
Log.i(TAG, "seviceConnected()");
Reader[] readers = seService.getReaders();
if (readers.length < 1) {
Toast.makeText(this, "No readers found", Toast.LENGTH_SHORT).show();
return;
}
for (Reader r : readers) {
if (r.isSecureElementPresent()) {
reader = r;
break;
}
}
if (reader == null) {
Toast.makeText(this, "No SEs found", Toast.LENGTH_LONG).show();
finish();
return;
}
new AsyncTask<Void, Void, Void>() {
Exception error;
@Override
protected void onPreExecute() {
setProgressBarIndeterminateVisibility(true);
}
@Override
protected Void doInBackground(Void... arg0) {
try {
session = reader.openSession();
if (session.getATR() != null) {
Log.d(TAG, "ATR " + toHex(session.getATR()));
}
try {
pm = new PmAppletClient(session);
appletInitialized = pm.isInitialized();
Log.d(TAG, "initialized: " + appletInitialized);
} finally {
if (pm != null) {
pm.disconnect();
}
}
} catch (Exception e) {
Log.e(TAG, "Error: " + e.getMessage(), e);
error = e;
}
return null;
}
@Override
protected void onPostExecute(Void result) {
setProgressBarIndeterminateVisibility(false);
if (error != null) {
Toast.makeText(
MainActivity.this,
"Error checking applet state: "
+ error.getMessage(), Toast.LENGTH_LONG)
.show();
return;
}
toggleUi();
loadPasswords();
}
}.execute();
}
private void checkAppletState() {
appletInitialized = false;
toggleUi();
new AsyncTask<Void, Void, Void>() {
Exception error;
@Override
protected void onPreExecute() {
setProgressBarIndeterminateVisibility(true);
}
@Override
protected Void doInBackground(Void... arg0) {
try {
try {
if (pm == null) {
pm = new PmAppletClient(session);
}
appletInitialized = pm.isInitialized();
Log.d(TAG, "initialized: " + appletInitialized);
} finally {
if (pm != null) {
pm.disconnect();
}
}
} catch (Exception e) {
Log.e(TAG, "Error: " + e.getMessage(), e);
error = e;
}
return null;
}
@Override
protected void onPostExecute(Void result) {
setProgressBarIndeterminateVisibility(false);
if (error != null) {
Toast.makeText(
MainActivity.this,
"Error checking applet state: "
+ error.getMessage(), Toast.LENGTH_LONG)
.show();
return;
}
toggleUi();
loadPasswords();
}
}.execute();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.init_pm_button:
initializeApplet();
break;
}
}
private void initializeApplet() {
new AsyncTask<Void, Void, Void>() {
Exception error;
@Override
protected void onPreExecute() {
setProgressBarIndeterminateVisibility(true);
initilizeButton.setEnabled(false);
messageText.setText("Initializing...");
}
@Override
protected Void doInBackground(Void... arg) {
try {
appletInitialized = pm.isInitialized();
Log.d(TAG, "initialized: " + appletInitialized);
if (!appletInitialized) {
Log.d(TAG, "generating keys...");
long start = System.currentTimeMillis();
pm.generateKeys();
Log.d(TAG,
String.format("Done: %d[ms]",
(System.currentTimeMillis() - start)));
}
appletInitialized = pm.isInitialized();
} catch (Exception e) {
Log.e(TAG, "Error: " + e.getMessage(), e);
error = e;
} finally {
if (pm != null) {
pm.disconnect();
}
}
return null;
}
@Override
protected void onPostExecute(Void result) {
setProgressBarIndeterminateVisibility(false);
if (error != null) {
Toast.makeText(MainActivity.this, error.getMessage(),
Toast.LENGTH_LONG).show();
return;
}
toggleUi();
}
}.execute();
}
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
if (currentActionMode != null) {
return false;
}
currentActionMode = startActionMode(new ContextCallback(position));
passwordsList.setItemChecked(position, true);
return true;
}
@SuppressLint("NewApi")
class ContextCallback implements ActionMode.Callback {
private int position;
ContextCallback(int position) {
this.position = position;
}
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.context, menu);
return true;
}
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
return false;
}
public boolean onActionItemClicked(ActionMode actionMode,
MenuItem menuItem) {
if (menuItem.getItemId() == R.id.action_show) {
showPassword(position);
actionMode.finish();
return true;
}
return false;
}
public void onDestroyActionMode(ActionMode actionMode) {
passwordsList.setItemChecked(position, false);
currentActionMode = null;
}
}
public void showPassword(int position) {
final PasswordEntry entry = (PasswordEntry) passwordsList.getAdapter()
.getItem(position);
new AsyncTask<Void, Void, String>() {
Exception error;
@Override
protected void onPreExecute() {
setProgressBarIndeterminateVisibility(true);
messageText.setText("");
}
@Override
protected String doInBackground(Void... arg) {
try {
return pm.decryptStr(entry.getEncryptedPasswod());
} catch (Exception e) {
Log.e(TAG, "Error: " + e.getMessage(), e);
error = e;
} finally {
if (pm != null) {
pm.disconnect();
}
}
return null;
}
@Override
protected void onPostExecute(String password) {
setProgressBarIndeterminateVisibility(false);
if (error != null) {
Toast.makeText(MainActivity.this, error.getMessage(),
Toast.LENGTH_LONG).show();
return;
}
nameText.setText(entry.getName());
passwordText.setText(password);
passwordText.requestFocus();
passwordText.selectAll();
// clear after 15 secs
Message msg = Message.obtain(handler);
handler.sendMessageDelayed(msg, 15 * 1000);
}
}.execute();
}
}