() {
@Override
protected Long onLoad(Context context, Bundle args) throws Throwable {
File file = new File(context.getCacheDir(), "crash.log");
if (file.exists()) {
// Get version info
StringBuilder sb = new StringBuilder();
sb.append(String.format("%s: %s\r\n", context.getString(R.string.app_name), BuildConfig.VERSION_NAME));
sb.append(String.format("Android: %s (SDK %d)\r\n", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
sb.append("\r\n");
// Get device info
sb.append(String.format("Brand: %s\r\n", Build.BRAND));
sb.append(String.format("Manufacturer: %s\r\n", Build.MANUFACTURER));
sb.append(String.format("Model: %s\r\n", Build.MODEL));
sb.append(String.format("Product: %s\r\n", Build.PRODUCT));
sb.append(String.format("Device: %s\r\n", Build.DEVICE));
sb.append(String.format("Host: %s\r\n", Build.HOST));
sb.append(String.format("Display: %s\r\n", Build.DISPLAY));
sb.append(String.format("Id: %s\r\n", Build.ID));
sb.append("\r\n");
BufferedReader in = null;
try {
String line;
in = new BufferedReader(new FileReader(file));
while ((line = in.readLine()) != null)
sb.append(line).append("\r\n");
} finally {
if (in != null)
in.close();
}
file.delete();
String body = "" + sb.toString().replaceAll("\\r?\\n", "
") + "
";
EntityMessage draft = null;
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityFolder drafts = db.folder().getPrimaryDrafts();
if (drafts != null) {
draft = new EntityMessage();
draft.account = drafts.account;
draft.folder = drafts.id;
draft.msgid = EntityMessage.generateMessageId();
draft.to = new Address[]{Helper.myAddress()};
draft.subject = context.getString(R.string.app_name) + " crash log";
draft.received = new Date().getTime();
draft.seen = false;
draft.ui_seen = false;
draft.ui_hide = false;
draft.id = db.message().insertMessage(draft);
draft.write(context, body);
}
EntityOperation.queue(db, draft, EntityOperation.ADD);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
EntityOperation.process(context);
return (draft == null ? null : draft.id);
}
return null;
}
@Override
protected void onLoaded(Bundle args, Long id) {
if (id != null)
startActivity(
new Intent(ActivityView.this, ActivityCompose.class)
.putExtra("action", "edit")
.putExtra("id", id));
}
}.load(this, new Bundle());
if (Helper.isPlayStoreInstall(this)) {
billingClient = BillingClient.newBuilder(this).setListener(this).build();
billingClient.startConnection(billingClientStateListener);
}
checkIntent(getIntent());
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("toggle", drawerToggle.isDrawerIndicatorEnabled());
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
drawerToggle.syncState();
}
@Override
protected void onNewIntent(Intent intent) {
if (intent.getBooleanExtra("setup", false))
intent.getExtras().remove("setup");
else
newIntent = true;
checkIntent(intent);
super.onNewIntent(intent);
}
@Override
protected void onResume() {
super.onResume();
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
IntentFilter iff = new IntentFilter();
iff.addAction(ACTION_VIEW_MESSAGES);
iff.addAction(ACTION_VIEW_MESSAGE);
iff.addAction(ACTION_EDIT_FOLDER);
iff.addAction(ACTION_STORE_ATTACHMENT);
iff.addAction(ACTION_PURCHASE);
iff.addAction(ACTION_ACTIVATE_PRO);
lbm.registerReceiver(receiver, iff);
if (newIntent) {
newIntent = false;
getSupportFragmentManager().popBackStack("unified", 0);
}
if (billingClient != null && billingClient.isReady())
queryPurchases();
}
@Override
protected void onPause() {
super.onPause();
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
lbm.unregisterReceiver(receiver);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
drawerToggle.onConfigurationChanged(newConfig);
}
@Override
protected void onDestroy() {
if (billingClient != null)
billingClient.endConnection();
super.onDestroy();
}
@Override
public void onBackPressed() {
if (drawerLayout.isDrawerOpen(drawerList))
drawerLayout.closeDrawer(drawerList);
else
super.onBackPressed();
}
@Override
public void onBackStackChanged() {
int count = getSupportFragmentManager().getBackStackEntryCount();
if (count == 0)
finish();
else {
if (drawerLayout.isDrawerOpen(drawerList))
drawerLayout.closeDrawer(drawerList);
drawerToggle.setDrawerIndicatorEnabled(count == 1);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (drawerToggle.onOptionsItemSelected(item))
return true;
switch (item.getItemId()) {
case android.R.id.home:
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED))
getSupportFragmentManager().popBackStack();
return true;
default:
return false;
}
}
private String getChallenge() throws NoSuchAlgorithmException {
String android_id = Settings.System.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
return Helper.sha256(android_id);
}
private String getResponse() throws NoSuchAlgorithmException {
return Helper.sha256(BuildConfig.APPLICATION_ID + getChallenge());
}
private void checkIntent(Intent intent) {
Log.i(Helper.TAG, "View intent=" + intent + " action=" + intent.getAction());
String action = intent.getAction();
if ("unseen".equals(action)) {
intent.setAction(null);
setIntent(intent);
Bundle args = new Bundle();
args.putLong("time", new Date().getTime());
new SimpleTask() {
@Override
protected Void onLoad(Context context, Bundle args) {
long time = args.getLong("time");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
for (EntityAccount account : db.account().getAccounts(true))
db.account().setAccountSeenUntil(account.id, time);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Toast.makeText(ActivityView.this, ex.toString(), Toast.LENGTH_LONG).show();
}
}.load(this, args);
}
}
private Intent getIntentPrivacy() {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://email.faircode.eu/privacy/"));
return intent;
}
private Intent getIntentFAQ() {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://github.com/M66B/open-source-email/blob/master/FAQ.md"));
return intent;
}
private Intent getIntentPro() {
if (Helper.isPlayStoreInstall(this))
return null;
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://email.faircode.eu/pro/?challenge=" + getChallenge()));
return intent;
} catch (NoSuchAlgorithmException ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
return null;
}
}
private Intent getIntentOtherApps() {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://play.google.com/store/apps/dev?id=8420080860664580239"));
return intent;
}
private void onMenuFolders(long account) {
getSupportFragmentManager().popBackStack("unified", 0);
Bundle args = new Bundle();
args.putLong("account", account);
FragmentFolders fragment = new FragmentFolders();
fragment.setArguments(args);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("folders");
fragmentTransaction.commit();
}
private void onMenuSetup() {
startActivity(new Intent(ActivityView.this, ActivitySetup.class));
}
private void onMenuOperations() {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, new FragmentOperations()).addToBackStack("operations");
fragmentTransaction.commit();
}
private void onMenuLegend() {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, new FragmentLegend()).addToBackStack("legend");
fragmentTransaction.commit();
}
private void onMenuFAQ() {
startActivity(getIntentFAQ());
}
private void onMenuPro() {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, new FragmentPro()).addToBackStack("pro");
fragmentTransaction.commit();
}
private void onMenuPrivacy() {
startActivity(getIntentPrivacy());
}
private void onMenuAbout() {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, new FragmentAbout()).addToBackStack("about");
fragmentTransaction.commit();
}
private void onMenuOtherApps() {
startActivity(getIntentOtherApps());
}
private class DrawerItem {
private int layout;
private int id;
private int icon;
private String title;
private Object data;
DrawerItem(int layout) {
this.layout = layout;
}
DrawerItem(Context context, int layout, int icon, int title) {
this.layout = layout;
this.id = title;
this.icon = icon;
this.title = context.getString(title);
}
DrawerItem(int layout, int id, int icon, String title, Object data) {
this.layout = layout;
this.id = id;
this.icon = icon;
this.title = title;
this.data = data;
}
public int getId() {
return this.id;
}
public Object getData() {
return this.data;
}
}
private static class ArrayAdapterDrawer extends ArrayAdapter {
ArrayAdapterDrawer(@NonNull Context context) {
super(context, -1);
}
@NonNull
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
DrawerItem item = getItem(position);
View row = LayoutInflater.from(getContext()).inflate(item.layout, null);
ImageView iv = row.findViewById(R.id.ivItem);
TextView tv = row.findViewById(R.id.tvItem);
if (iv != null)
iv.setImageResource(item.icon);
if (tv != null)
tv.setText(item.title);
return row;
}
}
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_VIEW_MESSAGES.equals(intent.getAction()))
onViewMessages(intent);
else if (ACTION_VIEW_MESSAGE.equals(intent.getAction()))
onViewMessage(intent);
else if (ACTION_EDIT_FOLDER.equals(intent.getAction()))
onEditFolder(intent);
else if (ACTION_STORE_ATTACHMENT.equals(intent.getAction()))
onStoreAttachment(intent);
else if (ACTION_PURCHASE.equals(intent.getAction()))
onPurchase(intent);
else if (ACTION_ACTIVATE_PRO.equals(intent.getAction()))
onActivatePro(intent);
}
};
private void onViewMessages(Intent intent) {
FragmentMessages fragment = new FragmentMessages();
fragment.setArguments(intent.getExtras());
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("messages");
fragmentTransaction.commit();
}
private void onViewMessage(Intent intent) {
new SimpleTask() {
@Override
protected Void onLoad(Context context, Bundle args) {
TupleMessageEx message = (TupleMessageEx) args.getSerializable("message");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
for (EntityMessage tmessage : db.message().getMessageByThread(message.account, message.thread)) {
db.message().setMessageUiSeen(tmessage.id, true);
EntityOperation.queue(db, tmessage, EntityOperation.SEEN, true);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
EntityOperation.process(context);
return null;
}
@Override
protected void onLoaded(Bundle args, Void result) {
FragmentMessage fragment = new FragmentMessage();
fragment.setArguments(args);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("message");
fragmentTransaction.commit();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Toast.makeText(ActivityView.this, ex.toString(), Toast.LENGTH_LONG).show();
}
}.load(ActivityView.this, intent.getExtras());
}
private void onEditFolder(Intent intent) {
FragmentFolder fragment = new FragmentFolder();
fragment.setArguments(intent.getExtras());
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("folder");
fragmentTransaction.commit();
}
private void onStoreAttachment(Intent intent) {
attachment = intent.getLongExtra("id", -1);
Intent create = new Intent(Intent.ACTION_CREATE_DOCUMENT);
create.addCategory(Intent.CATEGORY_OPENABLE);
create.setType(intent.getStringExtra("type"));
create.putExtra(Intent.EXTRA_TITLE, intent.getStringExtra("name"));
startActivityForResult(create, REQUEST_ATTACHMENT);
}
private void onPurchase(Intent intent) {
if (Helper.isPlayStoreInstall(this)) {
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSku(BuildConfig.APPLICATION_ID + ".pro")
.setType(BillingClient.SkuType.INAPP)
.build();
int responseCode = billingClient.launchBillingFlow(ActivityView.this, flowParams);
String text = Helper.getBillingResponseText(responseCode);
Log.i(Helper.TAG, "IAB launch billing flow response=" + text);
if (responseCode != BillingClient.BillingResponse.OK)
Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
} else
startActivity(getIntentPro());
}
private void onActivatePro(Intent intent) {
try {
Uri data = intent.getParcelableExtra("uri");
String challenge = getChallenge();
String response = data.getQueryParameter("response");
Log.i(Helper.TAG, "Challenge=" + challenge);
Log.i(Helper.TAG, "Response=" + response);
String expected = getResponse();
if (expected.equals(response)) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ActivityView.this);
prefs.edit().putBoolean("pro", true).apply();
Log.i(Helper.TAG, "Response valid");
Snackbar.make(view, R.string.title_pro_valid, Snackbar.LENGTH_LONG).show();
} else {
Log.i(Helper.TAG, "Response invalid");
Snackbar.make(view, R.string.title_pro_invalid, Snackbar.LENGTH_LONG).show();
}
intent.setData(null);
setIntent(intent);
} catch (NoSuchAlgorithmException ex) {
Log.e(Helper.TAG, Log.getStackTraceString(ex));
Toast.makeText(ActivityView.this, ex.getMessage(), Toast.LENGTH_LONG).show();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.i(Helper.TAG, "View onActivityResult request=" + requestCode + " result=" + resultCode + " data=" + data);
if (resultCode == Activity.RESULT_OK)
if (requestCode == REQUEST_ATTACHMENT) {
Bundle args = new Bundle();
args.putLong("id", attachment);
args.putParcelable("uri", data.getData());
new SimpleTask() {
@Override
protected Void onLoad(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
Uri uri = args.getParcelable("uri");
File file = EntityAttachment.getFile(context, id);
ParcelFileDescriptor pfd = null;
FileOutputStream fos = null;
FileInputStream fis = null;
try {
pfd = context.getContentResolver().openFileDescriptor(uri, "w");
fos = new FileOutputStream(pfd.getFileDescriptor());
fis = new FileInputStream(file);
byte[] buffer = new byte[ATTACHMENT_BUFFER_SIZE];
int read;
while ((read = fis.read(buffer)) != -1) {
fos.write(buffer, 0, read);
}
} finally {
try {
if (pfd != null)
pfd.close();
} catch (Throwable ex) {
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
}
try {
if (fos != null)
fos.close();
} catch (Throwable ex) {
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
}
try {
if (fis != null)
fis.close();
} catch (Throwable ex) {
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
}
}
return null;
}
@Override
protected void onLoaded(Bundle args, Void data) {
Toast.makeText(ActivityView.this, R.string.title_attachment_saved, Toast.LENGTH_LONG).show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
Toast.makeText(ActivityView.this, ex.toString(), Toast.LENGTH_LONG).show();
}
}.load(this, args);
}
}
private BillingClientStateListener billingClientStateListener = new BillingClientStateListener() {
private int backoff = 4; // seconds
@Override
public void onBillingSetupFinished(@BillingClient.BillingResponse int responseCode) {
String text = Helper.getBillingResponseText(responseCode);
Log.i(Helper.TAG, "IAB connected response=" + text);
if (responseCode == BillingClient.BillingResponse.OK) {
backoff = 4;
queryPurchases();
} else
Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
}
@Override
public void onBillingServiceDisconnected() {
backoff *= 2;
Log.i(Helper.TAG, "IAB disconnected retry in " + backoff + " s");
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (!billingClient.isReady())
billingClient.startConnection(billingClientStateListener);
}
}, backoff * 1000L);
}
};
@Override
public void onPurchasesUpdated(int responseCode, @android.support.annotation.Nullable List purchases) {
String text = Helper.getBillingResponseText(responseCode);
Log.i(Helper.TAG, "IAB purchases updated response=" + text);
if (responseCode == BillingClient.BillingResponse.OK)
checkPurchases(purchases);
else
Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
}
private void queryPurchases() {
Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
String text = Helper.getBillingResponseText(result.getResponseCode());
Log.i(Helper.TAG, "IAB query purchases response=" + text);
if (result.getResponseCode() == BillingClient.BillingResponse.OK)
checkPurchases(result.getPurchasesList());
else
Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
}
private void checkPurchases(List purchases) {
if (purchases != null) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = prefs.edit();
editor.remove("pro");
for (Purchase purchase : purchases) {
Log.i(Helper.TAG, "IAB SKU=" + purchase.getSku());
if ((BuildConfig.APPLICATION_ID + ".pro").equals(purchase.getSku())) {
editor.putBoolean("pro", true);
Log.i(Helper.TAG, "IAB pro features activated");
}
}
editor.apply();
}
}
}