Fixed build process

This commit is contained in:
str4d
2014-06-13 10:28:25 +00:00
parent 5008b980d1
commit 0d6e1bd1db
146 changed files with 20 additions and 301 deletions

109
app/botejars.xml Normal file
View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="botejars-imported">
<property file="local.properties" />
<property name="jar.libs.dir" value="libs" />
<!-- override with i2pbase=path/to/source in local.properties -->
<property environment="env"/>
<condition property="i2pbase" value="${env.I2P}">
<isset property="env.I2P"/>
</condition>
<property name="i2plib" value="${i2pbase}/lib"/>
<!-- override with botesrc=path/to/source in local.properties -->
<property name="botesrc" value="../i2p.i2p-bote" />
<property name="botebase" location="${botesrc}" />
<property name="botelib" location="${botebase}/WebContent/WEB-INF/lib" />
<available file="${botebase}" property="bote.present" />
<fail message="I2P-Bote source directory ${botebase} was not found. Install it there or set botesrc=/path/to/source in local.properties" >
<condition>
<not>
<isset property="bote.present" />
</not>
</condition>
</fail>
<echo message="Using I2P-Bote source at ${botebase}" />
<target name="precompile" depends="copyrouterlibs,copybotelibs" />
<target name="preclean">
<delete verbose="${verbose}">
<fileset dir="${jar.libs.dir}">
<exclude name="mail.jar" />
<exclude name="additionnal.jar" />
<exclude name="activation.jar" />
<exclude name="tokenautocomplete.jar" />
</fileset>
</delete>
<ant dir="${botebase}" inheritall="false" useNativeBasedir="true" >
<target name="clean" />
</ant>
</target>
<!-- new rules -->
<target name="checki2pbase">
<fail unless="i2pbase" message="The I2P environment variable is not set.${line.separator}It must point to an I2P installation. It is usually a path of the form ${line.separator}/xxx/yyy/i2p."/>
</target>
<target name="copyrouterlibs" depends="checki2pbase" >
<!-- core -->
<!-- remove classes that are overridden -->
<!-- lots of unneeded stuff could be deleted here -->
<jar destfile="${jar.libs.dir}/i2p.jar" >
<zipfileset src="${i2plib}/i2p.jar" >
<exclude name="net/i2p/util/LogWriter.class" />
</zipfileset>
</jar>
<!-- router -->
<copy file="${i2plib}/router.jar" todir="${jar.libs.dir}" />
<!-- streaming -->
<copy file="${i2plib}/mstreaming.jar" todir="${jar.libs.dir}" />
<copy file="${i2plib}/streaming.jar" todir="${jar.libs.dir}" />
</target>
<available property="have.bote" file="${botelib}/i2pbote.jar" />
<target name="buildbote" unless="have.bote">
<ant dir="${botebase}" inheritall="false" useNativeBasedir="true" >
<property name="i2pbase" value="${i2pbase}" />
<target name="jar" />
</ant>
</target>
<target name="copybotelibs" depends="buildbote">
<!-- bote -->
<jar destfile="${jar.libs.dir}/i2pbote.jar" >
<!-- remove classes that are overridden or stubbed out -->
<zipfileset src="${botelib}/i2pbote.jar" >
<exclude name="i2p/bote/imap/" />
<exclude name="i2p/bote/service/seedless/" />
<exclude name="i2p/bote/smtp/" />
</zipfileset>
<!-- Include necessary deps for stubbed-out classes -->
<zipfileset src="${botelib}/commons-lang-2.6.jar" >
<include name="org/apache/commons/lang/exception/Nestable.class" />
<include name="org/apache/commons/lang/exception/NestableException.class" />
</zipfileset>
<zipfileset src="${botelib}/commons-configuration-1.6.jar" >
<include name="org/apache/commons/configuration/ConfigurationException.class" />
</zipfileset>
</jar>
<!-- bote deps -->
<copy todir="${jar.libs.dir}">
<fileset dir="${botelib}">
<include name="bcprov-ecc-jdk16-146.jar" />
<!-- Regular JavaMail currently doesn't work on Android
<include name="mailapi.jar" />-->
<include name="lzma-9.20.jar" />
<include name="ntruenc-1.2.jar" />
<include name="flexi-gmss-1.7p1.jar" />
<include name="scrypt-1.4.0.jar" />
</fileset>
</copy>
</target>
</project>

27
app/build.gradle Normal file
View File

@@ -0,0 +1,27 @@
apply plugin: 'android'
android {
compileSdkVersion 19
buildToolsVersion "19.0.3"
defaultConfig {
minSdkVersion 9
targetSdkVersion 19
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
ant.importBuild('botejars.xml')
preBuild.dependsOn precompile
clean.dependsOn preclean
dependencies {
compile 'com.android.support:appcompat-v7:+'
compile fileTree(dir: 'libs', include: '*.jar')
}

BIN
app/libs/activation.jar Normal file

Binary file not shown.

BIN
app/libs/additionnal.jar Normal file

Binary file not shown.

BIN
app/libs/mail.jar Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="i2p.bote.android"
android:versionCode="1"
android:versionName="0.1" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-sdk
android:minSdkVersion="9"
android:targetSdkVersion="19" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<service android:name="i2p.bote.android.service.BoteService" />
<activity
android:name="i2p.bote.android.EmailListActivity"
android:label="@string/app_name"
android:launchMode="singleTop" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="i2p.bote.android.ViewEmailActivity"
android:parentActivityName="i2p.bote.android.EmailListActivity" />
<activity
android:name="i2p.bote.android.NewEmailActivity"
android:parentActivityName="i2p.bote.android.EmailListActivity" />
<activity
android:name="i2p.bote.android.addressbook.AddressBookActivity"
android:parentActivityName="i2p.bote.android.EmailListActivity" />
<activity
android:name="i2p.bote.android.addressbook.EditContactActivity"
android:parentActivityName="i2p.bote.android.addressbook.AddressBookActivity" />
<activity
android:name="i2p.bote.android.NetworkInfoActivity"
android:parentActivityName="i2p.bote.android.EmailListActivity" />
<activity
android:name="i2p.bote.android.config.SettingsActivity"
android:parentActivityName="i2p.bote.android.EmailListActivity" />
<activity
android:name="i2p.bote.android.config.SetPasswordActivity"
android:parentActivityName="i2p.bote.android.config.SettingsActivity" />
<activity
android:name="i2p.bote.android.config.ViewIdentityActivity"
android:parentActivityName="i2p.bote.android.config.SettingsActivity" />
<activity
android:name="i2p.bote.android.config.EditIdentityActivity"
android:parentActivityName="i2p.bote.android.config.ViewIdentityActivity" />
</application>
</manifest>

View File

@@ -0,0 +1,33 @@
package net.i2p.android.router.service;
import net.i2p.android.router.service.IRouterStateCallback;
/**
* An interface for determining the state of the I2P RouterService.
*/
interface IRouterState {
/**
* This allows I2P to inform on state changes.
*/
void registerCallback(IRouterStateCallback cb);
/**
* Remove registered callback interface.
*/
void unregisterCallback(IRouterStateCallback cb);
/**
* Determines whether the RouterService has been started. If it hasn't, no
* state changes will ever occur from this RouterService instance, and the
* client should unbind and inform the user that the I2P router is not
* running (and optionally send a net.i2p.android.router.START_I2P Intent).
*/
boolean isStarted();
/**
* Get the state of the I2P router
**/
String getState();
}

View File

@@ -0,0 +1,13 @@
package net.i2p.android.router.service;
/**
* Callback interface used to send synchronous notifications of the current
* RouterService state back to registered clients. Note that this is a
* one-way interface so the server does not block waiting for the client.
*/
oneway interface IRouterStateCallback {
/**
* Called when the state of the I2P router changes
*/
void stateChanged(String newState);
}

View File

@@ -0,0 +1,448 @@
package i2p.bote.android;
import net.i2p.android.router.service.IRouterState;
import i2p.bote.I2PBote;
import i2p.bote.android.addressbook.AddressBookActivity;
import i2p.bote.android.config.SettingsActivity;
import i2p.bote.android.service.BoteService;
import i2p.bote.android.service.Init;
import i2p.bote.android.service.Init.RouterChoice;
import i2p.bote.android.util.MoveToDialogFragment;
import i2p.bote.folder.EmailFolder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ActivityManager.RunningServiceInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.app.DialogFragment;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class EmailListActivity extends ActionBarActivity implements
EmailListFragment.OnEmailSelectedListener,
MoveToDialogFragment.MoveToDialogListener {
private CharSequence mDrawerTitle;
private CharSequence mTitle;
private SharedPreferences mSharedPrefs;
/**
* Navigation drawer variables
*/
private DrawerLayout mDrawerLayout;
private RelativeLayout mDrawerOuter;
private FolderListAdapter mFolderAdapter;
private ListView mFolderList;
private TextView mNetworkStatus;
private ActionBarDrawerToggle mDrawerToggle;
RouterChoice mRouterChoice;
IRouterState mStateService = null;
private static final String SHARED_PREFS = "i2p.bote";
private static final String PREF_NAV_DRAWER_OPENED = "navDrawerOpened";
private static final String ACTIVE_FOLDER = "activeFolder";
private static final int REQUEST_START_I2P = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initialize I2P settings
InitActivities init = new InitActivities(this);
init.initialize();
// Initialize variables
mTitle = mDrawerTitle = getTitle();
mSharedPrefs = getSharedPreferences(SHARED_PREFS, 0);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerOuter = (RelativeLayout) findViewById(R.id.drawer_outer);
mFolderAdapter = new FolderListAdapter(this);
mFolderList = (ListView) findViewById(R.id.drawer);
mNetworkStatus = (TextView) findViewById(R.id.network_status);
// Set the list of folders
// TODO: This is slow, needs a loader
mFolderAdapter.setData(I2PBote.getInstance().getEmailFolders());
// Set a custom shadow that overlays the main content when the drawer opens
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
// Set the adapter for the list view
mFolderList.setAdapter(mFolderAdapter);
// Set the list's click listener
mFolderList.setOnItemClickListener(new DrawerItemClickListener());
// Enable ActionBar app icon to behave as action to toggle nav drawer
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
// Set up drawer toggle
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {
private boolean wasDragged = false;
/** Called when a drawer has settled in a completely closed state. */
public void onDrawerClosed(View view) {
// Don't mark as opened if the user closed by dragging
// but uses the action bar icon to open
wasDragged = false;
getSupportActionBar().setTitle(mTitle);
supportInvalidateOptionsMenu();
}
/** Called when a drawer has settled in a completely open state. */
public void onDrawerOpened(View view) {
if (wasDragged && !mSharedPrefs.getBoolean(PREF_NAV_DRAWER_OPENED, false)) {
SharedPreferences.Editor edit = mSharedPrefs.edit();
edit.putBoolean(PREF_NAV_DRAWER_OPENED, true);
edit.commit();
}
getSupportActionBar().setTitle(mDrawerTitle);
supportInvalidateOptionsMenu();
}
/** Called when the drawer motion state changes. */
public void onDrawerStateChanged(int newState) {
if (newState == DrawerLayout.STATE_DRAGGING)
wasDragged = true;
// Update network status
Drawable statusIcon;
switch (I2PBote.getInstance().getNetworkStatus()) {
case DELAY:
mNetworkStatus.setText(R.string.connect_delay);
statusIcon = getResources().getDrawable(android.R.drawable.presence_away);
break;
case CONNECTING:
mNetworkStatus.setText(R.string.connecting);
statusIcon = getResources().getDrawable(android.R.drawable.presence_away);
break;
case CONNECTED:
mNetworkStatus.setText(R.string.connected);
statusIcon = getResources().getDrawable(android.R.drawable.presence_online);
break;
case ERROR:
mNetworkStatus.setText(R.string.error);
statusIcon = getResources().getDrawable(android.R.drawable.presence_busy);
break;
case NOT_STARTED:
default:
mNetworkStatus.setText(R.string.not_started);
statusIcon = getResources().getDrawable(android.R.drawable.presence_offline);
}
mNetworkStatus.setCompoundDrawablesWithIntrinsicBounds(
statusIcon, null, null, null);
}
};
// Set the drawer toggle as the DrawerListener
mDrawerLayout.setDrawerListener(mDrawerToggle);
if (savedInstanceState == null) {
EmailListFragment f = EmailListFragment.newInstance("inbox");
getSupportFragmentManager().beginTransaction()
.add(R.id.list_fragment, f).commit();
mFolderList.setItemChecked(0, true);
} else {
mFolderList.setItemChecked(
savedInstanceState.getInt(ACTIVE_FOLDER), true);
}
// Set up fixed actions
findViewById(R.id.address_book).setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent ai = new Intent(EmailListActivity.this, AddressBookActivity.class);
startActivity(ai);
}
});
mNetworkStatus.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
switch (I2PBote.getInstance().getNetworkStatus()) {
case NOT_STARTED:
case DELAY:
DialogFragment df = new DialogFragment() {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.network_info_unavailable)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
return builder.create();
}
};
df.show(getSupportFragmentManager(), "noinfo");
break;
default:
Intent nii = new Intent(EmailListActivity.this, NetworkInfoActivity.class);
startActivity(nii);
}
}
});
// Open nav drawer if the user has never opened it themselves
if (!mSharedPrefs.getBoolean(PREF_NAV_DRAWER_OPENED, false))
mDrawerLayout.openDrawer(mDrawerOuter);
}
private class DrawerItemClickListener implements ListView.OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
selectItem(pos);
}
}
private void selectItem(int position) {
// Create the new fragment
EmailFolder folder = mFolderAdapter.getItem(position);
EmailListFragment f = EmailListFragment.newInstance(folder.getName());
// Insert the fragment
getSupportFragmentManager().beginTransaction()
.replace(R.id.list_fragment, f).commit();
// Highlight the selected item and close the drawer
mFolderList.setItemChecked(position, true);
mDrawerLayout.closeDrawer(mDrawerOuter);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(ACTIVE_FOLDER, mFolderList.getSelectedItemPosition());
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu
getMenuInflater().inflate(R.menu.main, menu);
if (isBoteServiceRunning())
menu.findItem(R.id.action_start_bote).setVisible(false);
else
menu.findItem(R.id.action_stop_bote).setVisible(false);
return super.onCreateOptionsMenu(menu);
}
@Override
protected void onStart() {
super.onStart();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
if (prefs.getBoolean("i2pbote.router.auto", true) ||
prefs.getString("i2pbote.router.use", "internal").equals("android")) {
// Try to bind to I2P Android
Intent i2pIntent = new Intent(IRouterState.class.getName());
i2pIntent.setClassName("net.i2p.android.router",
"net.i2p.android.router.service.RouterService");
try {
mTriedBindState = bindService(
i2pIntent, mStateConnection, BIND_AUTO_CREATE);
} catch (SecurityException e) {
// Old version of I2P Android (pre-0.9.13), cannot use
mStateService = null;
mTriedBindState = false;
}
}
}
@Override
protected void onStop() {
super.onStop();
if (mTriedBindState)
unbindService(mStateConnection);
mTriedBindState = false;
}
private boolean isBoteServiceRunning() {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (BoteService.class.getName().equals(service.service.getClassName()))
return true;
}
return false;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// The action bar home/up action should open or close the drawer.
// ActionBarDrawerToggle will take care of this.
if(mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
switch (item.getItemId()) {
case R.id.action_start_bote:
// Init from settings
Init init = new Init(this);
mRouterChoice = init.initialize(mStateService);
if (mRouterChoice == RouterChoice.ANDROID) {
try {
if (mStateService == null) {
// I2P Android not installed
// TODO: handle
} else if (!mStateService.isStarted()) {
// Ask user to start I2P Android
DialogFragment df = new DialogFragment() {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.start_i2p_android)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
Intent i = new Intent("net.i2p.android.router");
i.setAction("net.i2p.android.router.START_I2P");
EmailListActivity.this.startActivityForResult(i, REQUEST_START_I2P);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
return builder.create();
}
};
df.show(getSupportFragmentManager(), "starti2p");
} else
startBote();
} catch (RemoteException e) {
// TODO log
}
} else
startBote();
return true;
case R.id.action_stop_bote:
Intent stop = new Intent(this, BoteService.class);
stopService(stop);
supportInvalidateOptionsMenu();
return true;
case R.id.action_settings:
Intent si = new Intent(this, SettingsActivity.class);
startActivity(si);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_START_I2P) {
if (resultCode == Activity.RESULT_OK) {
startBote();
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private void startBote() {
Intent start = new Intent(this, BoteService.class);
start.putExtra(BoteService.ROUTER_CHOICE, mRouterChoice);
startService(start);
supportInvalidateOptionsMenu();
}
@Override
public void setTitle(CharSequence title) {
mTitle = title;
getSupportActionBar().setTitle(mTitle);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
mDrawerToggle.syncState();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Pass any configuration change to the drawer toggle
mDrawerToggle.onConfigurationChanged(newConfig);
}
private class InitActivities {
private final Context ctx;
private final String myDir;
public InitActivities(Context c) {
ctx = c;
// This needs to be changed so that we can have an alternative place
myDir = c.getFilesDir().getAbsolutePath();
}
void initialize() {
// Set up the locations so settings can find them
System.setProperty("i2p.dir.base", myDir);
System.setProperty("i2p.dir.config", myDir);
System.setProperty("wrapper.logfile", myDir + "/wrapper.log");
}
}
// FolderFragment.OnEmailSelectedListener
@Override
public void onEmailSelected(String folderName, String messageId) {
// In single-pane mode, simply start the detail activity
// for the selected message ID.
Intent detailIntent = new Intent(this, ViewEmailActivity.class);
detailIntent.putExtra(ViewEmailActivity.FOLDER_NAME, folderName);
detailIntent.putExtra(ViewEmailActivity.MESSAGE_ID, messageId);
startActivity(detailIntent);
}
// MoveToDialogFragment.MoveToDialogListener
@Override
public void onFolderSelected(EmailFolder newFolder) {
EmailListFragment f = (EmailListFragment) getSupportFragmentManager().findFragmentById(R.id.list_fragment);
f.onFolderSelected(newFolder);
}
//
// I2P Android helpers
//
private boolean mTriedBindState;
private ServiceConnection mStateConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
mStateService = IRouterState.Stub.asInterface(service);
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mStateService = null;
}
};
}

