Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use AndroidX lifecycle functions for App Fore/Backgrounded #667

Merged
merged 5 commits into from Jun 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions analytics/build.gradle
Expand Up @@ -6,6 +6,9 @@ apply from: rootProject.file('gradle/android.gradle')
dependencies {
api rootProject.ext.deps.supportAnnotations

implementation 'androidx.lifecycle:lifecycle-process:2.2.0'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.2.0'

testImplementation 'junit:junit:4.12'
testImplementation('org.robolectric:robolectric:3.5') {
exclude group: 'commons-logging', module: 'commons-logging'
Expand Down
16 changes: 12 additions & 4 deletions analytics/src/main/java/com/segment/analytics/Analytics.java
Expand Up @@ -48,6 +48,8 @@
import android.os.Message;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.ProcessLifecycleOwner;
import com.segment.analytics.integrations.AliasPayload;
import com.segment.analytics.integrations.BasePayload;
import com.segment.analytics.integrations.GroupPayload;
Expand Down Expand Up @@ -132,7 +134,8 @@ public void handleMessage(Message msg) {
final Cartographer cartographer;
private final ProjectSettings.Cache projectSettingsCache;
final Crypto crypto;
@Private final Application.ActivityLifecycleCallbacks activityLifecycleCallback;
@Private final AnalyticsActivityLifecycleCallbacks activityLifecycleCallback;
@Private final Lifecycle lifecycle;
ProjectSettings projectSettings; // todo: make final (non-final for testing).
@Private final String writeKey;
final int flushQueueSize;
Expand Down Expand Up @@ -229,7 +232,8 @@ public static void setSingletonInstance(Analytics analytics) {
Crypto crypto,
@NonNull List<Middleware> sourceMiddleware,
@NonNull Map<String, List<Middleware>> destinationMiddleware,
@NonNull final ValueMap defaultProjectSettings) {
@NonNull final ValueMap defaultProjectSettings,
@NonNull Lifecycle lifecycle) {
this.application = application;
this.networkExecutor = networkExecutor;
this.stats = stats;
Expand All @@ -251,6 +255,7 @@ public static void setSingletonInstance(Analytics analytics) {
this.crypto = crypto;
this.sourceMiddleware = sourceMiddleware;
this.destinationMiddleware = destinationMiddleware;
this.lifecycle = lifecycle;

namespaceSharedPreferences();

Expand Down Expand Up @@ -315,6 +320,7 @@ public void run() {
.build();

application.registerActivityLifecycleCallbacks(activityLifecycleCallback);
lifecycle.addObserver(activityLifecycleCallback);
}

@Private
Expand Down Expand Up @@ -965,6 +971,7 @@ public void shutdown() {
return;
}
application.unregisterActivityLifecycleCallbacks(activityLifecycleCallback);
lifecycle.removeObserver(activityLifecycleCallback);
// Only supplied by us for testing, so it's ok to shut it down. If we were to make this public,
// we'll have to add a check similar to that of AnalyticsNetworkExecutorService below.
analyticsExecutor.shutdown();
Expand Down Expand Up @@ -1375,7 +1382,7 @@ public Analytics build() {
if (executor == null) {
executor = Executors.newSingleThreadExecutor();
}

Lifecycle lifecycle = ProcessLifecycleOwner.get().getLifecycle();
return new Analytics(
application,
networkExecutor,
Expand All @@ -1402,7 +1409,8 @@ public Analytics build() {
crypto,
srcMiddleware,
destMiddleware,
defaultProjectSettings);
defaultProjectSettings,
lifecycle);
}
}

Expand Down
Expand Up @@ -29,11 +29,15 @@
import android.content.pm.PackageInfo;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

class AnalyticsActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
class AnalyticsActivityLifecycleCallbacks
implements Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver {
private Analytics analytics;
private ExecutorService analyticsExecutor;
private Boolean shouldTrackApplicationLifecycleEvents;
Expand All @@ -44,7 +48,6 @@ class AnalyticsActivityLifecycleCallbacks implements Application.ActivityLifecyc

private AtomicBoolean trackedApplicationLifecycleEvents;
private AtomicInteger numberOfActivities;
private AtomicBoolean isChangingActivityConfigurations;
private AtomicBoolean firstLaunch;

private AnalyticsActivityLifecycleCallbacks(
Expand All @@ -57,7 +60,6 @@ private AnalyticsActivityLifecycleCallbacks(
PackageInfo packageInfo) {
this.trackedApplicationLifecycleEvents = new AtomicBoolean(false);
this.numberOfActivities = new AtomicInteger(1);
this.isChangingActivityConfigurations = new AtomicBoolean(false);
this.firstLaunch = new AtomicBoolean(false);
this.analytics = analytics;
this.analyticsExecutor = analyticsExecutor;
Expand All @@ -68,10 +70,29 @@ private AnalyticsActivityLifecycleCallbacks(
this.packageInfo = packageInfo;
}

@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
analytics.runOnMainThread(IntegrationOperation.onActivityCreated(activity, bundle));
public void onStop(@NonNull LifecycleOwner owner) {
// App in background
if (shouldTrackApplicationLifecycleEvents) {
analytics.track("Application Backgrounded");
}
}

public void onStart(@NonNull LifecycleOwner owner) {
// App in foreground
if (shouldTrackApplicationLifecycleEvents) {
Properties properties = new Properties();
if (firstLaunch.get()) {
properties
.putValue("version", packageInfo.versionName)
.putValue("build", String.valueOf(packageInfo.versionCode));
}
properties.putValue("from_background", !firstLaunch.getAndSet(false));
analytics.track("Application Opened", properties);
}
}

public void onCreate(@NonNull LifecycleOwner owner) {
// App created
if (!trackedApplicationLifecycleEvents.getAndSet(true)
&& shouldTrackApplicationLifecycleEvents) {
numberOfActivities.set(0);
Expand All @@ -87,28 +108,35 @@ public void run() {
}
});
}
}
}

if (!trackDeepLinks) {
return;
}
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
analytics.runOnMainThread(IntegrationOperation.onActivityCreated(activity, bundle));

Intent intent = activity.getIntent();
if (intent == null || intent.getData() == null) {
return;
}
if (trackDeepLinks) {
trackDeepLink(activity);
}
}

Properties properties = new Properties();
Uri uri = intent.getData();
for (String parameter : uri.getQueryParameterNames()) {
String value = uri.getQueryParameter(parameter);
if (value != null && !value.trim().isEmpty()) {
properties.put(parameter, value);
}
}
private void trackDeepLink(Activity activity) {
Intent intent = activity.getIntent();
if (intent == null || intent.getData() == null) {
return;
}

properties.put("url", uri.toString());
analytics.track("Deep Link Opened", properties);
Properties properties = new Properties();
Uri uri = intent.getData();
for (String parameter : uri.getQueryParameterNames()) {
String value = uri.getQueryParameter(parameter);
if (value != null && !value.trim().isEmpty()) {
properties.put(parameter, value);
}
}

properties.put("url", uri.toString());
analytics.track("Deep Link Opened", properties);
}

@Override
Expand All @@ -122,20 +150,6 @@ public void onActivityStarted(Activity activity) {
@Override
public void onActivityResumed(Activity activity) {
analytics.runOnMainThread(IntegrationOperation.onActivityResumed(activity));

if (shouldTrackApplicationLifecycleEvents
&& numberOfActivities.incrementAndGet() == 1
&& !isChangingActivityConfigurations.get()) {

Properties properties = new Properties();
if (firstLaunch.get()) {
properties
.putValue("version", packageInfo.versionName)
.putValue("build", String.valueOf(packageInfo.versionCode));
}
properties.putValue("from_background", !firstLaunch.getAndSet(false));
analytics.track("Application Opened", properties);
}
}

@Override
Expand All @@ -146,13 +160,6 @@ public void onActivityPaused(Activity activity) {
@Override
public void onActivityStopped(Activity activity) {
analytics.runOnMainThread(IntegrationOperation.onActivityStopped(activity));

isChangingActivityConfigurations.set(activity.isChangingConfigurations());
if (shouldTrackApplicationLifecycleEvents
&& numberOfActivities.decrementAndGet() == 0
&& !isChangingActivityConfigurations.get()) {
analytics.track("Application Backgrounded");
}
}

@Override
Expand Down