Skip to content

Commit

Permalink
Merge branch 'release/5.202.0' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
Dax the Deployer committed May 27, 2024
2 parents 18d8eeb + ca3c07d commit 716cbef
Show file tree
Hide file tree
Showing 221 changed files with 6,172 additions and 784 deletions.
2 changes: 2 additions & 0 deletions anrs/anrs-impl/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dependencies {
implementation project(':verified-installation-api')
implementation project(':library-loader-api')
implementation project(':feature-toggles-api')
implementation project(':data-store-api')

implementation AndroidX.core.ktx
implementation KotlinX.coroutines.core
Expand All @@ -48,6 +49,7 @@ dependencies {
implementation AndroidX.room.rxJava2

testImplementation project(':common-test')
implementation project(':data-store-test')
testImplementation Testing.junit4
testImplementation AndroidX.archCore.testing
testImplementation AndroidX.test.ext.junit
Expand Down
72 changes: 1 addition & 71 deletions anrs/anrs-impl/src/main/cpp/jni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,6 @@

///////////////////////////////////////////////////////////////////////////

static JavaVM *JVM = NULL;
jclass clsCrash;
jobject CLASS_JVM_CRASH = NULL;


static jobject jniGlobalRef(JNIEnv *env, jobject cls);
static jclass jniFindClass(JNIEnv *env, const char *name);
static jmethodID jniGetMethodID(JNIEnv *env, jclass cls, const char *name, const char *signature);

int loglevel = 0;
char appVersion[256];
char pname[256];
Expand All @@ -35,63 +26,6 @@ void __platform_log_print(int prio, const char *tag, const char *fmt, ...) {
va_end(argptr);
}

///////////////////////////////////////////////////////////////////////////
// JNI utils
///////////////////////////////////////////////////////////////////////////

static jobject jniGlobalRef(JNIEnv *env, jobject cls) {
jobject gcls = env->NewGlobalRef(cls);
if (gcls == NULL)
log_print(ANDROID_LOG_ERROR, "Global ref failed (out of memory?)");
return gcls;
}

static jclass jniFindClass(JNIEnv *env, const char *name) {
jclass cls = env->FindClass(name);
if (cls == NULL)
log_print(ANDROID_LOG_ERROR, "Class %s not found", name);
return cls;
}

static jmethodID jniGetMethodID(JNIEnv *env, jclass cls, const char *name, const char *signature) {
jmethodID method = env->GetMethodID(cls, name, signature);
if (method == NULL) {
log_print(ANDROID_LOG_ERROR, "Method %s %s not found", name, signature);
}
return method;
}

///////////////////////////////////////////////////////////////////////////
// JNI lifecycle
///////////////////////////////////////////////////////////////////////////

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
if ((vm)->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
log_print(ANDROID_LOG_INFO, "JNI load GetEnv failed");
return -1;
}

jint rs = env->GetJavaVM(&JVM);
if (rs != JNI_OK) {
log_print(ANDROID_LOG_ERROR, "Could not get JVM");
return -1;
}

return JNI_VERSION_1_6;
}

void JNI_OnUnload(JavaVM *vm, void *reserved) {
log_print(ANDROID_LOG_INFO, "JNI unload");

JNIEnv *env;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
log_print(ANDROID_LOG_INFO, "JNI load GetEnv failed");
else {
env->DeleteGlobalRef(clsCrash);
}
}

///////////////////////////////////////////////////////////////////////////
// native<>JVM interface
///////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -129,13 +63,9 @@ Java_com_duckduckgo_app_anr_ndk_NativeCrashInit_jni_1register_1sighandler(
// get and set isCustomTabs
isCustomTab = customtab_;

clsCrash = env->GetObjectClass(instance);
const char *emptyParamVoidSig = "()V";
CLASS_JVM_CRASH = env->NewGlobalRef(instance);

send_crash_handle_init_pixel();

log_print(ANDROID_LOG_ERROR, "Native crash handler successfully initialized.");
log_print(ANDROID_LOG_ERROR, "Native crash handler successfully initialized on %s.", pname);
}