View File

@@ -0,0 +1,155 @@
package i2p.bote.android;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.text.DateFormat;
import java.util.List;
import javax.mail.MessagingException;
import javax.mail.Part;
import i2p.bote.android.util.BoteHelper;
import i2p.bote.email.Email;
import i2p.bote.fileencryption.PasswordException;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class EmailListAdapter extends ArrayAdapter<Email> {
private final LayoutInflater mInflater;
private SparseBooleanArray mSelectedEmails;
private EmailSelector mSelector;
private boolean mIsOutbox;
public interface EmailSelector {
public void select(int position);
}
public EmailListAdapter(Context context, EmailSelector selector, boolean isOutbox) {
super(context, android.R.layout.simple_list_item_2);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mSelectedEmails = new SparseBooleanArray();
mSelector = selector;
mIsOutbox = isOutbox;
}
public void setData(List<Email> emails) {
clear();
if (emails != null) {
for (Email email : emails) {
add(email);
}
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = mInflater.inflate(R.layout.listitem_email, parent, false);
final Email email = getItem(position);
ImageView picture = (ImageView) v.findViewById(R.id.contact_picture);
TextView subject = (TextView) v.findViewById(R.id.email_subject);
TextView from = (TextView) v.findViewById(R.id.email_from);
TextView content = (TextView) v.findViewById(R.id.email_content);
TextView sent = (TextView) v.findViewById(R.id.email_sent);
picture.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
mSelector.select(getPosition(email));
}
});
if (mSelectedEmails.get(position)) {
((ImageView) v.findViewById(R.id.email_selected)).setVisibility(View.VISIBLE);
}
try {
String fromAddress = email.getOneFromAddress();
Bitmap pic = BoteHelper.getPictureForAddress(fromAddress);
if (pic != null)
picture.setImageBitmap(pic);
subject.setText(email.getSubject());
from.setText(BoteHelper.getNameAndShortDestination(fromAddress));
if (email.getSentDate() != null)
sent.setText(DateFormat.getInstance().format(
email.getSentDate()));
List<Part> parts = email.getParts();
for (Part part : parts) {
if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
((ImageView) v.findViewById(
R.id.email_attachment)).setVisibility(View.VISIBLE);
break;
}
}
if (email.isUnread()) {
subject.setTypeface(Typeface.DEFAULT_BOLD);
from.setTypeface(Typeface.DEFAULT_BOLD);
}
TextView emailStatus = (TextView) v.findViewById(R.id.email_status);
// Set email sending status if this is the outbox,
// or set email delivery status if we sent it.
if (mIsOutbox) {
emailStatus.setText(BoteHelper.getEmailStatusText(
getContext(), email, false));
emailStatus.setVisibility(View.VISIBLE);
} else if (BoteHelper.isSentEmail(email)) {
if (email.isDelivered())
emailStatus.setCompoundDrawablesWithIntrinsicBounds(
getContext().getResources().getDrawable(
R.drawable.ic_navigation_accept),
null, null, null);
else
emailStatus.setText(email.getDeliveryPercentage() + "%");
emailStatus.setVisibility(View.VISIBLE);
}
} catch (MessagingException e) {
subject.setText("ERROR: " + e.getMessage());
} catch (PasswordException e) {
subject.setText("ERROR: " + e.getMessage());
} catch (IOException e) {
subject.setText("ERROR: " + e.getMessage());
} catch (GeneralSecurityException e) {
subject.setText("ERROR: " + e.getMessage());
}
content.setText(email.getText());
return v;
}
public void toggleSelection(int position) {
selectView(position, !mSelectedEmails.get(position));
}
public void removeSelection() {
mSelectedEmails = new SparseBooleanArray();
notifyDataSetChanged();
}
public void selectView(int position, boolean value) {
if (value)
mSelectedEmails.put(position, value);
else
mSelectedEmails.delete(position);
notifyDataSetChanged();
}
public int getSelectedCount() {
return mSelectedEmails.size();
}
public SparseBooleanArray getSelectedIds() {
return mSelectedEmails;
}
}

View File

@@ -0,0 +1,464 @@
package i2p.bote.android;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.List;
import javax.mail.Flags.Flag;
import javax.mail.MessagingException;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
import i2p.bote.I2PBote;
import i2p.bote.android.util.BetterAsyncTaskLoader;
import i2p.bote.android.util.BoteHelper;
import i2p.bote.android.util.MoveToDialogFragment;
import i2p.bote.email.Email;
import i2p.bote.fileencryption.PasswordException;
import i2p.bote.folder.EmailFolder;
import i2p.bote.folder.FolderListener;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.view.ActionMode;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
public class EmailListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<List<Email>>,
MoveToDialogFragment.MoveToDialogListener,
EmailListAdapter.EmailSelector {
public static final String FOLDER_NAME = "folder_name";
private static final int EMAIL_LIST_LOADER = 1;
OnEmailSelectedListener mCallback;
private EmailListAdapter mAdapter;
private EmailFolder mFolder;
private ActionMode mMode;
private EditText mPasswordInput;
private TextView mPasswordError;
public static EmailListFragment newInstance(String folderName) {
EmailListFragment f = new EmailListFragment();
Bundle args = new Bundle();
args.putString(FOLDER_NAME, folderName);
f.setArguments(args);
return f;
}
// Container Activity must implement this interface
public interface OnEmailSelectedListener {
public void onEmailSelected(String folderName, String messageId);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnEmailSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnEmailSelectedListener");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
String folderName = getArguments().getString(FOLDER_NAME);
mFolder = BoteHelper.getMailFolder(folderName);
mAdapter = new EmailListAdapter(getActivity(), this,
BoteHelper.isOutbox(mFolder));
setListAdapter(mAdapter);
// Set up CAB
mMode = null;
getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
onListItemSelect(position);
return true;
}
});
if (mFolder == null) {
setEmptyText(getResources().getString(
R.string.folder_does_not_exist));
getActivity().setTitle(getResources().getString(R.string.app_name));
} else {
getActivity().setTitle(
BoteHelper.getFolderDisplayName(getActivity(), mFolder));
if (I2PBote.getInstance().isPasswordRequired()) {
// Request a password from the user.
requestPassword();
} else {
// Password is cached, or not set.
initializeList();
}
}
}
/**
* Request the password from the user, and try it.
*/
private void requestPassword() {
LayoutInflater li = LayoutInflater.from(getActivity());
View promptView = li.inflate(R.layout.dialog_password, null);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(promptView);
mPasswordInput = (EditText) promptView.findViewById(R.id.passwordInput);
mPasswordError = (TextView) promptView.findViewById(R.id.passwordError);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
InputMethodManager imm = (InputMethodManager) EmailListFragment.this
.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mPasswordInput.getWindowToken(), 0);
dialog.dismiss();
new PasswordWaiter().execute();
}
}).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
setEmptyText(getResources().getString(
R.string.not_authed));
dialog.cancel();
}
});
AlertDialog passwordDialog = builder.create();
passwordDialog.show();
}
private class PasswordWaiter extends AsyncTask<Void, Void, String> {
private final ProgressDialog dialog = new ProgressDialog(EmailListFragment.this.getActivity());
protected void onPreExecute() {
dialog.setMessage(getResources().getString(
R.string.checking_password));
dialog.setCancelable(false);
dialog.show();
}
protected String doInBackground(Void... params) {
try {
if (BoteHelper.tryPassword(mPasswordInput.getText().toString()))
return null;
else {
cancel(false);
return getResources().getString(
R.string.password_incorrect);
}
} catch (IOException e) {
cancel(false);
return getResources().getString(
R.string.password_file_error);
} catch (GeneralSecurityException e) {
cancel(false);
return getResources().getString(
R.string.password_file_error);
}
}
protected void onCancelled(String result) {
dialog.dismiss();
requestPassword();
mPasswordError.setText(result);
mPasswordError.setVisibility(View.VISIBLE);
}
protected void onPostExecute(String result) {
// Password is valid
initializeList();
dialog.dismiss();
}
}
/**
* Start loading the list of emails from this folder.
* Only called when we have a password cached, or no
* password is required.
*/
private void initializeList() {
setListShown(false);
setEmptyText(getResources().getString(
R.string.folder_empty));
getLoaderManager().initLoader(EMAIL_LIST_LOADER, null, this);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.email_list, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_new_email:
Intent nei = new Intent(getActivity(), NewEmailActivity.class);
startActivity(nei);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onListItemClick(ListView parent, View view, int pos, long id) {
super.onListItemClick(parent, view, pos, id);
if (mMode == null) {
mCallback.onEmailSelected(
mFolder.getName(), mAdapter.getItem(pos).getMessageID());
} else
onListItemSelect(pos);
}
private void onListItemSelect(int position) {
mAdapter.toggleSelection(position);
boolean hasCheckedElement = mAdapter.getSelectedCount() > 0;
if (hasCheckedElement && mMode == null) {
boolean unread = mAdapter.getItem(position).isUnread();
mMode = ((ActionBarActivity) getActivity()).startSupportActionMode(new ModeCallback(unread));
} else if (!hasCheckedElement && mMode != null) {
mMode.finish();
}
if (mMode != null)
mMode.setTitle(getResources().getString(
R.string.items_selected, mAdapter.getSelectedCount()));
}
private final class ModeCallback implements ActionMode.Callback {
private boolean areUnread;
public ModeCallback(boolean unread) {
super();
this.areUnread = unread;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// Respond to clicks on the actions in the CAB
switch (item.getItemId()) {
case R.id.action_delete:
SparseBooleanArray toDelete = mAdapter.getSelectedIds();
for (int i = (toDelete.size() - 1); i >= 0; i--) {
if (toDelete.valueAt(i)) {
Email email = mAdapter.getItem(toDelete.keyAt(i));
// The Loader will update mAdapter
I2PBote.getInstance().deleteEmail(mFolder, email.getMessageID());
}
}
mode.finish();
return true;
case R.id.action_mark_read:
case R.id.action_mark_unread:
SparseBooleanArray selected = mAdapter.getSelectedIds();
for (int i = (selected.size() - 1); i >= 0; i--) {
if (selected.valueAt(i)) {
Email email = mAdapter.getItem(selected.keyAt(i));
try {
// The Loader will update mAdapter
mFolder.setNew(email, !areUnread);
} catch (PasswordException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
areUnread = !areUnread;
mMode.invalidate();
return true;
case R.id.action_move_to:
DialogFragment f = MoveToDialogFragment.newInstance(mFolder);
f.show(getFragmentManager(), "moveTo");
return true;
default:
return false;
}
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Inflate the menu for the CAB
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.email_list_context, menu);
if (BoteHelper.isOutbox(mFolder)) {
menu.findItem(R.id.action_mark_read).setVisible(false);
menu.findItem(R.id.action_mark_unread).setVisible(false);
}
// Only allow moving from the trash
// TODO change this when user folders are implemented
if (!BoteHelper.isTrash(mFolder))
menu.findItem(R.id.action_move_to).setVisible(false);
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
// Here you can make any necessary updates to the activity when
// the CAB is removed.
mAdapter.removeSelection();
if (mode == mMode)
mMode = null;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// Here you can perform updates to the CAB due to
// an invalidate() request
if (!BoteHelper.isOutbox(mFolder)) {
menu.findItem(R.id.action_mark_read).setVisible(areUnread);
menu.findItem(R.id.action_mark_unread).setVisible(!areUnread);
}
return true;
}
}
// Called by EmailListActivity.onFolderSelected()
public void onFolderSelected(EmailFolder newFolder) {
SparseBooleanArray toMove = mAdapter.getSelectedIds();
for (int i = (toMove.size() - 1); i >= 0; i--) {
if (toMove.valueAt(i)) {
Email email = mAdapter.getItem(toMove.keyAt(i));
mFolder.move(email, newFolder);
}
}
mMode.finish();
}
// LoaderManager.LoaderCallbacks<List<Email>>
public Loader<List<Email>> onCreateLoader(int id, Bundle args) {
return new EmailListLoader(getActivity(), mFolder);
}
private static class EmailListLoader extends BetterAsyncTaskLoader<List<Email>> implements
FolderListener {
private EmailFolder mFolder;
public EmailListLoader(Context context, EmailFolder folder) {
super(context);
mFolder = folder;
}
@Override
public List<Email> loadInBackground() {
List<Email> emails = null;
try {
emails = BoteHelper.getEmails(mFolder, null, true);
} catch (PasswordException pe) {
// XXX: Should not get here.
}
return emails;
}
protected void onStartMonitoring() {
mFolder.addFolderListener(this);
}
protected void onStopMonitoring() {
mFolder.removeFolderListener(this);
}
protected void releaseResources(List<Email> data) {
}
// FolderListener
@Override
public void elementAdded(String messageId) {
onContentChanged();
}
@Override
public void elementUpdated() {
onContentChanged();
}
@Override
public void elementRemoved(String messageId) {
onContentChanged();
}
}
public void onLoadFinished(Loader<List<Email>> loader,
List<Email> data) {
// Clear recent flags
for (Email email : data)
try {
email.setFlag(Flag.RECENT, false);
} catch (MessagingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mAdapter.setData(data);
try {
getActivity().setTitle(
BoteHelper.getFolderDisplayNameWithNew(getActivity(), mFolder));
} catch (PasswordException e) {
// Should not get here.
Log log = I2PAppContext.getGlobalContext().logManager().getLog(EmailListFragment.class);
if (log.shouldLog(Log.WARN))
log.warn("Email list loader finished, but password is no longer cached", e);
}
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
public void onLoaderReset(Loader<List<Email>> loader) {
mAdapter.setData(null);
getActivity().setTitle(
BoteHelper.getFolderDisplayName(getActivity(), mFolder));
}
// EmailListAdapter.EmailSelector
public void select(int position) {
onListItemSelect(position);
}
}

View File

@@ -0,0 +1,70 @@
package i2p.bote.android;
import java.util.List;
import i2p.bote.android.util.BoteHelper;
import i2p.bote.fileencryption.PasswordException;
import i2p.bote.folder.EmailFolder;
import i2p.bote.folder.FolderListener;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
public class FolderListAdapter extends ArrayAdapter<EmailFolder> implements FolderListener {
private final LayoutInflater mInflater;
public FolderListAdapter(Context context) {
super(context, android.R.layout.simple_list_item_2);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setData(List<EmailFolder> folders) {
// Remove previous FolderListeners
for (int i = 0; i < getCount(); i++) {
getItem(i).removeFolderListener(this);
}
clear();
if (folders != null) {
for (EmailFolder folder : folders) {
add(folder);
folder.addFolderListener(this);
}
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = mInflater.inflate(R.layout.listitem_folder, parent, false);
EmailFolder folder = getItem(position);
TextView name = (TextView) v.findViewById(R.id.folder_name);
try {
name.setText(BoteHelper.getFolderDisplayNameWithNew(getContext(), folder));
} catch (PasswordException e) {
// Password fetching is handled in EmailListFragment
name.setText(BoteHelper.getFolderDisplayName(getContext(), folder));
}
return v;
}
// FolderListener
@Override
public void elementAdded(String messageId) {
notifyDataSetChanged();
}
@Override
public void elementUpdated() {
notifyDataSetChanged();
}
@Override
public void elementRemoved(String messageId) {
notifyDataSetChanged();
}
}

View File

@@ -0,0 +1,21 @@
package i2p.bote.android;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
public class NetworkInfoActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.compose);
// Enable ActionBar app icon to behave as action to go back
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (savedInstanceState == null) {
NetworkInfoFragment f = new NetworkInfoFragment();
getSupportFragmentManager().beginTransaction()
.add(android.R.id.content, f).commit();
}
}
}

View File

@@ -0,0 +1,52 @@
package i2p.bote.android;
import java.util.Collection;
import java.util.Set;
import net.i2p.data.Destination;
import i2p.bote.I2PBote;
import i2p.bote.Util;
import i2p.bote.network.BannedPeer;
import i2p.bote.network.DhtPeerStats;
import i2p.bote.network.RelayPeer;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class NetworkInfoFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_network_info, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Destination dest = I2PBote.getInstance().getLocalDestination();
if (dest != null)
((TextView) view.findViewById(R.id.local_destination)).setText(
Util.toBase32(dest));
DhtPeerStats dhtStats = I2PBote.getInstance().getDhtStats();
if (dhtStats != null)
((TextView) view.findViewById(R.id.kademlia_peers)).setText(
"" + dhtStats.getData().size());
Set<RelayPeer> relayPeers = I2PBote.getInstance().getRelayPeers();
((TextView) view.findViewById(R.id.relay_peers)).setText(
"" + relayPeers.size());
Collection<BannedPeer> bannedPeers = I2PBote.getInstance().getBannedPeers();
((TextView) view.findViewById(R.id.banned_peers)).setText(
"" + bannedPeers.size());
Exception e = I2PBote.getInstance().getConnectError();
if (e != null)
((TextView) view.findViewById(R.id.error)).setText(e.toString());
}
}

View File

@@ -0,0 +1,38 @@
package i2p.bote.android;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.Toast;
public class NewEmailActivity extends ActionBarActivity implements
NewEmailFragment.Callbacks {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.compose);
// Enable ActionBar app icon to behave as action to go back
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (savedInstanceState == null) {
String quoteMsgFolder = null;
String quoteMsgId = null;
Bundle args = getIntent().getExtras();
if (args != null) {
quoteMsgFolder = args.getString(NewEmailFragment.QUOTE_MSG_FOLDER);
quoteMsgId = args.getString(NewEmailFragment.QUOTE_MSG_ID);
}
NewEmailFragment f = NewEmailFragment.newInstance(quoteMsgFolder, quoteMsgId);
getSupportFragmentManager().beginTransaction()
.add(android.R.id.content, f).commit();
}
}
// NewEmailFragment.Callbacks
public void onTaskFinished() {
Toast.makeText(this, R.string.email_queued_for_sending,
Toast.LENGTH_SHORT).show();
finish();
}
}

