/**
* Copyright (C) 2013 Jonathan Gillett, Joseph Heron
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.tinfoil.sms.settings;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.NavUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.bugsense.trace.BugSenseHandler;
import com.tinfoil.sms.R;
import com.tinfoil.sms.crypto.KeyExchange;
import com.tinfoil.sms.dataStructures.Number;
import com.tinfoil.sms.dataStructures.TrustedContact;
import com.tinfoil.sms.database.DBAccessor;
import com.tinfoil.sms.utility.SMSUtility;
/**
* TODO add remove initiator flag.
* An activity used to edit a single number from a given contact.
*
* This activity returns a AddContact.RESULT_CODE if changes have been made and
* the list needs to be update, else returns default result code.
*
* The Extras are included are:
* EditNumber.UPDATE, whether the number has been updated or not
* EditNumber.NUMBER, the number that has been edited
* AddContact.POSITION, the position of the given number in the contact's
* numbers array list, -1 if it is a new number
* EditNumber.ADD, whether the number is to update or deleted
*/
public class EditNumber extends Activity{
public static final String UPDATE = "update";
public static final String NUMBER = "number";
public static final String ADD = "add";
public static final String DELETE = "delete";
public static final String IS_DELETED = "is_deleted";
private EditText phoneNumber;
private EditText sharedInfo1;
private EditText sharedInfo2;
private EditText bookPath;
private EditText bookInverse;
private EditText pubKey;
private Number number;
private String originalNumber;
private static int position;
private String name;
public static final int SHARED_INFO_MIN = 6;
public static final int SHARED_INFO_MAX = 128;
private ArrayList<RadioButton> keyExchangeSetting;
private DBAccessor dba;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.edit_number);
setupActionBar();
dba = new DBAccessor(this);
keyExchangeSetting = new ArrayList<RadioButton>();
keyExchangeSetting.add((RadioButton)this.findViewById(R.id.auto_exchange));
keyExchangeSetting.add((RadioButton)this.findViewById(R.id.manual_exchange));
keyExchangeSetting.add((RadioButton)this.findViewById(R.id.ignore_exchange));
phoneNumber = (EditText)findViewById(R.id.phone_number);
sharedInfo1 = (EditText)findViewById(R.id.shared_secret_1);
sharedInfo2 = (EditText)findViewById(R.id.shared_secret_2);
bookPath = (EditText)findViewById(R.id.book_path);
bookInverse = (EditText)findViewById(R.id.book_inverse);
pubKey = (EditText)findViewById(R.id.contact_pub_key);
boolean trusted = false;
Intent intent = this.getIntent();
/* Check if there is intent */
if(intent != null)
{
/*
* Get the extra values in the intent.
*/
name = intent.getStringExtra(AddContact.NAME);
originalNumber = intent.getStringExtra(AddContact.EDIT_NUMBER);
position = intent.getIntExtra(AddContact.POSITION, AddContact.NEW_NUMBER_CODE);
this.getIntent().removeExtra(AddContact.NAME);
this.getIntent().removeExtra(AddContact.EDIT_NUMBER);
this.getIntent().removeExtra(AddContact.POSITION);
}
else
{
finish();
}
/*
* Is the number a new number
*/
if(position != AddContact.NEW_NUMBER_CODE)
{
/*
* Initialize the values to be adjusted
*/
number = dba.getNumber(SMSUtility.format(originalNumber));
// Number was not found in the database
if(number == null) {
finish();
}
phoneNumber.setText(originalNumber);
sharedInfo1.setText(number.getSharedInfo1());
sharedInfo2.setText(number.getSharedInfo2());
bookPath.setText(number.getBookPath());
bookInverse.setText(number.getBookInversePath());
keyExchangeSetting.get(number.getKeyExchangeFlag()).setChecked(true);
if(dba.isTrustedContact(originalNumber))
{
trusted = true;
}
}
else
{
/*
* Initialize the values to the default values
*/
bookPath.setText(DBAccessor.DEFAULT_BOOK_PATH);
bookInverse.setText(DBAccessor.DEFAULT_BOOK_INVERSE_PATH);
}
if(trusted)
{
TextView pubKeyView = (TextView)findViewById(R.id.view_pub_key);
sharedInfo1.setEnabled(false);
sharedInfo2.setEnabled(false);
phoneNumber.setEnabled(false);
//pubKeyTitle.setVisibility(TextView.VISIBLE);
pubKey.setVisibility(EditText.INVISIBLE);
pubKeyView.setVisibility(TextView.VISIBLE);
pubKeyView.setText(new String(number.getPublicKey()));
// Find the radio button group view
View rg = (View)findViewById(R.id.radioGroup1);
/*
* Set it so now it is below book_inverse since the other two
* elements are invisible.
*/
RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
p.addRule(RelativeLayout.BELOW,R.id.view_pub_key);
rg.setLayoutParams(p);
}
}
/**
* The onClick action for when the user clicks on save information
* @param view The view that is involved
*/
public void saveNumberInfo(View view)
{
String s1 = sharedInfo1.getText().toString();
String s2 = sharedInfo2.getText().toString();
if (((s1 == null || s1.length() == 0) && (s2 == null || s2.length() == 0)) ||
(SMSUtility.checksharedSecret(s1) && SMSUtility.checksharedSecret(s2)))
{
/* Is there a valid number */
if(phoneNumber.getText().toString() != null &&
phoneNumber.getText().toString().length() > 0)
{
//Number tempNumber;
TrustedContact tc = null;
if(number == null)
{
/* Number is a new number */
number = new Number(phoneNumber.getText().toString());
}
else
{
/*
* Editing the current number. Get the Number row
* for the previous number.
*/
number.setNumber(phoneNumber.getText().toString());
}
/*
* Set the updated information
*/
//String newSharedInfo1 = s1;
//String newSharedInfo2 = s2;
if(SMSUtility.isChanged(number.getSharedInfo1(), s1) ||
SMSUtility.isChanged(number.getSharedInfo2(), s2))
{
number.setInitiator(false);
}
number.setSharedInfo1(s1);
number.setSharedInfo2(s2);
number.setBookPath(bookPath.getText().toString());
number.setBookInversePath(bookInverse.getText().toString());
if(pubKey.getVisibility() == EditText.VISIBLE)
{
String key = pubKey.getText().toString().trim();
if(key != "" && key.length() > 0)
{
number.setPublicKey(key.getBytes());
}
}
int index = 0;
if(keyExchangeSetting.get(Number.AUTO).isChecked())
{
index = Number.AUTO;
}
else if(keyExchangeSetting.get(Number.MANUAL).isChecked())
{
index = Number.MANUAL;
}
else
{
index = Number.IGNORE;
}
number.setKeyExchangeFlag(index);
/*
* Update/Add the number to the database
*/
if(originalNumber != null)
{
if(position == AddContact.NEW_NUMBER_CODE)
{
if(checkUniqueNumber())
{
tc = dba.getRow(originalNumber);
tc.addNumber(number);
dba.updateRow(tc, originalNumber);
}
else
{
finish();
}
}
else
{
if(number.getNumber().equals(originalNumber) || checkUniqueNumber())
{
dba.updateNumberRow(number, originalNumber, 0);
}
else
{
finish();
}
}
}
else
{
if(checkUniqueNumber())
{
tc = new TrustedContact();
tc.addNumber(number);
dba.addRow(tc);
}
else
{
finish();
}
}
Intent data = new Intent();
/*
* Return intent with given parameters
*/
if(originalNumber != null && number.getNumber().equalsIgnoreCase(originalNumber))
{
data.putExtra(EditNumber.UPDATE, false);
}
else
{
data.putExtra(EditNumber.UPDATE, true);
}
data.putExtra(EditNumber.NUMBER, number.getNumber());
data.putExtra(AddContact.POSITION, position);
data.putExtra(EditNumber.ADD, true);
data.putExtra(AddContact.NAME, name);
/*
* Set result code to identify that whether the list in
* ManageContactsActivity.
*/
EditNumber.this.setResult(AddContact.REQUEST_CODE, data);
EditNumber.this.finish();
}
}
else
{
//TODO create a better notification for invalid shared secrets (since they key board blocks them)
Toast.makeText(this, this.getString(R.string.shared_secret)
+ SHARED_INFO_MIN, Toast.LENGTH_LONG).show();
}
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
final MenuInflater inflater = this.getMenuInflater();
inflater.inflate(R.menu.edit_text_menu, menu);
return true;
}
private String[] fileNames;
private AlertDialog popup_alert;
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
NavUtils.navigateUpFromSameTask(this);
return true;
case R.id.import_key:
importKey();
return true;
case R.id.delete:
deleteNumber();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public static void exportOrSend(Context context,final Number number)
{
final DBAccessor dba = new DBAccessor(context);
final String name = dba.getRow(number.getNumber()).getName();
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(context.getString(R.string.key_exchange_respond)
+ " " + name + ", " + number.getNumber() + "?")
.setCancelable(true)
.setPositiveButton(R.string.sms_option, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dba.addMessageToQueue(number.getNumber(),
KeyExchange.sign(number, dba,
SMSUtility.user), true);
}})
.setNegativeButton(R.string.export_option, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
UserKeySettings.writeToFile(number.getNumber(), KeyExchange.sign(number,
dba, SMSUtility.user));
}
});
AlertDialog alert = builder.create();
alert.show();
}
private boolean checkUniqueNumber()
{
if(dba.inDatabase(number.getNumber()))
{
Intent returnIntent = new Intent();
setResult(RESULT_CANCELED, returnIntent);
Toast.makeText(this, R.string.number_exists, Toast.LENGTH_SHORT).show();
return false;
}
return true;
}
/**
* Set up the {@link android.app.ActionBar}, if the API is available.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void setupActionBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
getActionBar().setDisplayHomeAsUpEnabled(true);
}
}
private void deleteNumber()
{
Intent data = new Intent();
if (originalNumber == null || originalNumber == "" || dba.getRow(originalNumber) == null)
{
// User deleted the number so
finish();
}
else {
if (dba.getRow(originalNumber).getNumbers().size() == 1)
{
dba.removeRow(originalNumber);
data.putExtra(EditNumber.IS_DELETED, true);
}
else
{
dba.deleteNumber(originalNumber);
}
}
data.putExtra(EditNumber.UPDATE, true);
data.putExtra(AddContact.POSITION, position);
data.putExtra(EditNumber.ADD, true);
data.putExtra(EditNumber.DELETE, originalNumber);
setResult(AddContact.REQUEST_CODE, data);
finish();
}
private void importKey()
{
if (originalNumber == null || originalNumber == "" || dba.getRow(originalNumber) == null)
{
Toast.makeText(getApplicationContext(), R.string.number_must_exist, Toast.LENGTH_LONG).show();
}
else
{
if(SMSUtility.isMediaAvailable() && number != null && SMSUtility.checksharedSecret(number.getSharedInfo1())
&& SMSUtility.checksharedSecret(number.getSharedInfo2()))
{
final File root = Environment.getExternalStorageDirectory();
File dir = new File(root.getAbsolutePath() + UserKeySettings.path + "/");
File[] contents = dir.listFiles();
if(contents.length > 0) {
fileNames = new String[contents.length];
for (int i = 0; i < contents.length; i++)
{
fileNames[i] = contents[i].getAbsolutePath();
}
final AlertDialog.Builder popup_builder = new AlertDialog.Builder(this);
popup_builder.setTitle(R.string.export_number_title)
.setCancelable(true)
.setSingleChoiceItems(fileNames, 0, new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface dialog, final int which) {
popup_alert.dismiss();
String selectedFile = (String) fileNames[which];
if(selectedFile != null)
{
StringBuilder sb = new StringBuilder();
File pubKey = new File(selectedFile);
try {
FileInputStream f = new FileInputStream(pubKey);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(f));
String line;
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
bufferedReader.close();
f.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
BugSenseHandler.sendExceptionMessage("Type", "Import Public Key Not Found Error", e);
} catch (IOException e) {
e.printStackTrace();
BugSenseHandler.sendExceptionMessage("Type", "Import Public Key Error", e);
}
String keyExchangeMessage = sb.toString();
if(KeyExchange.isKeyExchange(keyExchangeMessage))
{
if(KeyExchange.verify(number, keyExchangeMessage))
{
Log.v("Key Exchange", "Exchange Key Message Received");
number.setPublicKey(KeyExchange.encodedPubKey(keyExchangeMessage));
number.setSignature(KeyExchange.encodedSignature(keyExchangeMessage));
dba.updateNumberRow(number, number.getNumber(), 0);
Toast.makeText(EditNumber.this, R.string.key_exchange_import, Toast.LENGTH_SHORT).show();
if(!number.isInitiator())
{
Log.v("Key Exchange", "Not Initiator");
//MessageService.dba.addMessageToQueue(number.getNumber(),
// KeyExchange.sign(number), true);
exportOrSend(EditNumber.this, number);
}
EditNumber.this.setResult(AddContact.UPDATED_NUMBER);
}
}
}
}
});
popup_alert = popup_builder.create();
popup_alert.show();
}
else {
Toast.makeText(this, R.string.empty_key_exchange_list, Toast.LENGTH_SHORT).show();
}
}
}
}
}