extern "C" JNIEXPORT void JNICALL
Expand Down
4 changes: 2 additions & 2 deletions anrs/anrs-impl/src/main/cpp/pixel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ void send_crash_pixel() {
char path[2048];
sprintf(path, "/t/m_app_native_crash_android?appVersion=%s&pn=%s&customTab=%s", appVersion, pname, isCustomTab ? "true" : "false");
send_request(host, path);
log_print(ANDROID_LOG_ERROR, "Native crash pixel sent");
log_print(ANDROID_LOG_ERROR, "Native crash pixel sent on %s", pname);
}

void send_crash_handle_init_pixel() {
const char* host = "improving.duckduckgo.com";
char path[2048];
sprintf(path, "/t/m_app_register_native_crash_handler_android?appVersion=%s&pn=%s&customTab=%s", appVersion, pname, isCustomTab ? "true" : "false");
send_request(host, path);
log_print(ANDROID_LOG_ERROR, "Native crash handler init pixel sent");
log_print(ANDROID_LOG_ERROR, "Native crash handler init pixel sent on %s", pname);
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ class AnrOfflinePixelSender @Inject constructor(
}

companion object {
const val ANR_STACKTRACE = "stackTrace"
const val ANR_WEBVIEW_VERSION = "webView"
const val ANR_CUSTOM_TAB = "customTab"
private const val ANR_STACKTRACE = "stackTrace"
private const val ANR_WEBVIEW_VERSION = "webView"
private const val ANR_CUSTOM_TAB = "customTab"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,29 @@

package com.duckduckgo.app.anr.ndk

import android.content.SharedPreferences
import androidx.core.content.edit
import com.duckduckgo.anvil.annotations.ContributesRemoteFeature
import com.duckduckgo.app.di.AppCoroutineScope
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.data.store.api.SharedPreferencesProvider
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.feature.toggles.api.RemoteFeatureStoreNamed
import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.feature.toggles.api.Toggle.State
import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dagger.SingleInstanceIn
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

@ContributesRemoteFeature(
scope = AppScope::class,
featureName = "androidNativeCrash",
toggleStore = NativeCrashFeatureMultiProcessStore::class,
)
interface NativeCrashFeature {
@Toggle.DefaultValue(true)
Expand All @@ -31,4 +47,42 @@ interface NativeCrashFeature {

@Toggle.DefaultValue(true)
fun nativeCrashHandling(): Toggle

@Toggle.DefaultValue(true)
fun nativeCrashHandlingSecondaryProcess(): Toggle
}

@ContributesBinding(AppScope::class)
@SingleInstanceIn(AppScope::class)
@RemoteFeatureStoreNamed(NativeCrashFeature::class)
class NativeCrashFeatureMultiProcessStore @Inject constructor(
@AppCoroutineScope private val coroutineScope: CoroutineScope,
private val dispatcherProvider: DispatcherProvider,
private val sharedPreferencesProvider: SharedPreferencesProvider,
moshi: Moshi,
) : Toggle.Store {

private val preferences: SharedPreferences by lazy {
sharedPreferencesProvider.getSharedPreferences(PREFS_FILENAME, multiprocess = true, migrate = false)
}

private val stateAdapter: JsonAdapter<State> by lazy {
moshi.newBuilder().add(KotlinJsonAdapterFactory()).build().adapter(State::class.java)
}

override fun set(key: String, state: State) {
coroutineScope.launch(dispatcherProvider.io()) {
preferences.edit(commit = true) { putString(key, stateAdapter.toJson(state)) }
}
}

override fun get(key: String): State? {
return preferences.getString(key, null)?.let {
stateAdapter.fromJson(it)
}
}

companion object {
private const val PREFS_FILENAME = "com.duckduckgo.app.androidNativeCrash.feature.v1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.duckduckgo.app.browser.customtabs.CustomTabDetector
import com.duckduckgo.app.di.AppCoroutineScope
import com.duckduckgo.app.di.IsMainProcess
import com.duckduckgo.app.lifecycle.MainProcessLifecycleObserver
import com.duckduckgo.app.lifecycle.VpnProcessLifecycleObserver
import com.duckduckgo.appbuildconfig.api.AppBuildConfig
import com.duckduckgo.appbuildconfig.api.isInternalBuild
import com.duckduckgo.common.utils.DispatcherProvider
Expand All @@ -42,6 +43,10 @@ import logcat.logcat
scope = AppScope::class,
boundType = MainProcessLifecycleObserver::class,
)
@ContributesMultibinding(
scope = AppScope::class,
boundType = VpnProcessLifecycleObserver::class,
)
@SingleInstanceIn(AppScope::class)
class NativeCrashInit @Inject constructor(
context: Context,
Expand All @@ -51,7 +56,7 @@ class NativeCrashInit @Inject constructor(
private val nativeCrashFeature: NativeCrashFeature,
private val dispatcherProvider: DispatcherProvider,
@AppCoroutineScope private val coroutineScope: CoroutineScope,
) : MainProcessLifecycleObserver {
) : MainProcessLifecycleObserver, VpnProcessLifecycleObserver {

private val isCustomTab: Boolean by lazy { customTabDetector.isCustomTab() }
private val processName: String by lazy { if (isMainProcess) "main" else "vpn" }
Expand All @@ -76,9 +81,20 @@ class NativeCrashInit @Inject constructor(
}
}

override fun onVpnProcessCreated() {
if (!isMainProcess) {
coroutineScope.launch {
jniRegisterNativeSignalHandler()
}
} else {
logcat(ERROR) { "ndk-crash: onCreate wrongly called in the main process" }
}
}

private suspend fun jniRegisterNativeSignalHandler() = withContext(dispatcherProvider.io()) {
runCatching {
if (!nativeCrashFeature.nativeCrashHandling().isEnabled()) return@withContext
if (isMainProcess && !nativeCrashFeature.nativeCrashHandling().isEnabled()) return@withContext
if (!isMainProcess && !nativeCrashFeature.nativeCrashHandlingSecondaryProcess().isEnabled()) return@withContext

val logLevel = if (appBuildConfig.isDebug || appBuildConfig.isInternalBuild()) {
Log.VERBOSE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.duckduckgo.app.anr.ndk

import com.duckduckgo.common.test.CoroutineTestRule
import com.duckduckgo.data.store.api.FakeSharedPreferencesProvider
import com.duckduckgo.feature.toggles.api.Toggle
import com.squareup.moshi.Moshi
import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Rule
import org.junit.Test

class NativeCrashFeatureMultiProcessStoreTest {

@get:Rule var coroutineRule = CoroutineTestRule()

private val store = NativeCrashFeatureMultiProcessStore(
coroutineRule.testScope,
coroutineRule.testDispatcherProvider,
FakeSharedPreferencesProvider(),
Moshi.Builder().build(),
)

@Test
fun `test set value`() = runTest {
val expected = Toggle.State(enable = true)
store.set("key", expected)

Assert.assertEquals(expected, store.get("key"))
}

@Test
fun `test get missing value`() = runTest {
Assert.assertNull(store.get("key"))
}

@Test
fun `test get when value is not present`() {
val expected = Toggle.State(enable = true)
store.set("key", expected)

Assert.assertNull(store.get("wrong key"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.anvil.annotations

import kotlin.reflect.KClass

/**
* Anvil annotation to contribute plugins into an Active Plugin Point.
* Active plugins are also guarded by remote feature flags
*
* This annotation is the counterpart of [ContributesActivePluginPoint]
*
* Usage:
* ```kotlin
* @ContributesActivePlugin(SomeDaggerScope::class)
* class MyPluginImpl : MyPlugin {
*
* }
*
* interface MyPlugin : ActivePlugin {...}
* ```
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class ContributesActivePlugin(
/** The scope in which to include this contributed PluginPoint */
val scope: KClass<*>,

/**
* This is the type of the plugin the annotated class is extending from
* This is a required member to help the code generation.
*/
val boundType: KClass<*>,

/**
* The default value of remote feature flag.
* Default is true (ie. enabled)
*/
val defaultActiveValue: Boolean = true,

/**
* The priority for the plugin.
* Lower priority values mean the associated plugin comes first in the list of plugins.
*
* This is equivalent to the [PriorityKey] annotation we use with [ContributesMultibinding] for normal plugins.
* The [ContributesActivePlugin] coalesce both
*/
val priority: Int = 0,
)
Loading

0 comments on commit 716cbef

Please sign in to comment.