Exports, some Kotlin stuff, and version 1.0-beta15
This commit is contained in:
parent
898c49802b
commit
e79bfdb244
@ -1,5 +1,6 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'idea'
|
apply plugin: 'idea'
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
@ -19,8 +20,8 @@ android {
|
|||||||
applicationId "ch.dissem.apps." + appName.toLowerCase()
|
applicationId "ch.dissem.apps." + appName.toLowerCase()
|
||||||
minSdkVersion 19
|
minSdkVersion 19
|
||||||
targetSdkVersion 25
|
targetSdkVersion 25
|
||||||
versionCode 14
|
versionCode 15
|
||||||
versionName "1.0-beta14"
|
versionName "1.0-beta15"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@ -43,6 +44,7 @@ ext.supportVersion = '25.3.1'
|
|||||||
dependencies {
|
dependencies {
|
||||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
||||||
|
compile "org.jetbrains.anko:anko:$anko_version"
|
||||||
|
|
||||||
compile "com.android.support:appcompat-v7:$supportVersion"
|
compile "com.android.support:appcompat-v7:$supportVersion"
|
||||||
compile "com.android.support:preference-v7:$supportVersion"
|
compile "com.android.support:preference-v7:$supportVersion"
|
||||||
|
@ -134,6 +134,17 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:syncable="true"/>
|
android:syncable="true"/>
|
||||||
|
|
||||||
|
<!-- Exports -->
|
||||||
|
<provider
|
||||||
|
android:name="android.support.v4.content.FileProvider"
|
||||||
|
android:authorities="ch.dissem.apps.abit.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".synchronization.AuthenticatorService"
|
android:name=".synchronization.AuthenticatorService"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
@ -1,160 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.apps.abit;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.v7.preference.Preference;
|
|
||||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.mikepenz.aboutlibraries.Libs;
|
|
||||||
import com.mikepenz.aboutlibraries.LibsBuilder;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.service.Singleton;
|
|
||||||
import ch.dissem.apps.abit.synchronization.SyncAdapter;
|
|
||||||
import ch.dissem.bitmessage.BitmessageContext;
|
|
||||||
|
|
||||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
|
|
||||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class SettingsFragment
|
|
||||||
extends PreferenceFragmentCompat
|
|
||||||
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
|
||||||
addPreferencesFromResource(R.xml.preferences);
|
|
||||||
|
|
||||||
Preference about = findPreference("about");
|
|
||||||
about.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
|
||||||
LibsBuilder libsBuilder = new LibsBuilder()
|
|
||||||
.withActivityTitle(getActivity().getString(R.string.about))
|
|
||||||
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
|
|
||||||
.withAboutIconShown(true)
|
|
||||||
.withAboutVersionShown(true)
|
|
||||||
.withAboutDescription(getString(R.string.about_app));
|
|
||||||
MainActivity activity = (MainActivity) getActivity();
|
|
||||||
if (activity.hasDetailPane()) {
|
|
||||||
activity.setDetailView(libsBuilder.supportFragment());
|
|
||||||
} else {
|
|
||||||
libsBuilder.start(getActivity());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final Preference cleanup = findPreference("cleanup");
|
|
||||||
cleanup.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
|
||||||
new AsyncTask<Void, Void, Void>() {
|
|
||||||
private Context ctx = getActivity().getApplicationContext();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
cleanup.setEnabled(false);
|
|
||||||
Toast.makeText(ctx, R.string.cleanup_notification_start, Toast
|
|
||||||
.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... voids) {
|
|
||||||
BitmessageContext bmc = Singleton.getBitmessageContext(ctx);
|
|
||||||
bmc.cleanup();
|
|
||||||
bmc.internals().getNodeRegistry().clear();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void aVoid) {
|
|
||||||
Toast.makeText(
|
|
||||||
ctx,
|
|
||||||
R.string.cleanup_notification_end,
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show();
|
|
||||||
cleanup.setEnabled(true);
|
|
||||||
}
|
|
||||||
}.execute();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Preference status = findPreference("status");
|
|
||||||
status.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
|
||||||
MainActivity activity = (MainActivity) getActivity();
|
|
||||||
if (activity.hasDetailPane()) {
|
|
||||||
activity.setDetailView(new StatusFragment());
|
|
||||||
} else {
|
|
||||||
startActivity(new Intent(getActivity(), StatusActivity.class));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context ctx) {
|
|
||||||
super.onAttach(ctx);
|
|
||||||
if (ctx instanceof MainActivity){
|
|
||||||
((MainActivity) ctx).getFloatingActionButton().hide();
|
|
||||||
}
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(ctx)
|
|
||||||
.registerOnSharedPreferenceChangeListener(this);
|
|
||||||
|
|
||||||
if (ctx instanceof MainActivity) {
|
|
||||||
((MainActivity) ctx).updateTitle(getString(R.string.settings));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
|
||||||
switch (key) {
|
|
||||||
case PREFERENCE_TRUSTED_NODE: {
|
|
||||||
String node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null);
|
|
||||||
if (node != null) {
|
|
||||||
SyncAdapter.startSync(getActivity());
|
|
||||||
} else {
|
|
||||||
SyncAdapter.stopSync(getActivity());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PREFERENCE_SERVER_POW: {
|
|
||||||
String node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null);
|
|
||||||
if (node != null) {
|
|
||||||
if (sharedPreferences.getBoolean(PREFERENCE_SERVER_POW, false)) {
|
|
||||||
SyncAdapter.startPowSync(getActivity());
|
|
||||||
} else {
|
|
||||||
SyncAdapter.stopPowSync(getActivity());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
185
app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt
Normal file
185
app/src/main/java/ch/dissem/apps/abit/SettingsFragment.kt
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.apps.abit
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.preference.PreferenceManager
|
||||||
|
import android.support.v4.content.FileProvider.getUriForFile
|
||||||
|
import android.support.v7.preference.Preference
|
||||||
|
import android.support.v7.preference.PreferenceFragmentCompat
|
||||||
|
import android.widget.Toast
|
||||||
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
|
import ch.dissem.apps.abit.synchronization.SyncAdapter
|
||||||
|
import ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW
|
||||||
|
import ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE
|
||||||
|
import ch.dissem.apps.abit.util.Preferences
|
||||||
|
import ch.dissem.bitmessage.exports.ContactExport
|
||||||
|
import ch.dissem.bitmessage.exports.MessageExport
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime
|
||||||
|
import com.mikepenz.aboutlibraries.Libs
|
||||||
|
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||||
|
import org.jetbrains.anko.doAsync
|
||||||
|
import org.jetbrains.anko.support.v4.indeterminateProgressDialog
|
||||||
|
import org.jetbrains.anko.uiThread
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
addPreferencesFromResource(R.xml.preferences)
|
||||||
|
|
||||||
|
findPreference("about")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
|
val libsBuilder = LibsBuilder()
|
||||||
|
.withActivityTitle(activity.getString(R.string.about))
|
||||||
|
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
|
||||||
|
.withAboutIconShown(true)
|
||||||
|
.withAboutVersionShown(true)
|
||||||
|
.withAboutDescription(getString(R.string.about_app))
|
||||||
|
val activity = activity as MainActivity
|
||||||
|
if (activity.hasDetailPane()) {
|
||||||
|
activity.setDetailView(libsBuilder.supportFragment())
|
||||||
|
} else {
|
||||||
|
libsBuilder.start(getActivity())
|
||||||
|
}
|
||||||
|
return@OnPreferenceClickListener true
|
||||||
|
}
|
||||||
|
val cleanup = findPreference("cleanup")
|
||||||
|
cleanup?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
|
val ctx = activity.applicationContext
|
||||||
|
cleanup.isEnabled = false
|
||||||
|
Toast.makeText(ctx, R.string.cleanup_notification_start, Toast
|
||||||
|
.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
doAsync {
|
||||||
|
val bmc = Singleton.getBitmessageContext(ctx)
|
||||||
|
bmc.cleanup()
|
||||||
|
bmc.internals.nodeRegistry.clear()
|
||||||
|
Preferences.cleanupExportDirectory(ctx)
|
||||||
|
|
||||||
|
uiThread {
|
||||||
|
Toast.makeText(
|
||||||
|
ctx,
|
||||||
|
R.string.cleanup_notification_end,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
cleanup.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@OnPreferenceClickListener true
|
||||||
|
}
|
||||||
|
|
||||||
|
findPreference("export")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
|
val dialog = indeterminateProgressDialog(R.string.export_data_summary, R.string.export_data)
|
||||||
|
doAsync {
|
||||||
|
val exportDirectory = Preferences.getExportDirectory(context)
|
||||||
|
exportDirectory.mkdirs()
|
||||||
|
val temp = File(exportDirectory, "export-${UnixTime.now}.zip")
|
||||||
|
ZipOutputStream(FileOutputStream(temp)).use { zip ->
|
||||||
|
zip.putNextEntry(ZipEntry("contacts.json"))
|
||||||
|
val addressRepo = Singleton.getAddressRepository(context)
|
||||||
|
val exportContacts = ContactExport.exportContacts(addressRepo.getContacts())
|
||||||
|
zip.write(
|
||||||
|
exportContacts.toJsonString(true).toByteArray()
|
||||||
|
)
|
||||||
|
zip.closeEntry()
|
||||||
|
|
||||||
|
val messageRepo = Singleton.getMessageRepository(context)
|
||||||
|
zip.putNextEntry(ZipEntry("labels.json"))
|
||||||
|
val exportLabels = MessageExport.exportLabels(messageRepo.getLabels())
|
||||||
|
zip.write(
|
||||||
|
exportLabels.toJsonString(true).toByteArray()
|
||||||
|
)
|
||||||
|
zip.closeEntry()
|
||||||
|
zip.putNextEntry(ZipEntry("messages.json"))
|
||||||
|
val exportMessages = MessageExport.exportMessages(messageRepo.getAllMessages())
|
||||||
|
zip.write(
|
||||||
|
exportMessages.toJsonString(true).toByteArray()
|
||||||
|
)
|
||||||
|
zip.closeEntry()
|
||||||
|
}
|
||||||
|
|
||||||
|
val contentUri = getUriForFile(context, "ch.dissem.apps.abit.fileprovider", temp)
|
||||||
|
val intent = Intent(android.content.Intent.ACTION_SEND)
|
||||||
|
intent.type = "application/zip"
|
||||||
|
intent.putExtra(Intent.EXTRA_SUBJECT, "abit-export.zip")
|
||||||
|
intent.putExtra(Intent.EXTRA_STREAM, contentUri)
|
||||||
|
startActivityForResult(Intent.createChooser(intent, ""), 0)
|
||||||
|
uiThread {
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@OnPreferenceClickListener true
|
||||||
|
}
|
||||||
|
|
||||||
|
findPreference("status").onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
|
val activity = activity as MainActivity
|
||||||
|
if (activity.hasDetailPane()) {
|
||||||
|
activity.setDetailView(StatusFragment())
|
||||||
|
} else {
|
||||||
|
startActivity(Intent(getActivity(), StatusActivity::class.java))
|
||||||
|
}
|
||||||
|
return@OnPreferenceClickListener true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
Preferences.cleanupExportDirectory(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttach(ctx: Context?) {
|
||||||
|
super.onAttach(ctx)
|
||||||
|
(ctx as? MainActivity)?.floatingActionButton?.hide()
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
|
||||||
|
(ctx as? MainActivity)?.updateTitle(getString(R.string.settings))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||||
|
when (key) {
|
||||||
|
PREFERENCE_TRUSTED_NODE -> {
|
||||||
|
val node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null)
|
||||||
|
if (node != null) {
|
||||||
|
SyncAdapter.startSync(activity)
|
||||||
|
} else {
|
||||||
|
SyncAdapter.stopSync(activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PREFERENCE_SERVER_POW -> {
|
||||||
|
val node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null)
|
||||||
|
if (node != null) {
|
||||||
|
if (sharedPreferences.getBoolean(PREFERENCE_SERVER_POW, false)) {
|
||||||
|
SyncAdapter.startPowSync(activity)
|
||||||
|
} else {
|
||||||
|
SyncAdapter.stopPowSync(activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.apps.abit.notification;
|
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.support.v7.app.NotificationCompat;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.MainActivity;
|
|
||||||
import ch.dissem.apps.abit.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ongoing notification while proof of work is in progress.
|
|
||||||
*/
|
|
||||||
public class ProofOfWorkNotification extends AbstractNotification {
|
|
||||||
public static final int ONGOING_NOTIFICATION_ID = 3;
|
|
||||||
|
|
||||||
public ProofOfWorkNotification(Context ctx) {
|
|
||||||
super(ctx);
|
|
||||||
update(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getNotificationId() {
|
|
||||||
return ONGOING_NOTIFICATION_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProofOfWorkNotification update(int numberOfItems) {
|
|
||||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx);
|
|
||||||
|
|
||||||
Intent showMessageIntent = new Intent(ctx, MainActivity.class);
|
|
||||||
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
|
|
||||||
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
||||||
.setUsesChronometer(true)
|
|
||||||
.setOngoing(true)
|
|
||||||
.setSmallIcon(R.drawable.ic_notification_proof_of_work)
|
|
||||||
.setContentTitle(ctx.getString(R.string.proof_of_work_title))
|
|
||||||
.setContentText(numberOfItems == 0
|
|
||||||
? ctx.getString(R.string.proof_of_work_text_0)
|
|
||||||
: ctx.getString(R.string.proof_of_work_text_n, numberOfItems))
|
|
||||||
.setContentIntent(pendingIntent);
|
|
||||||
|
|
||||||
notification = builder.build();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.apps.abit.notification
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.support.v7.app.NotificationCompat
|
||||||
|
|
||||||
|
import ch.dissem.apps.abit.MainActivity
|
||||||
|
import ch.dissem.apps.abit.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ongoing notification while proof of work is in progress.
|
||||||
|
*/
|
||||||
|
class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
update(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNotificationId(): Int {
|
||||||
|
return ONGOING_NOTIFICATION_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(numberOfItems: Int): ProofOfWorkNotification {
|
||||||
|
val builder = NotificationCompat.Builder(ctx)
|
||||||
|
|
||||||
|
val showMessageIntent = Intent(ctx, MainActivity::class.java)
|
||||||
|
val pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
|
||||||
|
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
|
.setUsesChronometer(true)
|
||||||
|
.setOngoing(true)
|
||||||
|
.setSmallIcon(R.drawable.ic_notification_proof_of_work)
|
||||||
|
.setContentTitle(ctx.getString(R.string.proof_of_work_title))
|
||||||
|
.setContentText(if (numberOfItems == 0)
|
||||||
|
ctx.getString(R.string.proof_of_work_text_0)
|
||||||
|
else
|
||||||
|
ctx.getString(R.string.proof_of_work_text_n, numberOfItems))
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
|
||||||
|
notification = builder.build()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField val ONGOING_NOTIFICATION_ID = 3
|
||||||
|
}
|
||||||
|
}
|
@ -1,119 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.apps.abit.service;
|
|
||||||
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Binder;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Queue;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.notification.ProofOfWorkNotification;
|
|
||||||
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
|
|
||||||
|
|
||||||
import static ch.dissem.apps.abit.notification.ProofOfWorkNotification.ONGOING_NOTIFICATION_ID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Proof of Work Service makes sure POW is done in a foreground process, so it shouldn't be
|
|
||||||
* killed by the system before the nonce is found.
|
|
||||||
*/
|
|
||||||
public class ProofOfWorkService extends Service {
|
|
||||||
// Object to use as a thread-safe lock
|
|
||||||
private static final ProofOfWorkEngine engine = new MultiThreadedPOWEngine();
|
|
||||||
private static final Queue<PowItem> queue = new LinkedList<>();
|
|
||||||
private static boolean calculating;
|
|
||||||
private ProofOfWorkNotification notification;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
notification = new ProofOfWorkNotification(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return new PowBinder(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class PowBinder extends Binder {
|
|
||||||
private final ProofOfWorkService service;
|
|
||||||
private final ProofOfWorkNotification notification;
|
|
||||||
|
|
||||||
private PowBinder(ProofOfWorkService service) {
|
|
||||||
this.service = service;
|
|
||||||
this.notification = service.notification;
|
|
||||||
}
|
|
||||||
|
|
||||||
void process(PowItem item) {
|
|
||||||
synchronized (queue) {
|
|
||||||
service.startService(new Intent(service, ProofOfWorkService.class));
|
|
||||||
service.startForeground(ONGOING_NOTIFICATION_ID,
|
|
||||||
notification.getNotification());
|
|
||||||
if (!calculating) {
|
|
||||||
calculating = true;
|
|
||||||
service.calculateNonce(item);
|
|
||||||
} else {
|
|
||||||
queue.add(item);
|
|
||||||
notification.update(queue.size()).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static class PowItem {
|
|
||||||
private final byte[] initialHash;
|
|
||||||
private final byte[] targetValue;
|
|
||||||
private final ProofOfWorkEngine.Callback callback;
|
|
||||||
|
|
||||||
PowItem(byte[] initialHash, byte[] targetValue, ProofOfWorkEngine.Callback callback) {
|
|
||||||
this.initialHash = initialHash;
|
|
||||||
this.targetValue = targetValue;
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void calculateNonce(final PowItem item) {
|
|
||||||
engine.calculateNonce(item.initialHash, item.targetValue, new ProofOfWorkEngine.Callback() {
|
|
||||||
@Override
|
|
||||||
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
|
|
||||||
try {
|
|
||||||
item.callback.onNonceCalculated(initialHash, nonce);
|
|
||||||
} finally {
|
|
||||||
PowItem next;
|
|
||||||
synchronized (queue) {
|
|
||||||
next = queue.poll();
|
|
||||||
if (next == null) {
|
|
||||||
calculating = false;
|
|
||||||
stopForeground(true);
|
|
||||||
stopSelf();
|
|
||||||
} else {
|
|
||||||
notification.update(queue.size()).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (next != null) {
|
|
||||||
calculateNonce(next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.apps.abit.service
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Binder
|
||||||
|
import android.os.IBinder
|
||||||
|
import ch.dissem.apps.abit.notification.ProofOfWorkNotification
|
||||||
|
import ch.dissem.apps.abit.notification.ProofOfWorkNotification.Companion.ONGOING_NOTIFICATION_ID
|
||||||
|
import ch.dissem.apps.abit.util.PowStats
|
||||||
|
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine
|
||||||
|
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Proof of Work Service makes sure POW is done in a foreground process, so it shouldn't be
|
||||||
|
* killed by the system before the nonce is found.
|
||||||
|
*/
|
||||||
|
class ProofOfWorkService : Service() {
|
||||||
|
private lateinit var notification: ProofOfWorkNotification
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
notification = ProofOfWorkNotification(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
|
return PowBinder(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
class PowBinder internal constructor(private val service: ProofOfWorkService) : Binder() {
|
||||||
|
private val notification: ProofOfWorkNotification
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.notification = service.notification
|
||||||
|
}
|
||||||
|
|
||||||
|
fun process(item: PowItem) {
|
||||||
|
synchronized(queue) {
|
||||||
|
service.startService(Intent(service, ProofOfWorkService::class.java))
|
||||||
|
service.startForeground(ONGOING_NOTIFICATION_ID,
|
||||||
|
notification.notification)
|
||||||
|
if (!calculating) {
|
||||||
|
calculating = true
|
||||||
|
service.calculateNonce(item)
|
||||||
|
} else {
|
||||||
|
queue.add(item)
|
||||||
|
notification.update(queue.size).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data class PowItem(val initialHash: ByteArray, val targetValue: ByteArray, val callback: ProofOfWorkEngine.Callback)
|
||||||
|
|
||||||
|
private fun calculateNonce(item: PowItem) {
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
|
engine.calculateNonce(item.initialHash, item.targetValue, object : ProofOfWorkEngine.Callback {
|
||||||
|
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
|
||||||
|
val time = System.currentTimeMillis() - startTime
|
||||||
|
PowStats.addPow(this@ProofOfWorkService, time, item.targetValue)
|
||||||
|
try {
|
||||||
|
item.callback.onNonceCalculated(initialHash, nonce)
|
||||||
|
} finally {
|
||||||
|
var next: PowItem? = null
|
||||||
|
synchronized(queue) {
|
||||||
|
next = queue.poll()
|
||||||
|
if (next == null) {
|
||||||
|
calculating = false
|
||||||
|
stopForeground(true)
|
||||||
|
stopSelf()
|
||||||
|
} else {
|
||||||
|
notification.update(queue.size).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next?.let { calculateNonce(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// Object to use as a thread-safe lock
|
||||||
|
private val engine = MultiThreadedPOWEngine()
|
||||||
|
private val queue = LinkedList<PowItem>()
|
||||||
|
private var calculating: Boolean = false
|
||||||
|
}
|
||||||
|
}
|
@ -27,6 +27,8 @@ public class Constants {
|
|||||||
public static final String PREFERENCE_SYNC_TIMEOUT = "sync_timeout";
|
public static final String PREFERENCE_SYNC_TIMEOUT = "sync_timeout";
|
||||||
public static final String PREFERENCE_SERVER_POW = "server_pow";
|
public static final String PREFERENCE_SERVER_POW = "server_pow";
|
||||||
public static final String PREFERENCE_FULL_NODE = "full_node";
|
public static final String PREFERENCE_FULL_NODE = "full_node";
|
||||||
|
public static final String PREFERENCE_POW_AVERAGE = "average_pow_time_ms";
|
||||||
|
public static final String PREFERENCE_POW_COUNT = "pow_count";
|
||||||
|
|
||||||
public static final String BITMESSAGE_URL_SCHEMA = "bitmessage:";
|
public static final String BITMESSAGE_URL_SCHEMA = "bitmessage:";
|
||||||
public static final Pattern BITMESSAGE_ADDRESS_PATTERN = Pattern.compile("\\bBM-[a-zA-Z0-9]+\\b");
|
public static final Pattern BITMESSAGE_ADDRESS_PATTERN = Pattern.compile("\\bBM-[a-zA-Z0-9]+\\b");
|
||||||
|
31
app/src/main/java/ch/dissem/apps/abit/util/PowStats.kt
Normal file
31
app/src/main/java/ch/dissem/apps/abit/util/PowStats.kt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package ch.dissem.apps.abit.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by chrigu on 02.08.17.
|
||||||
|
*/
|
||||||
|
object PowStats {
|
||||||
|
var powUnitTime: Long = 0
|
||||||
|
var powCount: Long = 0
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getExpectedPowTime(ctx: Context, target: ByteArray): Long {
|
||||||
|
// val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
// return preferences.getLong(Constants.PREFERENCE_POW_AVERAGE, 0L)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// fun updatePowTelemetry(ctx: Context, averagePowTime: Long, powCount: Long) {
|
||||||
|
// val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
// preferences.edit()
|
||||||
|
// .putLong(Constants.PREFERENCE_POW_AVERAGE, averagePowTime)
|
||||||
|
// .putLong(Constants.PREFERENCE_POW_COUNT, powCount)
|
||||||
|
// .apply()
|
||||||
|
// }
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun addPow(ctx: Context, time: Long, target: ByteArray) {
|
||||||
|
powCount++
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,8 @@ import ch.dissem.apps.abit.R
|
|||||||
import ch.dissem.apps.abit.listener.WifiReceiver
|
import ch.dissem.apps.abit.listener.WifiReceiver
|
||||||
import ch.dissem.apps.abit.notification.ErrorNotification
|
import ch.dissem.apps.abit.notification.ErrorNotification
|
||||||
import ch.dissem.apps.abit.util.Constants.*
|
import ch.dissem.apps.abit.util.Constants.*
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
|
||||||
@ -29,6 +31,8 @@ import java.net.InetAddress
|
|||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
object Preferences {
|
object Preferences {
|
||||||
|
private val LOG = LoggerFactory.getLogger(Preferences::class.java)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun useTrustedNode(ctx: Context): Boolean {
|
fun useTrustedNode(ctx: Context): Boolean {
|
||||||
val trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return false
|
val trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return false
|
||||||
@ -112,4 +116,25 @@ object Preferences {
|
|||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
preferences.edit().putBoolean(PREFERENCE_FULL_NODE, status).apply()
|
preferences.edit().putBoolean(PREFERENCE_FULL_NODE, status).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getExportDirectory(ctx: Context): File {
|
||||||
|
return File(ctx.filesDir, "exports")
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun cleanupExportDirectory(ctx: Context) {
|
||||||
|
val exportDirectory = getExportDirectory(ctx)
|
||||||
|
if (exportDirectory.exists()) {
|
||||||
|
exportDirectory.listFiles().forEach { file ->
|
||||||
|
try {
|
||||||
|
if (!file.delete()) {
|
||||||
|
file.deleteOnExit()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LOG.debug(e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,4 +119,6 @@ Als Alternative kann in den Einstellungen ein vertrauenswürdiger Knoten konfigu
|
|||||||
<string name="cleanup_summary">Veraltete Inventareinträge werden entfernt</string>
|
<string name="cleanup_summary">Veraltete Inventareinträge werden entfernt</string>
|
||||||
<string name="wait_for_wifi">Auf W-LAN warten</string>
|
<string name="wait_for_wifi">Auf W-LAN warten</string>
|
||||||
<string name="error_msg_recipient_missing">Bitte Empfänger angeben</string>
|
<string name="error_msg_recipient_missing">Bitte Empfänger angeben</string>
|
||||||
|
<string name="export_data">Export</string>
|
||||||
|
<string name="export_data_summary">Alle Nachrichten und Kontakte exportieren (aber keine Identitäten)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -118,4 +118,6 @@ As an alternative you could configure a trusted node in the settings, but as of
|
|||||||
<string name="cleanup_notification_end">Cleanup finished</string>
|
<string name="cleanup_notification_end">Cleanup finished</string>
|
||||||
<string name="wait_for_wifi">Wait for Wi-Fi</string>
|
<string name="wait_for_wifi">Wait for Wi-Fi</string>
|
||||||
<string name="error_msg_recipient_missing">Please set a recipient</string>
|
<string name="error_msg_recipient_missing">Please set a recipient</string>
|
||||||
|
<string name="export_data">Export</string>
|
||||||
|
<string name="export_data_summary">Export all messages and contacts (but not identities)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
4
app/src/main/res/xml/file_paths.xml
Normal file
4
app/src/main/res/xml/file_paths.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<files-path name="exports" path="exports/"/>
|
||||||
|
</paths>
|
@ -41,6 +41,11 @@
|
|||||||
android:title="@string/cleanup"
|
android:title="@string/cleanup"
|
||||||
android:summary="@string/cleanup_summary"
|
android:summary="@string/cleanup_summary"
|
||||||
/>
|
/>
|
||||||
|
<Preference
|
||||||
|
android:key="export"
|
||||||
|
android:title="@string/export_data"
|
||||||
|
android:summary="@string/export_data_summary"
|
||||||
|
/>
|
||||||
<Preference
|
<Preference
|
||||||
android:key="status"
|
android:key="status"
|
||||||
android:summary="@string/status_summary"
|
android:summary="@string/status_summary"
|
||||||
|
@ -6,6 +6,7 @@ configurations.all {
|
|||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.1.3-2'
|
ext.kotlin_version = '1.1.3-2'
|
||||||
|
ext.anko_version = '0.10.1'
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user