Prevent android generating duplicate analytics events
dolphin-start event was being generated twice for the normal end-user case, as can be seen in analytics data for some years. The problem occured when: * Android reaped the process hosting the dolphin activity (e.g. for power/memory saving). and * Dolphin activity was in "stopped" state for > 6 hours before being switched back to. Under above conditions, both calls to ReportStartToAnalytics would be performed, as dolphin thought it was being launched anew, and also thought it had been asleep for > 6 hours. fixes https://bugs.dolphin-emu.org/issues/13675
This commit is contained in:
parent
4f210df86a
commit
257ce10232
|
@ -103,11 +103,6 @@ class MainActivity : AppCompatActivity(), MainView, OnRefreshListener, ThemeProv
|
|||
presenter.onResume()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
StartupHandler.checkSessionReset(this)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
if (isChangingConfigurations) {
|
||||
|
@ -116,8 +111,6 @@ class MainActivity : AppCompatActivity(), MainView, OnRefreshListener, ThemeProv
|
|||
// If the currently selected platform tab changed, save it to disk
|
||||
NativeConfig.save(NativeConfig.LAYER_BASE)
|
||||
}
|
||||
|
||||
StartupHandler.setSessionTime(this)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
|
|
|
@ -76,17 +76,11 @@ class TvMainActivity : FragmentActivity(), MainView, OnRefreshListener {
|
|||
presenter.onResume()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
StartupHandler.checkSessionReset(this)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
if (isChangingConfigurations) {
|
||||
MainPresenter.skipRescanningLibrary()
|
||||
}
|
||||
StartupHandler.setSessionTime(this)
|
||||
}
|
||||
|
||||
private fun setupUI() {
|
||||
|
|
|
@ -3,14 +3,29 @@ package org.dolphinemu.dolphinemu.utils
|
|||
import android.app.Activity
|
||||
import android.app.Application.ActivityLifecycleCallbacks
|
||||
import android.os.Bundle
|
||||
import org.dolphinemu.dolphinemu.ui.main.MainView
|
||||
|
||||
class ActivityTracker : ActivityLifecycleCallbacks {
|
||||
val resumedActivities = HashSet<Activity>()
|
||||
var backgroundExecutionAllowed = false
|
||||
private val resumedActivities = HashSet<Activity>()
|
||||
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, firstStart)
|
||||
firstStart = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResumed(activity: 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)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
|
||||
|
||||
|
|
|
@ -84,7 +84,6 @@ public final class DirectoryInitialization
|
|||
|
||||
extractSysDirectory(context);
|
||||
NativeLibrary.Initialize();
|
||||
NativeLibrary.ReportStartToAnalytics();
|
||||
|
||||
areDirectoriesAvailable = true;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
package org.dolphinemu.dolphinemu.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
@ -11,6 +12,7 @@ import android.os.Bundle;
|
|||
import android.text.TextUtils;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.dolphinemu.dolphinemu.NativeLibrary;
|
||||
|
@ -22,7 +24,7 @@ import java.util.Objects;
|
|||
|
||||
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)
|
||||
{
|
||||
|
@ -88,30 +90,41 @@ public final class StartupHandler
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* There isn't a good way to determine a new session. setSessionTime is called if the main
|
||||
* activity goes into the background.
|
||||
*/
|
||||
public static void setSessionTime(Context context)
|
||||
private static Instant getSessionTimestamp(Context context)
|
||||
{
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
long timestamp = preferences.getLong(SESSION_TIMESTAMP, 0);
|
||||
return Instant.ofEpochMilli(timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on activity stop / to set timestamp to "now".
|
||||
*/
|
||||
public static void updateSessionTimestamp(Activity activity)
|
||||
{
|
||||
Context context = activity.getApplicationContext();
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor sPrefsEditor = preferences.edit();
|
||||
sPrefsEditor.putLong(LAST_CLOSED, System.currentTimeMillis());
|
||||
sPrefsEditor.putLong(SESSION_TIMESTAMP, Instant.now().toEpochMilli());
|
||||
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(Activity activity, boolean firstStart)
|
||||
{
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
long lastOpen = preferences.getLong(LAST_CLOSED, 0);
|
||||
final Instant current = Instant.now();
|
||||
final Instant lastOpened = Instant.ofEpochMilli(lastOpen);
|
||||
if (current.isAfter(lastOpened.plus(6, ChronoUnit.HOURS)))
|
||||
Context context = activity.getApplicationContext();
|
||||
final Instant sessionTimestamp = getSessionTimestamp(context);
|
||||
final Instant now = Instant.now();
|
||||
if (firstStart || now.isAfter(sessionTimestamp.plus(6, ChronoUnit.HOURS)))
|
||||
{
|
||||
new AfterDirectoryInitializationRunner().runWithoutLifecycle(
|
||||
// Just in case: ensure start event won't be accidentally sent too often.
|
||||
updateSessionTimestamp(activity);
|
||||
|
||||
new AfterDirectoryInitializationRunner().runWithLifecycle((LifecycleOwner) activity,
|
||||
NativeLibrary::ReportStartToAnalytics);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue