mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +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 STORED_REPORTS = "dev-reports";
|
||||
private static final String STORED_LOGCAT = "dev-logcat";
|
||||
|
||||
public static Collection<String> getSupportedArchitectures() {
|
||||
List<String> abis = new ArrayList<>();
|
||||
@@ -107,6 +108,10 @@ public class AndroidUtils {
|
||||
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.
|
||||
* GIFs can't be compressed on API < 24 so they're not supported.
|
||||
|
||||
@@ -19,4 +19,10 @@ public interface StreamDecrypterFactory {
|
||||
*/
|
||||
StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
|
||||
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
|
||||
* 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);
|
||||
}
|
||||
|
||||
@@ -13,4 +13,6 @@ public interface DevConfig {
|
||||
String getDevOnionAddress();
|
||||
|
||||
File getReportDir();
|
||||
|
||||
File getLogcatFile();
|
||||
}
|
||||
|
||||
@@ -16,8 +16,13 @@ public interface StreamReaderFactory {
|
||||
|
||||
/**
|
||||
* Creates an {@link InputStream InputStream} for reading from a contact
|
||||
* exchangestream.
|
||||
* exchange stream.
|
||||
*/
|
||||
InputStream createContactExchangeStreamReader(InputStream in,
|
||||
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
|
||||
public interface StreamWriterFactory {
|
||||
|
||||
/**
|
||||
* Creates an {@link OutputStream OutputStream} for writing to a
|
||||
* transport stream
|
||||
* Creates a {@link StreamWriter} for writing to a transport stream.
|
||||
*/
|
||||
StreamWriter createStreamWriter(OutputStream out, StreamContext ctx);
|
||||
|
||||
/**
|
||||
* Creates an {@link OutputStream OutputStream} for writing to a contact
|
||||
* exchange stream.
|
||||
* Creates a {@link StreamWriter} for writing to a contact exchange stream.
|
||||
*/
|
||||
StreamWriter createContactExchangeStreamWriter(OutputStream out,
|
||||
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 static java.lang.Math.min;
|
||||
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.now;
|
||||
|
||||
class ScryptKdf implements PasswordBasedKdf {
|
||||
|
||||
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 MAX_COST = 1024 * 1024; // Max parameter N
|
||||
@@ -33,10 +35,20 @@ class ScryptKdf implements PasswordBasedKdf {
|
||||
|
||||
@Override
|
||||
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
|
||||
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;
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("KDF cost parameter " + cost);
|
||||
return cost;
|
||||
|
||||
@@ -36,4 +36,10 @@ class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
|
||||
SecretKey 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
|
||||
public StreamEncrypter createContactExchangeStreamDecrypter(
|
||||
public StreamEncrypter createContactExchangeStreamEncrypter(
|
||||
OutputStream out, SecretKey headerKey) {
|
||||
AuthenticatedCipher cipher = cipherProvider.get();
|
||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||
@@ -60,4 +60,10 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
||||
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce,
|
||||
headerKey, frameKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamEncrypter createLogStreamEncrypter(OutputStream out,
|
||||
SecretKey headerKey) {
|
||||
return createContactExchangeStreamEncrypter(out, headerKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,15 +24,21 @@ class StreamReaderFactoryImpl implements StreamReaderFactory {
|
||||
|
||||
@Override
|
||||
public InputStream createStreamReader(InputStream in, StreamContext ctx) {
|
||||
return new StreamReaderImpl(
|
||||
streamDecrypterFactory.createStreamDecrypter(in, ctx));
|
||||
return new StreamReaderImpl(streamDecrypterFactory
|
||||
.createStreamDecrypter(in, ctx));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream createContactExchangeStreamReader(InputStream in,
|
||||
SecretKey headerKey) {
|
||||
return new StreamReaderImpl(
|
||||
streamDecrypterFactory.createContactExchangeStreamDecrypter(in,
|
||||
headerKey));
|
||||
return new StreamReaderImpl(streamDecrypterFactory
|
||||
.createContactExchangeStreamDecrypter(in, 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
|
||||
public StreamWriter createStreamWriter(OutputStream out,
|
||||
StreamContext ctx) {
|
||||
return new StreamWriterImpl(
|
||||
streamEncrypterFactory.createStreamEncrypter(out, ctx));
|
||||
return new StreamWriterImpl(streamEncrypterFactory
|
||||
.createStreamEncrypter(out, ctx));
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamWriter createContactExchangeStreamWriter(OutputStream out,
|
||||
SecretKey headerKey) {
|
||||
return new StreamWriterImpl(
|
||||
streamEncrypterFactory.createContactExchangeStreamDecrypter(out,
|
||||
headerKey));
|
||||
return new StreamWriterImpl(streamEncrypterFactory
|
||||
.createContactExchangeStreamEncrypter(out, 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-junit4:$jmockVersion"
|
||||
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
|
||||
testAnnotationProcessor "com.google.dagger:dagger-compiler:2.24"
|
||||
|
||||
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
|
||||
@@ -25,7 +25,7 @@ class BriarAccountManager extends AndroidAccountManager {
|
||||
public void deleteAccount() {
|
||||
synchronized (stateChangeLock) {
|
||||
super.deleteAccount();
|
||||
Localizer.reinitialize();
|
||||
Localizer.reinitialize(appContext);
|
||||
UiUtils.setTheme(appContext,
|
||||
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.media.MediaModule;
|
||||
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.view.EmojiTextInputView;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
@@ -179,6 +180,10 @@ public interface AndroidComponent
|
||||
|
||||
AndroidWakeLockManager wakeLockManager();
|
||||
|
||||
CachingLogHandler logHandler();
|
||||
|
||||
Thread.UncaughtExceptionHandler exceptionHandler();
|
||||
|
||||
void inject(SignInReminderReceiver briarService);
|
||||
|
||||
void inject(BriarService briarService);
|
||||
|
||||
@@ -579,6 +579,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
|
||||
@UiThread
|
||||
private void updateContactAddedNotification() {
|
||||
if (contactAddedTotal == 0) return;
|
||||
BriarNotificationBuilder b =
|
||||
new BriarNotificationBuilder(appContext, CONTACT_CHANNEL_ID);
|
||||
b.setSmallIcon(R.drawable.notification_contact_added);
|
||||
@@ -713,4 +714,16 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
public void unblockAllBlogPostNotifications() {
|
||||
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.forum.ForumModule;
|
||||
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.navdrawer.NavDrawerModule;
|
||||
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.reporting.DevReportModule;
|
||||
import org.briarproject.briar.android.settings.SettingsModule;
|
||||
import org.briarproject.briar.android.sharing.SharingModule;
|
||||
import org.briarproject.briar.android.test.TestAvatarCreatorImpl;
|
||||
import org.briarproject.briar.android.viewmodel.ViewModelModule;
|
||||
@@ -74,6 +75,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
SetupModule.class,
|
||||
DozeHelperModule.class,
|
||||
ContactExchangeModule.class,
|
||||
LoggingModule.class,
|
||||
LoginModule.class,
|
||||
NavDrawerModule.class,
|
||||
ViewModelModule.class,
|
||||
@@ -192,6 +194,11 @@ public class AppModule {
|
||||
public File getReportDir() {
|
||||
return AndroidUtils.getReportDir(app.getApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getLogcatFile() {
|
||||
return AndroidUtils.getLogcatFile(app.getApplicationContext());
|
||||
}
|
||||
};
|
||||
return devConfig;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,6 @@ import android.content.SharedPreferences;
|
||||
import org.briarproject.bramble.BrambleApplication;
|
||||
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
|
||||
* directly to the Briar application object.
|
||||
@@ -17,8 +14,6 @@ public interface BriarApplication extends BrambleApplication {
|
||||
|
||||
Class<? extends Activity> ENTRY_ACTIVITY = NavDrawerActivity.class;
|
||||
|
||||
Collection<LogRecord> getRecentLogRecords();
|
||||
|
||||
AndroidComponent getApplicationComponent();
|
||||
|
||||
SharedPreferences getDefaultSharedPreferences();
|
||||
|
||||
@@ -20,12 +20,10 @@ import org.briarproject.bramble.BrambleCoreEagerSingletons;
|
||||
import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.logging.CachingLogHandler;
|
||||
import org.briarproject.briar.android.reporting.BriarExceptionHandler;
|
||||
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.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -42,16 +40,11 @@ public class BriarApplicationImpl extends Application
|
||||
private static final Logger LOG =
|
||||
getLogger(BriarApplicationImpl.class.getName());
|
||||
|
||||
private final CachingLogHandler logHandler = new CachingLogHandler();
|
||||
private final BriarExceptionHandler exceptionHandler =
|
||||
new BriarExceptionHandler(this);
|
||||
|
||||
private AndroidComponent applicationComponent;
|
||||
private volatile SharedPreferences prefs;
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
|
||||
if (prefs == null)
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(base);
|
||||
// Loading the language needs to be done here.
|
||||
@@ -67,6 +60,11 @@ public class BriarApplicationImpl extends Application
|
||||
|
||||
if (IS_DEBUG_BUILD) enableStrictMode();
|
||||
|
||||
applicationComponent = createApplicationComponent();
|
||||
UncaughtExceptionHandler exceptionHandler =
|
||||
applicationComponent.exceptionHandler();
|
||||
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
|
||||
|
||||
Logger rootLogger = getLogger("");
|
||||
Handler[] handlers = rootLogger.getHandlers();
|
||||
// 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
|
||||
for (Handler handler : handlers) rootLogger.addHandler(handler);
|
||||
}
|
||||
CachingLogHandler logHandler = applicationComponent.logHandler();
|
||||
rootLogger.addHandler(logHandler);
|
||||
rootLogger.setLevel(IS_DEBUG_BUILD ? FINE : INFO);
|
||||
|
||||
LOG.info("Created");
|
||||
Localizer.getInstance().setLocaleLegacy(this);
|
||||
|
||||
applicationComponent = createApplicationComponent();
|
||||
EmojiManager.install(new GoogleEmojiProvider());
|
||||
}
|
||||
|
||||
@@ -106,7 +105,9 @@ public class BriarApplicationImpl extends Application
|
||||
@Override
|
||||
public void onConfigurationChanged(@NonNull Configuration 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) {
|
||||
@@ -136,11 +137,6 @@ public class BriarApplicationImpl extends Application
|
||||
return applicationComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<LogRecord> getRecentLogRecords() {
|
||||
return logHandler.getRecentLogRecords();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidComponent getApplicationComponent() {
|
||||
return applicationComponent;
|
||||
|
||||
@@ -6,12 +6,16 @@ import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import androidx.core.text.TextUtilsCompat;
|
||||
|
||||
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;
|
||||
|
||||
@NotNullByDefault
|
||||
@@ -21,6 +25,7 @@ public class Localizer {
|
||||
@Nullable
|
||||
private static Localizer INSTANCE;
|
||||
private final Locale systemLocale;
|
||||
@Nullable
|
||||
private final Locale locale;
|
||||
|
||||
private Localizer(SharedPreferences sharedPreferences) {
|
||||
@@ -30,10 +35,17 @@ public class Localizer {
|
||||
|
||||
private Localizer(Locale systemLocale, @Nullable Locale userLocale) {
|
||||
this.systemLocale = systemLocale;
|
||||
if (userLocale == null) locale = systemLocale;
|
||||
else locale = userLocale;
|
||||
locale = userLocale;
|
||||
setLocaleAndSystemConfiguration(locale);
|
||||
}
|
||||
|
||||
private Localizer(Locale systemLocale) {
|
||||
this.systemLocale = systemLocale;
|
||||
locale = null;
|
||||
setLocaleAndSystemConfiguration(systemLocale);
|
||||
}
|
||||
|
||||
|
||||
// Instantiate the Localizer.
|
||||
public static synchronized void initialize(SharedPreferences prefs) {
|
||||
if (INSTANCE == null)
|
||||
@@ -41,9 +53,11 @@ public class Localizer {
|
||||
}
|
||||
|
||||
// Reinstantiate the Localizer with the system locale
|
||||
public static synchronized void reinitialize() {
|
||||
if (INSTANCE != null)
|
||||
INSTANCE = new Localizer(INSTANCE.systemLocale, null);
|
||||
public static synchronized void reinitialize(Context appContext) {
|
||||
if (INSTANCE != null && INSTANCE.locale != null) {
|
||||
INSTANCE = new Localizer(INSTANCE.systemLocale);
|
||||
INSTANCE.forceLocale(appContext, INSTANCE.systemLocale);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the current instance.
|
||||
@@ -64,29 +78,98 @@ public class Localizer {
|
||||
if (tag.contains("-")) {
|
||||
String[] langArray = tag.split("-");
|
||||
return new Locale(langArray[0], langArray[1]);
|
||||
} else
|
||||
} else {
|
||||
return new Locale(tag);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the localized version of context
|
||||
public Context setLocale(Context context) {
|
||||
if (locale == null || SDK_INT < 17) return context;
|
||||
Resources res = context.getResources();
|
||||
Configuration conf = res.getConfiguration();
|
||||
Locale currentLocale;
|
||||
if (SDK_INT >= 24) {
|
||||
currentLocale = conf.getLocales().get(0);
|
||||
} else
|
||||
currentLocale = conf.locale;
|
||||
if (locale.equals(currentLocale))
|
||||
return context;
|
||||
Locale.setDefault(locale);
|
||||
updateConfiguration(conf, locale);
|
||||
return context.createConfigurationContext(conf);
|
||||
}
|
||||
|
||||
// For API < 17 only.
|
||||
public void setLocaleLegacy(Context appContext) {
|
||||
if (SDK_INT >= 17 || locale == null) return;
|
||||
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) {
|
||||
conf.setLocale(locale);
|
||||
context.createConfigurationContext(conf);
|
||||
} else
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
public void updatePendingContact(String name, PendingContact p) {
|
||||
void updatePendingContact(String name, PendingContact p) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
contactManager.removePendingContact(p.getId());
|
||||
|
||||
@@ -29,11 +29,11 @@ import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog.Builder;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
@@ -48,6 +48,7 @@ import static org.briarproject.briar.android.util.UiUtils.getDialogIcon;
|
||||
public class NicknameFragment extends BaseFragment {
|
||||
|
||||
private static final String TAG = NicknameFragment.class.getName();
|
||||
private static final String SAVED_LINK = "savedLink";
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
@@ -67,6 +68,20 @@ public class NicknameFragment extends BaseFragment {
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
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
|
||||
@@ -74,14 +89,9 @@ public class NicknameFragment extends BaseFragment {
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
if (getActivity() == null || getContext() == null) return null;
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_nickname,
|
||||
container, false);
|
||||
|
||||
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
|
||||
.get(AddContactViewModel.class);
|
||||
|
||||
contactNameLayout = v.findViewById(R.id.contactNameLayout);
|
||||
contactNameInput = v.findViewById(R.id.contactNameInput);
|
||||
|
||||
@@ -93,6 +103,12 @@ public class NicknameFragment extends BaseFragment {
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString(SAVED_LINK, viewModel.getRemoteHandshakeLink());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getNicknameOrNull() {
|
||||
Editable text = contactNameInput.getText();
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
import java.util.logging.Formatter;
|
||||
@@ -17,6 +18,16 @@ import static java.util.Locale.US;
|
||||
@NotNullByDefault
|
||||
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 DateFormat dateFormat; // Locking: lock
|
||||
private final Date date; // Locking: lock
|
||||
|
||||
@@ -21,6 +21,10 @@ public class CachingLogHandler extends Handler {
|
||||
// Locking: lock
|
||||
private final Queue<LogRecord> recent = new LinkedList<>();
|
||||
|
||||
// package-private constructor
|
||||
CachingLogHandler() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
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.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
||||
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.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||
@@ -297,6 +298,12 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
finish();
|
||||
} else if (fm.getBackStackEntryCount() == 0
|
||||
&& 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
|
||||
* user sees is the same as the last fragment the user sees before
|
||||
|
||||
@@ -1,29 +1,40 @@
|
||||
package org.briarproject.briar.android.reporting;
|
||||
|
||||
import android.content.Context;
|
||||
import android.app.Application;
|
||||
import android.os.Process;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.android.logging.LogEncrypter;
|
||||
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.android.util.UiUtils.startDevReportActivity;
|
||||
|
||||
@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;
|
||||
|
||||
public BriarExceptionHandler(Context ctx) {
|
||||
this.ctx = ctx;
|
||||
this.appStartTime = System.currentTimeMillis();
|
||||
@Inject
|
||||
BriarExceptionHandler(Application app, LogEncrypter logEncrypter) {
|
||||
this.app = app;
|
||||
this.logEncrypter = logEncrypter;
|
||||
appStartTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
startDevReportActivity(ctx, CrashReportActivity.class, e, appStartTime);
|
||||
startDevReportActivity(app.getApplicationContext(),
|
||||
CrashReportActivity.class, e, appStartTime, logKey);
|
||||
Process.killProcess(Process.myPid());
|
||||
System.exit(10);
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.BuildConfig;
|
||||
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.ReportItem;
|
||||
import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo;
|
||||
@@ -41,8 +39,6 @@ import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@@ -74,8 +70,8 @@ class BriarReportCollector {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
public ReportData collectReportData(@Nullable Throwable t,
|
||||
long appStartTime) {
|
||||
ReportData collectReportData(@Nullable Throwable t, long appStartTime,
|
||||
String logs) {
|
||||
ReportData reportData = new ReportData()
|
||||
.add(getBasicInfo(t))
|
||||
.add(getDeviceInfo());
|
||||
@@ -86,7 +82,7 @@ class BriarReportCollector {
|
||||
.add(getStorage())
|
||||
.add(getConnectivity())
|
||||
.add(getBuildConfig())
|
||||
.add(getLogcat())
|
||||
.add(getLogcat(logs))
|
||||
.add(getDeviceFeatures());
|
||||
}
|
||||
|
||||
@@ -313,15 +309,8 @@ class BriarReportCollector {
|
||||
buildConfig);
|
||||
}
|
||||
|
||||
private ReportItem getLogcat() {
|
||||
BriarApplication app = (BriarApplication) ctx.getApplicationContext();
|
||||
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 getLogcat(String logs) {
|
||||
return new ReportItem("Logcat", R.string.dev_report_logcat, logs);
|
||||
}
|
||||
|
||||
private ReportItem getDeviceFeatures() {
|
||||
|
||||
@@ -35,6 +35,7 @@ public class CrashReportActivity extends BaseActivity
|
||||
|
||||
public static final String EXTRA_THROWABLE = "throwable";
|
||||
public static final String EXTRA_APP_START_TIME = "appStartTime";
|
||||
public static final String EXTRA_APP_LOGCAT = "logcat";
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
@@ -56,7 +57,8 @@ public class CrashReportActivity extends BaseActivity
|
||||
Intent intent = getIntent();
|
||||
Throwable t = (Throwable) intent.getSerializableExtra(EXTRA_THROWABLE);
|
||||
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 -> {
|
||||
if (show) displayFragment(true);
|
||||
});
|
||||
|
||||
@@ -15,4 +15,8 @@ public abstract class DevReportModule {
|
||||
@ViewModelKey(ReportViewModel.class)
|
||||
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.util.AndroidUtils;
|
||||
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.viewmodel.LiveEvent;
|
||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||
@@ -19,6 +22,7 @@ import org.json.JSONException;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
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.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
import static org.briarproject.briar.android.logging.BriefLogFormatter.formatLog;
|
||||
|
||||
@NotNullByDefault
|
||||
public class ReportViewModel extends AndroidViewModel {
|
||||
class ReportViewModel extends AndroidViewModel {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ReportViewModel.class.getName());
|
||||
|
||||
private final CachingLogHandler logHandler;
|
||||
private final LogDecrypter logDecrypter;
|
||||
private final BriarReportCollector collector;
|
||||
private final DevReporter reporter;
|
||||
private final PluginManager pluginManager;
|
||||
@@ -58,18 +65,39 @@ public class ReportViewModel extends AndroidViewModel {
|
||||
private boolean isFeedback;
|
||||
|
||||
@Inject
|
||||
public ReportViewModel(@NonNull Application application,
|
||||
DevReporter reporter, PluginManager pluginManager) {
|
||||
ReportViewModel(@NonNull Application application,
|
||||
CachingLogHandler logHandler,
|
||||
LogDecrypter logDecrypter,
|
||||
DevReporter reporter,
|
||||
PluginManager pluginManager) {
|
||||
super(application);
|
||||
this.collector = new BriarReportCollector(application);
|
||||
collector = new BriarReportCollector(application);
|
||||
this.logHandler = logHandler;
|
||||
this.logDecrypter = logDecrypter;
|
||||
this.reporter = reporter;
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
void init(@Nullable Throwable t, long appStartTime) {
|
||||
void init(@Nullable Throwable t, long appStartTime,
|
||||
@Nullable byte[] logKey) {
|
||||
isFeedback = t == null;
|
||||
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);
|
||||
}).start();
|
||||
}
|
||||
@@ -110,8 +138,8 @@ public class ReportViewModel extends AndroidViewModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* The content of the report
|
||||
* that will be loaded after {@link #init(Throwable, long)} was called.
|
||||
* The content of the report that will be loaded after
|
||||
* {@link #init(Throwable, long, byte[])} was called.
|
||||
*/
|
||||
LiveData<ReportData> getReportData() {
|
||||
return reportData;
|
||||
|
||||
@@ -44,7 +44,6 @@ import javax.inject.Inject;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.text.TextUtilsCompat;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
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.System.DEFAULT_NOTIFICATION_URI;
|
||||
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.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
@@ -296,19 +294,27 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
Locale locale = Localizer.getLocaleFromTag(tag);
|
||||
if (locale == null)
|
||||
throw new IllegalStateException();
|
||||
// Exclude RTL locales on API < 17, they won't be laid out correctly
|
||||
if (SDK_INT < 17 && !isLeftToRight(locale)) {
|
||||
// Check if the locale is supported on this device
|
||||
if (!Localizer.isLocaleSupported(locale)) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Skipping RTL locale " + tag);
|
||||
LOG.info("Skipping unsupported locale " + tag);
|
||||
continue;
|
||||
}
|
||||
String 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;
|
||||
String nativeName;
|
||||
// Unknown languages won't be translated to their native name.
|
||||
if (locale.getLanguage().equals("ast")) {
|
||||
nativeName = "Asturianu";
|
||||
} else if (locale.getLanguage().equals("oc")) {
|
||||
nativeName = "Occitan";
|
||||
} 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
|
||||
entries.add("\u200E" + nativeName.substring(0, 1).toUpperCase()
|
||||
@@ -319,13 +325,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
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) {
|
||||
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.briar.BuildConfig.APPLICATION_ID;
|
||||
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_THROWABLE;
|
||||
|
||||
@@ -357,16 +358,17 @@ public class UiUtils {
|
||||
}
|
||||
|
||||
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,
|
||||
Class<? extends FragmentActivity> activity, @Nullable Throwable t,
|
||||
@Nullable Long appStartTime) {
|
||||
@Nullable Long appStartTime, @Nullable byte[] logKey) {
|
||||
final Intent dialogIntent = new Intent(ctx, activity);
|
||||
dialogIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||
dialogIntent.putExtra(EXTRA_THROWABLE, t);
|
||||
dialogIntent.putExtra(EXTRA_APP_START_TIME, appStartTime);
|
||||
dialogIntent.putExtra(EXTRA_APP_LOGCAT, logKey);
|
||||
ctx.startActivity(dialogIntent);
|
||||
}
|
||||
|
||||
|
||||
@@ -93,4 +93,7 @@ public interface AndroidNotificationManager {
|
||||
void blockAllBlogPostNotifications();
|
||||
|
||||
void unblockAllBlogPostNotifications();
|
||||
|
||||
void restartNotifications(boolean locked, boolean mayAlertAgain);
|
||||
|
||||
}
|
||||
|
||||
@@ -112,7 +112,6 @@
|
||||
android:textColor="@color/briar_primary"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="18sp"
|
||||
android:typeface="monospace"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/yourLinkIcon"
|
||||
|
||||
@@ -139,10 +139,10 @@
|
||||
<Space
|
||||
android:id="@+id/space"
|
||||
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_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHeight_default="wrap"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/contactNameLayout" />
|
||||
|
||||
@@ -173,4 +173,4 @@
|
||||
|
||||
</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