package com.example.passrepo;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.util.Base64;
import android.view.*;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.SearchView;
import android.widget.Toast;
import com.example.passrepo.crypto.Encryption;
import com.example.passrepo.crypto.Encryption.CipherText;
import com.example.passrepo.crypto.PasswordHasher;
import com.example.passrepo.crypto.PasswordHasher.ScryptParameters;
import com.example.passrepo.dummy.DummyContent;
import com.example.passrepo.events.PasswordListUpdatedEvent;
import com.example.passrepo.events.SearchQueryUpdatedEvent;
import com.example.passrepo.io.IO;
import com.example.passrepo.io.StubGoogleDriveIO;
import com.example.passrepo.model.Model;
import com.example.passrepo.model.PasswordEntry;
import com.example.passrepo.util.GsonHelper;
import com.example.passrepo.util.Logger;
import com.google.common.base.Charsets;
import com.squareup.otto.Bus;
public class PasswordEntryListActivity extends FragmentActivity implements PasswordEntryListFragment.Callbacks, SearchView.OnQueryTextListener {
private boolean mTwoPane;
private SearchView mSearchView;
private static final boolean TEST_ENCRYPTION = false;
private final Bus bus;
public PasswordEntryListActivity() {
this.bus = BusWrapper.globalBus;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
testDriveEncryption();
initActivity();
}
@Override
public void onItemSelected(String id) {
if (mTwoPane) {
findViewById(R.id.passwordentry_detail_container).setVisibility(View.VISIBLE);
PasswordEntryDetailFragmentBase.switchDetailFragment(this, id, new PasswordEntryDetailFragment());
InputMethodManager imm = (InputMethodManager)getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(findViewById(R.id.searchView).getWindowToken(), 0);
} else {
Intent detailIntent = new Intent(this, PasswordEntryDetailActivity.class);
detailIntent.putExtra(Consts.ARG_ITEM_ID, id);
startActivity(detailIntent);
}
}
@Override
protected void onPause() {
super.onPause();
IO.saveModelToDisk(this);
}
@Override
protected void onStop() {
super.onStop();
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).cancel(Consts.COPY_PASSWORD_NOTIFICATION_ID);
}
protected void onResume() {
super.onResume();
testDriveEncryption();
}
private void initActivity() {
// Once we have an updated model, set the passwords list content view. Avoid it if the list is already set.
if (Model.currentModel != null && findViewById(android.R.id.content) != null) {
setContentView(R.layout.activity_passwordentry_list);
if (findViewById(R.id.passwordentry_detail_container) != null) {
mTwoPane = true;
((PasswordEntryListFragment) getSupportFragmentManager().findFragmentById(R.id.passwordentry_list))
.setActivateOnItemClick(true);
}
} else {
// TODO: probably belongs to onResume
final ProgressDialog loadingDialog = new ProgressDialog(this);
loadingDialog.setMessage("Loading Passwords..");
loadingDialog.setCancelable(false);
loadingDialog.show();
new StubGoogleDriveIO(this).startSyncFromGoogleDriveToDisk(new Runnable() {
@Override
public void run() {
Logger.i("PasswordEntryListActivity", "Done Syncing from Drive, loading model from disk..");
IO.loadModelFromDisk(PasswordEntryListActivity.this);
Logger.i("PasswordEntryListActivity", "Updating UI..");
runOnUiThread(new Runnable() {
public void run() {
loadingDialog.dismiss();
startActivity(getIntent());
finish();
}
});
}
});
}
}
@SuppressWarnings("unused")
private void testDriveEncryption() {
if (!TEST_ENCRYPTION)
return;
String encryptedString = IO.modelToEncryptedString(DummyContent.model);
Model decryptedModel = null;
try {
decryptedModel = IO.modelFromEncryptedString(encryptedString, DummyContent.model.keys);
} catch (PassRepoBaseSecurityException e) {
throw new RuntimeException(e);
}
Logger.i("TEST", "originalModel=%s", GsonHelper.customGson.toJson(DummyContent.model));
Logger.i("TEST", "encryptedString=%s", encryptedString);
Logger.i("TEST", "decryptedModel=%s", GsonHelper.customGson.toJson(decryptedModel));
if (false) {
ScryptParameters scryptParameters = new ScryptParameters(new byte[]{});
final PasswordHasher.Keys keys = PasswordHasher.hash("foo", scryptParameters);
CipherText encrypted = Encryption.encrypt("What's up man?".getBytes(Charsets.UTF_8), keys);
String decrypted = new String(Encryption.decrypt(encrypted, keys.encryptionKey), Charsets.UTF_8);
Logger.i("TEST", "derivedKey=%s", Base64.encodeToString(keys.encryptionKey, Base64.NO_WRAP));
Logger.i("TEST", "encrypted=%s", Base64.encodeToString(encrypted.bytes, Base64.NO_WRAP));
Logger.i("TEST", "decrypted=%s", decrypted);
}
}
private static final int MENU_CHANGE_PASSWORD = 1;
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(Menu.NONE, MENU_CHANGE_PASSWORD, Menu.NONE, R.string.change_password_menu_label).setIcon(
android.R.drawable.ic_menu_agenda);
MenuInflater inflater = getMenuInflater();
if (!mTwoPane) {
inflater.inflate(R.menu.searchview_in_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
mSearchView = (SearchView) searchItem.getActionView();
setupSearchView(searchItem);
}
inflater.inflate(R.menu.main_actions, menu);
return true;
}
private void setupSearchView(MenuItem searchItem) {
if (isAlwaysExpanded()) {
mSearchView.setIconifiedByDefault(false);
} else {
searchItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM
| MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
}
mSearchView.setOnQueryTextListener(this);
}
public boolean onQueryTextChange(String newText) {
bus.post(new SearchQueryUpdatedEvent(newText));
return false;
}
public boolean onQueryTextSubmit(String query) {
// do nothing, the correct results should be available from the realtime text updates
return false;
}
protected boolean isAlwaysExpanded() {
return false;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_CHANGE_PASSWORD:
showChangePasswordDialog();
return true;
case R.id.action_bar_add:
showAddEntryDialog();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void showChangePasswordDialog() {
LayoutInflater factory = LayoutInflater.from(this);
final View textEntryView = factory.inflate(R.layout.change_password_alert_dialog, null);
new AlertDialog.Builder(this).setView(textEntryView).setPositiveButton("Update", new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String password = ((EditText) textEntryView.findViewById(R.id.password_entry_1)).getText().toString();
final PasswordHasher.Keys keys = PasswordHasher.hash(password, Model.currentModel.scryptParameters);
Model.currentModel.keys = keys;
new StubGoogleDriveIO(PasswordEntryListActivity.this).saveModelAndStartSyncFromDiskToGoogleDrive(new Runnable() {
public void run() {
PasswordEntryListActivity.this.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(PasswordEntryListActivity.this, "Password updated", Toast.LENGTH_LONG).show();
}
});
}
});
}
}).setNegativeButton("Not now", null).setCancelable(true).setOnCancelListener(null).show();
}
private void showAddEntryDialog() {
LayoutInflater factory = LayoutInflater.from(this);
final View addEntryView = factory.inflate(R.layout.add_entry_dialog, null);
new AlertDialog.Builder(this).setView(addEntryView).setPositiveButton("Add", new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String title = ((EditText)addEntryView.findViewById(R.id.title)).getText().toString();
String userName = ((EditText)addEntryView.findViewById(R.id.username)).getText().toString();
String password = ((EditText)addEntryView.findViewById(R.id.password)).getText().toString();
Model.currentModel.addPasswordEntry(new PasswordEntry(title, userName, password));
bus.post(new PasswordListUpdatedEvent());
}
}).setNegativeButton("Cancel", null).setCancelable(true).setOnCancelListener(null).show();
}
}