View File

@@ -0,0 +1,372 @@
package i2p.bote.android;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import com.tokenautocomplete.FilteredArrayAdapter;
import net.i2p.data.DataFormatException;
import i2p.bote.I2PBote;
import i2p.bote.android.util.BoteHelper;
import i2p.bote.android.util.ContactsCompletionView;
import i2p.bote.android.util.Person;
import i2p.bote.email.Attachment;
import i2p.bote.email.Email;
import i2p.bote.email.EmailIdentity;
import i2p.bote.fileencryption.PasswordException;
import i2p.bote.packet.dht.Contact;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
public class NewEmailFragment extends Fragment {
private Callbacks mCallbacks = sDummyCallbacks;
public interface Callbacks {
public void onTaskFinished();
}
private static Callbacks sDummyCallbacks = new Callbacks() {
public void onTaskFinished() {};
};
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof Callbacks))
throw new IllegalStateException("Activity must implement fragment's callbacks.");
mCallbacks = (Callbacks) activity;
}
@Override
public void onDetach() {
super.onDetach();
mCallbacks = sDummyCallbacks;
}
public static final String QUOTE_MSG_FOLDER = "sender";
public static final String QUOTE_MSG_ID = "recipient";
private String mSenderKey;
Spinner mSpinner;
int mDefaultPos;
ArrayAdapter<Person> mAdapter;
ContactsCompletionView mRecipients;
EditText mSubject;
EditText mContent;
public static NewEmailFragment newInstance(String quoteMsgFolder, String quoteMsgId) {
NewEmailFragment f = new NewEmailFragment();
Bundle args = new Bundle();
args.putString(QUOTE_MSG_FOLDER, quoteMsgFolder);
args.putString(QUOTE_MSG_ID, quoteMsgId);
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_new_email, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
String quoteMsgFolder = getArguments().getString(QUOTE_MSG_FOLDER);
String quoteMsgId = getArguments().getString(QUOTE_MSG_ID);
Email origEmail = null;
String recipientAddr = null;
String origSubject = null;
String origContent = null;
String origFrom = null;
try {
origEmail = BoteHelper.getEmail(quoteMsgFolder, quoteMsgId);
if (origEmail != null) {
mSenderKey = BoteHelper.extractEmailDestination(
BoteHelper.getOneLocalRecipient(origEmail).toString());
recipientAddr = BoteHelper.getNameAndDestination(
origEmail.getReplyAddress(I2PBote.getInstance().getIdentities()));
origSubject = origEmail.getSubject();
origContent = origEmail.getText();
origFrom = BoteHelper.getShortSenderName(origEmail.getOneFromAddress(), 50);
}
} catch (PasswordException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (MessagingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mSpinner = (Spinner) view.findViewById(R.id.sender_spinner);
IdentityAdapter identities = new IdentityAdapter(getActivity());
mSpinner.setAdapter(identities);
mSpinner.setSelection(mDefaultPos);
List<Person> contacts = new ArrayList<Person>();
try {
for (Contact contact : I2PBote.getInstance().getAddressBook().getAll()) {
contacts.add(new Person(contact.getName(), contact.getBase64Dest()));
}
} catch (PasswordException e) {
// TODO handle
e.printStackTrace();
}
mAdapter = new FilteredArrayAdapter<Person>(getActivity(), android.R.layout.simple_list_item_1, contacts) {
@Override
protected boolean keepObject(Person obj, String mask) {
mask = mask.toLowerCase(Locale.US);
return obj.getName().toLowerCase(Locale.US).startsWith(mask) || obj.getAddress().toLowerCase(Locale.US).startsWith(mask);
}
};
mRecipients = (ContactsCompletionView) view.findViewById(R.id.recipients);
mRecipients.setAdapter(mAdapter);
if (recipientAddr != null) {
String name = BoteHelper.extractName(recipientAddr);
String address = BoteHelper.extractEmailDestination(recipientAddr);
if (address == null) { // Assume external address
address = recipientAddr;
if (name.isEmpty())
name = address;
} else if (name.isEmpty()) // Dest with no name
name = address.substring(0, 5);
mRecipients.addObject(new Person(name, address));
}
mSubject = (EditText) view.findViewById(R.id.subject);
mContent = (EditText) view.findViewById(R.id.message);
boolean hide = I2PBote.getInstance().getConfiguration().getHideLocale();
if (origSubject != null) {
String responsePrefix = getResources().getString(
hide ? R.string.response_prefix_re_hide
: R.string.response_prefix_re);
if (!origSubject.startsWith(responsePrefix))
origSubject = responsePrefix + " " + origSubject;
mSubject.setText(origSubject);
}
if (origContent != null) {
StringBuilder quotation = new StringBuilder();
quotation.append("\n\n");
quotation.append(getResources().getString(
hide ? R.string.response_quote_wrote
: R.string.response_quote_wrote_hide,
origFrom));
String[] lines = origContent.split("\r?\n|\r");
for (String line: lines)
quotation = quotation.append("\n> ").append(line);
mContent.setText(quotation);
}
if (savedInstanceState == null) {
mRecipients.setPrefix(getResources().getString(R.string.email_to) + " ");
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.new_email, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_send_email:
if (sendEmail())
mCallbacks.onTaskFinished();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private boolean sendEmail() {
Email email = new Email(I2PBote.getInstance().getConfiguration().getIncludeSentTime());
try {
// Set sender
EmailIdentity sender = (EmailIdentity) mSpinner.getSelectedItem();
InternetAddress ia = new InternetAddress(
sender == null ? "Anonymous" :
BoteHelper.getNameAndDestination(sender.getKey()));
email.setFrom(ia);
// We must continue to set "Sender:" even with only one mailbox
// in "From:", which is against RFC 2822 but required for older
// Bote versions to see a sender (and validate the signature).
email.setSender(ia);
for (Object obj : mRecipients.getObjects()) {
Person person = (Person) obj;
email.addRecipient(Message.RecipientType.TO, new InternetAddress(
person.getAddress(), person.getName()));
}
// Check that we have someone to send to
Address[] rcpts = email.getAllRecipients();
if (rcpts == null || rcpts.length == 0) {
// No recipients
DialogFragment df = new DialogFragment() {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.add_one_recipient)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
return builder.create();
}
};
df.show(getActivity().getSupportFragmentManager(), "norecipients");
return false;
}
email.setSubject(mSubject.getText().toString(), "UTF-8");
// Set the text and add attachments
email.setContent(mContent.getText().toString(), (List<Attachment>) null);
// Send the email
I2PBote.getInstance().sendEmail(email);
return true;
} catch (PasswordException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (DataFormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (MessagingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
private class IdentityAdapter extends ArrayAdapter<EmailIdentity> {
private LayoutInflater mInflater;
public IdentityAdapter(Context context) {
super(context, android.R.layout.simple_spinner_item);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
try {
Collection<EmailIdentity> identities = I2PBote.getInstance().getIdentities().getAll();
mDefaultPos = 0;
for (EmailIdentity identity : identities) {
add(identity);
if ((mSenderKey == null && identity.isDefaultIdentity()) ||
(mSenderKey != null && identity.getKey().equals(mSenderKey)))
mDefaultPos = getPosition(identity);
}
} catch (PasswordException e) {
// TODO Handle
e.printStackTrace();
} catch (IOException e) {
// TODO Handle
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Handle
e.printStackTrace();
}
}
@Override
public EmailIdentity getItem(int position) {
if (position > 0)
return super.getItem(position - 1);
else
return null;
}
@Override
public int getPosition(EmailIdentity item) {
if (item != null)
return super.getPosition(item) + 1;
else
return 0;
}
@Override
public int getCount() {
return super.getCount() + 1;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v;
if (convertView == null)
v = mInflater.inflate(android.R.layout.simple_spinner_item, parent, false);
else
v = convertView;
setViewText(v, position);
return v;
}
@Override
public View getDropDownView (int position, View convertView, ViewGroup parent) {
View v;
if (convertView == null)
v = mInflater.inflate(android.R.layout.simple_spinner_dropdown_item, parent, false);
else
v = convertView;
setViewText(v, position);
return v;
}
private void setViewText(View v, int position) {
TextView text = (TextView) v.findViewById(android.R.id.text1);
EmailIdentity identity = getItem(position);
if (identity == null)
text.setText("Anonymous");
else
text.setText(identity.getPublicName());
}
}
}

View File

@@ -0,0 +1,209 @@
package i2p.bote.android;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.List;
import i2p.bote.android.util.BetterAsyncTaskLoader;
import i2p.bote.android.util.BoteHelper;
import i2p.bote.email.Email;
import i2p.bote.fileencryption.PasswordException;
import i2p.bote.folder.EmailFolder;
import i2p.bote.folder.FolderListener;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBarActivity;
public class ViewEmailActivity extends ActionBarActivity implements
LoaderManager.LoaderCallbacks<List<String>> {
public static final String FOLDER_NAME = "folder_name";
public static final String MESSAGE_ID = "message_id";
private static final int MESSAGE_ID_LIST_LOADER = 1;
private EmailFolder mFolder;
// The messageId of the currently-viewed Email
private String mMessageId;
private ViewPager mPager;
private ViewEmailPagerAdapter mPagerAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_email);
// Enable ActionBar app icon to behave as action to go back
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
Intent i = getIntent();
String folderName = i.getStringExtra(FOLDER_NAME);
mFolder = BoteHelper.getMailFolder(
folderName == null ? "inbox" : folderName);
mMessageId = i.getStringExtra(MESSAGE_ID);
// Instantiate the ViewPager and PagerAdapter
mPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ViewEmailPagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
mMessageId = mPagerAdapter.getMessageId(position);
// Mark the visible email as not new
if (mMessageId != null) {
try {
if (!BoteHelper.isOutbox(mFolder))
mFolder.setNew(mMessageId, false);
mFolder.setRecent(mMessageId, false);
} catch (PasswordException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
// Fire off a Loader to fetch the list of Emails
getSupportLoaderManager().initLoader(MESSAGE_ID_LIST_LOADER, null, this);
}
private class ViewEmailPagerAdapter extends FragmentStatePagerAdapter {
private List<String> mIds;
public ViewEmailPagerAdapter(FragmentManager fm) {
super(fm);
}
public void setData(List<String> data) {
mIds = data;
notifyDataSetChanged();
}
public int getPosition(String messageId) {
if (mIds == null)
return 0;
else
return mIds.indexOf(messageId);
}
public String getMessageId(int position) {
if (mIds == null)
return null;
else
return mIds.get(position);
}
@Override
public Fragment getItem(int position) {
if (mIds == null)
return null;
else
return ViewEmailFragment.newInstance(
mFolder.getName(), mIds.get(position));
}
@Override
public int getCount() {
if (mIds == null)
return 0;
else
return mIds.size();
}
}
// LoaderManager.LoaderCallbacks<List<String>>
public Loader<List<String>> onCreateLoader(int id, Bundle args) {
return new MessageIdListLoader(this, mFolder);
}
private static class MessageIdListLoader extends BetterAsyncTaskLoader<List<String>> implements
FolderListener {
private EmailFolder mFolder;
public MessageIdListLoader(Context context, EmailFolder folder) {
super(context);
mFolder = folder;
}
@Override
public List<String> loadInBackground() {
List<String> messageIds = null;
try {
List<Email> emails = BoteHelper.getEmails(mFolder, null, true);
messageIds = new ArrayList<String>();
for (Email email : emails)
messageIds.add(email.getMessageID());
} catch (PasswordException pe) {
// TODO: Handle this error properly (get user to log in)
}
return messageIds;
}
protected void onStartMonitoring() {
mFolder.addFolderListener(this);
}
protected void onStopMonitoring() {
mFolder.removeFolderListener(this);
}
protected void releaseResources(List<String> data) {
}
// FolderListener
@Override
public void elementAdded(String messageId) {
onContentChanged();
}
@Override
public void elementUpdated() {
onContentChanged();
}
@Override
public void elementRemoved(String messageId) {
onContentChanged();
}
}
public void onLoadFinished(Loader<List<String>> loader,
List<String> data) {
mPagerAdapter.setData(data);
mPager.setCurrentItem(
mPagerAdapter.getPosition(mMessageId));
// Mark the current email as not new
if (mMessageId != null) {
try {
if (!BoteHelper.isOutbox(mFolder))
mFolder.setNew(mMessageId, false);
mFolder.setRecent(mMessageId, false);
} catch (PasswordException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void onLoaderReset(Loader<List<String>> loader) {
mPagerAdapter.setData(null);
}
}

View File

@@ -0,0 +1,158 @@
package i2p.bote.android;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.text.DateFormat;
import javax.mail.Address;
import javax.mail.MessagingException;
import i2p.bote.android.util.BoteHelper;
import i2p.bote.email.Email;
import i2p.bote.fileencryption.PasswordException;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TableRow;
import android.widget.TextView;
public class ViewEmailFragment extends Fragment {
private String mFolderName;
private String mMessageId;
private boolean mIsAnonymous;
public static ViewEmailFragment newInstance(
String folderName, String messageId) {
ViewEmailFragment f = new ViewEmailFragment();
Bundle args = new Bundle();
args.putString("folderName", folderName);
args.putString("messageId", messageId);
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mFolderName = getArguments() != null ? getArguments().getString("folderName") : "inbox";
mMessageId = getArguments() != null ? getArguments().getString("messageId") : "1";
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_view_email, container, false);
try {
Email e = BoteHelper.getEmail(mFolderName, mMessageId);
if (e != null) {
displayEmail(e, v);
} else {
TextView subject = (TextView) v.findViewById(R.id.email_subject);
subject.setText("Email not found");
}
} catch (PasswordException e) {
// TODO: Handle
e.printStackTrace();
}
return v;
}
private void displayEmail(Email email, View v) {
TextView subject = (TextView) v.findViewById(R.id.email_subject);
ImageView picture = (ImageView) v.findViewById(R.id.picture);
TextView sender = (TextView) v.findViewById(R.id.email_sender);
LinearLayout recipients = (LinearLayout) v.findViewById(R.id.email_recipients);
TextView sent = (TextView) v.findViewById(R.id.email_sent);
TextView received = (TextView) v.findViewById(R.id.email_received);
TextView content = (TextView) v.findViewById(R.id.email_content);
try {
String fromAddress = email.getOneFromAddress();
subject.setText(email.getSubject());
Bitmap pic = BoteHelper.getPictureForAddress(fromAddress);
if (pic != null)
picture.setImageBitmap(pic);
sender.setText(BoteHelper.getDisplayAddress(fromAddress));
Address[] emailRecipients = email.getToAddresses();
if (emailRecipients != null) {
for (Address recipient : emailRecipients) {
TextView tv = new TextView(getActivity());
tv.setText(BoteHelper.getDisplayAddress(recipient.toString()));
recipients.addView(tv);
}
}
if (email.getSentDate() != null)
sent.setText(DateFormat.getInstance().format(
email.getSentDate()));
if (email.getReceivedDate() != null)
received.setText(DateFormat.getInstance().format(
email.getReceivedDate()));
content.setText(email.getText());
// Prepare fields for replying
mIsAnonymous = email.isAnonymous();
} catch (MessagingException e) {
// TODO Handle
e.printStackTrace();
} catch (PasswordException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (BoteHelper.isOutbox(mFolderName)) {
((TextView) v.findViewById(R.id.email_status)).setText(
BoteHelper.getEmailStatusText(getActivity(), email, true));
((TableRow) v.findViewById(R.id.email_status_row)).setVisibility(View.VISIBLE);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.view_email, menu);
if (mIsAnonymous)
menu.findItem(R.id.action_reply).setVisible(false);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_reply:
Intent nei = new Intent(getActivity(), NewEmailActivity.class);
nei.putExtra(NewEmailFragment.QUOTE_MSG_FOLDER, mFolderName);
nei.putExtra(NewEmailFragment.QUOTE_MSG_ID, mMessageId);
startActivity(nei);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@@ -0,0 +1,54 @@
package i2p.bote.android.addressbook;
import i2p.bote.android.R;
import i2p.bote.packet.dht.Contact;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
public class AddressBookActivity extends ActionBarActivity implements
AddressBookFragment.OnContactSelectedListener {
static final int ALTER_CONTACT_LIST = 1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.address_book);
// Enable ActionBar app icon to behave as action to go back
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (savedInstanceState == null) {
AddressBookFragment f = new AddressBookFragment();
getSupportFragmentManager().beginTransaction()
.add(android.R.id.content, f).commit();
}
}
@Override
public void onContactSelected(Contact contact) {
if (getIntent().getAction() == Intent.ACTION_PICK) {
Intent result = new Intent();
result.putExtra(EditContactFragment.CONTACT_DESTINATION, contact.getBase64Dest());
setResult(Activity.RESULT_OK, result);
finish();
} else {
Intent i = new Intent(this, EditContactActivity.class);
i.putExtra(EditContactFragment.CONTACT_DESTINATION, contact.getBase64Dest());
startActivityForResult(i, ALTER_CONTACT_LIST);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == ALTER_CONTACT_LIST) {
if (resultCode == Activity.RESULT_OK) {
AddressBookFragment f = (AddressBookFragment) getSupportFragmentManager().findFragmentById(android.R.id.content);
f.updateContactList();
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
}

View File

@@ -0,0 +1,147 @@
package i2p.bote.android.addressbook;
import i2p.bote.I2PBote;
import i2p.bote.android.R;
import i2p.bote.android.util.BetterAsyncTaskLoader;
import i2p.bote.fileencryption.PasswordException;
import i2p.bote.packet.dht.Contact;
import java.util.SortedSet;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
public class AddressBookFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<SortedSet<Contact>> {
OnContactSelectedListener mCallback;
private ContactAdapter mAdapter;
// Container Activity must implement this interface
public interface OnContactSelectedListener {
public void onContactSelected(Contact contact);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnContactSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnContactSelectedListener");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAdapter = new ContactAdapter(getActivity());
setListAdapter(mAdapter);
setListShown(false);
setEmptyText(getResources().getString(
R.string.address_book_empty));
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.address_book_list, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_new_contact:
Intent nci = new Intent(getActivity(), EditContactActivity.class);
getActivity().startActivityForResult(nci, AddressBookActivity.ALTER_CONTACT_LIST);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onListItemClick(ListView parent, View view, int pos, long id) {
mCallback.onContactSelected(mAdapter.getItem(pos));
}
protected void updateContactList() {
setListShown(false);
getLoaderManager().restartLoader(0, null, this);
}
// LoaderManager.LoaderCallbacks<SortedSet<Contact>>
public Loader<SortedSet<Contact>> onCreateLoader(int id, Bundle args) {
return new AddressBookLoader(getActivity());
}
private static class AddressBookLoader extends BetterAsyncTaskLoader<SortedSet<Contact>> {
public AddressBookLoader(Context context) {
super(context);
}
@Override
public SortedSet<Contact> loadInBackground() {
SortedSet<Contact> contacts = null;
try {
contacts = I2PBote.getInstance().getAddressBook().getAll();
} catch (PasswordException e) {
// TODO handle, but should not get here
e.printStackTrace();
}
return contacts;
}
@Override
protected void onStartMonitoring() {
}
@Override
protected void onStopMonitoring() {
}
@Override
protected void releaseResources(SortedSet<Contact> data) {
}
}
@Override
public void onLoadFinished(Loader<SortedSet<Contact>> loader,
SortedSet<Contact> data) {
mAdapter.setData(data);
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
@Override
public void onLoaderReset(Loader<SortedSet<Contact>> loader) {
mAdapter.setData(null);
}
}

View File

@@ -0,0 +1,50 @@
package i2p.bote.android.addressbook;
import i2p.bote.android.R;
import i2p.bote.android.util.BoteHelper;
import i2p.bote.packet.dht.Contact;
import java.util.SortedSet;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class ContactAdapter extends ArrayAdapter<Contact> {
private final LayoutInflater mInflater;
public ContactAdapter(Context context) {
super(context, android.R.layout.simple_list_item_2);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setData(SortedSet<Contact> contacts) {
clear();
if (contacts != null) {
for (Contact contact : contacts) {
add(contact);
}
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = mInflater.inflate(R.layout.listitem_contact, parent, false);
Contact contact = getItem(position);
ImageView picture = (ImageView) v.findViewById(R.id.contact_picture);
TextView name = (TextView) v.findViewById(R.id.contact_name);
String pic = contact.getPictureBase64();
if (pic != null && !pic.isEmpty())
picture.setImageBitmap(BoteHelper.decodePicture(pic));
name.setText(contact.getName());
return v;
}
}

View File

@@ -0,0 +1,24 @@
package i2p.bote.android.addressbook;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
public class EditContactActivity extends ActionBarActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Enable ActionBar app icon to behave as action to go back
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (savedInstanceState == null) {
String destination = null;
Bundle args = getIntent().getExtras();
if (args != null)
destination = args.getString(EditContactFragment.CONTACT_DESTINATION);
EditContactFragment f = EditContactFragment.newInstance(destination);
getSupportFragmentManager().beginTransaction()
.add(android.R.id.content, f).commit();
}
}
}

View File

@@ -0,0 +1,215 @@
package i2p.bote.android.addressbook;
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.security.GeneralSecurityException;
import i2p.bote.android.R;
import i2p.bote.android.util.BoteHelper;
import i2p.bote.android.util.EditPictureFragment;
import i2p.bote.fileencryption.PasswordException;
import i2p.bote.packet.dht.Contact;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class EditContactFragment extends EditPictureFragment {
public static final String CONTACT_DESTINATION = "contact_destination";
static final int REQUEST_DESTINATION_FILE = 3;
private String mDestination;
EditText mNameField;
EditText mDestinationField;
EditText mTextField;
TextView mError;
public static EditContactFragment newInstance(String destination) {
EditContactFragment f = new EditContactFragment();
Bundle args = new Bundle();
args.putString(CONTACT_DESTINATION, destination);
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_edit_contact, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mDestination = getArguments().getString(CONTACT_DESTINATION);
mNameField = (EditText) view.findViewById(R.id.contact_name);
mDestinationField = (EditText) view.findViewById(R.id.destination);
mTextField = (EditText) view.findViewById(R.id.text);
mError = (TextView) view.findViewById(R.id.error);
if (mDestination != null) {
try {
Contact contact = BoteHelper.getContact(mDestination);
String pic = contact.getPictureBase64();
if (pic != null && !pic.isEmpty()) {
setPictureB64(pic);
}
mNameField.setText(contact.getName());
mDestinationField.setText(mDestination);
mTextField.setText(contact.getText());
} catch (PasswordException e) {
// TODO Handle
e.printStackTrace();
}
}
Button b = (Button) view.findViewById(R.id.import_destination_from_file);
b.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.setType("text/plain");
i.addCategory(Intent.CATEGORY_OPENABLE);
try {
startActivityForResult(
Intent.createChooser(i,"Select file containing Email Destination"),
REQUEST_DESTINATION_FILE);
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(getActivity(), "Please install a File Manager.",
Toast.LENGTH_SHORT).show();
}
}
});
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.edit_contact, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_save_contact:
String picture = getPictureB64();
String name = mNameField.getText().toString();
String destination = mDestinationField.getText().toString();
String text = mTextField.getText().toString();
mError.setText("");
try {
String err = BoteHelper.saveContact(destination, name, picture, text);
if (err == null) {
if (mDestination == null) // Only set if adding new contact
getActivity().setResult(Activity.RESULT_OK);
getActivity().finish();
} else
mError.setText(err);
} catch (PasswordException e) {
// TODO Auto-generated catch block
e.printStackTrace();
mError.setText(e.getLocalizedMessage());
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
mError.setText(e.getLocalizedMessage());
}
return true;
case R.id.action_delete_contact:
DialogFragment df = new DialogFragment() {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.delete_contact)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
try {
String err = BoteHelper.deleteContact(mDestination);
if (err == null) {
getActivity().setResult(Activity.RESULT_OK);
getActivity().finish();
} else
mError.setText(err);
} catch (PasswordException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
return builder.create();
}
};
df.show(getActivity().getSupportFragmentManager(), "deletecontact");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_DESTINATION_FILE) {
if (resultCode == Activity.RESULT_OK) {
Uri result = data.getData();
String path = result.getPath();
File file = new File(path);
BufferedReader br;
try {
br = new BufferedReader(
new InputStreamReader(
new FileInputStream(file)));
try {
mDestinationField.setText(br.readLine());
} catch (IOException ioe) {
Toast.makeText(getActivity(), "Failed to read Email Destination file.",
Toast.LENGTH_SHORT).show();
}
} catch (FileNotFoundException fnfe) {
Toast.makeText(getActivity(), "Could not find Email Destination file.",
Toast.LENGTH_SHORT).show();
}
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
}

View File

@@ -0,0 +1,36 @@
package i2p.bote.android.config;
import i2p.bote.android.R;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.Toast;
public class EditIdentityActivity extends ActionBarActivity implements
EditIdentityFragment.Callbacks {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_identity);
// Enable ActionBar app icon to behave as action to go back
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (savedInstanceState == null) {
String key = null;
Bundle args = getIntent().getExtras();
if (args != null)
key = args.getString(EditIdentityFragment.IDENTITY_KEY);
EditIdentityFragment f = EditIdentityFragment.newInstance(key);
getSupportFragmentManager().beginTransaction()
.add(R.id.edit_identity_frag, f).commit();
}
}
// EditIdentityFragment.Callbacks
public void onTaskFinished() {
Toast.makeText(this, R.string.identity_saved,
Toast.LENGTH_SHORT).show();
finish();
}
}

View File

@@ -0,0 +1,370 @@
package i2p.bote.android.config;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.List;
import i2p.bote.I2PBote;
import i2p.bote.android.R;
import i2p.bote.android.util.BoteHelper;
import i2p.bote.android.util.EditPictureFragment;
import i2p.bote.android.util.RobustAsyncTask;
import i2p.bote.android.util.TaskFragment;
import i2p.bote.StatusListener;
import i2p.bote.crypto.CryptoFactory;
import i2p.bote.crypto.CryptoImplementation;
import i2p.bote.email.EmailIdentity;
import i2p.bote.fileencryption.PasswordException;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
public class EditIdentityFragment extends EditPictureFragment {
private Callbacks mCallbacks = sDummyCallbacks;
public interface Callbacks {
public void onTaskFinished();
}
private static Callbacks sDummyCallbacks = new Callbacks() {
public void onTaskFinished() {};
};
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof Callbacks))
throw new IllegalStateException("Activity must implement fragment's callbacks.");
mCallbacks = (Callbacks) activity;
}
@Override
public void onDetach() {
super.onDetach();
mCallbacks = sDummyCallbacks;
}
public static final String IDENTITY_KEY = "identity_key";
// Code to identify the fragment that is calling onActivityResult().
static final int IDENTITY_WAITER = 3;
// Tag so we can find the task fragment again, in another
// instance of this fragment after rotation.
static final String IDENTITY_WAITER_TAG = "identityWaiterTask";
static final int DEFAULT_CRYPTO_IMPL = 2;
private String mKey;
private FragmentManager mFM;
MenuItem mSave;
EditText mNameField;
EditText mDescField;
Spinner mCryptoField;
int mDefaultPos;
CheckBox mDefaultField;
TextView mError;
public static EditIdentityFragment newInstance(String key) {
EditIdentityFragment f = new EditIdentityFragment();
Bundle args = new Bundle();
args.putString(IDENTITY_KEY, key);
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mFM = getFragmentManager();
IdentityWaiterFrag f = (IdentityWaiterFrag) mFM.findFragmentByTag(IDENTITY_WAITER_TAG);
if (f != null)
f.setTargetFragment(this, IDENTITY_WAITER);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_edit_identity, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mKey = getArguments().getString(IDENTITY_KEY);
mNameField = (EditText) view.findViewById(R.id.public_name);
mDescField = (EditText) view.findViewById(R.id.description);
mDefaultField = (CheckBox) view.findViewById(R.id.default_identity);
mError = (TextView) view.findViewById(R.id.error);
if (mKey == null) {
// Show the encryption choice field
mCryptoField = (Spinner) view.findViewById(R.id.crypto_impl);
CryptoAdapter adapter = new CryptoAdapter(getActivity());
mCryptoField.setAdapter(adapter);
mCryptoField.setSelection(mDefaultPos);
mCryptoField.setVisibility(View.VISIBLE);
} else {
// Load the identity to edit
try {
EmailIdentity identity = BoteHelper.getIdentity(mKey);
String pic = identity.getPictureBase64();
if (pic != null && !pic.isEmpty()) {
setPictureB64(pic);
}
mNameField.setText(identity.getPublicName());
mDescField.setText(identity.getDescription());
mDefaultField.setChecked(identity.isDefaultIdentity());
} catch (PasswordException e) {
// TODO Handle
e.printStackTrace();
} catch (IOException e) {
// TODO Handle
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Handle
e.printStackTrace();
}
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.edit_identity, menu);
mSave = menu.findItem(R.id.action_save_identity);
IdentityWaiterFrag f = (IdentityWaiterFrag) mFM.findFragmentByTag(IDENTITY_WAITER_TAG);
if (f != null)
setInterfaceEnabled(false);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_save_identity:
String picture = getPictureB64();
String publicName = mNameField.getText().toString();
String description = mDescField.getText().toString();
boolean setDefault = mDefaultField.isChecked();
int cryptoImplId = -1;
if (mKey == null)
cryptoImplId = ((CryptoImplementation) mCryptoField.getSelectedItem()).getId();
InputMethodManager imm = (InputMethodManager)getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mNameField.getWindowToken(), 0);
setInterfaceEnabled(false);
mError.setText("");
IdentityWaiterFrag f = IdentityWaiterFrag.newInstance(
(mKey == null ? true : false),
cryptoImplId,
null,
mKey,
publicName,
description,
null,
setDefault);
f.setTask(new IdentityWaiter());
f.setTargetFragment(EditIdentityFragment.this, IDENTITY_WAITER);
mFM.beginTransaction()
.replace(R.id.identity_waiter_frag, f, IDENTITY_WAITER_TAG)
.commit();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == IDENTITY_WAITER) {
if (resultCode == Activity.RESULT_OK) {
mCallbacks.onTaskFinished();
} else if (resultCode == Activity.RESULT_CANCELED) {
setInterfaceEnabled(true);
mError.setText(data.getStringExtra("error"));
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private void setInterfaceEnabled(boolean enabled) {
mSave.setVisible(enabled);
mNameField.setEnabled(enabled);
mDescField.setEnabled(enabled);
mDefaultField.setEnabled(enabled);
}
private class CryptoAdapter extends ArrayAdapter<CryptoImplementation> {
public CryptoAdapter(Context context) {
super(context, android.R.layout.simple_spinner_item);
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
List<CryptoImplementation> instances = CryptoFactory.getInstances();
mDefaultPos = 0;
for (CryptoImplementation instance : instances) {
add(instance);
if (instance.getId() == DEFAULT_CRYPTO_IMPL)
mDefaultPos = getPosition(instance);
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = super.getView(position, convertView, parent);
setViewText(v, position);
return v;
}
@Override
public View getDropDownView (int position, View convertView, ViewGroup parent) {
View v = super.getDropDownView(position, convertView, parent);
setViewText(v, position);
return v;
}
private void setViewText(View v, int position) {
TextView text = (TextView) v.findViewById(android.R.id.text1);
text.setText(getItem(position).getName());
}
}
public static class IdentityWaiterFrag extends TaskFragment<Object, String, String> {
static final String CREATE_NEW = "create_new";
static final String CRYPTO_IMPL_ID = "crypto_impl_id";
static final String VANITY_PREFIX = "vanity_prefix";
static final String KEY = "key";
static final String PUBLIC_NAME = "public_name";
static final String DESCRIPTION = "description";
static final String EMAIL_ADDRESS = "email_address";
static final String SET_DEFAULT = "set_default";
String currentStatus;
TextView mStatus;
public static IdentityWaiterFrag newInstance(
boolean createNew, int cryptoImplId, String vanity_prefix,
String key, String publicName, String description,
String emailAddress, boolean setDefault) {
IdentityWaiterFrag f = new IdentityWaiterFrag();
Bundle args = new Bundle();
args.putBoolean(CREATE_NEW, createNew);
args.putInt(CRYPTO_IMPL_ID, cryptoImplId);
args.putString(VANITY_PREFIX, vanity_prefix);
args.putString(KEY, key);
args.putString(PUBLIC_NAME, publicName);
args.putString(DESCRIPTION, description);
args.putString(EMAIL_ADDRESS, emailAddress);
args.putBoolean(SET_DEFAULT, setDefault);
f.setArguments(args);
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.dialog_status, container, false);
mStatus = (TextView) v.findViewById(R.id.status);
if (currentStatus != null && !currentStatus.isEmpty())
mStatus.setText(currentStatus);
return v;
}
@Override
public Object[] getParams() {
Bundle args = getArguments();
return new Object[] {
Boolean.valueOf(args.getBoolean(CREATE_NEW)),
Integer.valueOf(args.getInt(CRYPTO_IMPL_ID)),
args.getString(VANITY_PREFIX),
args.getString(KEY),
args.getString(PUBLIC_NAME),
args.getString(DESCRIPTION),
args.getString(EMAIL_ADDRESS),
Boolean.valueOf(args.getBoolean(SET_DEFAULT)),
};
}
@Override
public void updateProgress(String... values) {
currentStatus = values[0];
mStatus.setText(currentStatus);
}
@Override
public void taskFinished(String result) {
super.taskFinished(result);
if (getTargetFragment() != null) {
Intent i = new Intent();
i.putExtra("result", result);
getTargetFragment().onActivityResult(
getTargetRequestCode(), Activity.RESULT_OK, i);
}
}
@Override
public void taskCancelled(String error) {
super.taskCancelled(error);
if (getTargetFragment() != null) {
Intent i = new Intent();
i.putExtra("error", error);
getTargetFragment().onActivityResult(
getTargetRequestCode(), Activity.RESULT_CANCELED, i);
}
}
}
private class IdentityWaiter extends RobustAsyncTask<Object, String, String> {
protected String doInBackground(Object... params) {
StatusListener lsnr = new StatusListener() {
public void updateStatus(String status) {
publishProgress(status);
}
};
try {
BoteHelper.createOrModifyIdentity(
(Boolean) params[0],
(Integer) params[1],
(String) params[2],
(String) params[3],
(String) params[4],
(String) params[5],
(String) params[6],
(Boolean) params[7],
lsnr);
lsnr.updateStatus("Saving identity");
I2PBote.getInstance().getIdentities().save();
return null;
} catch (Throwable e) {
cancel(false);
return e.getMessage();
}
}
}
}

View File

@@ -0,0 +1,27 @@
package i2p.bote.android.config;
import i2p.bote.android.R;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.Toast;
public class SetPasswordActivity extends ActionBarActivity implements
SetPasswordFragment.Callbacks {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.pref_title_change_password);
setContentView(R.layout.activity_set_password);
// Enable ActionBar app icon to behave as action to go back
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
// SetPasswordFragment.Callbacks
public void onTaskFinished() {
Toast.makeText(this, R.string.password_changed,
Toast.LENGTH_SHORT).show();
finish();
}
}

View File

@@ -0,0 +1,226 @@
package i2p.bote.android.config;
import i2p.bote.I2PBote;
import i2p.bote.android.R;
import i2p.bote.android.util.RobustAsyncTask;
import i2p.bote.android.util.TaskFragment;
import i2p.bote.StatusListener;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
public class SetPasswordFragment extends Fragment {
private Callbacks mCallbacks = sDummyCallbacks;
public interface Callbacks {
public void onTaskFinished();
}
private static Callbacks sDummyCallbacks = new Callbacks() {
public void onTaskFinished() {};
};
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof Callbacks))
throw new IllegalStateException("Activity must implement fragment's callbacks.");
mCallbacks = (Callbacks) activity;
}
@Override
public void onDetach() {
super.onDetach();
mCallbacks = sDummyCallbacks;
}
// Code to identify the fragment that is calling onActivityResult().
static final int PASSWORD_WAITER = 0;
// Tag so we can find the task fragment again, in another
// instance of this fragment after rotation.
static final String PASSWORD_WAITER_TAG = "passwordWaiterTask";
private FragmentManager mFM;
MenuItem mSave;
EditText mOldField;
EditText mNewField;
EditText mConfirmField;
TextView mError;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mFM = getFragmentManager();
PasswordWaiterFrag f = (PasswordWaiterFrag) mFM.findFragmentByTag(PASSWORD_WAITER_TAG);
if (f != null)
f.setTargetFragment(this, PASSWORD_WAITER);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_set_password, container);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mOldField = (EditText) view.findViewById(R.id.password_old);
mNewField = (EditText) view.findViewById(R.id.password_new);
mConfirmField = (EditText) view.findViewById(R.id.password_confirm);
mError = (TextView) view.findViewById(R.id.error);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.set_password, menu);
mSave = menu.findItem(R.id.action_set_password);
// If task is running, disable the save button.
PasswordWaiterFrag f = (PasswordWaiterFrag) mFM.findFragmentByTag(PASSWORD_WAITER_TAG);
if (f != null)
setInterfaceEnabled(false);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_set_password:
String oldPassword = mOldField.getText().toString();
String newPassword = mNewField.getText().toString();
String confirmNewPassword = mConfirmField.getText().toString();
InputMethodManager imm = (InputMethodManager)getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mNewField.getWindowToken(), 0);
setInterfaceEnabled(false);
mError.setText("");
PasswordWaiterFrag f = PasswordWaiterFrag.newInstance(oldPassword, newPassword, confirmNewPassword);
f.setTask(new PasswordWaiter());
f.setTargetFragment(SetPasswordFragment.this, PASSWORD_WAITER);
mFM.beginTransaction()
.replace(R.id.password_waiter_frag, f, PASSWORD_WAITER_TAG)
.commit();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == PASSWORD_WAITER) {
if (resultCode == Activity.RESULT_OK) {
mCallbacks.onTaskFinished();
} else if (resultCode == Activity.RESULT_CANCELED) {
setInterfaceEnabled(true);
mError.setText(data.getStringExtra("error"));
}
}
}
private void setInterfaceEnabled(boolean enabled) {
mSave.setVisible(enabled);
mOldField.setEnabled(enabled);
mNewField.setEnabled(enabled);
mConfirmField.setEnabled(enabled);
}
public static class PasswordWaiterFrag extends TaskFragment<String, String, String> {
String currentStatus;
TextView mStatus;
public static PasswordWaiterFrag newInstance(String... params) {
PasswordWaiterFrag f = new PasswordWaiterFrag();
Bundle args = new Bundle();
args.putStringArray("params", params);
f.setArguments(args);
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.dialog_status, container, false);
mStatus = (TextView) v.findViewById(R.id.status);
if (currentStatus != null && !currentStatus.isEmpty())
mStatus.setText(currentStatus);
return v;
}
@Override
public String[] getParams() {
Bundle args = getArguments();
return args.getStringArray("params");
}
@Override
public void updateProgress(String... values) {
currentStatus = values[0];
mStatus.setText(currentStatus);
}
@Override
public void taskFinished(String result) {
super.taskFinished(result);
if (getTargetFragment() != null) {
Intent i = new Intent();
i.putExtra("result", result);
getTargetFragment().onActivityResult(
getTargetRequestCode(), Activity.RESULT_OK, i);
}
}
@Override
public void taskCancelled(String error) {
super.taskCancelled(error);
if (getTargetFragment() != null) {
Intent i = new Intent();
i.putExtra("error", error);
getTargetFragment().onActivityResult(
getTargetRequestCode(), Activity.RESULT_CANCELED, i);
}
}
}
private class PasswordWaiter extends RobustAsyncTask<String, String, String> {
protected String doInBackground(String... params) {
StatusListener lsnr = new StatusListener() {
public void updateStatus(String status) {
publishProgress(status);
}
};
try {
I2PBote.getInstance().changePassword(
params[0].getBytes(),
params[1].getBytes(),
params[2].getBytes(),
lsnr);
return null;
} catch (Throwable e) {
cancel(false);
return e.getMessage();
}
}
}
}

