mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-24 02:25:19 +00:00
Merge pull request #765 from hjubb/restore_log_report
Restore log report
This commit is contained in:
commit
d190ac8335
@ -54,15 +54,19 @@ import org.thoughtcrime.securesms.crypto.KeyPairUtilities;
|
|||||||
import org.thoughtcrime.securesms.database.JobDatabase;
|
import org.thoughtcrime.securesms.database.JobDatabase;
|
||||||
import org.thoughtcrime.securesms.database.LokiAPIDatabase;
|
import org.thoughtcrime.securesms.database.LokiAPIDatabase;
|
||||||
import org.thoughtcrime.securesms.database.Storage;
|
import org.thoughtcrime.securesms.database.Storage;
|
||||||
|
import org.thoughtcrime.securesms.database.LokiAPIDatabase;
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseModule;
|
import org.thoughtcrime.securesms.dependencies.DatabaseModule;
|
||||||
import org.thoughtcrime.securesms.groups.OpenGroupManager;
|
import org.thoughtcrime.securesms.groups.OpenGroupManager;
|
||||||
import org.thoughtcrime.securesms.home.HomeActivity;
|
import org.thoughtcrime.securesms.home.HomeActivity;
|
||||||
|
import org.thoughtcrime.securesms.groups.OpenGroupManager;
|
||||||
|
import org.thoughtcrime.securesms.home.HomeActivity;
|
||||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
|
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
|
||||||
import org.thoughtcrime.securesms.jobs.FastJobStorage;
|
import org.thoughtcrime.securesms.jobs.FastJobStorage;
|
||||||
import org.thoughtcrime.securesms.jobs.JobManagerFactories;
|
import org.thoughtcrime.securesms.jobs.JobManagerFactories;
|
||||||
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
||||||
|
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
||||||
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
|
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
|
||||||
import org.thoughtcrime.securesms.notifications.BackgroundPollWorker;
|
import org.thoughtcrime.securesms.notifications.BackgroundPollWorker;
|
||||||
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
|
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
|
||||||
@ -126,6 +130,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
public Broadcaster broadcaster = null;
|
public Broadcaster broadcaster = null;
|
||||||
private Job firebaseInstanceIdJob;
|
private Job firebaseInstanceIdJob;
|
||||||
private Handler conversationListNotificationHandler;
|
private Handler conversationListNotificationHandler;
|
||||||
|
private PersistentLogger persistentLogger;
|
||||||
|
|
||||||
@Inject LokiAPIDatabase lokiAPIDatabase;
|
@Inject LokiAPIDatabase lokiAPIDatabase;
|
||||||
@Inject Storage storage;
|
@Inject Storage storage;
|
||||||
@ -146,6 +151,10 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
return this.conversationListNotificationHandler;
|
return this.conversationListNotificationHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PersistentLogger getPersistentLogger() {
|
||||||
|
return this.persistentLogger;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
DatabaseModule.init(this);
|
DatabaseModule.init(this);
|
||||||
@ -281,7 +290,10 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeLogging() {
|
private void initializeLogging() {
|
||||||
Log.initialize(new AndroidLogger());
|
if (persistentLogger == null) {
|
||||||
|
persistentLogger = new PersistentLogger(this);
|
||||||
|
}
|
||||||
|
Log.initialize(new AndroidLogger(), persistentLogger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeCrashHandling() {
|
private void initializeCrashHandling() {
|
||||||
|
@ -123,7 +123,7 @@ class LogFile {
|
|||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String readEntry() throws IOException {
|
String readEntry() throws IOException {
|
||||||
try {
|
try {
|
||||||
Util.readFully(inputStream, ivBuffer);
|
Util.readFully(inputStream, ivBuffer);
|
||||||
Util.readFully(inputStream, intBuffer);
|
Util.readFully(inputStream, intBuffer);
|
||||||
|
@ -0,0 +1,250 @@
|
|||||||
|
package org.thoughtcrime.securesms.logging;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.AnyThread;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
|
import org.session.libsignal.utilities.ListenableFuture;
|
||||||
|
import org.session.libsignal.utilities.Log;
|
||||||
|
import org.session.libsignal.utilities.NoExternalStorageException;
|
||||||
|
import org.session.libsignal.utilities.SettableFuture;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
public class PersistentLogger extends Log.Logger {
|
||||||
|
|
||||||
|
private static final String TAG = PersistentLogger.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final String LOG_V = "V";
|
||||||
|
private static final String LOG_D = "D";
|
||||||
|
private static final String LOG_I = "I";
|
||||||
|
private static final String LOG_W = "W";
|
||||||
|
private static final String LOG_E = "E";
|
||||||
|
private static final String LOG_WTF = "A";
|
||||||
|
|
||||||
|
private static final String LOG_DIRECTORY = "log";
|
||||||
|
private static final String FILENAME_PREFIX = "log-";
|
||||||
|
private static final int MAX_LOG_FILES = 5;
|
||||||
|
private static final int MAX_LOG_SIZE = 300 * 1024;
|
||||||
|
private static final int MAX_LOG_EXPORT = 10_000;
|
||||||
|
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS zzz");
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final Executor executor;
|
||||||
|
private final byte[] secret;
|
||||||
|
|
||||||
|
private LogFile.Writer writer;
|
||||||
|
|
||||||
|
public PersistentLogger(Context context) {
|
||||||
|
this.context = context.getApplicationContext();
|
||||||
|
this.secret = LogSecretProvider.getOrCreateAttachmentSecret(context);
|
||||||
|
this.executor = Executors.newSingleThreadExecutor(r -> {
|
||||||
|
Thread thread = new Thread(r, "PersistentLogger");
|
||||||
|
thread.setPriority(Thread.MIN_PRIORITY);
|
||||||
|
return thread;
|
||||||
|
});
|
||||||
|
|
||||||
|
executor.execute(this::initializeWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void v(String tag, String message, Throwable t) {
|
||||||
|
write(LOG_V, tag, message, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void d(String tag, String message, Throwable t) {
|
||||||
|
write(LOG_D, tag, message, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void i(String tag, String message, Throwable t) {
|
||||||
|
write(LOG_I, tag, message, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void w(String tag, String message, Throwable t) {
|
||||||
|
write(LOG_W, tag, message, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void e(String tag, String message, Throwable t) {
|
||||||
|
write(LOG_E, tag, message, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void wtf(String tag, String message, Throwable t) {
|
||||||
|
write(LOG_WTF, tag, message, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void blockUntilAllWritesFinished() {
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
executor.execute(latch::countDown);
|
||||||
|
|
||||||
|
try {
|
||||||
|
latch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
android.util.Log.w(TAG, "Failed to wait for all writes.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
public ListenableFuture<String> getLogs() {
|
||||||
|
final SettableFuture<String> future = new SettableFuture<>();
|
||||||
|
|
||||||
|
executor.execute(() -> {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
long entriesWritten = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
File[] logs = getSortedLogFiles();
|
||||||
|
for (int i = logs.length - 1; i >= 0 && entriesWritten <= MAX_LOG_EXPORT; i--) {
|
||||||
|
try {
|
||||||
|
LogFile.Reader reader = new LogFile.Reader(secret, logs[i]);
|
||||||
|
String entry;
|
||||||
|
while ((entry = reader.readEntry()) != null) {
|
||||||
|
entriesWritten++;
|
||||||
|
builder.append(entry).append('\n');
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
android.util.Log.w(TAG, "Failed to read log at index " + i + ". Removing reference.");
|
||||||
|
logs[i].delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
future.set(builder.toString());
|
||||||
|
} catch (NoExternalStorageException e) {
|
||||||
|
future.setException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private void initializeWriter() {
|
||||||
|
try {
|
||||||
|
writer = new LogFile.Writer(secret, getOrCreateActiveLogFile());
|
||||||
|
} catch (NoExternalStorageException | IOException e) {
|
||||||
|
android.util.Log.e(TAG, "Failed to initialize writer.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
private void write(String level, String tag, String message, Throwable t) {
|
||||||
|
executor.execute(() -> {
|
||||||
|
try {
|
||||||
|
if (writer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (writer.getLogSize() >= MAX_LOG_SIZE) {
|
||||||
|
writer.close();
|
||||||
|
writer = new LogFile.Writer(secret, createNewLogFile());
|
||||||
|
trimLogFilesOverMax();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String entry : buildLogEntries(level, tag, message, t)) {
|
||||||
|
writer.writeEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (NoExternalStorageException e) {
|
||||||
|
android.util.Log.w(TAG, "Cannot persist logs.", e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
android.util.Log.w(TAG, "Failed to write line. Deleting all logs and starting over.");
|
||||||
|
deleteAllLogs();
|
||||||
|
initializeWriter();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trimLogFilesOverMax() throws NoExternalStorageException {
|
||||||
|
File[] logs = getSortedLogFiles();
|
||||||
|
if (logs.length > MAX_LOG_FILES) {
|
||||||
|
for (int i = MAX_LOG_FILES; i < logs.length; i++) {
|
||||||
|
logs[i].delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteAllLogs() {
|
||||||
|
try {
|
||||||
|
File[] logs = getSortedLogFiles();
|
||||||
|
for (File log : logs) {
|
||||||
|
log.delete();
|
||||||
|
}
|
||||||
|
} catch (NoExternalStorageException e) {
|
||||||
|
android.util.Log.w(TAG, "Was unable to delete logs.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getOrCreateActiveLogFile() throws NoExternalStorageException {
|
||||||
|
File[] logs = getSortedLogFiles();
|
||||||
|
if (logs.length > 0) {
|
||||||
|
return logs[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return createNewLogFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private File createNewLogFile() throws NoExternalStorageException {
|
||||||
|
return new File(getOrCreateLogDirectory(), FILENAME_PREFIX + System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
private File[] getSortedLogFiles() throws NoExternalStorageException {
|
||||||
|
File[] logs = getOrCreateLogDirectory().listFiles();
|
||||||
|
if (logs != null) {
|
||||||
|
Arrays.sort(logs, (o1, o2) -> o2.getName().compareTo(o1.getName()));
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
return new File[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getOrCreateLogDirectory() throws NoExternalStorageException {
|
||||||
|
File logDir = new File(context.getCacheDir(), LOG_DIRECTORY);
|
||||||
|
if (!logDir.exists() && !logDir.mkdir()) {
|
||||||
|
throw new NoExternalStorageException("Unable to create log directory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return logDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> buildLogEntries(String level, String tag, String message, Throwable t) {
|
||||||
|
List<String> entries = new LinkedList<>();
|
||||||
|
Date date = new Date();
|
||||||
|
|
||||||
|
entries.add(buildEntry(level, tag, message, date));
|
||||||
|
|
||||||
|
if (t != null) {
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
t.printStackTrace(new PrintStream(outputStream));
|
||||||
|
|
||||||
|
String trace = new String(outputStream.toByteArray());
|
||||||
|
String[] lines = trace.split("\\n");
|
||||||
|
|
||||||
|
for (String line : lines) {
|
||||||
|
entries.add(buildEntry(level, tag, line, date));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildEntry(String level, String tag, String message, Date date) {
|
||||||
|
return DATE_FORMAT.format(date) + ' ' + level + ' ' + tag + ": " + message;
|
||||||
|
}
|
||||||
|
}
|
@ -85,6 +85,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
helpTranslateButton.setOnClickListener { helpTranslate() }
|
helpTranslateButton.setOnClickListener { helpTranslate() }
|
||||||
seedButton.setOnClickListener { showSeed() }
|
seedButton.setOnClickListener { showSeed() }
|
||||||
clearAllDataButton.setOnClickListener { clearAllData() }
|
clearAllDataButton.setOnClickListener { clearAllData() }
|
||||||
|
supportButton.setOnClickListener { shareLogs() }
|
||||||
val isLightMode = UiModeUtilities.isDayUiMode(this)
|
val isLightMode = UiModeUtilities.isDayUiMode(this)
|
||||||
oxenLogoImageView.setImageResource(if (isLightMode) R.drawable.oxen_light_mode else R.drawable.oxen_dark_mode)
|
oxenLogoImageView.setImageResource(if (isLightMode) R.drawable.oxen_light_mode else R.drawable.oxen_dark_mode)
|
||||||
versionTextView.text = String.format(getString(R.string.version_s), "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
|
versionTextView.text = String.format(getString(R.string.version_s), "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
|
||||||
@ -321,6 +322,10 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog")
|
ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun shareLogs() {
|
||||||
|
ShareLogsDialog().show(supportFragmentManager,"Share Logs Dialog")
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
private inner class DisplayNameEditActionModeCallback: ActionMode.Callback {
|
private inner class DisplayNameEditActionModeCallback: ActionMode.Callback {
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
package org.thoughtcrime.securesms.preferences
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.android.synthetic.main.dialog_share_logs.view.*
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import network.loki.messenger.BuildConfig
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||||
|
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||||
|
|
||||||
|
class ShareLogsDialog : BaseDialog() {
|
||||||
|
|
||||||
|
private var shareJob: Job? = null
|
||||||
|
|
||||||
|
override fun setContentView(builder: AlertDialog.Builder) {
|
||||||
|
val contentView =
|
||||||
|
LayoutInflater.from(requireContext()).inflate(R.layout.dialog_share_logs, null)
|
||||||
|
contentView.cancelButton.setOnClickListener {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
contentView.shareButton.setOnClickListener {
|
||||||
|
// start the export and share
|
||||||
|
shareLogs()
|
||||||
|
}
|
||||||
|
builder.setView(contentView)
|
||||||
|
builder.setCancelable(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shareLogs() {
|
||||||
|
shareJob?.cancel()
|
||||||
|
shareJob = lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val persistentLogger = ApplicationContext.getInstance(context).persistentLogger
|
||||||
|
try {
|
||||||
|
val logs = persistentLogger.logs.get()
|
||||||
|
val fileName = "${Build.MANUFACTURER}-${Build.DEVICE}-API${Build.VERSION.SDK_INT}-v${BuildConfig.VERSION_NAME}.log"
|
||||||
|
val logUri = BlobProvider().forData(logs.toByteArray())
|
||||||
|
.withFileName(fileName)
|
||||||
|
.withMimeType("text/plain")
|
||||||
|
.createForSingleSessionOnDisk(requireContext(),null)
|
||||||
|
|
||||||
|
val shareIntent = Intent().apply {
|
||||||
|
action = Intent.ACTION_SEND
|
||||||
|
putExtra(Intent.EXTRA_STREAM, logUri)
|
||||||
|
type = "text/plain"
|
||||||
|
}
|
||||||
|
|
||||||
|
dismiss()
|
||||||
|
|
||||||
|
startActivity(Intent.createChooser(shareIntent, getString(R.string.share)))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(context,"Error saving logs", Toast.LENGTH_LONG).show()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -227,6 +227,18 @@
|
|||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="@string/activity_settings_survey_feedback" />
|
android:text="@string/activity_settings_survey_feedback" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:padding="@dimen/small_spacing"
|
||||||
|
android:id="@+id/supportButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/medium_spacing"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/medium_font_size"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/activity_settings_support" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:padding="@dimen/small_spacing"
|
android:padding="@dimen/small_spacing"
|
||||||
android:id="@+id/helpTranslateButton"
|
android:id="@+id/helpTranslateButton"
|
||||||
|
64
app/src/main/res/layout/dialog_share_logs.xml
Normal file
64
app/src/main/res/layout/dialog_share_logs.xml
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:background="@drawable/default_dialog_background_inset"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:padding="32dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/dialog_share_logs_title"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="@dimen/medium_font_size" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialogDescriptionText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/large_spacing"
|
||||||
|
android:text="@string/dialog_share_logs_explanation"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/small_font_size"
|
||||||
|
android:textAlignment="center" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/large_spacing"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="@style/Widget.Session.Button.Dialog.Unimportant"
|
||||||
|
android:id="@+id/cancelButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/small_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/cancel" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="@style/Widget.Session.Button.Dialog.Unimportant"
|
||||||
|
android:id="@+id/shareButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/small_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
|
android:text="@string/share" />
|
||||||
|
|
||||||
|
<com.github.ybq.android.spinkit.SpinKitView
|
||||||
|
style="@style/SpinKitView.Small.ThreeBounce"
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/small_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
app:SpinKit_Color="@color/accent"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -899,5 +899,8 @@
|
|||||||
<string name="delete_message_for_everyone">Delete for everyone</string>
|
<string name="delete_message_for_everyone">Delete for everyone</string>
|
||||||
<string name="delete_message_for_me_and_recipient">Delete for me and %s</string>
|
<string name="delete_message_for_me_and_recipient">Delete for me and %s</string>
|
||||||
<string name="activity_settings_survey_feedback">Feedback/Survey</string>
|
<string name="activity_settings_survey_feedback">Feedback/Survey</string>
|
||||||
|
<string name="activity_settings_support">Support</string>
|
||||||
|
<string name="dialog_share_logs_title">Share Logs</string>
|
||||||
|
<string name="dialog_share_logs_explanation">Would you like to export your application logs to be able to share for troubleshooting?</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -19,7 +19,6 @@ class JobQueue : JobDelegate {
|
|||||||
private val jobTimestampMap = ConcurrentHashMap<Long, AtomicInteger>()
|
private val jobTimestampMap = ConcurrentHashMap<Long, AtomicInteger>()
|
||||||
private val rxDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
private val rxDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||||
private val txDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
private val txDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||||
private val attachmentDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
|
|
||||||
private val scope = GlobalScope + SupervisorJob()
|
private val scope = GlobalScope + SupervisorJob()
|
||||||
private val queue = Channel<Job>(UNLIMITED)
|
private val queue = Channel<Job>(UNLIMITED)
|
||||||
private val pendingJobIds = mutableSetOf<String>()
|
private val pendingJobIds = mutableSetOf<String>()
|
||||||
@ -39,18 +38,15 @@ class JobQueue : JobDelegate {
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
val rxQueue = Channel<Job>(capacity = 4096)
|
val rxQueue = Channel<Job>(capacity = 4096)
|
||||||
val txQueue = Channel<Job>(capacity = 4096)
|
val txQueue = Channel<Job>(capacity = 4096)
|
||||||
val attachmentQueue = Channel<Job>(capacity = 4096)
|
|
||||||
|
|
||||||
val receiveJob = processWithDispatcher(rxQueue, rxDispatcher)
|
val receiveJob = processWithDispatcher(rxQueue, rxDispatcher)
|
||||||
val txJob = processWithDispatcher(txQueue, txDispatcher)
|
val txJob = processWithDispatcher(txQueue, txDispatcher)
|
||||||
val attachmentJob = processWithDispatcher(attachmentQueue, attachmentDispatcher)
|
|
||||||
|
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
for (job in queue) {
|
for (job in queue) {
|
||||||
when (job) {
|
when (job) {
|
||||||
is NotifyPNServerJob, is AttachmentUploadJob, is MessageSendJob -> txQueue.send(job)
|
is NotifyPNServerJob, is AttachmentUploadJob, is MessageSendJob -> txQueue.send(job)
|
||||||
is AttachmentDownloadJob -> attachmentQueue.send(job)
|
is MessageReceiveJob, is TrimThreadJob, is BatchMessageReceiveJob, is AttachmentDownloadJob-> rxQueue.send(job)
|
||||||
is MessageReceiveJob, is BatchMessageReceiveJob, is TrimThreadJob -> rxQueue.send(job)
|
|
||||||
else -> throw IllegalStateException("Unexpected job type.")
|
else -> throw IllegalStateException("Unexpected job type.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,7 +55,6 @@ class JobQueue : JobDelegate {
|
|||||||
// The job has been cancelled
|
// The job has been cancelled
|
||||||
receiveJob.cancel()
|
receiveJob.cancel()
|
||||||
txJob.cancel()
|
txJob.cancel()
|
||||||
attachmentJob.cancel()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import org.session.libsession.snode.SnodeAPI
|
|||||||
import org.session.libsession.utilities.GroupUtil
|
import org.session.libsession.utilities.GroupUtil
|
||||||
import org.session.libsignal.crypto.getRandomElementOrNull
|
import org.session.libsignal.crypto.getRandomElementOrNull
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.successBackground
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.ScheduledFuture
|
import java.util.concurrent.ScheduledFuture
|
||||||
@ -82,7 +81,6 @@ class ClosedGroupPollerV2 {
|
|||||||
val limit: Long = 12 * 60 * 60 * 1000
|
val limit: Long = 12 * 60 * 60 * 1000
|
||||||
val a = (Companion.maxPollInterval - minPollInterval).toDouble() / limit.toDouble()
|
val a = (Companion.maxPollInterval - minPollInterval).toDouble() / limit.toDouble()
|
||||||
val nextPollInterval = a * min(timeSinceLastMessage, limit) + minPollInterval
|
val nextPollInterval = a * min(timeSinceLastMessage, limit) + minPollInterval
|
||||||
Log.d("Loki", "Next poll interval for closed group with public key: $groupPublicKey is ${nextPollInterval / 1000} s.")
|
|
||||||
executorService?.schedule({
|
executorService?.schedule({
|
||||||
poll(groupPublicKey).success {
|
poll(groupPublicKey).success {
|
||||||
pollRecursively(groupPublicKey)
|
pollRecursively(groupPublicKey)
|
||||||
@ -108,7 +106,7 @@ class ClosedGroupPollerV2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
promise.fail {
|
promise.fail {
|
||||||
Log.d("Loki", "Polling failed for closed group with public key: $groupPublicKey due to error: $it.")
|
Log.d("Loki", "Polling failed for closed group due to error: $it.")
|
||||||
}
|
}
|
||||||
return promise.map { }
|
return promise.map { }
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user