Skip to content

Commit

Permalink
Cherry pick commit c6a11bd995889fd5b8ef910959dce1206542cf21,improve f…
Browse files Browse the repository at this point in the history
…irstLaunchAsUpdate detection rule

commit_hash:237cd2174f6d882201aaaa68225df4500a03e656
  • Loading branch information
alexklints committed Oct 8, 2024
1 parent 93751ac commit 818ff2b
Show file tree
Hide file tree
Showing 17 changed files with 135 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
package io.appmetrica.analytics.impl

import android.content.Context
import io.appmetrica.analytics.AppMetricaConfig
import io.appmetrica.analytics.impl.db.preferences.PreferencesClientDbStorage
import io.appmetrica.analytics.logger.appmetrica.internal.DebugLogger

internal class AppMetricaConfigForAnonymousActivationProvider(
private val context: Context,
private val preferences: PreferencesClientDbStorage
) {

private val tag = "[AppMetricaConfigForAnonymousActivationProvider]"

private val defaultAnonymousConfigProvider = AppMetricaDefaultAnonymousConfigProvider()

val config: AppMetricaConfig
get() = preferences.appMetricaConfig ?: defaultAnonymousConfigProvider.getConfig(context)
get() {
val configFromPreferences = preferences.appMetricaConfig
if (configFromPreferences != null) {
DebugLogger.info(tag, "Choose saved config")
return configFromPreferences
}
DebugLogger.info(tag, "Choose default anonymous config")
return defaultAnonymousConfigProvider.getConfig()
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
package io.appmetrica.analytics.impl

import android.content.Context
import io.appmetrica.analytics.AppMetricaConfig
import io.appmetrica.analytics.impl.utils.FirstLaunchDetector
import io.appmetrica.analytics.impl.utils.MainProcessDetector
import io.appmetrica.analytics.logger.appmetrica.internal.DebugLogger

class AppMetricaDefaultAnonymousConfigProvider {

private val tag = "[AppMetricaDefaultAnonymousConfigProvider]"

private val anonymousApiKey: String = "629a824d-c717-4ba5-bc0f-3f3968554d01"
private val mainProcessDetector: MainProcessDetector = ClientServiceLocator.getInstance().mainProcessDetector
private val firstLaunchDetector = FirstLaunchDetector()
private val firstLaunchDetector = ClientServiceLocator.getInstance().firstLaunchDetector

fun getConfig(context: Context): AppMetricaConfig {
fun getConfig(): AppMetricaConfig {
val builder = AppMetricaConfig.newConfigBuilder(anonymousApiKey)
if (mainProcessDetector.isMainProcess && firstLaunchDetector.detectNotFirstLaunch(context)) {
if (mainProcessDetector.isMainProcess && firstLaunchDetector.isNotFirstLaunch()) {
DebugLogger.info(tag, "Add handleFirstActivationAsUpdate value to config")
builder.handleFirstActivationAsUpdate(true)
} else {
DebugLogger.info(tag, "Use default config")
}
return builder.build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

public class AppMetricaFacade implements IReporterFactoryProvider {

private static final String TAG = "[AppMetricaImpl]";
private static final String TAG = "[AppMetricaFacade]";

@NonNull
private final Context mContext;
Expand Down Expand Up @@ -55,11 +55,14 @@ public AppMetricaFacade(@NonNull final Context context) {
}

public void init(boolean async) {
ClientServiceLocator clientServiceLocator = ClientServiceLocator.getInstance();
Executor executorForInit = async
? ClientServiceLocator.getInstance().getClientExecutorProvider().getDefaultExecutor()
? clientServiceLocator.getClientExecutorProvider().getDefaultExecutor()
: new BlockingExecutor();

executorForInit.execute(() -> {
DebugLogger.INSTANCE.info(TAG, "Init first launch detector");
clientServiceLocator.getFirstLaunchDetector().init(mContext);
DebugLogger.INSTANCE.info(TAG, "Check client migration");
new ClientMigrationManager(mContext).checkMigration(mContext);
DebugLogger.INSTANCE.info(TAG, "Warm up uuid");
Expand Down Expand Up @@ -239,6 +242,7 @@ public void requestStartupParams(
@NonNull final StartupParamsCallback callback,
@NonNull final List<String> params
) {
DebugLogger.INSTANCE.info(TAG, "requestStartupParams");
getImpl().requestStartupParams(callback, params);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ internal class AppMetricaImpl @WorkerThread internal constructor(
startupHelper
)
sessionsTrackingManager = clientServiceLocator.sessionsTrackingManager
anonymousConfigProvider = AppMetricaConfigForAnonymousActivationProvider(context, clientPreferences)
anonymousConfigProvider = AppMetricaConfigForAnonymousActivationProvider(clientPreferences)
}

@WorkerThread
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io.appmetrica.analytics.impl.reporter.ReporterLifecycleListener;
import io.appmetrica.analytics.impl.startup.uuid.MultiProcessSafeUuidProvider;
import io.appmetrica.analytics.impl.startup.uuid.UuidFromClientPreferencesImporter;
import io.appmetrica.analytics.impl.utils.FirstLaunchDetector;
import io.appmetrica.analytics.impl.utils.MainProcessDetector;
import io.appmetrica.analytics.impl.utils.ProcessDetector;
import io.appmetrica.analytics.impl.utils.executors.ClientExecutorProvider;
Expand Down Expand Up @@ -72,6 +73,8 @@ public static ClientServiceLocator getInstance() {
private ScreenInfoRetriever screenInfoRetriever;
@NonNull
private final AppMetricaFacadeProvider appMetricaFacadeProvider = new AppMetricaFacadeProvider();
@NonNull
private final FirstLaunchDetector firstLaunchDetector = new FirstLaunchDetector();

private ClientServiceLocator() {
this(new MainProcessDetector(), new ActivityLifecycleManager(), new ClientExecutorProvider());
Expand Down Expand Up @@ -264,6 +267,11 @@ public AppMetricaFacadeProvider getAppMetricaFacadeProvider() {
return appMetricaFacadeProvider;
}

@NonNull
public FirstLaunchDetector getFirstLaunchDetector() {
return firstLaunchDetector;
}

@VisibleForTesting(otherwise = VisibleForTesting.NONE)
public static void setInstance(@Nullable ClientServiceLocator clientServiceLocator) {
sHolder = clientServiceLocator;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import io.appmetrica.analytics.impl.ClientServiceLocator
import io.appmetrica.analytics.impl.events.LibraryEventConstructor
import io.appmetrica.analytics.impl.proxy.synchronous.LibraryAdapterSynchronousStageExecutor
import io.appmetrica.analytics.impl.proxy.validation.LibraryAdapterBarrier
import io.appmetrica.analytics.logger.appmetrica.internal.DebugLogger

class AppMetricaLibraryAdapterProxy {

private val tag = "[AppMetricaLibraryAdapterProxy]"

private val provider: AppMetricaFacadeProvider =
ClientServiceLocator.getInstance().appMetricaFacadeProvider
private val barrier = LibraryAdapterBarrier(provider)
Expand All @@ -19,10 +22,12 @@ class AppMetricaLibraryAdapterProxy {
ClientServiceLocator.getInstance().clientExecutorProvider.defaultExecutor

fun activate(context: Context) {
DebugLogger.info(tag, "Activate")
barrier.activate(context)
val applicationContext = context.applicationContext
synchronousStageExecutor.activate(applicationContext)
executor.execute {
DebugLogger.info(tag, "Activate full")
provider.getInitializedImpl(applicationContext).activateFull()
}
provider.markActivated()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ public void requestStartupParams(
@NonNull final StartupParamsCallback callback,
@NonNull final List<String> params
) {
DebugLogger.INSTANCE.info(TAG, "requestStartupParams for keys: %s", params);
barrier.requestStartupParams(context, callback, params);
synchronousStageExecutor.requestStartupParams(context.getApplicationContext(), callback, params);
getExecutor().execute(new Runnable() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ import io.appmetrica.analytics.impl.WebViewJsInterfaceHandler
import io.appmetrica.analytics.impl.crash.AppMetricaThrowable
import io.appmetrica.analytics.impl.proxy.AppMetricaFacadeProvider
import io.appmetrica.analytics.impl.proxy.AppMetricaProxy
import io.appmetrica.analytics.impl.utils.FirstLaunchDetector
import io.appmetrica.analytics.profile.UserProfile

class SynchronousStageExecutor @VisibleForTesting constructor(
private val provider: AppMetricaFacadeProvider,
private val webViewJsInterfaceHandler: WebViewJsInterfaceHandler,
private val activityLifecycleManager: ActivityLifecycleManager,
private val sessionsTrackingManager: SessionsTrackingManager,
private val contextAppearedListener: ContextAppearedListener
private val contextAppearedListener: ContextAppearedListener,
private val firstLaunchDetector: FirstLaunchDetector
) {

constructor(
Expand All @@ -48,7 +50,8 @@ class SynchronousStageExecutor @VisibleForTesting constructor(
webViewJsInterfaceHandler,
ClientServiceLocator.getInstance().activityLifecycleManager,
ClientServiceLocator.getInstance().sessionsTrackingManager,
ClientServiceLocator.getInstance().contextAppearedListener
ClientServiceLocator.getInstance().contextAppearedListener,
ClientServiceLocator.getInstance().firstLaunchDetector
)

fun putAppEnvironmentValue(
Expand Down Expand Up @@ -196,6 +199,7 @@ class SynchronousStageExecutor @VisibleForTesting constructor(

fun getUuid(context: Context) {
contextAppearedListener.onProbablyAppeared(context)
firstLaunchDetector.init(context)
}

fun registerAnrListener(listener: AnrListener) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,27 @@ class FirstLaunchDetector {

private val tag = "[FirstLaunchDetector]"

fun detectNotFirstLaunch(context: Context): Boolean = try {
@Volatile
private var notAFirstLaunch: Boolean? = null

fun init(context: Context) {
DebugLogger.info(tag, "init")
var localValue = notAFirstLaunch
if (localValue == null) {
synchronized(this) {
localValue = notAFirstLaunch
if (localValue == null) {
localValue = detectNotFirstLaunch(context)
DebugLogger.info(tag, "detected not a first launch value: $localValue")
notAFirstLaunch = localValue
}
}
}
}

fun isNotFirstLaunch(): Boolean = notAFirstLaunch == true

private fun detectNotFirstLaunch(context: Context): Boolean = try {
val legacyExists = FileUtils.getFileFromAppStorage(context, FileConstants.UUID_FILE_NAME)?.exists() ?: false
val actualExists = FileUtils.getFileFromSdkStorage(context, FileConstants.UUID_FILE_NAME)?.exists() ?: false
legacyExists || actualExists
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,18 @@ import org.mockito.kotlin.whenever

class AppMetricaConfigForAnonymousActivationProviderTest : CommonTest() {

private val context: Context = mock()
private val configFromPreferences: AppMetricaConfig = mock()
private val defaultConfig: AppMetricaConfig = mock()
private val preferences: PreferencesClientDbStorage = mock()

@get:Rule
val defaultAnonymousConfigProviderMockedConstructionRule =
constructionRule<AppMetricaDefaultAnonymousConfigProvider> {
on { getConfig(context) } doReturn defaultConfig
on { getConfig() } doReturn defaultConfig
}

private val configProvider: AppMetricaConfigForAnonymousActivationProvider by setUp {
AppMetricaConfigForAnonymousActivationProvider(context, preferences)
AppMetricaConfigForAnonymousActivationProvider(preferences)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,20 @@ import io.appmetrica.analytics.impl.utils.FirstLaunchDetector
import io.appmetrica.analytics.impl.utils.MainProcessDetector
import io.appmetrica.analytics.testutils.ClientServiceLocatorRule
import io.appmetrica.analytics.testutils.CommonTest
import io.appmetrica.analytics.testutils.constructionRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.mock
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.whenever

class AppMetricaDefaultAnonymousConfigProviderTest : CommonTest() {

private val context: Context = mock()

@get:Rule
val clientServiceLocatorRule = ClientServiceLocatorRule()

private lateinit var mainProcessDetector: MainProcessDetector

@get:Rule
val firstLaunchDetectorMockedConstructionRule = constructionRule<FirstLaunchDetector> {
on { detectNotFirstLaunch(context) } doReturn false
}

private val firstLaunchDetector: FirstLaunchDetector by firstLaunchDetectorMockedConstructionRule
private lateinit var firstLaunchDetector: FirstLaunchDetector

private val apiKey = "629a824d-c717-4ba5-bc0f-3f3968554d01"

Expand All @@ -39,30 +29,31 @@ class AppMetricaDefaultAnonymousConfigProviderTest : CommonTest() {

@Before
fun setUp() {
firstLaunchDetector = ClientServiceLocator.getInstance().firstLaunchDetector
mainProcessDetector = ClientServiceLocator.getInstance().mainProcessDetector
whenever(mainProcessDetector.isMainProcess).thenReturn(true)
}

@Test
fun getConfig() {
assertThat(appMetricaDefaultAnonymousConfigProvider.getConfig(context))
assertThat(appMetricaDefaultAnonymousConfigProvider.getConfig())
.usingRecursiveComparison()
.isEqualTo(AppMetricaConfig.newConfigBuilder(apiKey).build())
}

@Test
fun `getConfig for main process and not first launch`() {
whenever(firstLaunchDetector.detectNotFirstLaunch(context)).thenReturn(true)
assertThat(appMetricaDefaultAnonymousConfigProvider.getConfig(context))
whenever(firstLaunchDetector.isNotFirstLaunch()).thenReturn(true)
assertThat(appMetricaDefaultAnonymousConfigProvider.getConfig())
.usingRecursiveComparison()
.isEqualTo(AppMetricaConfig.newConfigBuilder(apiKey).handleFirstActivationAsUpdate(true).build())
}

@Test
fun `getConfig for non main process and not first launch`() {
whenever(firstLaunchDetector.detectNotFirstLaunch(context)).thenReturn(true)
whenever(firstLaunchDetector.isNotFirstLaunch()).thenReturn(true)
whenever(mainProcessDetector.isMainProcess).thenReturn(false)
assertThat(appMetricaDefaultAnonymousConfigProvider.getConfig(context))
assertThat(appMetricaDefaultAnonymousConfigProvider.getConfig())
.usingRecursiveComparison()
.isEqualTo(AppMetricaConfig.newConfigBuilder(apiKey).build())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import io.appmetrica.analytics.StartupParamsCallback;
import io.appmetrica.analytics.coreapi.internal.executors.IHandlerExecutor;
import io.appmetrica.analytics.impl.startup.Constants;
import io.appmetrica.analytics.impl.startup.uuid.MultiProcessSafeUuidProvider;
import io.appmetrica.analytics.impl.utils.FirstLaunchDetector;
import io.appmetrica.analytics.impl.utils.executors.ClientExecutorProvider;
import io.appmetrica.analytics.testutils.ClientServiceLocatorRule;
import io.appmetrica.analytics.testutils.CommonTest;
Expand All @@ -23,12 +25,14 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
Expand Down Expand Up @@ -104,7 +108,13 @@ public void futureIsNotInited() {
assertThat(clientMigrationManagerMockedConstructionRule.getConstructionMock().constructed()).hasSize(1);
assertThat(clientMigrationManagerMockedConstructionRule.getArgumentInterceptor().flatArguments())
.containsOnly(mContext);
verify(ClientServiceLocator.getInstance().getMultiProcessSafeUuidProvider(mContext), times(1)).readUuid();
FirstLaunchDetector firstLaunchDetector = ClientServiceLocator.getInstance().getFirstLaunchDetector();
MultiProcessSafeUuidProvider multiProcessSafeUuidProvider =
ClientServiceLocator.getInstance().getMultiProcessSafeUuidProvider(mContext);
InOrder inOrder = inOrder(firstLaunchDetector, multiProcessSafeUuidProvider);
inOrder.verify(firstLaunchDetector).init(mContext);
inOrder.verify(multiProcessSafeUuidProvider).readUuid();
inOrder.verifyNoMoreInteractions();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.appmetrica.analytics.impl.reporter.ReporterLifecycleListener;
import io.appmetrica.analytics.impl.startup.uuid.MultiProcessSafeUuidProvider;
import io.appmetrica.analytics.impl.startup.uuid.UuidFromClientPreferencesImporter;
import io.appmetrica.analytics.impl.utils.FirstLaunchDetector;
import io.appmetrica.analytics.impl.utils.MainProcessDetector;
import io.appmetrica.analytics.impl.utils.executors.ClientExecutorProvider;
import io.appmetrica.analytics.testutils.CommonTest;
Expand Down Expand Up @@ -86,6 +87,10 @@ public class ClientServiceLocatorTest extends CommonTest {
public MockedConstructionRule<AppMetricaFacadeProvider> appMetricaFacadeProviderMockedConstructionRule =
new MockedConstructionRule<>(AppMetricaFacadeProvider.class);

@Rule
public MockedConstructionRule<FirstLaunchDetector> firstLaunchDetectorMockedConstructionRule =
new MockedConstructionRule<>(FirstLaunchDetector.class);

@Mock
private DatabaseStorageFactory databaseStorage;
@Mock
Expand Down Expand Up @@ -174,7 +179,7 @@ public void getMainReporterLifecycleListener() {
public void allFieldsFilled() throws Exception {
ObjectPropertyAssertions(mClientServiceLocator)
.withDeclaredAccessibleFields(true)
.withIgnoredFields("moduleEntryPointsRegister", "appMetricaFacadeProvider")
.withIgnoredFields("moduleEntryPointsRegister", "appMetricaFacadeProvider", "firstLaunchDetector")
.checkField("mainProcessDetector", "getMainProcessDetector", mMainProcessDetector)
.checkField("defaultOneShotConfig", "getDefaultOneShotConfig", mDefaultOneShotMetricaConfig)
.checkField("clientExecutorProvider", "getClientExecutorProvider", mClientExecutorProvider)
Expand Down Expand Up @@ -247,4 +252,12 @@ public void getAppMetricaFacadeProvider() {
assertThat(appMetricaFacadeProviderMockedConstructionRule.getConstructionMock().constructed()).hasSize(1);
assertThat(appMetricaFacadeProviderMockedConstructionRule.getArgumentInterceptor().flatArguments()).isEmpty();
}

@Test
public void getFirstLaunchDetector() {
assertThat(mClientServiceLocator.getFirstLaunchDetector())
.isEqualTo(firstLaunchDetectorMockedConstructionRule.getConstructionMock().constructed().get(0));
assertThat(firstLaunchDetectorMockedConstructionRule.getConstructionMock().constructed()).hasSize(1);
assertThat(firstLaunchDetectorMockedConstructionRule.getArgumentInterceptor().flatArguments()).isEmpty();
}
}
Loading

0 comments on commit 818ff2b

Please sign in to comment.