View File

@@ -0,0 +1,539 @@
package i2p.bote.android.config;
import i2p.bote.Configuration;
import i2p.bote.I2PBote;
import i2p.bote.android.R;
import i2p.bote.android.util.SummaryEditTextPreference;
import i2p.bote.email.EmailIdentity;
import i2p.bote.fileencryption.PasswordException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.TextView;
public class SettingsActivity extends PreferenceActivity {
// Actions for legacy settings
private static final String ACTION_PREFS_GENERAL = "i2p.bote.PREFS_GENERAL";
static final int ALTER_IDENTITY_LIST = 1;
// Preference Header vars
private Header[] mIdentityListHeaders;
private String mRequestedIdentityKey;
private String mDeletingIdentityKey;
// Async tasks
private LoadIdentityListTask mLoadIdentityListTask;
//
// Android lifecycle
//
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String action = getIntent().getAction();
if (action != null) {
loadLegacySettings(action);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// Load the legacy preferences headers
buildLegacyHeaders();
}
}
@Override
public void onResume() {
super.onResume();
updateIdentities();
}
@Override
protected void onPause() {
Configuration config = I2PBote.getInstance().getConfiguration();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
Map<String, ?> all = prefs.getAll();
Iterator<String> iterator = all.keySet().iterator();
while (iterator.hasNext()) {
String x = iterator.next();
if (x.startsWith("i2pbote.")) // Skip over Android-specific settings
continue;
else if ("autoMailCheckEnabled".equals(x))
config.setAutoMailCheckEnabled(prefs.getBoolean(x, true));
else if ("mailCheckInterval".equals(x))
config.setMailCheckInterval(prefs.getInt(x, 30));
else if ("deliveryCheckEnabled".equals(x))
config.setDeliveryCheckEnabled(prefs.getBoolean(x, true));
else if ("hideLocale".equals(x))
config.setHideLocale(prefs.getBoolean(x, true));
else if ("includeSentTime".equals(x))
config.setIncludeSentTime(prefs.getBoolean(x, true));
else if ("numSendHops".equals(x))
config.setNumStoreHops(Integer.parseInt(prefs.getString(x, "0")));
else if ("relayMinDelay".equals(x))
config.setRelayMinDelay(prefs.getInt(x, 5));
else if ("relayMaxDelay".equals(x))
config.setRelayMaxDelay(prefs.getInt(x, 40));
}
config.save();
// Store the settings in Android
super.onPause();
}
@Override
protected boolean isValidFragment(String fragmentName) {
return SettingsFragment.class.getName().equals(fragmentName);
}
//
// Building Headers
//
@SuppressWarnings("deprecation")
private void buildLegacyHeaders() {
// Always add general preferences as first header
addPreferencesFromResource(R.xml.settings_headers_legacy);
// Then add zero or more identity headers as necessary
// TODO: implement
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onBuildHeaders(List<Header> target) {
// The resource com.android.internal.R.bool.preferences_prefer_dual_pane
// has different definitions based upon screen size. At present, it will
// be true for -sw720dp devices, false otherwise. For your curiosity, in
// Nexus 7 it is false.
// Always add general preferences as first header
target.clear();
loadHeadersFromResource(R.xml.settings_headers, target);
// Then add zero or more identity headers as necessary
if (mIdentityListHeaders != null) {
final int headerCount = mIdentityListHeaders.length;
for (int index = 0; index < headerCount; index++) {
Header header = mIdentityListHeaders[index];
if (header != null && header.id != HEADER_ID_UNDEFINED) {
String key = header.extras.getString(
ViewIdentityFragment.IDENTITY_KEY);
if (key != mDeletingIdentityKey) {
target.add(header);
if (key == mRequestedIdentityKey) {
mRequestedIdentityKey = null;
}
}
}
}
}
}
//
// Settings pages
//
@SuppressWarnings("deprecation")
private void loadLegacySettings(String action) {
if (ACTION_PREFS_GENERAL.equals(action)) {
addPreferencesFromResource(R.xml.settings_general);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class SettingsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String settings = getArguments().getString("settings");
if ("general".equals(settings)) {
addPreferencesFromResource(R.xml.settings_general);
final PreferenceCategory i2pCat = (PreferenceCategory)findPreference("i2pCategory");
CheckBoxPreference routerAuto = (CheckBoxPreference)findPreference("i2pbote.router.auto");
if (!routerAuto.isChecked()) {
setupI2PCategory(getActivity(), i2pCat);
}
routerAuto.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
final Boolean checked = (Boolean) newValue;
if (!checked.booleanValue()) {
setupI2PCategory(getActivity(), i2pCat);
} else {
Preference p1 = i2pCat.findPreference("i2pbote.router.use");
Preference p2 = i2pCat.findPreference("i2pbote.i2cp.tcp.host");
Preference p3 = i2pCat.findPreference("i2pbote.i2cp.tcp.port");
if (p1 != null)
i2pCat.removePreference(p1);
if (p2 != null)
i2pCat.removePreference(p2);
if (p3 != null)
i2pCat.removePreference(p3);
}
return true;
}
});
}
}
}
private static void setupI2PCategory(Context context, PreferenceCategory i2pCat) {
final ListPreference routerChoice = createRouterChoice(context);
final EditTextPreference hostField = createHostField(context);
final EditTextPreference portField = createPortField(context);
i2pCat.addPreference(routerChoice);
i2pCat.addPreference(hostField);
i2pCat.addPreference(portField);
if ("remote".equals(routerChoice.getValue())) {
hostField.setEnabled(true);
portField.setEnabled(true);
}
routerChoice.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
final String val = newValue.toString();
int index = routerChoice.findIndexOfValue(val);
if (index == 2) {
hostField.setEnabled(true);
hostField.setText("127.0.0.1");
portField.setEnabled(true);
portField.setText("7654");
} else {
hostField.setEnabled(false);
hostField.setText("internal");
portField.setEnabled(false);
portField.setText("internal");
}
return true;
}
});
}
private static ListPreference createRouterChoice(Context context) {
ListPreference routerChoice = new ListPreference(context);
routerChoice.setKey("i2pbote.router.use");
routerChoice.setEntries(R.array.routerOptionNames);
routerChoice.setEntryValues(R.array.routerOptions);
routerChoice.setTitle("Router");
routerChoice.setSummary("%s");
routerChoice.setDialogTitle("Router to use");
routerChoice.setDefaultValue("internal");
return routerChoice;
}
private static EditTextPreference createHostField(Context context) {
EditTextPreference p = new SummaryEditTextPreference(context);
p.setKey("i2pbote.i2cp.tcp.host");
p.setTitle(R.string.pref_title_i2cp_host);
p.setSummary("%s");
p.setDefaultValue("internal");
p.setEnabled(false);
return p;
}
private static EditTextPreference createPortField(Context context) {
EditTextPreference p = new SummaryEditTextPreference(context);
p.setKey("i2pbote.i2cp.tcp.port");
p.setTitle(R.string.pref_title_i2cp_port);
p.setSummary("%s");
p.setDefaultValue("internal");
p.setEnabled(false);
return p;
}
//
// Update list of identities in headers
//
/**
* Starts the async reload of the identities list
* (if the headers are being displayed)
*/
private void updateIdentities() {
if (shouldUpdateIdentities()) {
mLoadIdentityListTask = (LoadIdentityListTask)
new LoadIdentityListTask().execute();
}
}
private boolean shouldUpdateIdentities() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
return getIntent().getAction() == null;
else
return showingHeaders();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private boolean showingHeaders() {
return hasHeaders();
}
//
// Load list of identities and convert
// into appropriate type of header
//
private class LoadIdentityListTask extends AsyncTask<String, Void, Object[]> {
protected Object[] doInBackground(String... params) {
try {
Collection<EmailIdentity> identities = I2PBote.getInstance().getIdentities().getAll();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// Return legacy headers
return loadLegacyHeaders(identities);
} else {
// Return list of Headers
return loadHeaders(identities);
}
} catch (PasswordException e) {
cancel(false);
return new Object[] {e};
} catch (IOException e) {
cancel(false);
return new Object[] {e};
} catch (GeneralSecurityException e) {
cancel(false);
return new Object[] {e};
}
}
private Object[] loadLegacyHeaders(Collection<EmailIdentity> identities) {
// TODO: implement
return new Object[] {null};
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private Object[] loadHeaders(Collection<EmailIdentity> identities) {
Header[] result = new Header[identities.size()];
int index = 0;
for (EmailIdentity identity : identities) {
final long id = identity.getHash().hashCode();
final String name = identity.getPublicName();
final String desc = identity.getDescription();
final String key = identity.getKey();
final Intent intent = new Intent(
getApplicationContext(), ViewIdentityActivity.class);
final Bundle args = new Bundle();
args.putString(ViewIdentityFragment.IDENTITY_KEY, key);
intent.putExtras(args);
final Header newHeader = new Header();
newHeader.id = id;
newHeader.title = name;
newHeader.summary = desc;
newHeader.intent = intent;
newHeader.extras = args;
result[index++] = newHeader;
}
return new Object[] {result};
}
@Override
protected void onPostExecute(Object[] result) {
if (isCancelled()) return;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
showLegacyHeaders(result);
} else {
showHeaders(result);
}
}
private void showLegacyHeaders(Object[] result) {
// TODO: implement
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void showHeaders(Object[] result) {
final Header[] headers = (Header[]) result[0];
mIdentityListHeaders = headers;
invalidateHeaders();
}
@Override
protected void onCancelled(Object[] result) {
}
}
//
// Styling for headers
//
@Override
public void setListAdapter(ListAdapter adapter) {
if (adapter == null) {
super.setListAdapter(null);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
super.setListAdapter(adapter); // TODO: implement legacy headers styling
} else {
// TODO: Fix NPE when rotating screen
super.setListAdapter(new HeaderAdapter(this, adapter));
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static class HeaderAdapter extends ArrayAdapter<Header> {
static final int HEADER_TYPE_CATEGORY = 0;
static final int HEADER_TYPE_NORMAL = 1;
private static final int HEADER_TYPE_COUNT = HEADER_TYPE_NORMAL + 1;
private static class HeaderViewHolder {
TextView title;
TextView summary;
}
private ListAdapter mAdapter;
private LayoutInflater mInflater;
static int getHeaderType(Header header) {
if (header.fragment == null && header.intent == null) {
return HEADER_TYPE_CATEGORY;
} else {
return HEADER_TYPE_NORMAL;
}
}
@Override
public int getItemViewType(int position) {
Header header = getItem(position);
return getHeaderType(header);
}
@Override
public boolean areAllItemsEnabled() {
return false; // because of categories
}
@Override
public boolean isEnabled(int position) {
try {
return getItemViewType(position) != HEADER_TYPE_CATEGORY;
} catch (IndexOutOfBoundsException e) {
// Happens when deleting an identity
return false;
}
}
@Override
public int getViewTypeCount() {
return HEADER_TYPE_COUNT;
}
@Override
public boolean hasStableIds() {
return true;
}
public HeaderAdapter(Context context, ListAdapter adapter) {
super(context, 0);
mAdapter = adapter;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public Header getItem(int position) {
return (Header) mAdapter.getItem(position);
}
@Override
public int getCount() {
return mAdapter.getCount();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
HeaderViewHolder holder;
Header header = getItem(position);
int headerType = getHeaderType(header);
View view = null;
if (convertView == null) {
holder = new HeaderViewHolder();
switch (headerType) {
case HEADER_TYPE_CATEGORY:
view = new TextView(getContext(), null,
android.R.attr.listSeparatorTextViewStyle);
holder.title = (TextView) view;
break;
case HEADER_TYPE_NORMAL:
view = mInflater.inflate(
R.layout.preference_header_item, parent,
false);
holder.title = (TextView)
view.findViewById(android.R.id.title);
holder.summary = (TextView)
view.findViewById(android.R.id.summary);
break;
}
view.setTag(holder);
} else {
view = convertView;
holder = (HeaderViewHolder) view.getTag();
}
// All view fields must be updated every time, because the view may be recycled
switch (headerType) {
case HEADER_TYPE_CATEGORY:
holder.title.setText(header.getTitle(getContext().getResources()));
break;
case HEADER_TYPE_NORMAL:
updateCommonHeaderView(header, holder);
break;
}
return view;
}
private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
holder.title.setText(header.getTitle(getContext().getResources()));
CharSequence summary = header.getSummary(getContext().getResources());
if (summary != null && !summary.toString().isEmpty()) {
holder.summary.setVisibility(View.VISIBLE);
holder.summary.setText(summary);
} else {
holder.summary.setVisibility(View.GONE);
}
}
}
}

View File

@@ -0,0 +1,24 @@
package i2p.bote.android.config;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
public class ViewIdentityActivity extends ActionBarActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Enable ActionBar app icon to behave as action to go back
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (savedInstanceState == null) {
String key = null;
Bundle args = getIntent().getExtras();
if (args != null)
key = args.getString(ViewIdentityFragment.IDENTITY_KEY);
ViewIdentityFragment f = ViewIdentityFragment.newInstance(key);
getSupportFragmentManager().beginTransaction()
.add(android.R.id.content, f).commit();
}
}
}

