Fixed build process
109
app/botejars.xml
Normal 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
@@ -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
BIN
app/libs/additionnal.jar
Normal file
BIN
app/libs/mail.jar
Normal file
BIN
app/libs/tokenautocomplete.jar
Normal file
57
app/src/main/AndroidManifest.xml
Normal 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>
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
448
app/src/main/java/i2p/bote/android/EmailListActivity.java
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
155
app/src/main/java/i2p/bote/android/EmailListAdapter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
464
app/src/main/java/i2p/bote/android/EmailListFragment.java
Normal 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);
|
||||
}
|
||||
}
|
||||
70
app/src/main/java/i2p/bote/android/FolderListAdapter.java
Normal 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();
|
||||
}
|
||||
}
|
||||
21
app/src/main/java/i2p/bote/android/NetworkInfoActivity.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
52
app/src/main/java/i2p/bote/android/NetworkInfoFragment.java
Normal 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());
|
||||
}
|
||||
}
|
||||
38
app/src/main/java/i2p/bote/android/NewEmailActivity.java
Normal 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();
|
||||
}
|
||||
}
|
||||
372
app/src/main/java/i2p/bote/android/NewEmailFragment.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
209
app/src/main/java/i2p/bote/android/ViewEmailActivity.java
Normal 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);
|
||||
}
|
||||
}
|
||||
158
app/src/main/java/i2p/bote/android/ViewEmailFragment.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
539
app/src/main/java/i2p/bote/android/config/SettingsActivity.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
232
app/src/main/java/i2p/bote/android/service/BoteService.java
Normal 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());
|
||||
}
|
||||
}
|
||||
74
app/src/main/java/i2p/bote/android/service/Init.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
231
app/src/main/java/i2p/bote/android/util/BoteHelper.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
106
app/src/main/java/i2p/bote/android/util/EditPictureFragment.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
27
app/src/main/java/i2p/bote/android/util/Person.java
Normal 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; }
|
||||
}
|
||||
30
app/src/main/java/i2p/bote/android/util/RobustAsyncTask.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
69
app/src/main/java/i2p/bote/android/util/TaskFragment.java
Normal 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;
|
||||
}
|
||||
}
|
||||
27
app/src/main/java/i2p/bote/imap/ImapService.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
25
app/src/main/java/i2p/bote/smtp/SmtpService.java
Normal 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() {
|
||||
}
|
||||
}
|
||||
182
app/src/main/java/net/i2p/util/LogWriter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
app/src/main/jniLibs/armeabi/libscrypt.so
Normal file
BIN
app/src/main/res/drawable-hdpi/drawer_shadow.9.png
Normal file
|
After Width: | Height: | Size: 171 B |
BIN
app/src/main/res/drawable-hdpi/ic_collections_collection.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_contact_picture.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_content_attachment.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_content_discard.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_content_edit.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_content_new_email.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_content_read.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_content_save.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_content_unread.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_device_access_new_account.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_drawer.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_navigation_accept.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_social_add_person.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_social_reply.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_social_send_now.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/drawable-mdpi/drawer_shadow.9.png
Normal file
|
After Width: | Height: | Size: 158 B |
BIN
app/src/main/res/drawable-mdpi/ic_collections_collection.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_contact_picture.png
Normal file
|
After Width: | Height: | Size: 976 B |
BIN
app/src/main/res/drawable-mdpi/ic_content_attachment.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_content_discard.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_content_edit.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_content_new_email.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_content_read.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_content_save.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_content_unread.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_device_access_new_account.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_drawer.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_navigation_accept.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_social_add_person.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_social_reply.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_social_send_now.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable-xhdpi/drawer_shadow.9.png
Normal file
|
After Width: | Height: | Size: 182 B |
BIN
app/src/main/res/drawable-xhdpi/ic_collections_collection.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_contact_picture.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_content_attachment.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_content_discard.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_content_edit.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_content_new_email.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_content_read.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_content_save.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_content_unread.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_device_access_new_account.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_drawer.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_navigation_accept.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_social_add_person.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_social_reply.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_social_send_now.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
@@ -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>
|
||||