Merge pull request #13520 from shuffle2/android-start

Prevent android generating duplicate analytics events
This commit is contained in:
JMC47 2025-04-20 20:17:30 -04:00 committed by GitHub
commit 41408076e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 47 additions and 31 deletions

View file

@ -103,11 +103,6 @@ class MainActivity : AppCompatActivity(), MainView, OnRefreshListener, ThemeProv
presenter.onResume() presenter.onResume()
} }
override fun onStart() {
super.onStart()
StartupHandler.checkSessionReset(this)
}
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
if (isChangingConfigurations) { if (isChangingConfigurations) {
@ -116,8 +111,6 @@ class MainActivity : AppCompatActivity(), MainView, OnRefreshListener, ThemeProv
// If the currently selected platform tab changed, save it to disk // If the currently selected platform tab changed, save it to disk
NativeConfig.save(NativeConfig.LAYER_BASE) NativeConfig.save(NativeConfig.LAYER_BASE)
} }
StartupHandler.setSessionTime(this)
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {

View file

@ -76,17 +76,11 @@ class TvMainActivity : FragmentActivity(), MainView, OnRefreshListener {
presenter.onResume() presenter.onResume()
} }
override fun onStart() {
super.onStart()
StartupHandler.checkSessionReset(this)
}
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
if (isChangingConfigurations) { if (isChangingConfigurations) {
MainPresenter.skipRescanningLibrary() MainPresenter.skipRescanningLibrary()
} }
StartupHandler.setSessionTime(this)
} }
private fun setupUI() { private fun setupUI() {

View file

@ -3,14 +3,29 @@ package org.dolphinemu.dolphinemu.utils
import android.app.Activity import android.app.Activity
import android.app.Application.ActivityLifecycleCallbacks import android.app.Application.ActivityLifecycleCallbacks
import android.os.Bundle import android.os.Bundle
import org.dolphinemu.dolphinemu.ui.main.MainView
class ActivityTracker : ActivityLifecycleCallbacks { class ActivityTracker : ActivityLifecycleCallbacks {
val resumedActivities = HashSet<Activity>() private val resumedActivities = HashSet<Activity>()
var backgroundExecutionAllowed = false private var backgroundExecutionAllowed = false
private var firstStart = true
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {} private fun isMainActivity(activity: Activity): Boolean {
return activity is MainView
}
override fun onActivityStarted(activity: Activity) {} override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
if (isMainActivity(activity)) {
firstStart = bundle == null
}
}
override fun onActivityStarted(activity: Activity) {
if (isMainActivity(activity)) {
StartupHandler.reportStartToAnalytics(activity.applicationContext, firstStart)
firstStart = false
}
}
override fun onActivityResumed(activity: Activity) { override fun onActivityResumed(activity: Activity) {
resumedActivities.add(activity) resumedActivities.add(activity)
@ -28,7 +43,11 @@ class ActivityTracker : ActivityLifecycleCallbacks {
} }
} }
override fun onActivityStopped(activity: Activity) {} override fun onActivityStopped(activity: Activity) {
if (isMainActivity(activity)) {
StartupHandler.updateSessionTimestamp(activity.applicationContext)
}
}
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {} override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}

View file

@ -84,7 +84,6 @@ public final class DirectoryInitialization
extractSysDirectory(context); extractSysDirectory(context);
NativeLibrary.Initialize(); NativeLibrary.Initialize();
NativeLibrary.ReportStartToAnalytics();
areDirectoriesAvailable = true; areDirectoriesAvailable = true;

View file

@ -2,6 +2,7 @@
package org.dolphinemu.dolphinemu.utils; package org.dolphinemu.dolphinemu.utils;
import android.app.Activity;
import android.content.ClipData; import android.content.ClipData;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -11,6 +12,7 @@ import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.NativeLibrary;
@ -22,7 +24,7 @@ import java.util.Objects;
public final class StartupHandler public final class StartupHandler
{ {
public static final String LAST_CLOSED = "LAST_CLOSED"; private static final String SESSION_TIMESTAMP = "SESSION_TIMESTAMP";
public static void HandleInit(FragmentActivity parent) public static void HandleInit(FragmentActivity parent)
{ {
@ -88,29 +90,38 @@ public final class StartupHandler
return null; return null;
} }
private static Instant getSessionTimestamp(Context context)
{
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
long timestamp = preferences.getLong(SESSION_TIMESTAMP, 0);
return Instant.ofEpochMilli(timestamp);
}
/** /**
* There isn't a good way to determine a new session. setSessionTime is called if the main * Called on activity stop / to set timestamp to "now".
* activity goes into the background.
*/ */
public static void setSessionTime(Context context) public static void updateSessionTimestamp(Context context)
{ {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor sPrefsEditor = preferences.edit(); SharedPreferences.Editor sPrefsEditor = preferences.edit();
sPrefsEditor.putLong(LAST_CLOSED, System.currentTimeMillis()); sPrefsEditor.putLong(SESSION_TIMESTAMP, Instant.now().toEpochMilli());
sPrefsEditor.apply(); sPrefsEditor.apply();
} }
/** /**
* Called to determine if we treat this activity start as a new session. * Called on activity start. Generates analytics start event if it's a fresh start of the app, or
* if it's a start after a long period of the app not being used (during which time the process
* may be restarted for power/memory saving reasons, although app state persists).
*/ */
public static void checkSessionReset(Context context) public static void reportStartToAnalytics(Context context, boolean firstStart)
{ {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); final Instant sessionTimestamp = getSessionTimestamp(context);
long lastOpen = preferences.getLong(LAST_CLOSED, 0); final Instant now = Instant.now();
final Instant current = Instant.now(); if (firstStart || now.isAfter(sessionTimestamp.plus(6, ChronoUnit.HOURS)))
final Instant lastOpened = Instant.ofEpochMilli(lastOpen);
if (current.isAfter(lastOpened.plus(6, ChronoUnit.HOURS)))
{ {
// Just in case: ensure start event won't be accidentally sent too often.
updateSessionTimestamp(context);
new AfterDirectoryInitializationRunner().runWithoutLifecycle( new AfterDirectoryInitializationRunner().runWithoutLifecycle(
NativeLibrary::ReportStartToAnalytics); NativeLibrary::ReportStartToAnalytics);
} }