View File

@@ -0,0 +1,156 @@
package i2p.bote.android.config;
import java.io.IOException;
import java.security.GeneralSecurityException;
import i2p.bote.android.R;
import i2p.bote.android.util.BoteHelper;
import i2p.bote.email.EmailIdentity;
import i2p.bote.fileencryption.PasswordException;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
public class ViewIdentityFragment extends Fragment {
public static final String IDENTITY_KEY = "identity_key";
private String mKey;
private EmailIdentity mIdentity;
ImageView mIdentityPicture;
TextView mNameField;
TextView mDescField;
TextView mCryptoField;
TextView mKeyField;
public static ViewIdentityFragment newInstance(String key) {
ViewIdentityFragment f = new ViewIdentityFragment();
Bundle args = new Bundle();
args.putString(IDENTITY_KEY, key);
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_view_identity, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mIdentityPicture = (ImageView) view.findViewById(R.id.identity_picture);
mNameField = (TextView) view.findViewById(R.id.public_name);
mDescField = (TextView) view.findViewById(R.id.description);
mCryptoField = (TextView) view.findViewById(R.id.crypto_impl);
mKeyField = (TextView) view.findViewById(R.id.key);
mKey = getArguments().getString(IDENTITY_KEY);
if (mKey != null) {
try {
mIdentity = BoteHelper.getIdentity(mKey);
} catch (PasswordException e) {
// TODO Handle
e.printStackTrace();
} catch (IOException e) {
// TODO Handle
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Handle
e.printStackTrace();
}
}
}
@Override
public void onResume() {
super.onResume();
if (mIdentity != null) {
Bitmap picture = BoteHelper.decodePicture(mIdentity.getPictureBase64());
if (picture != null)
mIdentityPicture.setImageBitmap(picture);
mNameField.setText(mIdentity.getPublicName());
mDescField.setText(mIdentity.getDescription());
mCryptoField.setText(mIdentity.getCryptoImpl().getName());
mKeyField.setText(mKey);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.view_identity, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_edit_identity:
Intent ei = new Intent(getActivity(), EditIdentityActivity.class);
ei.putExtra(EditIdentityFragment.IDENTITY_KEY, mKey);
startActivity(ei);
return true;
case R.id.action_delete_identity:
DialogFragment df = new DialogFragment() {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.delete_identity)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
try {
BoteHelper.deleteIdentity(mKey);
getActivity().setResult(Activity.RESULT_OK);
getActivity().finish();
} catch (PasswordException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
return builder.create();
}
};
df.show(getActivity().getSupportFragmentManager(), "deletecontact");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@@ -0,0 +1,232 @@
package i2p.bote.android.service;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.List;
import javax.mail.MessagingException;
import net.i2p.android.router.service.IRouterState;
import net.i2p.android.router.service.IRouterStateCallback;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.RouterLaunch;
import i2p.bote.I2PBote;
import i2p.bote.android.EmailListActivity;
import i2p.bote.android.R;
import i2p.bote.android.ViewEmailActivity;
import i2p.bote.android.service.Init.RouterChoice;
import i2p.bote.android.util.BoteHelper;
import i2p.bote.email.Email;
import i2p.bote.fileencryption.PasswordException;
import i2p.bote.folder.EmailFolder;
import i2p.bote.folder.NewEmailListener;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v4.app.NotificationCompat;
public class BoteService extends Service implements NewEmailListener {
public static final String ROUTER_CHOICE = "router_choice";
public static final int NOTIF_ID_NEW_EMAIL = 80739047;
RouterChoice mRouterChoice;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mRouterChoice = (RouterChoice) intent.getSerializableExtra(ROUTER_CHOICE);
if (mRouterChoice == RouterChoice.INTERNAL)
new Thread(new RouterStarter()).start();
I2PBote bote = I2PBote.getInstance();
bote.startUp();
bote.addNewEmailListener(this);
if (mRouterChoice == RouterChoice.ANDROID) {
// Bind to I2P Android
Intent i2pIntent = new Intent(IRouterState.class.getName());
i2pIntent.setClassName("net.i2p.android.router",
"net.i2p.android.router.service.RouterService");
mTriedBindState = bindService(
i2pIntent, mStateConnection, 0);
} else if (mRouterChoice == RouterChoice.REMOTE)
bote.connectNow();
return START_REDELIVER_INTENT;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
if (mTriedBindState) {
try {
mStateService.unregisterCallback(mStatusListener);
} catch (RemoteException e) {}
unbindService(mStateConnection);
}
mTriedBindState = false;
I2PBote.getInstance().removeNewEmailListener(this);
I2PBote.getInstance().shutDown();
if (mRouterChoice == RouterChoice.INTERNAL)
new Thread(new RouterStopper()).start();
}
//
// Internal router helpers
//
private RouterContext mRouterContext;
private class RouterStarter implements Runnable {
public void run() {
RouterLaunch.main(null);
List<RouterContext> contexts = RouterContext.listContexts();
mRouterContext = contexts.get(0);
mRouterContext.router().setKillVMOnEnd(false);
}
}
private class RouterStopper implements Runnable {
public void run() {
RouterContext ctx = mRouterContext;
if (ctx != null)
ctx.router().shutdown(Router.EXIT_HARD);
}
}
//
// I2P Android helpers
//
private IRouterState mStateService = null;
private boolean mTriedBindState;
private ServiceConnection mStateConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
mStateService = IRouterState.Stub.asInterface(service);
try {
mStateService.registerCallback(mStatusListener);
String state = mStateService.getState();
if ("RUNNING".equals(state) ||"ACTIVE".equals(state))
I2PBote.getInstance().connectNow();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mStateService = null;
}
};
private final IRouterStateCallback.Stub mStatusListener =
new IRouterStateCallback.Stub() {
public void stateChanged(String newState) throws RemoteException {
if ("STOPPING".equals(newState) ||
"MANUAL_STOPPING".equals(newState) ||
"MANUAL_QUITTING".equals(newState) ||
"NETWORK_STOPPING".equals(newState))
stopSelf();
}
};
// NewEmailListener
@Override
public void emailReceived(String messageId) {
NotificationManager nm = (NotificationManager) getSystemService(
Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder b =
new NotificationCompat.Builder(this)
.setAutoCancel(true);
try {
EmailFolder inbox = I2PBote.getInstance().getInbox();
// Set the new email as \Recent
inbox.setRecent(messageId, true);
// Now display/update notification with all \Recent emails
List<Email> newEmails = BoteHelper.getRecentEmails(inbox);
int numNew = newEmails.size();
switch (numNew) {
case 0:
nm.cancel(NOTIF_ID_NEW_EMAIL);
return;
case 1:
Email email = newEmails.get(0);
Bitmap picture = BoteHelper.getPictureForAddress(email.getOneFromAddress());
if (picture != null)
b.setLargeIcon(picture);
else
b.setSmallIcon(R.drawable.ic_contact_picture);
b.setContentTitle(BoteHelper.getNameAndShortDestination(
email.getOneFromAddress()));
b.setContentText(email.getSubject());
Intent vei = new Intent(this, ViewEmailActivity.class);
vei.putExtra(ViewEmailActivity.FOLDER_NAME, inbox.getName());
vei.putExtra(ViewEmailActivity.MESSAGE_ID, email.getMessageID());
vei.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pvei = PendingIntent.getActivity(this, 0, vei, PendingIntent.FLAG_UPDATE_CURRENT);
b.setContentIntent(pvei);
break;
default:
b.setSmallIcon(R.drawable.ic_launcher);
b.setContentTitle(getResources().getQuantityString(
R.plurals.n_new_emails, numNew, numNew));
String bigText = "";
for (Email ne : newEmails) {
bigText += BoteHelper.getNameAndShortDestination(
ne.getOneFromAddress());
bigText += ": " + ne.getSubject() + "\n";
}
b.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText));
Intent eli = new Intent(this, EmailListActivity.class);
eli.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent peli = PendingIntent.getActivity(this, 0, eli, PendingIntent.FLAG_UPDATE_CURRENT);
b.setContentIntent(peli);
}
} catch (PasswordException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (MessagingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
nm.notify(NOTIF_ID_NEW_EMAIL, b.build());
}
}

View File

@@ -0,0 +1,74 @@
package i2p.bote.android.service;
import net.i2p.android.router.service.IRouterState;
import net.i2p.client.I2PClient;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
public class Init {
private final Context ctx;
private final String myDir;
public enum RouterChoice {
INTERNAL,
ANDROID,
REMOTE;
}
public Init(Context c) {
ctx = c;
// This needs to be changed so that we can have an alternative place
myDir = c.getFilesDir().getAbsolutePath();
}
/**
* Parses settings and prepares the system for starting the Bote service.
* @return true if we should use the internal router, false otherwise.
*/
public RouterChoice initialize(IRouterState stateService) {
// Set up the locations so Router and WorkingDir can find them
// We do this again here, in the event settings were changed.
System.setProperty("i2p.dir.base", myDir);
System.setProperty("i2p.dir.config", myDir);
System.setProperty("wrapper.logfile", myDir + "/wrapper.log");
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
RouterChoice routerChoice;
String i2cpHost, i2cpPort;
if (prefs.getBoolean("i2pbote.router.auto", true)) {
if (stateService != null) {
routerChoice = RouterChoice.ANDROID;
// TODO fetch settings from I2P Android
i2cpHost = "127.0.0.1";
i2cpPort = "7654";
} else {
routerChoice = RouterChoice.INTERNAL;
i2cpHost = "internal";
i2cpPort = "internal";
}
} else {
// Check manual settings
String which = prefs.getString("i2pbote.router.use", "internal");
if ("internal".equals(which)) {
routerChoice = RouterChoice.INTERNAL;
i2cpHost = "internal";
i2cpPort = "internal";
} else if ("android".equals(which)) {
routerChoice = RouterChoice.ANDROID;
// TODO fetch settings from I2P Android
i2cpHost = "127.0.0.1";
i2cpPort = "7654";
} else { // Remote router
routerChoice = RouterChoice.REMOTE;
i2cpHost = prefs.getString("i2pbote.i2cp.tcp.host", "127.0.0.1");
i2cpPort = prefs.getString("i2pbote.i2cp.tcp.port", "7654");
}
}
// Set the I2CP host/port
System.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);
System.setProperty(I2PClient.PROP_TCP_PORT, i2cpPort);
return routerChoice;
}
}

