mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-05-25 04:22:37 +00:00
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
a1691a4031
5 changed files with 47 additions and 31 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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) {}
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,6 @@ public final class DirectoryInitialization
|
||||||
|
|
||||||
extractSysDirectory(context);
|
extractSysDirectory(context);
|
||||||
NativeLibrary.Initialize();
|
NativeLibrary.Initialize();
|
||||||
NativeLibrary.ReportStartToAnalytics();
|
|
||||||
|
|
||||||
areDirectoriesAvailable = true;
|
areDirectoriesAvailable = true;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue