/*
* Copyright (c) 2013, Psiphon Inc.
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package ca.psiphon.ploggy;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.text.Html;
import android.widget.Toast;
/**
* Helper for adding friends by exchanging email attachments.
*
* On the sending side, this launches an email draft containing self's
* public identity as a ".ploggy" attachment. On the receiving side,
* ActivityAddFriend handles ".ploggy" attachment invocations.
*
* This work flow can compromise unlinkability:
* - The sent email will expose the Ploggy link between the sender
* and recipient to all email servers along the delivery path.
* - The email attachment must be located on public storage for the email
* client app to read. This exposes the identity to all other apps.
* - Because email is not face-to-face, it may be less likely that users
* will perform a sound, out-of-bound fingerprint verification process.
*
* There's a warning prompt before the action is taken.
* TODO: consider disabling the option entirely if the user selects an "unlinkable" posture.
*/
public class ExportIdentity {
private static final String LOG_TAG = "Export Identity";
private static final String PUBLIC_STORAGE_DIRECTORY = "Ploggy";
// TODO: per-persona filenames?
private static final String IDENTITY_FILENAME = "identity.ploggy";
public static void composeEmail(Context context) {
final Context finalContext = context;
new AlertDialog.Builder(finalContext)
.setTitle(finalContext.getString(R.string.label_email_self_title))
.setMessage(finalContext.getString(R.string.label_email_self_message))
.setPositiveButton(finalContext.getString(R.string.label_email_self_positive),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
String json = Json.toJson(Data.getInstance().getSelf().mPublicIdentity);
String payload = URLEncoder.encode(json, "UTF-8");
String link = ActivityAddFriend.IDENTITY_LINK_PREFIX + payload;
String body = finalContext.getString(R.string.identity_email_body)
.replace("href=\"\"", "href=\""+link+"\"");
Intent intent = new Intent(Intent.ACTION_SEND, Uri.parse("mailto:"));
intent.setType("text/html");
intent.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(body));
finalContext.startActivity(intent);
} catch (IOException e) {
Log.addEntry(LOG_TAG, e.getMessage());
Log.addEntry(LOG_TAG, "failed to compose email with identity link");
} catch (ActivityNotFoundException e) {
Log.addEntry(LOG_TAG, e.getMessage());
Log.addEntry(LOG_TAG, "failed to compose email with identity link");
} catch (Utils.ApplicationError e) {
Log.addEntry(LOG_TAG, "failed to compose email with identity link");
}
}
})
.setNegativeButton(finalContext.getString(R.string.label_email_self_negative), null)
.show();
}
public static void saveIdentityToFile(Activity context) {
final Activity finalContext = context;
new AlertDialog.Builder(finalContext)
.setTitle(finalContext.getString(R.string.label_save_identity_to_file_title))
.setMessage(finalContext.getString(R.string.label_save_identity_to_file_message))
.setPositiveButton(finalContext.getString(R.string.label_save_identity_to_file_positive),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
File directory = new File(Environment.getExternalStorageDirectory(), PUBLIC_STORAGE_DIRECTORY);
directory.mkdirs();
File attachmentFile = new File(directory, IDENTITY_FILENAME);
Utils.writeStringToFile(Json.toJson(Data.getInstance().getSelf().mPublicIdentity), attachmentFile);
String toast = finalContext.getString(
R.string.toast_identity_saved_to_file,
IDENTITY_FILENAME,
PUBLIC_STORAGE_DIRECTORY);
Toast.makeText(finalContext, toast, Toast.LENGTH_LONG).show();
} catch (IOException e) {
Log.addEntry(LOG_TAG, e.getMessage());
Log.addEntry(LOG_TAG, "failed to save identity to file");
} catch (ActivityNotFoundException e) {
Log.addEntry(LOG_TAG, e.getMessage());
Log.addEntry(LOG_TAG, "failed to save identity to file");
} catch (Utils.ApplicationError e) {
Log.addEntry(LOG_TAG, "failed to save identity to file");
}
}
})
.setNegativeButton(finalContext.getString(R.string.label_save_identity_to_file_negative), null)
.show();
}
}