View File

@@ -0,0 +1,125 @@
package i2p.bote.android.util;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
public abstract class BetterAsyncTaskLoader<T> extends AsyncTaskLoader<T> {
protected T mData;
public BetterAsyncTaskLoader(Context context) {
super(context);
}
/**
* Called when there is new data to deliver to the client. The
* super class will take care of delivering it; the implementation
* here just adds a little more logic.
*/
@Override
public void deliverResult(T data) {
if (isReset()) {
// An async query came in while the loader is stopped. We
// don't need the result.
if (data != null) {
releaseResources(data);
}
}
// Hold a reference to the old data so it doesn't get garbage collected.
// We must protect it until the new data has been delivered.
T oldData = mData;
mData = data;
if (isStarted()) {
// If the Loader is currently started, we can immediately
// deliver its results.
super.deliverResult(data);
}
// Invalidate the old data as we don't need it any more.
if (oldData != null && oldData != data) {
releaseResources(oldData);
}
}
/**
* Handles a request to start the Loader.
*/
@Override
protected void onStartLoading() {
if (mData != null) {
// Deliver any previously loaded data immediately.
deliverResult(mData);
}
// Start watching for changes
onStartMonitoring();
if (takeContentChanged() || mData == null) {
// When the observer detects a change, it should call onContentChanged()
// on the Loader, which will cause the next call to takeContentChanged()
// to return true. If this is ever the case (or if the current data is
// null), we force a new load.
forceLoad();
}
}
/**
* Handles a request to stop the Loader.
*/
@Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
// Note that we leave the observer as is. Loaders in a stopped state
// should still monitor the data source for changes so that the Loader
// will know to force a new load if it is ever started again.
}
/**
* Handles a request to completely reset the Loader.
*/
@Override
protected void onReset() {
super.onReset();
// Ensure the loader has been stopped.
onStopLoading();
// At this point we can release the resources associated with 'mData'.
if (mData != null) {
releaseResources(mData);
mData = null;
}
// Stop monitoring for changes.
onStopMonitoring();
}
/**
* Handles a request to cancel a load.
*/
@Override
public void onCanceled(T data) {
// Attempt to cancel the current asynchronous load.
super.onCanceled(data);
// The load has been canceled, so we should release the resources
// associated with 'data'.
releaseResources(data);
}
protected abstract void onStartMonitoring();
protected abstract void onStopMonitoring();
/**
* Helper function to take care of releasing resources associated
* with an actively loaded data set.
* For a simple List, there is nothing to do. For something like a Cursor, we
* would close it in this method. All resources associated with the Loader
* should be released here.
*/
protected abstract void releaseResources(T data);
}

