mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
25 Commits
user-testi
...
fix_nonloc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e24728055c | ||
|
|
b2dc99bf26 | ||
|
|
50d144c917 | ||
|
|
da7f57e0af | ||
|
|
2374f8b4c9 | ||
|
|
5b77f28ce0 | ||
|
|
c247e3aa4e | ||
|
|
8e0b71c76f | ||
|
|
88f57893e8 | ||
|
|
b111abb484 | ||
|
|
4415598d3d | ||
|
|
40e14d3e94 | ||
|
|
1b19b331b1 | ||
|
|
d151a2d7f7 | ||
|
|
9712a4b849 | ||
|
|
cf1ac5e3e5 | ||
|
|
0b9345f867 | ||
|
|
12988120d1 | ||
|
|
8d6c866e62 | ||
|
|
8f82cf3c73 | ||
|
|
21112ce092 | ||
|
|
21ee3ea00d | ||
|
|
bb964101b3 | ||
|
|
d796eff0f6 | ||
|
|
700ea2b387 |
14
.idea/dictionaries/briar.xml
generated
Normal file
14
.idea/dictionaries/briar.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<component name="ProjectDictionaryState">
|
||||||
|
<dictionary name="briar">
|
||||||
|
<words>
|
||||||
|
<w>briar</w>
|
||||||
|
<w>briarproject</w>
|
||||||
|
<w>emoji</w>
|
||||||
|
<w>encrypter</w>
|
||||||
|
<w>identicon</w>
|
||||||
|
<w>introducee</w>
|
||||||
|
<w>introducer</w>
|
||||||
|
<w>onboarding</w>
|
||||||
|
</words>
|
||||||
|
</dictionary>
|
||||||
|
</component>
|
||||||
@@ -31,6 +31,7 @@ public class AndroidUtils {
|
|||||||
private static final String FAKE_BLUETOOTH_ADDRESS = "02:00:00:00:00:00";
|
private static final String FAKE_BLUETOOTH_ADDRESS = "02:00:00:00:00:00";
|
||||||
|
|
||||||
private static final String STORED_REPORTS = "dev-reports";
|
private static final String STORED_REPORTS = "dev-reports";
|
||||||
|
private static final String STORED_LOGCAT = "dev-logcat";
|
||||||
|
|
||||||
public static Collection<String> getSupportedArchitectures() {
|
public static Collection<String> getSupportedArchitectures() {
|
||||||
List<String> abis = new ArrayList<>();
|
List<String> abis = new ArrayList<>();
|
||||||
@@ -107,6 +108,10 @@ public class AndroidUtils {
|
|||||||
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
|
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static File getLogcatFile(Context ctx) {
|
||||||
|
return new File(ctx.getFilesDir(), STORED_LOGCAT);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of supported content types for image attachments.
|
* Returns an array of supported content types for image attachments.
|
||||||
* GIFs can't be compressed on API < 24 so they're not supported.
|
* GIFs can't be compressed on API < 24 so they're not supported.
|
||||||
|
|||||||
@@ -19,4 +19,10 @@ public interface StreamDecrypterFactory {
|
|||||||
*/
|
*/
|
||||||
StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
|
StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
|
||||||
SecretKey headerKey);
|
SecretKey headerKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link StreamDecrypter} for decrypting a log stream.
|
||||||
|
*/
|
||||||
|
StreamDecrypter createLogStreamDecrypter(InputStream in,
|
||||||
|
SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ public interface StreamEncrypterFactory {
|
|||||||
* Creates a {@link StreamEncrypter} for encrypting a contact exchange
|
* Creates a {@link StreamEncrypter} for encrypting a contact exchange
|
||||||
* stream.
|
* stream.
|
||||||
*/
|
*/
|
||||||
StreamEncrypter createContactExchangeStreamDecrypter(OutputStream out,
|
StreamEncrypter createContactExchangeStreamEncrypter(OutputStream out,
|
||||||
|
SecretKey headerKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link StreamEncrypter} for encrypting a log stream.
|
||||||
|
*/
|
||||||
|
StreamEncrypter createLogStreamEncrypter(OutputStream out,
|
||||||
SecretKey headerKey);
|
SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,6 @@ public interface DevConfig {
|
|||||||
String getDevOnionAddress();
|
String getDevOnionAddress();
|
||||||
|
|
||||||
File getReportDir();
|
File getReportDir();
|
||||||
|
|
||||||
|
File getLogcatFile();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,13 @@ public interface StreamReaderFactory {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link InputStream InputStream} for reading from a contact
|
* Creates an {@link InputStream InputStream} for reading from a contact
|
||||||
* exchangestream.
|
* exchange stream.
|
||||||
*/
|
*/
|
||||||
InputStream createContactExchangeStreamReader(InputStream in,
|
InputStream createContactExchangeStreamReader(InputStream in,
|
||||||
SecretKey headerKey);
|
SecretKey headerKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an {@link InputStream} for reading from a log stream.
|
||||||
|
*/
|
||||||
|
InputStream createLogStreamReader(InputStream in, SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,19 @@ import java.io.OutputStream;
|
|||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface StreamWriterFactory {
|
public interface StreamWriterFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link OutputStream OutputStream} for writing to a
|
* Creates a {@link StreamWriter} for writing to a transport stream.
|
||||||
* transport stream
|
|
||||||
*/
|
*/
|
||||||
StreamWriter createStreamWriter(OutputStream out, StreamContext ctx);
|
StreamWriter createStreamWriter(OutputStream out, StreamContext ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link OutputStream OutputStream} for writing to a contact
|
* Creates a {@link StreamWriter} for writing to a contact exchange stream.
|
||||||
* exchange stream.
|
|
||||||
*/
|
*/
|
||||||
StreamWriter createContactExchangeStreamWriter(OutputStream out,
|
StreamWriter createContactExchangeStreamWriter(OutputStream out,
|
||||||
SecretKey headerKey);
|
SecretKey headerKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link StreamWriter} for writing to a log stream.
|
||||||
|
*/
|
||||||
|
StreamWriter createLogStreamWriter(OutputStream out, SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,16 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.lang.Math.min;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
|
||||||
class ScryptKdf implements PasswordBasedKdf {
|
class ScryptKdf implements PasswordBasedKdf {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(ScryptKdf.class.getName());
|
getLogger(ScryptKdf.class.getName());
|
||||||
|
|
||||||
private static final int MIN_COST = 256; // Min parameter N
|
private static final int MIN_COST = 256; // Min parameter N
|
||||||
private static final int MAX_COST = 1024 * 1024; // Max parameter N
|
private static final int MAX_COST = 1024 * 1024; // Max parameter N
|
||||||
@@ -33,10 +35,20 @@ class ScryptKdf implements PasswordBasedKdf {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int chooseCostParameter() {
|
public int chooseCostParameter() {
|
||||||
|
// Scrypt uses at least 128 * N * r bytes of memory. Don't use more
|
||||||
|
// than half of the JVM's max heap size or we may run out of memory.
|
||||||
|
// https://blog.filippo.io/the-scrypt-parameters/
|
||||||
|
long maxMemory = Runtime.getRuntime().maxMemory();
|
||||||
|
long maxCost = min(MAX_COST, maxMemory / BLOCK_SIZE / 256);
|
||||||
|
if (LOG.isLoggable(INFO) && maxCost < MAX_COST) {
|
||||||
|
LOG.info("Max cost capped at " + maxCost
|
||||||
|
+ " due to max heap size " + maxMemory);
|
||||||
|
}
|
||||||
// Increase the cost from min to max while measuring performance
|
// Increase the cost from min to max while measuring performance
|
||||||
int cost = MIN_COST;
|
int cost = MIN_COST;
|
||||||
while (cost * 2 <= MAX_COST && measureDuration(cost) * 2 <= TARGET_MS)
|
while (cost * 2 <= maxCost && measureDuration(cost) * 2 <= TARGET_MS) {
|
||||||
cost *= 2;
|
cost *= 2;
|
||||||
|
}
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("KDF cost parameter " + cost);
|
LOG.info("KDF cost parameter " + cost);
|
||||||
return cost;
|
return cost;
|
||||||
|
|||||||
@@ -36,4 +36,10 @@ class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
|
|||||||
SecretKey headerKey) {
|
SecretKey headerKey) {
|
||||||
return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
|
return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamDecrypter createLogStreamDecrypter(InputStream in,
|
||||||
|
SecretKey headerKey) {
|
||||||
|
return createContactExchangeStreamDecrypter(in, headerKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StreamEncrypter createContactExchangeStreamDecrypter(
|
public StreamEncrypter createContactExchangeStreamEncrypter(
|
||||||
OutputStream out, SecretKey headerKey) {
|
OutputStream out, SecretKey headerKey) {
|
||||||
AuthenticatedCipher cipher = cipherProvider.get();
|
AuthenticatedCipher cipher = cipherProvider.get();
|
||||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||||
@@ -60,4 +60,10 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
|||||||
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce,
|
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce,
|
||||||
headerKey, frameKey);
|
headerKey, frameKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamEncrypter createLogStreamEncrypter(OutputStream out,
|
||||||
|
SecretKey headerKey) {
|
||||||
|
return createContactExchangeStreamEncrypter(out, headerKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,15 +24,21 @@ class StreamReaderFactoryImpl implements StreamReaderFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream createStreamReader(InputStream in, StreamContext ctx) {
|
public InputStream createStreamReader(InputStream in, StreamContext ctx) {
|
||||||
return new StreamReaderImpl(
|
return new StreamReaderImpl(streamDecrypterFactory
|
||||||
streamDecrypterFactory.createStreamDecrypter(in, ctx));
|
.createStreamDecrypter(in, ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream createContactExchangeStreamReader(InputStream in,
|
public InputStream createContactExchangeStreamReader(InputStream in,
|
||||||
SecretKey headerKey) {
|
SecretKey headerKey) {
|
||||||
return new StreamReaderImpl(
|
return new StreamReaderImpl(streamDecrypterFactory
|
||||||
streamDecrypterFactory.createContactExchangeStreamDecrypter(in,
|
.createContactExchangeStreamDecrypter(in, headerKey));
|
||||||
headerKey));
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream createLogStreamReader(InputStream in,
|
||||||
|
SecretKey headerKey) {
|
||||||
|
return new StreamReaderImpl(streamDecrypterFactory
|
||||||
|
.createLogStreamDecrypter(in, headerKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,15 +26,21 @@ class StreamWriterFactoryImpl implements StreamWriterFactory {
|
|||||||
@Override
|
@Override
|
||||||
public StreamWriter createStreamWriter(OutputStream out,
|
public StreamWriter createStreamWriter(OutputStream out,
|
||||||
StreamContext ctx) {
|
StreamContext ctx) {
|
||||||
return new StreamWriterImpl(
|
return new StreamWriterImpl(streamEncrypterFactory
|
||||||
streamEncrypterFactory.createStreamEncrypter(out, ctx));
|
.createStreamEncrypter(out, ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StreamWriter createContactExchangeStreamWriter(OutputStream out,
|
public StreamWriter createContactExchangeStreamWriter(OutputStream out,
|
||||||
SecretKey headerKey) {
|
SecretKey headerKey) {
|
||||||
return new StreamWriterImpl(
|
return new StreamWriterImpl(streamEncrypterFactory
|
||||||
streamEncrypterFactory.createContactExchangeStreamDecrypter(out,
|
.createContactExchangeStreamEncrypter(out, headerKey));
|
||||||
headerKey));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public StreamWriter createLogStreamWriter(OutputStream out,
|
||||||
|
SecretKey headerKey) {
|
||||||
|
return new StreamWriterImpl(streamEncrypterFactory
|
||||||
|
.createLogStreamEncrypter(out, headerKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ dependencies {
|
|||||||
testImplementation "org.jmock:jmock:$jmockVersion"
|
testImplementation "org.jmock:jmock:$jmockVersion"
|
||||||
testImplementation "org.jmock:jmock-junit4:$jmockVersion"
|
testImplementation "org.jmock:jmock-junit4:$jmockVersion"
|
||||||
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
|
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
|
||||||
|
testAnnotationProcessor "com.google.dagger:dagger-compiler:2.24"
|
||||||
|
|
||||||
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class BriarAccountManager extends AndroidAccountManager {
|
|||||||
public void deleteAccount() {
|
public void deleteAccount() {
|
||||||
synchronized (stateChangeLock) {
|
synchronized (stateChangeLock) {
|
||||||
super.deleteAccount();
|
super.deleteAccount();
|
||||||
Localizer.reinitialize();
|
Localizer.reinitialize(appContext);
|
||||||
UiUtils.setTheme(appContext,
|
UiUtils.setTheme(appContext,
|
||||||
appContext.getString(R.string.pref_theme_light_value));
|
appContext.getString(R.string.pref_theme_light_value));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import org.briarproject.briar.BriarCoreModule;
|
|||||||
import org.briarproject.briar.android.attachment.AttachmentModule;
|
import org.briarproject.briar.android.attachment.AttachmentModule;
|
||||||
import org.briarproject.briar.android.attachment.media.MediaModule;
|
import org.briarproject.briar.android.attachment.media.MediaModule;
|
||||||
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
|
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
|
||||||
|
import org.briarproject.briar.android.logging.CachingLogHandler;
|
||||||
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
||||||
import org.briarproject.briar.android.view.EmojiTextInputView;
|
import org.briarproject.briar.android.view.EmojiTextInputView;
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
@@ -179,6 +180,10 @@ public interface AndroidComponent
|
|||||||
|
|
||||||
AndroidWakeLockManager wakeLockManager();
|
AndroidWakeLockManager wakeLockManager();
|
||||||
|
|
||||||
|
CachingLogHandler logHandler();
|
||||||
|
|
||||||
|
Thread.UncaughtExceptionHandler exceptionHandler();
|
||||||
|
|
||||||
void inject(SignInReminderReceiver briarService);
|
void inject(SignInReminderReceiver briarService);
|
||||||
|
|
||||||
void inject(BriarService briarService);
|
void inject(BriarService briarService);
|
||||||
|
|||||||
@@ -579,6 +579,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void updateContactAddedNotification() {
|
private void updateContactAddedNotification() {
|
||||||
|
if (contactAddedTotal == 0) return;
|
||||||
BriarNotificationBuilder b =
|
BriarNotificationBuilder b =
|
||||||
new BriarNotificationBuilder(appContext, CONTACT_CHANNEL_ID);
|
new BriarNotificationBuilder(appContext, CONTACT_CHANNEL_ID);
|
||||||
b.setSmallIcon(R.drawable.notification_contact_added);
|
b.setSmallIcon(R.drawable.notification_contact_added);
|
||||||
@@ -713,4 +714,16 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
public void unblockAllBlogPostNotifications() {
|
public void unblockAllBlogPostNotifications() {
|
||||||
androidExecutor.runOnUiThread((Runnable) () -> blockBlogs = false);
|
androidExecutor.runOnUiThread((Runnable) () -> blockBlogs = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void restartNotifications(boolean locked, boolean mayAlertAgain) {
|
||||||
|
androidExecutor.runOnUiThread(() -> {
|
||||||
|
updateForegroundNotification(locked);
|
||||||
|
updateContactNotification(mayAlertAgain);
|
||||||
|
updateBlogPostNotification(mayAlertAgain);
|
||||||
|
updateForumPostNotification(mayAlertAgain);
|
||||||
|
updateGroupMessageNotification(mayAlertAgain);
|
||||||
|
updateContactAddedNotification();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,12 +33,13 @@ import org.briarproject.briar.android.account.SetupModule;
|
|||||||
import org.briarproject.briar.android.contact.ContactListModule;
|
import org.briarproject.briar.android.contact.ContactListModule;
|
||||||
import org.briarproject.briar.android.forum.ForumModule;
|
import org.briarproject.briar.android.forum.ForumModule;
|
||||||
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
|
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
|
||||||
|
import org.briarproject.briar.android.logging.LoggingModule;
|
||||||
import org.briarproject.briar.android.login.LoginModule;
|
import org.briarproject.briar.android.login.LoginModule;
|
||||||
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
|
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
|
||||||
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
|
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
|
||||||
import org.briarproject.briar.android.settings.SettingsModule;
|
|
||||||
import org.briarproject.briar.android.privategroup.list.GroupListModule;
|
import org.briarproject.briar.android.privategroup.list.GroupListModule;
|
||||||
import org.briarproject.briar.android.reporting.DevReportModule;
|
import org.briarproject.briar.android.reporting.DevReportModule;
|
||||||
|
import org.briarproject.briar.android.settings.SettingsModule;
|
||||||
import org.briarproject.briar.android.sharing.SharingModule;
|
import org.briarproject.briar.android.sharing.SharingModule;
|
||||||
import org.briarproject.briar.android.test.TestAvatarCreatorImpl;
|
import org.briarproject.briar.android.test.TestAvatarCreatorImpl;
|
||||||
import org.briarproject.briar.android.viewmodel.ViewModelModule;
|
import org.briarproject.briar.android.viewmodel.ViewModelModule;
|
||||||
@@ -74,6 +75,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
|||||||
SetupModule.class,
|
SetupModule.class,
|
||||||
DozeHelperModule.class,
|
DozeHelperModule.class,
|
||||||
ContactExchangeModule.class,
|
ContactExchangeModule.class,
|
||||||
|
LoggingModule.class,
|
||||||
LoginModule.class,
|
LoginModule.class,
|
||||||
NavDrawerModule.class,
|
NavDrawerModule.class,
|
||||||
ViewModelModule.class,
|
ViewModelModule.class,
|
||||||
@@ -192,6 +194,11 @@ public class AppModule {
|
|||||||
public File getReportDir() {
|
public File getReportDir() {
|
||||||
return AndroidUtils.getReportDir(app.getApplicationContext());
|
return AndroidUtils.getReportDir(app.getApplicationContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getLogcatFile() {
|
||||||
|
return AndroidUtils.getLogcatFile(app.getApplicationContext());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return devConfig;
|
return devConfig;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ import android.content.SharedPreferences;
|
|||||||
import org.briarproject.bramble.BrambleApplication;
|
import org.briarproject.bramble.BrambleApplication;
|
||||||
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.logging.LogRecord;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This exists so that the Application object will not necessarily be cast
|
* This exists so that the Application object will not necessarily be cast
|
||||||
* directly to the Briar application object.
|
* directly to the Briar application object.
|
||||||
@@ -17,8 +14,6 @@ public interface BriarApplication extends BrambleApplication {
|
|||||||
|
|
||||||
Class<? extends Activity> ENTRY_ACTIVITY = NavDrawerActivity.class;
|
Class<? extends Activity> ENTRY_ACTIVITY = NavDrawerActivity.class;
|
||||||
|
|
||||||
Collection<LogRecord> getRecentLogRecords();
|
|
||||||
|
|
||||||
AndroidComponent getApplicationComponent();
|
AndroidComponent getApplicationComponent();
|
||||||
|
|
||||||
SharedPreferences getDefaultSharedPreferences();
|
SharedPreferences getDefaultSharedPreferences();
|
||||||
|
|||||||
@@ -20,12 +20,10 @@ import org.briarproject.bramble.BrambleCoreEagerSingletons;
|
|||||||
import org.briarproject.briar.BriarCoreEagerSingletons;
|
import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.logging.CachingLogHandler;
|
import org.briarproject.briar.android.logging.CachingLogHandler;
|
||||||
import org.briarproject.briar.android.reporting.BriarExceptionHandler;
|
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.lang.Thread.UncaughtExceptionHandler;
|
||||||
import java.util.logging.Handler;
|
import java.util.logging.Handler;
|
||||||
import java.util.logging.LogRecord;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -42,16 +40,11 @@ public class BriarApplicationImpl extends Application
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(BriarApplicationImpl.class.getName());
|
getLogger(BriarApplicationImpl.class.getName());
|
||||||
|
|
||||||
private final CachingLogHandler logHandler = new CachingLogHandler();
|
|
||||||
private final BriarExceptionHandler exceptionHandler =
|
|
||||||
new BriarExceptionHandler(this);
|
|
||||||
|
|
||||||
private AndroidComponent applicationComponent;
|
private AndroidComponent applicationComponent;
|
||||||
private volatile SharedPreferences prefs;
|
private volatile SharedPreferences prefs;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(Context base) {
|
protected void attachBaseContext(Context base) {
|
||||||
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
|
|
||||||
if (prefs == null)
|
if (prefs == null)
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(base);
|
prefs = PreferenceManager.getDefaultSharedPreferences(base);
|
||||||
// Loading the language needs to be done here.
|
// Loading the language needs to be done here.
|
||||||
@@ -67,6 +60,11 @@ public class BriarApplicationImpl extends Application
|
|||||||
|
|
||||||
if (IS_DEBUG_BUILD) enableStrictMode();
|
if (IS_DEBUG_BUILD) enableStrictMode();
|
||||||
|
|
||||||
|
applicationComponent = createApplicationComponent();
|
||||||
|
UncaughtExceptionHandler exceptionHandler =
|
||||||
|
applicationComponent.exceptionHandler();
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
|
||||||
|
|
||||||
Logger rootLogger = getLogger("");
|
Logger rootLogger = getLogger("");
|
||||||
Handler[] handlers = rootLogger.getHandlers();
|
Handler[] handlers = rootLogger.getHandlers();
|
||||||
// Disable the Android logger for release builds
|
// Disable the Android logger for release builds
|
||||||
@@ -78,12 +76,13 @@ public class BriarApplicationImpl extends Application
|
|||||||
// Restore the default handlers after the level raising handler
|
// Restore the default handlers after the level raising handler
|
||||||
for (Handler handler : handlers) rootLogger.addHandler(handler);
|
for (Handler handler : handlers) rootLogger.addHandler(handler);
|
||||||
}
|
}
|
||||||
|
CachingLogHandler logHandler = applicationComponent.logHandler();
|
||||||
rootLogger.addHandler(logHandler);
|
rootLogger.addHandler(logHandler);
|
||||||
rootLogger.setLevel(IS_DEBUG_BUILD ? FINE : INFO);
|
rootLogger.setLevel(IS_DEBUG_BUILD ? FINE : INFO);
|
||||||
|
|
||||||
LOG.info("Created");
|
LOG.info("Created");
|
||||||
|
Localizer.getInstance().setLocaleLegacy(this);
|
||||||
|
|
||||||
applicationComponent = createApplicationComponent();
|
|
||||||
EmojiManager.install(new GoogleEmojiProvider());
|
EmojiManager.install(new GoogleEmojiProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +105,9 @@ public class BriarApplicationImpl extends Application
|
|||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
||||||
super.onConfigurationChanged(newConfig);
|
super.onConfigurationChanged(newConfig);
|
||||||
Localizer.getInstance().setLocale(this);
|
Localizer.getInstance().applicationConfigurationChanged(this, newConfig,
|
||||||
|
applicationComponent.androidNotificationManager(),
|
||||||
|
applicationComponent.lockManager().isLocked());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setTheme(Context ctx, SharedPreferences prefs) {
|
private void setTheme(Context ctx, SharedPreferences prefs) {
|
||||||
@@ -136,11 +137,6 @@ public class BriarApplicationImpl extends Application
|
|||||||
return applicationComponent;
|
return applicationComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<LogRecord> getRecentLogRecords() {
|
|
||||||
return logHandler.getRecentLogRecords();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AndroidComponent getApplicationComponent() {
|
public AndroidComponent getApplicationComponent() {
|
||||||
return applicationComponent;
|
return applicationComponent;
|
||||||
|
|||||||
@@ -6,12 +6,16 @@ import android.content.res.Configuration;
|
|||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import androidx.core.text.TextUtilsCompat;
|
||||||
|
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR;
|
||||||
import static org.briarproject.briar.android.settings.SettingsFragment.LANGUAGE;
|
import static org.briarproject.briar.android.settings.SettingsFragment.LANGUAGE;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -21,6 +25,7 @@ public class Localizer {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private static Localizer INSTANCE;
|
private static Localizer INSTANCE;
|
||||||
private final Locale systemLocale;
|
private final Locale systemLocale;
|
||||||
|
@Nullable
|
||||||
private final Locale locale;
|
private final Locale locale;
|
||||||
|
|
||||||
private Localizer(SharedPreferences sharedPreferences) {
|
private Localizer(SharedPreferences sharedPreferences) {
|
||||||
@@ -30,10 +35,17 @@ public class Localizer {
|
|||||||
|
|
||||||
private Localizer(Locale systemLocale, @Nullable Locale userLocale) {
|
private Localizer(Locale systemLocale, @Nullable Locale userLocale) {
|
||||||
this.systemLocale = systemLocale;
|
this.systemLocale = systemLocale;
|
||||||
if (userLocale == null) locale = systemLocale;
|
locale = userLocale;
|
||||||
else locale = userLocale;
|
setLocaleAndSystemConfiguration(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Localizer(Locale systemLocale) {
|
||||||
|
this.systemLocale = systemLocale;
|
||||||
|
locale = null;
|
||||||
|
setLocaleAndSystemConfiguration(systemLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Instantiate the Localizer.
|
// Instantiate the Localizer.
|
||||||
public static synchronized void initialize(SharedPreferences prefs) {
|
public static synchronized void initialize(SharedPreferences prefs) {
|
||||||
if (INSTANCE == null)
|
if (INSTANCE == null)
|
||||||
@@ -41,9 +53,11 @@ public class Localizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reinstantiate the Localizer with the system locale
|
// Reinstantiate the Localizer with the system locale
|
||||||
public static synchronized void reinitialize() {
|
public static synchronized void reinitialize(Context appContext) {
|
||||||
if (INSTANCE != null)
|
if (INSTANCE != null && INSTANCE.locale != null) {
|
||||||
INSTANCE = new Localizer(INSTANCE.systemLocale, null);
|
INSTANCE = new Localizer(INSTANCE.systemLocale);
|
||||||
|
INSTANCE.forceLocale(appContext, INSTANCE.systemLocale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the current instance.
|
// Get the current instance.
|
||||||
@@ -64,29 +78,98 @@ public class Localizer {
|
|||||||
if (tag.contains("-")) {
|
if (tag.contains("-")) {
|
||||||
String[] langArray = tag.split("-");
|
String[] langArray = tag.split("-");
|
||||||
return new Locale(langArray[0], langArray[1]);
|
return new Locale(langArray[0], langArray[1]);
|
||||||
} else
|
} else {
|
||||||
return new Locale(tag);
|
return new Locale(tag);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the localized version of context
|
// Returns the localized version of context
|
||||||
public Context setLocale(Context context) {
|
public Context setLocale(Context context) {
|
||||||
|
if (locale == null || SDK_INT < 17) return context;
|
||||||
Resources res = context.getResources();
|
Resources res = context.getResources();
|
||||||
Configuration conf = res.getConfiguration();
|
Configuration conf = res.getConfiguration();
|
||||||
Locale currentLocale;
|
updateConfiguration(conf, locale);
|
||||||
if (SDK_INT >= 24) {
|
return context.createConfigurationContext(conf);
|
||||||
currentLocale = conf.getLocales().get(0);
|
}
|
||||||
} else
|
|
||||||
currentLocale = conf.locale;
|
// For API < 17 only.
|
||||||
if (locale.equals(currentLocale))
|
public void setLocaleLegacy(Context appContext) {
|
||||||
return context;
|
if (SDK_INT >= 17 || locale == null) return;
|
||||||
Locale.setDefault(locale);
|
forceLocale(appContext, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forces the update of the resources through the deprecated API.
|
||||||
|
private void forceLocale(Context context, Locale locale) {
|
||||||
|
Resources res = context.getResources();
|
||||||
|
Configuration conf = res.getConfiguration();
|
||||||
|
updateConfiguration(conf, locale);
|
||||||
|
res.updateConfiguration(conf, res.getDisplayMetrics());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateConfiguration(Configuration conf, Locale locale) {
|
||||||
if (SDK_INT >= 17) {
|
if (SDK_INT >= 17) {
|
||||||
conf.setLocale(locale);
|
conf.setLocale(locale);
|
||||||
context.createConfigurationContext(conf);
|
} else {
|
||||||
} else
|
|
||||||
conf.locale = locale;
|
conf.locale = locale;
|
||||||
//noinspection deprecation
|
}
|
||||||
res.updateConfiguration(conf, res.getDisplayMetrics());
|
|
||||||
return context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setLocaleAndSystemConfiguration(@Nullable Locale locale) {
|
||||||
|
if (locale == null) return;
|
||||||
|
Locale.setDefault(locale);
|
||||||
|
if (SDK_INT >= 23) return;
|
||||||
|
Resources systemResources = Resources.getSystem();
|
||||||
|
Configuration systemConfiguration = systemResources.getConfiguration();
|
||||||
|
updateConfiguration(systemConfiguration, locale);
|
||||||
|
// DateUtils uses the system resources, so we need to update them too.
|
||||||
|
//noinspection deprecation
|
||||||
|
systemResources.updateConfiguration(systemConfiguration,
|
||||||
|
systemResources.getDisplayMetrics());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Locale getLocaleFromConfig(Configuration config) {
|
||||||
|
if (SDK_INT >= 24) {
|
||||||
|
return config.getLocales().get(0);
|
||||||
|
} else {
|
||||||
|
//noinspection deprecation
|
||||||
|
return config.locale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applicationConfigurationChanged(Context appContext,
|
||||||
|
Configuration newConfig,
|
||||||
|
AndroidNotificationManager androidNotificationManager,
|
||||||
|
boolean locked) {
|
||||||
|
Locale newLocale = getLocaleFromConfig(newConfig);
|
||||||
|
if (locale == null && newLocale != systemLocale) {
|
||||||
|
androidNotificationManager.restartNotifications(locked, false);
|
||||||
|
}
|
||||||
|
if (newLocale == locale) return;
|
||||||
|
setLocaleAndSystemConfiguration(locale);
|
||||||
|
if (SDK_INT < 17) setLocaleLegacy(appContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the language represented by locale
|
||||||
|
* should be offered to the user on this device.
|
||||||
|
* * Android doesn't pick up Asturian on API < 21
|
||||||
|
* * RTL languages are supported since API >= 17
|
||||||
|
*/
|
||||||
|
public static boolean isLocaleSupported(Locale locale) {
|
||||||
|
if (SDK_INT >= 21) return true;
|
||||||
|
if (locale.getLanguage().equals("ast")) return false;
|
||||||
|
if (SDK_INT >= 17) return true;
|
||||||
|
return isLeftToRight(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude RTL locales on API < 17, they won't be laid out correctly
|
||||||
|
private static boolean isLeftToRight(Locale locale) {
|
||||||
|
// TextUtilsCompat returns the wrong direction for Hebrew on some phones
|
||||||
|
String language = locale.getLanguage();
|
||||||
|
if (language.equals("iw") || language.equals("he")) return false;
|
||||||
|
int direction =
|
||||||
|
TextUtilsCompat.getLayoutDirectionFromLocale(locale);
|
||||||
|
return direction == LAYOUT_DIRECTION_LTR;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ public class AddContactViewModel extends DbViewModel {
|
|||||||
return addContactResult;
|
return addContactResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updatePendingContact(String name, PendingContact p) {
|
void updatePendingContact(String name, PendingContact p) {
|
||||||
runOnDbThread(() -> {
|
runOnDbThread(() -> {
|
||||||
try {
|
try {
|
||||||
contactManager.removePendingContact(p.getId());
|
contactManager.removePendingContact(p.getId());
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ import org.briarproject.briar.android.fragment.BaseFragment;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.AlertDialog.Builder;
|
import androidx.appcompat.app.AlertDialog.Builder;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
|
||||||
|
|
||||||
import static android.view.View.INVISIBLE;
|
import static android.view.View.INVISIBLE;
|
||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
@@ -48,6 +48,7 @@ import static org.briarproject.briar.android.util.UiUtils.getDialogIcon;
|
|||||||
public class NicknameFragment extends BaseFragment {
|
public class NicknameFragment extends BaseFragment {
|
||||||
|
|
||||||
private static final String TAG = NicknameFragment.class.getName();
|
private static final String TAG = NicknameFragment.class.getName();
|
||||||
|
private static final String SAVED_LINK = "savedLink";
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
@@ -67,6 +68,20 @@ public class NicknameFragment extends BaseFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void injectFragment(ActivityComponent component) {
|
public void injectFragment(ActivityComponent component) {
|
||||||
component.inject(this);
|
component.inject(this);
|
||||||
|
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||||
|
.get(AddContactViewModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
// When the activity (and the ViewModel) get destroyed,
|
||||||
|
// the link will not be available anymore and needs to be restored.
|
||||||
|
// TODO migrate to SavedStateViewModelFactory (once we can use it)
|
||||||
|
String savedLink = savedInstanceState.getString(SAVED_LINK);
|
||||||
|
if (savedLink != null) viewModel.setRemoteHandshakeLink(savedLink);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -74,14 +89,9 @@ public class NicknameFragment extends BaseFragment {
|
|||||||
public View onCreateView(LayoutInflater inflater,
|
public View onCreateView(LayoutInflater inflater,
|
||||||
@Nullable ViewGroup container,
|
@Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
if (getActivity() == null || getContext() == null) return null;
|
|
||||||
|
|
||||||
View v = inflater.inflate(R.layout.fragment_nickname,
|
View v = inflater.inflate(R.layout.fragment_nickname,
|
||||||
container, false);
|
container, false);
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
|
|
||||||
.get(AddContactViewModel.class);
|
|
||||||
|
|
||||||
contactNameLayout = v.findViewById(R.id.contactNameLayout);
|
contactNameLayout = v.findViewById(R.id.contactNameLayout);
|
||||||
contactNameInput = v.findViewById(R.id.contactNameInput);
|
contactNameInput = v.findViewById(R.id.contactNameInput);
|
||||||
|
|
||||||
@@ -93,6 +103,12 @@ public class NicknameFragment extends BaseFragment {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putString(SAVED_LINK, viewModel.getRemoteHandshakeLink());
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String getNicknameOrNull() {
|
private String getNicknameOrNull() {
|
||||||
Editable text = contactNameInput.getText();
|
Editable text = contactNameInput.getText();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.logging.Formatter;
|
import java.util.logging.Formatter;
|
||||||
@@ -17,6 +18,16 @@ import static java.util.Locale.US;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class BriefLogFormatter extends Formatter {
|
public class BriefLogFormatter extends Formatter {
|
||||||
|
|
||||||
|
public static String formatLog(Formatter formatter,
|
||||||
|
Collection<LogRecord> logRecords) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (LogRecord record : logRecords) {
|
||||||
|
String formatted = formatter.format(record);
|
||||||
|
sb.append(formatted).append('\n');
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
private final DateFormat dateFormat; // Locking: lock
|
private final DateFormat dateFormat; // Locking: lock
|
||||||
private final Date date; // Locking: lock
|
private final Date date; // Locking: lock
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ public class CachingLogHandler extends Handler {
|
|||||||
// Locking: lock
|
// Locking: lock
|
||||||
private final Queue<LogRecord> recent = new LinkedList<>();
|
private final Queue<LogRecord> recent = new LinkedList<>();
|
||||||
|
|
||||||
|
// package-private constructor
|
||||||
|
CachingLogHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void publish(LogRecord record) {
|
public void publish(LogRecord record) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.briarproject.briar.android.logging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface LogDecrypter {
|
||||||
|
/**
|
||||||
|
* Returns decrypted log records from {@link AndroidUtils#getLogcatFile}
|
||||||
|
* or null if there was an error reading the logs.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
String decryptLogs(@Nullable byte[] logKey);
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package org.briarproject.briar.android.logging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.reporting.DevConfig;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class LogDecrypterImpl implements LogDecrypter {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(LogDecrypterImpl.class.getName());
|
||||||
|
|
||||||
|
private final DevConfig devConfig;
|
||||||
|
private final StreamReaderFactory streamReaderFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
LogDecrypterImpl(DevConfig devConfig,
|
||||||
|
StreamReaderFactory streamReaderFactory) {
|
||||||
|
this.devConfig = devConfig;
|
||||||
|
this.streamReaderFactory = streamReaderFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String decryptLogs(@Nullable byte[] logKey) {
|
||||||
|
if (logKey == null) return null;
|
||||||
|
SecretKey key = new SecretKey(logKey);
|
||||||
|
File logFile = devConfig.getLogcatFile();
|
||||||
|
try (InputStream in = new FileInputStream(logFile)) {
|
||||||
|
InputStream reader =
|
||||||
|
streamReaderFactory.createLogStreamReader(in, key);
|
||||||
|
Scanner s = new Scanner(reader);
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
while (s.hasNextLine()) sb.append(s.nextLine()).append("\n");
|
||||||
|
s.close();
|
||||||
|
return sb.toString();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
logFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.briarproject.briar.android.logging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface LogEncrypter {
|
||||||
|
/**
|
||||||
|
* Writes encrypted log records to {@link AndroidUtils#getLogcatFile}
|
||||||
|
* and returns the encryption key if everything went fine.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
byte[] encryptLogs();
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package org.briarproject.briar.android.logging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.reporting.DevConfig;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.logging.Formatter;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class LogEncrypterImpl implements LogEncrypter {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(LogEncrypterImpl.class.getName());
|
||||||
|
|
||||||
|
private final DevConfig devConfig;
|
||||||
|
private final CachingLogHandler logHandler;
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
private final StreamWriterFactory streamWriterFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
LogEncrypterImpl(DevConfig devConfig,
|
||||||
|
CachingLogHandler logHandler,
|
||||||
|
CryptoComponent crypto,
|
||||||
|
StreamWriterFactory streamWriterFactory) {
|
||||||
|
this.devConfig = devConfig;
|
||||||
|
this.logHandler = logHandler;
|
||||||
|
this.crypto = crypto;
|
||||||
|
this.streamWriterFactory = streamWriterFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public byte[] encryptLogs() {
|
||||||
|
SecretKey logKey = crypto.generateSecretKey();
|
||||||
|
File logFile = devConfig.getLogcatFile();
|
||||||
|
try (OutputStream out = new FileOutputStream(logFile)) {
|
||||||
|
StreamWriter streamWriter =
|
||||||
|
streamWriterFactory.createLogStreamWriter(out, logKey);
|
||||||
|
Writer writer =
|
||||||
|
new OutputStreamWriter(streamWriter.getOutputStream());
|
||||||
|
writeLogString(writer);
|
||||||
|
writer.close();
|
||||||
|
return logKey.getBytes();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeLogString(Writer writer) throws IOException {
|
||||||
|
Formatter formatter = new BriefLogFormatter();
|
||||||
|
for (LogRecord record : logHandler.getRecentLogRecords()) {
|
||||||
|
String formatted = formatter.format(record);
|
||||||
|
writer.append(formatted).append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.briarproject.briar.android.logging;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class LoggingModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
CachingLogHandler provideCachingLogHandler() {
|
||||||
|
return new CachingLogHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
LogEncrypter provideLogEncrypter(LogEncrypterImpl logEncrypter) {
|
||||||
|
return logEncrypter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
LogDecrypter provideLogDecrypter(LogDecrypterImpl logDecrypter) {
|
||||||
|
return logDecrypter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -68,6 +68,7 @@ import static android.view.View.VISIBLE;
|
|||||||
import static androidx.core.view.GravityCompat.START;
|
import static androidx.core.view.GravityCompat.START;
|
||||||
import static androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
import static androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
||||||
import static androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
|
import static androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
|
||||||
|
import static androidx.lifecycle.Lifecycle.State.STARTED;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||||
@@ -297,6 +298,12 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
finish();
|
finish();
|
||||||
} else if (fm.getBackStackEntryCount() == 0
|
} else if (fm.getBackStackEntryCount() == 0
|
||||||
&& fm.findFragmentByTag(ContactListFragment.TAG) == null) {
|
&& fm.findFragmentByTag(ContactListFragment.TAG) == null) {
|
||||||
|
// don't start fragments in the wrong part of lifecycle (#1904)
|
||||||
|
if (!getLifecycle().getCurrentState().isAtLeast(STARTED)) {
|
||||||
|
LOG.warning("Tried to start contacts fragment in state " +
|
||||||
|
getLifecycle().getCurrentState().name());
|
||||||
|
return;
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* This makes sure that the first fragment (ContactListFragment) the
|
* This makes sure that the first fragment (ContactListFragment) the
|
||||||
* user sees is the same as the last fragment the user sees before
|
* user sees is the same as the last fragment the user sees before
|
||||||
|
|||||||
@@ -1,29 +1,40 @@
|
|||||||
package org.briarproject.briar.android.reporting;
|
package org.briarproject.briar.android.reporting;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.app.Application;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.logging.LogEncrypter;
|
||||||
|
|
||||||
import java.lang.Thread.UncaughtExceptionHandler;
|
import java.lang.Thread.UncaughtExceptionHandler;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.startDevReportActivity;
|
import static org.briarproject.briar.android.util.UiUtils.startDevReportActivity;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class BriarExceptionHandler implements UncaughtExceptionHandler {
|
class BriarExceptionHandler implements UncaughtExceptionHandler {
|
||||||
|
|
||||||
private final Context ctx;
|
private final Application app;
|
||||||
|
private final LogEncrypter logEncrypter;
|
||||||
private final long appStartTime;
|
private final long appStartTime;
|
||||||
|
|
||||||
public BriarExceptionHandler(Context ctx) {
|
@Inject
|
||||||
this.ctx = ctx;
|
BriarExceptionHandler(Application app, LogEncrypter logEncrypter) {
|
||||||
this.appStartTime = System.currentTimeMillis();
|
this.app = app;
|
||||||
|
this.logEncrypter = logEncrypter;
|
||||||
|
appStartTime = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void uncaughtException(Thread t, Throwable e) {
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
|
// encrypt logs to disk before handing over to new process
|
||||||
|
// the intent has limited space, so we can't reliably store them there.
|
||||||
|
byte[] logKey = logEncrypter.encryptLogs();
|
||||||
|
|
||||||
// activity runs in its own process, so we can kill the old one
|
// activity runs in its own process, so we can kill the old one
|
||||||
startDevReportActivity(ctx, CrashReportActivity.class, e, appStartTime);
|
startDevReportActivity(app.getApplicationContext(),
|
||||||
|
CrashReportActivity.class, e, appStartTime, logKey);
|
||||||
Process.killProcess(Process.myPid());
|
Process.killProcess(Process.myPid());
|
||||||
System.exit(10);
|
System.exit(10);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ import org.briarproject.bramble.api.Pair;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.BuildConfig;
|
import org.briarproject.briar.BuildConfig;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.BriarApplication;
|
|
||||||
import org.briarproject.briar.android.logging.BriefLogFormatter;
|
|
||||||
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
|
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
|
||||||
import org.briarproject.briar.android.reporting.ReportData.ReportItem;
|
import org.briarproject.briar.android.reporting.ReportData.ReportItem;
|
||||||
import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo;
|
import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo;
|
||||||
@@ -41,8 +39,6 @@ import java.net.InetAddress;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.logging.Formatter;
|
|
||||||
import java.util.logging.LogRecord;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
@@ -74,8 +70,8 @@ class BriarReportCollector {
|
|||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReportData collectReportData(@Nullable Throwable t,
|
ReportData collectReportData(@Nullable Throwable t, long appStartTime,
|
||||||
long appStartTime) {
|
String logs) {
|
||||||
ReportData reportData = new ReportData()
|
ReportData reportData = new ReportData()
|
||||||
.add(getBasicInfo(t))
|
.add(getBasicInfo(t))
|
||||||
.add(getDeviceInfo());
|
.add(getDeviceInfo());
|
||||||
@@ -86,7 +82,7 @@ class BriarReportCollector {
|
|||||||
.add(getStorage())
|
.add(getStorage())
|
||||||
.add(getConnectivity())
|
.add(getConnectivity())
|
||||||
.add(getBuildConfig())
|
.add(getBuildConfig())
|
||||||
.add(getLogcat())
|
.add(getLogcat(logs))
|
||||||
.add(getDeviceFeatures());
|
.add(getDeviceFeatures());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,15 +309,8 @@ class BriarReportCollector {
|
|||||||
buildConfig);
|
buildConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReportItem getLogcat() {
|
private ReportItem getLogcat(String logs) {
|
||||||
BriarApplication app = (BriarApplication) ctx.getApplicationContext();
|
return new ReportItem("Logcat", R.string.dev_report_logcat, logs);
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
Formatter formatter = new BriefLogFormatter();
|
|
||||||
for (LogRecord record : app.getRecentLogRecords()) {
|
|
||||||
sb.append(formatter.format(record)).append('\n');
|
|
||||||
}
|
|
||||||
return new ReportItem("Logcat", R.string.dev_report_logcat,
|
|
||||||
sb.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReportItem getDeviceFeatures() {
|
private ReportItem getDeviceFeatures() {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public class CrashReportActivity extends BaseActivity
|
|||||||
|
|
||||||
public static final String EXTRA_THROWABLE = "throwable";
|
public static final String EXTRA_THROWABLE = "throwable";
|
||||||
public static final String EXTRA_APP_START_TIME = "appStartTime";
|
public static final String EXTRA_APP_START_TIME = "appStartTime";
|
||||||
|
public static final String EXTRA_APP_LOGCAT = "logcat";
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
@@ -56,7 +57,8 @@ public class CrashReportActivity extends BaseActivity
|
|||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
Throwable t = (Throwable) intent.getSerializableExtra(EXTRA_THROWABLE);
|
Throwable t = (Throwable) intent.getSerializableExtra(EXTRA_THROWABLE);
|
||||||
long appStartTime = intent.getLongExtra(EXTRA_APP_START_TIME, -1);
|
long appStartTime = intent.getLongExtra(EXTRA_APP_START_TIME, -1);
|
||||||
viewModel.init(t, appStartTime);
|
byte[] logKey = intent.getByteArrayExtra(EXTRA_APP_LOGCAT);
|
||||||
|
viewModel.init(t, appStartTime, logKey);
|
||||||
viewModel.getShowReport().observeEvent(this, show -> {
|
viewModel.getShowReport().observeEvent(this, show -> {
|
||||||
if (show) displayFragment(true);
|
if (show) displayFragment(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,4 +15,8 @@ public abstract class DevReportModule {
|
|||||||
@ViewModelKey(ReportViewModel.class)
|
@ViewModelKey(ReportViewModel.class)
|
||||||
abstract ViewModel bindReportViewModel(ReportViewModel reportViewModel);
|
abstract ViewModel bindReportViewModel(ReportViewModel reportViewModel);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract Thread.UncaughtExceptionHandler bindUncaughtExceptionHandler(
|
||||||
|
BriarExceptionHandler handler);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import org.briarproject.bramble.api.plugin.TorConstants;
|
|||||||
import org.briarproject.bramble.api.reporting.DevReporter;
|
import org.briarproject.bramble.api.reporting.DevReporter;
|
||||||
import org.briarproject.bramble.util.AndroidUtils;
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.logging.BriefLogFormatter;
|
||||||
|
import org.briarproject.briar.android.logging.CachingLogHandler;
|
||||||
|
import org.briarproject.briar.android.logging.LogDecrypter;
|
||||||
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
|
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||||
@@ -19,6 +22,7 @@ import org.json.JSONException;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Formatter;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -36,13 +40,16 @@ import static java.util.logging.Logger.getLogger;
|
|||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
import static org.briarproject.briar.android.logging.BriefLogFormatter.formatLog;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class ReportViewModel extends AndroidViewModel {
|
class ReportViewModel extends AndroidViewModel {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(ReportViewModel.class.getName());
|
getLogger(ReportViewModel.class.getName());
|
||||||
|
|
||||||
|
private final CachingLogHandler logHandler;
|
||||||
|
private final LogDecrypter logDecrypter;
|
||||||
private final BriarReportCollector collector;
|
private final BriarReportCollector collector;
|
||||||
private final DevReporter reporter;
|
private final DevReporter reporter;
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
@@ -58,18 +65,39 @@ public class ReportViewModel extends AndroidViewModel {
|
|||||||
private boolean isFeedback;
|
private boolean isFeedback;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ReportViewModel(@NonNull Application application,
|
ReportViewModel(@NonNull Application application,
|
||||||
DevReporter reporter, PluginManager pluginManager) {
|
CachingLogHandler logHandler,
|
||||||
|
LogDecrypter logDecrypter,
|
||||||
|
DevReporter reporter,
|
||||||
|
PluginManager pluginManager) {
|
||||||
super(application);
|
super(application);
|
||||||
this.collector = new BriarReportCollector(application);
|
collector = new BriarReportCollector(application);
|
||||||
|
this.logHandler = logHandler;
|
||||||
|
this.logDecrypter = logDecrypter;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(@Nullable Throwable t, long appStartTime) {
|
void init(@Nullable Throwable t, long appStartTime,
|
||||||
|
@Nullable byte[] logKey) {
|
||||||
isFeedback = t == null;
|
isFeedback = t == null;
|
||||||
if (reportData.getValue() == null) new SingleShotAndroidExecutor(() -> {
|
if (reportData.getValue() == null) new SingleShotAndroidExecutor(() -> {
|
||||||
ReportData data = collector.collectReportData(t, appStartTime);
|
String decryptedLogs;
|
||||||
|
if (isFeedback) {
|
||||||
|
Formatter formatter = new BriefLogFormatter();
|
||||||
|
decryptedLogs =
|
||||||
|
formatLog(formatter, logHandler.getRecentLogRecords());
|
||||||
|
} else {
|
||||||
|
decryptedLogs = logDecrypter.decryptLogs(logKey);
|
||||||
|
if (decryptedLogs == null) {
|
||||||
|
// error decrypting logs, get logs from this process
|
||||||
|
Formatter formatter = new BriefLogFormatter();
|
||||||
|
decryptedLogs = formatLog(formatter,
|
||||||
|
logHandler.getRecentLogRecords());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReportData data =
|
||||||
|
collector.collectReportData(t, appStartTime, decryptedLogs);
|
||||||
reportData.postValue(data);
|
reportData.postValue(data);
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
@@ -110,8 +138,8 @@ public class ReportViewModel extends AndroidViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The content of the report
|
* The content of the report that will be loaded after
|
||||||
* that will be loaded after {@link #init(Throwable, long)} was called.
|
* {@link #init(Throwable, long, byte[])} was called.
|
||||||
*/
|
*/
|
||||||
LiveData<ReportData> getReportData() {
|
LiveData<ReportData> getReportData() {
|
||||||
return reportData;
|
return reportData;
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import javax.inject.Inject;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.text.TextUtilsCompat;
|
|
||||||
import androidx.preference.ListPreference;
|
import androidx.preference.ListPreference;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.Preference.OnPreferenceChangeListener;
|
import androidx.preference.Preference.OnPreferenceChangeListener;
|
||||||
@@ -70,7 +69,6 @@ import static android.provider.Settings.EXTRA_APP_PACKAGE;
|
|||||||
import static android.provider.Settings.EXTRA_CHANNEL_ID;
|
import static android.provider.Settings.EXTRA_CHANNEL_ID;
|
||||||
import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI;
|
import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR;
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
@@ -296,19 +294,27 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
Locale locale = Localizer.getLocaleFromTag(tag);
|
Locale locale = Localizer.getLocaleFromTag(tag);
|
||||||
if (locale == null)
|
if (locale == null)
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
// Exclude RTL locales on API < 17, they won't be laid out correctly
|
// Check if the locale is supported on this device
|
||||||
if (SDK_INT < 17 && !isLeftToRight(locale)) {
|
if (!Localizer.isLocaleSupported(locale)) {
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Skipping RTL locale " + tag);
|
LOG.info("Skipping unsupported locale " + tag);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String nativeName = locale.getDisplayName(locale);
|
String nativeName;
|
||||||
// Fallback to English if the name is unknown in both native and
|
// Unknown languages won't be translated to their native name.
|
||||||
// current locale.
|
if (locale.getLanguage().equals("ast")) {
|
||||||
if (nativeName.equals(tag)) {
|
nativeName = "Asturianu";
|
||||||
String tmp = locale.getDisplayLanguage(Locale.ENGLISH);
|
} else if (locale.getLanguage().equals("oc")) {
|
||||||
if (!tmp.isEmpty() && !tmp.equals(nativeName))
|
nativeName = "Occitan";
|
||||||
nativeName = tmp;
|
} else {
|
||||||
|
nativeName = locale.getDisplayName(locale);
|
||||||
|
// Fallback to English if the name is unknown in both native and
|
||||||
|
// current locale.
|
||||||
|
if (nativeName.equals(tag)) {
|
||||||
|
String tmp = locale.getDisplayLanguage(Locale.ENGLISH);
|
||||||
|
if (!tmp.isEmpty() && !tmp.equals(nativeName))
|
||||||
|
nativeName = tmp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Prefix with LRM marker to prevent any RTL direction
|
// Prefix with LRM marker to prevent any RTL direction
|
||||||
entries.add("\u200E" + nativeName.substring(0, 1).toUpperCase()
|
entries.add("\u200E" + nativeName.substring(0, 1).toUpperCase()
|
||||||
@@ -319,13 +325,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
language.setEntryValues(entryValues.toArray(new CharSequence[0]));
|
language.setEntryValues(entryValues.toArray(new CharSequence[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isLeftToRight(Locale locale) {
|
|
||||||
// TextUtilsCompat returns the wrong direction for Hebrew on some phones
|
|
||||||
String language = locale.getLanguage();
|
|
||||||
if (language.equals("iw") || language.equals("he")) return false;
|
|
||||||
int direction = TextUtilsCompat.getLayoutDirectionFromLocale(locale);
|
|
||||||
return direction == LAYOUT_DIRECTION_LTR;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setTorNetworkSummary(int torNetworkSetting) {
|
private void setTorNetworkSummary(int torNetworkSetting) {
|
||||||
if (torNetworkSetting != PREF_TOR_NETWORK_AUTOMATIC) {
|
if (torNetworkSetting != PREF_TOR_NETWORK_AUTOMATIC) {
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ import static java.util.concurrent.TimeUnit.DAYS;
|
|||||||
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
|
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
|
||||||
import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
|
import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
|
||||||
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
|
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
|
||||||
|
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_LOGCAT;
|
||||||
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME;
|
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME;
|
||||||
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_THROWABLE;
|
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_THROWABLE;
|
||||||
|
|
||||||
@@ -357,16 +358,17 @@ public class UiUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void triggerFeedback(Context ctx) {
|
public static void triggerFeedback(Context ctx) {
|
||||||
startDevReportActivity(ctx, FeedbackActivity.class, null, null);
|
startDevReportActivity(ctx, FeedbackActivity.class, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void startDevReportActivity(Context ctx,
|
public static void startDevReportActivity(Context ctx,
|
||||||
Class<? extends FragmentActivity> activity, @Nullable Throwable t,
|
Class<? extends FragmentActivity> activity, @Nullable Throwable t,
|
||||||
@Nullable Long appStartTime) {
|
@Nullable Long appStartTime, @Nullable byte[] logKey) {
|
||||||
final Intent dialogIntent = new Intent(ctx, activity);
|
final Intent dialogIntent = new Intent(ctx, activity);
|
||||||
dialogIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
dialogIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||||
dialogIntent.putExtra(EXTRA_THROWABLE, t);
|
dialogIntent.putExtra(EXTRA_THROWABLE, t);
|
||||||
dialogIntent.putExtra(EXTRA_APP_START_TIME, appStartTime);
|
dialogIntent.putExtra(EXTRA_APP_START_TIME, appStartTime);
|
||||||
|
dialogIntent.putExtra(EXTRA_APP_LOGCAT, logKey);
|
||||||
ctx.startActivity(dialogIntent);
|
ctx.startActivity(dialogIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,4 +93,7 @@ public interface AndroidNotificationManager {
|
|||||||
void blockAllBlogPostNotifications();
|
void blockAllBlogPostNotifications();
|
||||||
|
|
||||||
void unblockAllBlogPostNotifications();
|
void unblockAllBlogPostNotifications();
|
||||||
|
|
||||||
|
void restartNotifications(boolean locked, boolean mayAlertAgain);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,6 @@
|
|||||||
android:textColor="@color/briar_primary"
|
android:textColor="@color/briar_primary"
|
||||||
android:textIsSelectable="true"
|
android:textIsSelectable="true"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:typeface="monospace"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/yourLinkIcon"
|
app:layout_constraintTop_toBottomOf="@+id/yourLinkIcon"
|
||||||
|
|||||||
@@ -139,10 +139,10 @@
|
|||||||
<Space
|
<Space
|
||||||
android:id="@+id/space"
|
android:id="@+id/space"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constrainedHeight="true"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/addButton"
|
app:layout_constraintBottom_toTopOf="@+id/addButton"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHeight_default="wrap"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/contactNameLayout" />
|
app:layout_constraintTop_toBottomOf="@+id/contactNameLayout" />
|
||||||
|
|
||||||
@@ -173,4 +173,4 @@
|
|||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package org.briarproject.briar.android.logging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.FINE;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.SEVERE;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
|
import static org.briarproject.briar.android.logging.BriefLogFormatter.formatLog;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class LogEncryptionDecryptionTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
@ClassRule
|
||||||
|
public static TemporaryFolder folder = new TemporaryFolder();
|
||||||
|
|
||||||
|
private final SecureRandom random;
|
||||||
|
private final CachingLogHandler cachingLogHandler;
|
||||||
|
private final LogEncrypter logEncrypter;
|
||||||
|
private final LogDecrypter logDecrypter;
|
||||||
|
private final BriefLogFormatter logFormatter = new BriefLogFormatter();
|
||||||
|
|
||||||
|
public LogEncryptionDecryptionTest() throws IOException {
|
||||||
|
LoggingComponent loggingComponent = DaggerLoggingComponent.builder()
|
||||||
|
.loggingTestModule(new LoggingTestModule(folder.newFile()))
|
||||||
|
.build();
|
||||||
|
random = loggingComponent.random();
|
||||||
|
logEncrypter = loggingComponent.logEncrypter();
|
||||||
|
logDecrypter = loggingComponent.logDecrypter();
|
||||||
|
cachingLogHandler = loggingComponent.cachingLogHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptedMatchesDecrypted() {
|
||||||
|
ArrayList<LogRecord> logRecords =
|
||||||
|
new ArrayList<>(random.nextInt(99) + 1);
|
||||||
|
for (int i = 0; i < logRecords.size(); i++) {
|
||||||
|
LogRecord logRecord = getRandomLogRecord();
|
||||||
|
cachingLogHandler.publish(logRecord);
|
||||||
|
logRecords.add(logRecord);
|
||||||
|
}
|
||||||
|
byte[] logKey = logEncrypter.encryptLogs();
|
||||||
|
assertEquals(formatLog(logFormatter, logRecords),
|
||||||
|
logDecrypter.decryptLogs(logKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
private LogRecord getRandomLogRecord() {
|
||||||
|
Level[] levels = {SEVERE, WARNING, INFO, FINE};
|
||||||
|
Level level = levels[random.nextInt(levels.length)];
|
||||||
|
LogRecord logRecord =
|
||||||
|
new LogRecord(level, getRandomString(random.nextInt(128) + 1));
|
||||||
|
logRecord.setLoggerName(getRandomString(random.nextInt(23) + 1));
|
||||||
|
return logRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.briarproject.briar.android.logging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.BrambleCoreModule;
|
||||||
|
import org.briarproject.bramble.test.TestSecureRandomModule;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Component;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Component(modules = {
|
||||||
|
BrambleCoreModule.class,
|
||||||
|
TestSecureRandomModule.class,
|
||||||
|
LoggingModule.class,
|
||||||
|
LoggingTestModule.class,
|
||||||
|
})
|
||||||
|
public interface LoggingComponent {
|
||||||
|
|
||||||
|
SecureRandom random();
|
||||||
|
|
||||||
|
CachingLogHandler cachingLogHandler();
|
||||||
|
|
||||||
|
LogEncrypter logEncrypter();
|
||||||
|
|
||||||
|
LogDecrypter logDecrypter();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package org.briarproject.briar.android.logging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.reporting.DevConfig;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
class LoggingTestModule {
|
||||||
|
|
||||||
|
private final File logFile;
|
||||||
|
|
||||||
|
LoggingTestModule(File logFile) {
|
||||||
|
this.logFile = logFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
DevConfig provideDevConfig() {
|
||||||
|
@NotNullByDefault
|
||||||
|
DevConfig devConfig = new DevConfig() {
|
||||||
|
@Override
|
||||||
|
public PublicKey getDevPublicKey() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDevOnionAddress() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getReportDir() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getLogcatFile() {
|
||||||
|
return logFile;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return devConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user