Skip to content

Commit

Permalink
Merge branch 'next' into integration/v6
Browse files Browse the repository at this point in the history
  • Loading branch information
lemnik committed Jul 5, 2023
2 parents 4b935f6 + aa5b60a commit fd2b155
Show file tree
Hide file tree
Showing 31 changed files with 470 additions and 77 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## TBD

### Enhancements

* Added `Session.apiKey` so that it can be changed in an `OnSessionCallback`
[#1855](https://github.com/bugsnag/bugsnag-android/pull/1855)

### Bug fixes

* Prevent rare app crash while migrating old `SharedPreferences` data from older versions of `bugsnag-android`
[#1860](https://github.com/bugsnag/bugsnag-android/pull/1860)

* Prevent free memory calculation from potentially crashing the app when `ActivityManager` cannot be reached.
[#1861](https://github.com/bugsnag/bugsnag-android/pull/1861)

## 5.30.0 (2023-05-11)

### Enhancements
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2023.0218.1)
multi_test (0.1.2)
nokogiri (1.15.1-arm64-darwin)
racc (~> 1.4)
nokogiri (1.15.1-x86_64-darwin)
racc (~> 1.4)
optimist (3.0.1)
Expand Down Expand Up @@ -121,6 +123,7 @@ GEM
rexml

PLATFORMS
arm64-darwin-22
x86_64-darwin-20

DEPENDENCIES
Expand Down
2 changes: 2 additions & 0 deletions bugsnag-android-core/api/bugsnag-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -573,11 +573,13 @@ public abstract interface class com/bugsnag/android/Plugin {
}

public final class com/bugsnag/android/Session : com/bugsnag/android/JsonStream$Streamable, com/bugsnag/android/UserAware {
public fun getApiKey ()Ljava/lang/String;
public fun getApp ()Lcom/bugsnag/android/App;
public fun getDevice ()Lcom/bugsnag/android/Device;
public fun getId ()Ljava/lang/String;
public fun getStartedAt ()Ljava/util/Date;
public fun getUser ()Lcom/bugsnag/android/User;
public fun setApiKey (Ljava/lang/String;)V
public fun setId (Ljava/lang/String;)V
public fun setStartedAt (Ljava/util/Date;)V
public fun setUser (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
Expand Down
3 changes: 3 additions & 0 deletions bugsnag-android-core/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,15 @@
<ID>SwallowedException:BugsnagEventMapper.kt$BugsnagEventMapper$catch (pe: IllegalArgumentException) { ndkDateFormatHolder.get()!!.parse(this) ?: throw IllegalArgumentException("cannot parse date $this") }</ID>
<ID>SwallowedException:ConnectivityCompat.kt$ConnectivityLegacy$catch (e: NullPointerException) { // in some rare cases we get a remote NullPointerException via Parcel.readException null }</ID>
<ID>SwallowedException:ContextExtensions.kt$catch (exc: RuntimeException) { null }</ID>
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (e: Throwable) { null }</ID>
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (e: Throwable) { return null }</ID>
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exc: Exception) { false }</ID>
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exception: Exception) { logger.w("Could not get battery status") }</ID>
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exception: Exception) { logger.w("Could not get locationStatus") }</ID>
<ID>SwallowedException:DeviceIdFilePersistence.kt$DeviceIdFilePersistence$catch (exc: OverlappingFileLockException) { Thread.sleep(FILE_LOCK_WAIT_MS) }</ID>
<ID>SwallowedException:JsonHelperTest.kt$JsonHelperTest$catch (e: IllegalArgumentException) { didThrow = true }</ID>
<ID>SwallowedException:PluginClient.kt$PluginClient$catch (exc: ClassNotFoundException) { if (isWarningEnabled) { logger.d("Plugin '$clz' is not on the classpath - functionality will not be enabled.") } null }</ID>
<ID>SwallowedException:SharedPrefMigrator.kt$SharedPrefMigrator$catch (e: RuntimeException) { null }</ID>
<ID>ThrowsCount:JsonHelper.kt$JsonHelper$ fun jsonToLong(value: Any?): Long?</ID>
<ID>TooManyFunctions:ConfigInternal.kt$ConfigInternal : CallbackAwareMetadataAwareUserAwareFeatureFlagAware</ID>
<ID>TooManyFunctions:DeviceDataCollector.kt$DeviceDataCollector</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ static Client generateClient() {

static Session generateSession() {
return new Session("test", new Date(), new User(), false,
new Notifier(), NoopLogger.INSTANCE);
new Notifier(), NoopLogger.INSTANCE, "TEST APIKEY");
}

static Event generateEvent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void writeLegacyFile(Session session) throws IOException {
*/
@Test
public void testSessionFromFile() throws Exception {
Session payload = new Session(file, new Notifier(), NoopLogger.INSTANCE);
Session payload = new Session(file, new Notifier(), NoopLogger.INSTANCE, "TEST APIKEY");
payload.setApp(generateApp());
payload.setDevice(generateDevice());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void testSessionFromFile() throws Exception {
session.toStream(stream);
out.flush();

Session payload = new Session(file, new Notifier(), NoopLogger.INSTANCE);
Session payload = new Session(file, new Notifier(), NoopLogger.INSTANCE, "TEST APIKEY");
JSONObject obj = BugsnagTestUtils.streamableToJson(payload);
JSONObject rootNode = obj.getJSONArray("sessions").getJSONObject(0);
assertNotNull(rootNode);
Expand All @@ -71,7 +71,15 @@ public void testSessionFromFile() throws Exception {

@Test
public void testAutoCapturedOverride() throws Exception {
session = new Session("id", new Date(), null, false, new Notifier(), NoopLogger.INSTANCE);
session = new Session(
"id",
new Date(),
null,
false,
new Notifier(),
NoopLogger.INSTANCE,
"TEST APIKEY"
);
assertFalse(session.isAutoCaptured());
session.setAutoCaptured(true);
assertTrue(session.isAutoCaptured());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ internal class BugsnagEventMapper(
// populate session
val sessionMap = map["session"] as? Map<String, Any?>
sessionMap?.let {
event.session = Session(it, logger)
event.session = Session(it, logger, apiKey)
}

// populate threads
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,21 +242,26 @@ internal class DeviceDataCollector(
/**
* Get the amount of memory remaining on the device
*/
private fun calculateFreeMemory(): Long? {
fun calculateFreeMemory(): Long? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
val freeMemory = appContext.getActivityManager()
?.let { am -> ActivityManager.MemoryInfo().also { am.getMemoryInfo(it) } }
?.availMem

if (freeMemory != null) {
return freeMemory
try {
val freeMemory = appContext.getActivityManager()
?.let { am -> ActivityManager.MemoryInfo().also { am.getMemoryInfo(it) } }
?.availMem
if (freeMemory != null) {
return freeMemory
}
} catch (e: Throwable) {
return null
}
}

return runCatching {
return try {
@Suppress("PrivateApi")
AndroidProcess::class.java.getDeclaredMethod("getFreeMemory").invoke(null) as Long?
}.getOrNull()
} catch (e: Throwable) {
null
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,19 @@ public final class Session implements JsonStream.Streamable, UserAware {
private final AtomicBoolean tracked = new AtomicBoolean(false);
final AtomicBoolean isPaused = new AtomicBoolean(false);

private String apiKey;

static Session copySession(Session session) {
Session copy = new Session(session.id, session.startedAt, session.user,
session.unhandledCount.get(), session.handledCount.get(), session.notifier,
session.logger);
session.logger, session.getApiKey());
copy.tracked.set(session.tracked.get());
copy.autoCaptured.set(session.isAutoCaptured());
return copy;
}

Session(Map<String, Object> map, Logger logger) {
this(null, null, logger);
Session(Map<String, Object> map, Logger logger, String apiKey) {
this(null, null, logger, apiKey);
setId((String) map.get("id"));

String timestamp = (String) map.get("startedAt");
Expand All @@ -61,25 +63,28 @@ static Session copySession(Session session) {
}

Session(String id, Date startedAt, User user, boolean autoCaptured,
Notifier notifier, Logger logger) {
this(null, notifier, logger);
Notifier notifier, Logger logger, String apiKey) {
this(null, notifier, logger, apiKey);
this.id = id;
this.startedAt = new Date(startedAt.getTime());
this.user = user;
this.autoCaptured.set(autoCaptured);
this.apiKey = apiKey;
}

Session(String id, Date startedAt, User user, int unhandledCount, int handledCount,
Notifier notifier, Logger logger) {
this(id, startedAt, user, false, notifier, logger);
Notifier notifier, Logger logger, String apiKey) {
this(id, startedAt, user, false, notifier, logger, apiKey);
this.unhandledCount.set(unhandledCount);
this.handledCount.set(handledCount);
this.tracked.set(true);
this.apiKey = apiKey;
}

Session(File file, Notifier notifier, Logger logger) {
Session(File file, Notifier notifier, Logger logger, String apiKey) {
this.file = file;
this.logger = logger;
this.apiKey = SessionFilenameInfo.findApiKeyInFilename(file, apiKey);
if (notifier != null) {
Notifier copy = new Notifier(notifier.getName(),
notifier.getVersion(), notifier.getUrl());
Expand Down Expand Up @@ -261,4 +266,25 @@ void serializeSessionInfo(@NonNull JsonStream writer) throws IOException {
writer.name("user").value(user);
writer.endObject();
}

/**
* The API key used for session sent to Bugsnag. Even though the API key is set when Bugsnag
* is initialized, you may choose to send certain sessions to a different Bugsnag project.
*/
public void setApiKey(@NonNull String apiKey) {
if (apiKey != null) {
this.apiKey = apiKey;
} else {
logNull("apiKey");
}
}

/**
* The API key used for session sent to Bugsnag. Even though the API key is set when Bugsnag
* is initialized, you may choose to send certain sessions to a different Bugsnag project.
*/
@NonNull
public String getApiKey() {
return apiKey;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bugsnag.android

import com.bugsnag.android.internal.ImmutableConfig
import java.io.File
import java.util.UUID

Expand All @@ -11,12 +12,13 @@ import java.util.UUID
* timestamp - to sort error reports by time of capture
*/
internal data class SessionFilenameInfo(
var apiKey: String,
val timestamp: Long,
val uuid: String,
val uuid: String
) {

fun encode(): String {
return toFilename(timestamp, uuid)
return toFilename(apiKey, timestamp, uuid)
}

internal companion object {
Expand All @@ -27,29 +29,63 @@ internal data class SessionFilenameInfo(
* Generates a filename for the session in the format
* "[UUID][timestamp]_v2.json"
*/
fun toFilename(timestamp: Long, uuid: String): String {
return "${uuid}${timestamp}_v2.json"
fun toFilename(apiKey: String, timestamp: Long, uuid: String): String {
return "${apiKey}_${uuid}${timestamp}_v3.json"
}

@JvmStatic
fun defaultFilename(): String {
return toFilename(System.currentTimeMillis(), UUID.randomUUID().toString())
fun defaultFilename(
obj: Any,
config: ImmutableConfig
): SessionFilenameInfo {
val sanitizedApiKey = when (obj) {
is Session -> obj.apiKey
else -> config.apiKey
}

return SessionFilenameInfo(
sanitizedApiKey,
System.currentTimeMillis(),
UUID.randomUUID().toString()
)
}

fun fromFile(file: File): SessionFilenameInfo {
fun fromFile(file: File, defaultApiKey: String): SessionFilenameInfo {
return SessionFilenameInfo(
findApiKeyInFilename(file, defaultApiKey),
findTimestampInFilename(file),
findUuidInFilename(file)
)
}

private fun findUuidInFilename(file: File): String {
return file.name.substring(0, uuidLength - 1)
@JvmStatic
fun findUuidInFilename(file: File): String {
var fileName = file.name
if (isFileV3(file)) {
fileName = file.name.substringAfter('_')
}
return fileName.takeIf { it.length >= uuidLength }?.take(uuidLength) ?: ""
}

@JvmStatic
fun findTimestampInFilename(file: File): Long {
return file.name.substring(uuidLength, file.name.indexOf("_")).toLongOrNull() ?: -1
var fileName = file.name
if (isFileV3(file)) {
fileName = file.name.substringAfter('_')
}
return fileName.drop(findUuidInFilename(file).length)
.substringBefore('_')
.toLongOrNull() ?: -1
}

@JvmStatic
fun findApiKeyInFilename(file: File?, defaultApiKey: String): String {
if (file == null || !isFileV3(file)) {
return defaultApiKey
}
return file.name.substringBefore('_').takeUnless { it.isEmpty() } ?: defaultApiKey
}

internal fun isFileV3(file: File): Boolean = file.name.endsWith("_v3.json")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
class SessionStore extends FileStore {

private final ImmutableConfig config;
static final Comparator<File> SESSION_COMPARATOR = new Comparator<File>() {
@Override
public int compare(File lhs, File rhs) {
Expand All @@ -43,12 +44,15 @@ public int compare(File lhs, File rhs) {
SESSION_COMPARATOR,
logger,
delegate);
this.config = config;
}

@NonNull
@Override
String getFilename(Object object) {
return SessionFilenameInfo.defaultFilename();
SessionFilenameInfo sessionInfo
= SessionFilenameInfo.defaultFilename(object, config);
return sessionInfo.encode();
}

public boolean isTooOld(File file) {
Expand Down
Loading

0 comments on commit fd2b155

Please sign in to comment.