View File

@@ -0,0 +1,231 @@
package i2p.bote.android.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Iterator;
import java.util.List;
import javax.mail.Address;
import javax.mail.Flags.Flag;
import javax.mail.MessagingException;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.util.Base64;
import i2p.bote.android.R;
import i2p.bote.email.Email;
import i2p.bote.email.EmailDestination;
import i2p.bote.email.EmailIdentity;
import i2p.bote.fileencryption.PasswordException;
import i2p.bote.folder.EmailFolder;
import i2p.bote.folder.Outbox.EmailStatus;
import i2p.bote.packet.dht.Contact;
import i2p.bote.util.GeneralHelper;
public class BoteHelper extends GeneralHelper {
/**
* Get the translated name of the folder.
* Built-in folders are special-cased; other folders are created by the
* user, so their name is already "translated".
* @param ctx Android Context to get strings from.
* @param folder The folder.
* @param showNew Should the name contain the number of new messages?
* @return The name of the folder.
* @throws PasswordException
*/
public static String getFolderDisplayName(Context ctx, EmailFolder folder) {
String name = folder.getName();
if ("inbox".equals(name))
return ctx.getResources().getString(R.string.folder_inbox);
else if ("outbox".equals(name))
return ctx.getResources().getString(R.string.folder_outbox);
else if ("sent".equals(name))
return ctx.getResources().getString(R.string.folder_sent);
else if ("trash".equals(name))
return ctx.getResources().getString(R.string.folder_trash);
else
return name;
}
/**
* Get the translated name of the folder with the number of
* new messages it contains appended.
* @param ctx Android Context to get strings from.
* @param folder The folder.
* @return The name of the folder.
* @throws PasswordException
*/
public static String getFolderDisplayNameWithNew(Context ctx, EmailFolder folder) throws PasswordException {
String displayName = getFolderDisplayName(ctx, folder);
int numNew = folder.getNumNewEmails();
if (numNew > 0)
displayName = displayName + " (" + numNew + ")";
return displayName;
}
public static String getDisplayAddress(String address) throws PasswordException, IOException, GeneralSecurityException, MessagingException {
String fullAdr = getNameAndDestination(address);
String emailDest = extractEmailDestination(fullAdr);
String name = extractName(fullAdr);
return (emailDest == null ? address
: (name.isEmpty() ? emailDest.substring(0, 10)
: name + " <" + emailDest.substring(0, 10) + "...>"));
}
/**
* Get a Bitmap containing the picture for the contact or identity
* corresponding to the given address.
* @param address
* @return a Bitmap, or null if no picture was found.
* @throws PasswordException
* @throws IOException
* @throws GeneralSecurityException
*/
public static Bitmap getPictureForAddress(String address) throws PasswordException, IOException, GeneralSecurityException {
String fullAdr = getNameAndDestination(address);
if (!address.equals(fullAdr)) {
// Address was found; try address book first
String base64dest = EmailDestination.extractBase64Dest(fullAdr);
Contact c = getContact(base64dest);
if (c != null) {
// Address is in address book
String pic = c.getPictureBase64();
if (pic != null) {
return decodePicture(pic);
}
} else {
// Address is an identity
EmailIdentity i = getIdentity(base64dest);
if (i != null) {
String pic = i.getPictureBase64();
if (pic != null) {
return decodePicture(pic);
}
}
}
}
// Address not found anywhere, or found and has no picture
return null;
}
public static Bitmap decodePicture(String picB64) {
if (picB64 == null)
return null;
byte[] decodedPic = Base64.decode(picB64, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(decodedPic, 0, decodedPic.length);
}
public static String encodePicture(Bitmap picture) {
if (picture == null)
return null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// TODO something is corrupting here
picture.compress(CompressFormat.PNG, 0, baos);
return Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT);
}
public static boolean isSentEmail(Email email) throws PasswordException, IOException, GeneralSecurityException, MessagingException {
// Is the sender anonymous and we are not the recipient?
if (email.isAnonymous()) {
Address[] recipients = email.getAllRecipients();
for (int i = 0; i < recipients.length; i++) {
String toDest = EmailDestination.extractBase64Dest(recipients[i].toString());
if (toDest != null && getIdentity(toDest) != null)
// We are a recipient
return false;
}
// We are not a recipient
return true;
}
// Are we the sender?
String fromAddress = email.getOneFromAddress();
String fromDest = EmailDestination.extractBase64Dest(fromAddress);
if ((fromDest != null && getIdentity(fromDest) != null))
return true;
// We are not the sender
return false;
}
public static String getEmailStatusText(Context ctx, Email email, boolean full) {
Resources res = ctx.getResources();
EmailStatus emailStatus = getEmailStatus(email);
switch (emailStatus.getStatus()) {
case QUEUED:
return res.getString(R.string.queued);
case SENDING:
return res.getString(R.string.sending);
case SENT_TO:
if (full)
return res.getString(R.string.sent_to,
emailStatus.getParam1(), emailStatus.getParam2());
else
return res.getString(R.string.sent_to_short,
emailStatus.getParam1(), emailStatus.getParam2());
case EMAIL_SENT:
return res.getString(R.string.email_sent);
case GATEWAY_DISABLED:
return res.getString(R.string.gateway_disabled);
case NO_IDENTITY_MATCHES:
if (full)
return res.getString(R.string.no_identity_matches,
emailStatus.getParam1());
case INVALID_RECIPIENT:
if (full)
return res.getString(R.string.invalid_recipient,
emailStatus.getParam1());
case ERROR_CREATING_PACKETS:
if (full)
return res.getString(R.string.error_creating_packets,
emailStatus.getParam1());
case ERROR_SENDING:
if (full)
return res.getString(R.string.error_sending,
emailStatus.getParam1());
case ERROR_SAVING_METADATA:
if (full)
return res.getString(R.string.error_saving_metadata,
emailStatus.getParam1());
default:
// Short string for errors and unknown status
return res.getString(R.string.error);
}
}
public static boolean isOutbox(EmailFolder folder) {
return isOutbox(folder.getName());
}
public static boolean isOutbox(String folderName) {
return "Outbox".equalsIgnoreCase(folderName);
}
public static boolean isTrash(EmailFolder folder) {
return isTrash(folder.getName());
}
public static boolean isTrash(String folderName) {
return "Trash".equalsIgnoreCase(folderName);
}
public static List<Email> getRecentEmails(EmailFolder folder) throws PasswordException, MessagingException {
List<Email> emails = folder.getElements();
Iterator<Email> iter = emails.iterator();
while (iter.hasNext()) {
Email email = iter.next();
if (!email.isSet(Flag.RECENT))
iter.remove();
}
return emails;
}
}

View File

@@ -0,0 +1,76 @@
package i2p.bote.android.util;
import java.security.GeneralSecurityException;
import java.util.SortedSet;
import i2p.bote.I2PBote;
import i2p.bote.android.R;
import i2p.bote.email.EmailDestination;
import i2p.bote.fileencryption.PasswordException;
import i2p.bote.packet.dht.Contact;
import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.tokenautocomplete.TokenCompleteTextView;
public class ContactsCompletionView extends TokenCompleteTextView {
public ContactsCompletionView(Context context, AttributeSet attrs) {
super(context, attrs);
allowDuplicates(false);
}
@Override
protected View getViewForObject(Object object) {
Person person = (Person) object;
LayoutInflater l = (LayoutInflater)getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
LinearLayout view = (LinearLayout)l.inflate(R.layout.contact_token, (ViewGroup)ContactsCompletionView.this.getParent(), false);
((TextView)view.findViewById(R.id.contact_name)).setText(person.getName());
return view;
}
@Override
protected Object defaultObject(String completionText) {
// Stupid simple example of guessing if we have an email or not
int index = completionText.indexOf('@');
if (index == -1) {
try {
// Check if it is a known Destination
Contact c = BoteHelper.getContact(completionText);
if (c != null)
return new Person(c.getName(), c.getBase64Dest());
// Check if it is a name
SortedSet<Contact> contacts = I2PBote.getInstance().getAddressBook().getAll();
for (Contact contact : contacts) {
if (contact.getName().startsWith(completionText))
return new Person(contact.getName(), contact.getBase64Dest());
}
// Try as a new Destination
try {
new EmailDestination(completionText);
return new Person(completionText.substring(0, 5), completionText);
} catch (GeneralSecurityException e) {
// Not a valid Destination
// Assume the user meant an external address
completionText = completionText.replace(" ", "") + "@example.com";
return new Person(completionText, completionText, true);
}
} catch (PasswordException e) {
// TODO handle
completionText = completionText.replace(" ", "") + "@example.com";
return new Person(completionText, completionText, true);
}
} else {
return new Person(completionText, completionText, true);
}
}
}

View File

@@ -0,0 +1,106 @@
package i2p.bote.android.util;
import java.io.File;
import java.util.List;
import i2p.bote.android.R;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
public class EditPictureFragment extends Fragment {
static final int REQUEST_PICTURE_FILE = 1;
static final int CROP_PICTURE = 2;
Uri mPictureCaptureUri;
Bitmap mPicture;
ImageView mPictureView;
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
mPictureView = (ImageView) view.findViewById(R.id.picture);
// Set up listener for picture changing
mPictureView.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.setType("image/*");
startActivityForResult(
Intent.createChooser(i, "Select a picture"),
REQUEST_PICTURE_FILE);
}
});
}
protected void setPictureB64(String pic) {
mPicture = BoteHelper.decodePicture(pic);
System.out.println("mPicture == null? " + (mPicture == null));
mPictureView.setImageBitmap(mPicture);
}
protected String getPictureB64() {
return BoteHelper.encodePicture(mPicture);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {
if (resultCode == Activity.RESULT_CANCELED) {
System.out.println("Cancelled");
if (mPictureCaptureUri != null ) {
getActivity().getContentResolver().delete(mPictureCaptureUri, null, null);
mPictureCaptureUri = null;
}
}
return;
}
switch (requestCode) {
case REQUEST_PICTURE_FILE:
mPictureCaptureUri = data.getData();
cropPicture();
break;
case CROP_PICTURE:
Bundle extras = data.getExtras();
if (extras != null) {
mPicture = extras.getParcelable("data");
mPictureView.setImageBitmap(mPicture);
}
File f = new File(mPictureCaptureUri.getPath());
if (f.exists())
f.delete();
break;
}
}
private void cropPicture() {
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setType("image/*");
List<ResolveInfo> list = getActivity().getPackageManager().queryIntentActivities(intent, 0);
if (list.size() == 0) {
Toast.makeText(getActivity(), "No image cropping app found", Toast.LENGTH_SHORT).show();
} else {
intent.setData(mPictureCaptureUri);
intent.putExtra("outputX", 72);
intent.putExtra("outputY", 72);
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("scale", true);
intent.putExtra("return-data", true);
startActivityForResult(
Intent.createChooser(intent, "Select a cropping app"),
CROP_PICTURE);
}
}
}

View File

@@ -0,0 +1,39 @@
package i2p.bote.android.util;
import android.content.Context;
import android.preference.EditTextPreference;
import android.text.InputType;
import android.util.AttributeSet;
public class IntEditTextPreference extends EditTextPreference {
public IntEditTextPreference(Context context) {
super(context);
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
}
public IntEditTextPreference(Context context, AttributeSet attrs) {
super(context, attrs);
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
}
public IntEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
}
@Override
public CharSequence getSummary() {
return String.format((String) super.getSummary(), getText());
}
@Override
protected String getPersistedString(String defaultReturnValue) {
return String.valueOf(getPersistedInt(-1));
}
@Override
protected boolean persistString(String value) {
return persistInt(Integer.valueOf(value));
}
}

View File

@@ -0,0 +1,84 @@
package i2p.bote.android.util;
import i2p.bote.I2PBote;
import i2p.bote.android.R;
import i2p.bote.folder.EmailFolder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
public class MoveToDialogFragment extends DialogFragment {
public static final String CURRENT_FOLDER = "current_folder";
public static MoveToDialogFragment newInstance(EmailFolder currentFolder) {
MoveToDialogFragment f = new MoveToDialogFragment();
Bundle args = new Bundle();
args.putString(CURRENT_FOLDER, currentFolder.getName());
f.setArguments(args);
return f;
}
public interface MoveToDialogListener {
public void onFolderSelected(EmailFolder newFolder);
}
MoveToDialogListener mListener;
List<EmailFolder> mFolders;
List<String> mFolderDisplayNames;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (MoveToDialogListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement MoveToDialogListener");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFolders = I2PBote.getInstance().getEmailFolders();
mFolderDisplayNames = new ArrayList<String>();
String curFolder = getArguments().getString(CURRENT_FOLDER);
Iterator<EmailFolder> i = mFolders.iterator();
while (i.hasNext()) {
EmailFolder folder = i.next();
if (folder.getName().equals(curFolder) || BoteHelper.isOutbox(folder.getName()))
i.remove();
else
mFolderDisplayNames.add(
BoteHelper.getFolderDisplayName(getActivity(), folder));
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.action_move_to)
.setItems(mFolderDisplayNames.toArray(new String[mFolderDisplayNames.size()]),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mListener.onFolderSelected(mFolders.get(which));
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
return builder.create();
}
}

View File

@@ -0,0 +1,27 @@
package i2p.bote.android.util;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = -2874686247798691378L;
private String name;
private String address;
private boolean isExternal;
public Person(String n, String a) { this(n, a, false); }
public Person(String n, String a, boolean e) { name = n; address = a; isExternal = e; }
public String getName() { return name; }
public String getAddress() { return address; }
public boolean isExternal() { return isExternal; }
@Override
public boolean equals(Object other) {
if (!(other instanceof Person))
return false;
return address.equals(((Person)other).address);
}
@Override
public String toString() { return name; }
}

View File

@@ -0,0 +1,30 @@
package i2p.bote.android.util;
import android.os.AsyncTask;
public abstract class RobustAsyncTask<Params, Progress, Result> extends
AsyncTask<Params, Progress, Result> {
TaskFragment<Params, Progress, Result> mDialog;
void setFragment(TaskFragment<Params, Progress, Result> fragment) {
mDialog = fragment;
}
@Override
protected void onProgressUpdate(Progress... values) {
if (mDialog != null)
mDialog.updateProgress(values);
}
@Override
protected void onPostExecute(Result result) {
if (mDialog != null)
mDialog.taskFinished(result);
}
@Override
protected void onCancelled(Result result) {
if (mDialog != null)
mDialog.taskCancelled(result);
}
}

View File

@@ -0,0 +1,25 @@
package i2p.bote.android.util;
import android.content.Context;
import android.preference.EditTextPreference;
import android.util.AttributeSet;
public class SummaryEditTextPreference extends EditTextPreference {
public SummaryEditTextPreference(Context context) {
super(context);
}
public SummaryEditTextPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SummaryEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public CharSequence getSummary() {
return String.format((String) super.getSummary(), getText());
}
}

View File

@@ -0,0 +1,69 @@
package i2p.bote.android.util;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
public class TaskFragment<Params, Progress, Result> extends DialogFragment {
RobustAsyncTask<Params, Progress, Result> mTask;
public void setTask(RobustAsyncTask<Params, Progress, Result> task) {
mTask = task;
mTask.setFragment(this);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retain this instance so it isn't destroyed
setRetainInstance(true);
// Start the task
if (mTask != null)
mTask.execute(getParams());
}
// This is to work around what is apparently a bug. If you don't have it
// here the dialog will be dismissed on rotation, so tell it not to dismiss.
@Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
@Override
public void onResume()
{
super.onResume();
// This is a little hacky, but we will see if the task has finished
// while we weren't in this activity, and then we can dismiss ourselves.
if (mTask == null)
dismiss();
}
public Params[] getParams() {
return null;
}
public void updateProgress(Progress... values) {}
public void taskFinished(Result result) {
finishTask();
}
public void taskCancelled(Result result) {
finishTask();
}
private void finishTask() {
// Make sure we check if it is resumed because we will crash if trying
// to dismiss the dialog after the user has switched to another app.
if (isResumed())
dismiss();
// If we aren't resumed, setting the task to null will allow us to
// dismiss ourselves in onResume().
mTask = null;
}
}

View File

@@ -0,0 +1,27 @@
package i2p.bote.imap;
import org.apache.commons.configuration.ConfigurationException;
import i2p.bote.Configuration;
import i2p.bote.fileencryption.PasswordVerifier;
import i2p.bote.folder.EmailFolderManager;
/**
* Stubbed-out ImapService
*/
public class ImapService {
public ImapService(Configuration configuration, final PasswordVerifier passwordVerifier, EmailFolderManager folderManager) throws ConfigurationException {
}
public boolean isStarted() {
return false;
}
public boolean start() {
return false;
}
public boolean stop() {
return true;
}
}

View File

@@ -0,0 +1,26 @@
package i2p.bote.service.seedless;
import java.util.Collection;
import java.util.Collections;
import i2p.bote.network.DhtPeerSource;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.data.Destination;
import net.i2p.util.I2PAppThread;
/**
* Stubbed-out SeedlessInitializer
*/
public class SeedlessInitializer extends I2PAppThread implements DhtPeerSource {
public SeedlessInitializer(I2PSocketManager socketManager) {
super("SeedlessInit");
}
@Override
public void run() {}
@Override
public Collection<Destination> getPeers() {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,25 @@
package i2p.bote.smtp;
import java.net.UnknownHostException;
import i2p.bote.Configuration;
import i2p.bote.MailSender;
import i2p.bote.fileencryption.PasswordVerifier;
/**
* Stubbed-out SmtpService
*/
public class SmtpService {
public SmtpService(Configuration configuration, PasswordVerifier passwordVerifier, MailSender mailSender) throws UnknownHostException {
}
public boolean isRunning() {
return false;
}
public void start() {
}
public void stop() {
}
}

View File

@@ -0,0 +1,182 @@
package net.i2p.util;
/*
* public domain
*
*/
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Queue;
/**
* bridge to android logging
*
* @author zzz
*/
class LogWriter implements Runnable {
private final static long CONFIG_READ_ITERVAL = 10 * 1000;
private long _lastReadConfig = 0;
private long _numBytesInCurrentFile = 0;
private OutputStream _currentOut; // = System.out
private int _rotationNum = -1;
private String _logFilenamePattern;
private File _currentFile;
private LogManager _manager;
private boolean _write;
private LogWriter() { // nop
}
public LogWriter(LogManager manager) {
_manager = manager;
}
public void stopWriting() {
_write = false;
}
public void run() {
_write = true;
try {
while (_write) {
flushRecords();
if (_write)
rereadConfig();
}
} catch (Exception e) {
System.err.println("Error writing the logs: " + e.getMessage());
e.printStackTrace(System.err);
}
}
public void flushRecords() { flushRecords(true); }
public void flushRecords(boolean shouldWait) {
try {
// zero copy, drain the manager queue directly
Queue<LogRecord> records = _manager.getQueue();
if (records == null) return;
if (!records.isEmpty()) {
LogRecord rec;
while ((rec = records.poll()) != null) {
writeRecord(rec);
}
try {
if (_currentOut != null)
_currentOut.flush();
} catch (IOException ioe) {
//if (++_diskFullMessageCount < MAX_DISKFULL_MESSAGES)
System.err.println("Error writing the router log - disk full? " + ioe);
}
}
} catch (Throwable t) {
t.printStackTrace(System.err);
} finally {
if (shouldWait) {
try {
synchronized (this) {
this.wait(10*1000);
}
} catch (InterruptedException ie) { // nop
}
}
}
}
public String currentFile() {
return _currentFile != null ? _currentFile.getAbsolutePath() : "uninitialized";
}
private void rereadConfig() {
long now = Clock.getInstance().now();
if (now - _lastReadConfig > CONFIG_READ_ITERVAL) {
_manager.rereadConfig();
_lastReadConfig = now;
}
}
private void writeRecord(LogRecord rec) {
if (rec.getThrowable() == null)
log(rec.getPriority(), rec.getSource(), rec.getSourceName(), rec.getThreadName(), rec.getMessage());
else
log(rec.getPriority(), rec.getSource(), rec.getSourceName(), rec.getThreadName(), rec.getMessage(), rec.getThrowable());
// to be viewed on android screen
String val = LogRecordFormatter.formatRecord(_manager, rec, true);
_manager.getBuffer().add(val);
if (rec.getPriority() >= Log.ERROR)
_manager.getBuffer().addCritical(val);
// we always add to the console buffer, but only sometimes write to stdout
if (_manager.getDisplayOnScreenLevel() <= rec.getPriority()) {
if (_manager.displayOnScreen()) {
// android log already does time stamps, so reformat without the date
System.out.print(LogRecordFormatter.formatRecord(_manager, rec, false));
}
}
}
private static final String ANDROID_LOG_TAG = "I2P-Bote";
public void log(int priority, Class<?> src, String name, String threadName, String msg) {
if (src != null) {
String tag = src.getName();
int dot = tag.lastIndexOf(".");
if (dot >= 0)
tag = tag.substring(dot + 1);
android.util.Log.println(toAndroidLevel(priority),
ANDROID_LOG_TAG,
tag +
" [" + threadName + "] " + msg);
} else if (name != null)
android.util.Log.println(toAndroidLevel(priority),
ANDROID_LOG_TAG,
name +
" [" + threadName + "] " + msg);
else
android.util.Log.println(toAndroidLevel(priority),
ANDROID_LOG_TAG,
'[' + threadName + "] " + msg);
}
public void log(int priority, Class<?> src, String name, String threadName, String msg, Throwable t) {
if (src != null) {
String tag = src.getName();
int dot = tag.lastIndexOf(".");
if (dot >= 0)
tag = tag.substring(dot + 1);
android.util.Log.println(toAndroidLevel(priority),
ANDROID_LOG_TAG,
tag +
" [" + threadName + "] " + msg +
' ' + t.toString() + ' ' + android.util.Log.getStackTraceString(t));
} else if (name != null)
android.util.Log.println(toAndroidLevel(priority),
ANDROID_LOG_TAG,
name +
" [" + threadName + "] " + msg +
' ' + t.toString() + ' ' + android.util.Log.getStackTraceString(t));
else
android.util.Log.println(toAndroidLevel(priority),
ANDROID_LOG_TAG,
'[' + threadName + "] " +
msg + ' ' + t.toString() + ' ' + android.util.Log.getStackTraceString(t));
}
private static int toAndroidLevel(int level) {
switch (level) {
case Log.DEBUG:
return android.util.Log.DEBUG;
case Log.INFO:
return android.util.Log.INFO;
case Log.WARN:
return android.util.Log.WARN;
case Log.ERROR:
case Log.CRIT:
default:
return android.util.Log.ERROR;
}
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/uva_color" android:state_activated="true" />
<item android:drawable="@color/uva_color" android:state_checked="true" />
<item android:drawable="@color/uva_color" android:state_pressed="true" />
<item android:drawable="@color/uva_color" android:state_selected="true" />
<item android:drawable="@color/default_color" />
</selector>

Some files were not shown because too many files have changed in this diff Show More