mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-23 06:09:50 -06:00
Android: Add usage statistics to android.
Added an option in General config to enable/disable usage statistics. Added a popup on first open if the user would like to engage in reporting. Clicking cancel or out of the box opts out. Only clicking 'Ok' will enable reporting. Also added a new android specific values to report.
This commit is contained in:
@ -1,17 +1,28 @@
|
||||
package org.dolphinemu.dolphinemu;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||
import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
|
||||
import org.dolphinemu.dolphinemu.utils.VolleyUtil;
|
||||
|
||||
public class DolphinApplication extends Application
|
||||
{
|
||||
public static final String FIRST_OPEN = "FIRST_OPEN";
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
|
||||
// Passed at emulation start to trigger first open event.
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
SharedPreferences.Editor sPrefsEditor = preferences.edit();
|
||||
sPrefsEditor.putBoolean(FIRST_OPEN, true);
|
||||
sPrefsEditor.apply();
|
||||
|
||||
VolleyUtil.init(getApplicationContext());
|
||||
System.loadLibrary("main");
|
||||
|
||||
if (PermissionsHandler.hasWriteAccess(getApplicationContext()))
|
||||
|
@ -322,7 +322,7 @@ public final class NativeLibrary
|
||||
/**
|
||||
* Begins emulation.
|
||||
*/
|
||||
public static native void Run(String path);
|
||||
public static native void Run(String path, boolean firstOpen);
|
||||
|
||||
/**
|
||||
* Begins emulation from the specified savestate.
|
||||
|
@ -27,13 +27,15 @@ public class Settings
|
||||
|
||||
public static final String SECTION_BINDINGS = "Android";
|
||||
|
||||
public static final String SECTION_ANALYTICS = "Analytics";
|
||||
|
||||
private String gameId;
|
||||
|
||||
private static final Map<String, List<String>> configFileSectionsMap = new HashMap<>();
|
||||
|
||||
static
|
||||
{
|
||||
configFileSectionsMap.put(SettingsFile.FILE_NAME_DOLPHIN, Arrays.asList(SECTION_INI_CORE, SECTION_INI_INTERFACE, SECTION_BINDINGS));
|
||||
configFileSectionsMap.put(SettingsFile.FILE_NAME_DOLPHIN, Arrays.asList(SECTION_INI_CORE, SECTION_INI_INTERFACE, SECTION_BINDINGS, SECTION_ANALYTICS));
|
||||
configFileSectionsMap.put(SettingsFile.FILE_NAME_GFX, Arrays.asList(SECTION_GFX_SETTINGS, SECTION_GFX_ENHANCEMENTS, SECTION_GFX_HACKS, SECTION_STEREOSCOPY));
|
||||
configFileSectionsMap.put(SettingsFile.FILE_NAME_WIIMOTE, Arrays.asList(SECTION_WIIMOTE + 1, SECTION_WIIMOTE + 2, SECTION_WIIMOTE + 3, SECTION_WIIMOTE + 4));
|
||||
}
|
||||
|
@ -210,14 +210,17 @@ public final class SettingsFragmentPresenter
|
||||
Setting overclock = null;
|
||||
Setting speedLimit = null;
|
||||
Setting audioStretch = null;
|
||||
Setting analytics = null;
|
||||
|
||||
SettingSection coreSection = mSettings.getSection(Settings.SECTION_INI_CORE);
|
||||
SettingSection analyticsSection = mSettings.getSection(Settings.SECTION_ANALYTICS);
|
||||
cpuCore = coreSection.getSetting(SettingsFile.KEY_CPU_CORE);
|
||||
dualCore = coreSection.getSetting(SettingsFile.KEY_DUAL_CORE);
|
||||
overclockEnable = coreSection.getSetting(SettingsFile.KEY_OVERCLOCK_ENABLE);
|
||||
overclock = coreSection.getSetting(SettingsFile.KEY_OVERCLOCK_PERCENT);
|
||||
speedLimit = coreSection.getSetting(SettingsFile.KEY_SPEED_LIMIT);
|
||||
audioStretch = coreSection.getSetting(SettingsFile.KEY_AUDIO_STRETCH);
|
||||
analytics = analyticsSection.getSetting(SettingsFile.KEY_ANALYTICS_ENABLED);
|
||||
|
||||
// TODO: Having different emuCoresEntries/emuCoresValues for each architecture is annoying.
|
||||
// The proper solution would be to have one emuCoresEntries and one emuCoresValues
|
||||
@ -246,6 +249,7 @@ public final class SettingsFragmentPresenter
|
||||
sl.add(new SliderSetting(SettingsFile.KEY_OVERCLOCK_PERCENT, Settings.SECTION_INI_CORE, R.string.overclock_title, R.string.overclock_title_description, 400, "%", 100, overclock));
|
||||
sl.add(new SliderSetting(SettingsFile.KEY_SPEED_LIMIT, Settings.SECTION_INI_CORE, R.string.speed_limit, 0, 200, "%", 100, speedLimit));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_AUDIO_STRETCH, Settings.SECTION_INI_CORE, R.string.audio_stretch, R.string.audio_stretch_description, false, audioStretch));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_ANALYTICS_ENABLED, Settings.SECTION_ANALYTICS, R.string.analytics, 0, false, analytics));
|
||||
}
|
||||
|
||||
private void addInterfaceSettings(ArrayList<SettingsItem> sl)
|
||||
|
@ -50,6 +50,9 @@ public final class SettingsFile
|
||||
public static final String KEY_SLOT_A_DEVICE = "SlotA";
|
||||
public static final String KEY_SLOT_B_DEVICE = "SlotB";
|
||||
|
||||
public static final String KEY_ANALYTICS_ENABLED = "Enabled";
|
||||
public static final String KEY_ANALYTICS_PERMISSION_ASKED = "PermissionAsked";
|
||||
|
||||
public static final String KEY_USE_PANIC_HANDLERS = "UsePanicHandlers";
|
||||
public static final String KEY_OSD_MESSAGES = "OnScreenDisplayMessages";
|
||||
|
||||
@ -294,12 +297,14 @@ public final class SettingsFile
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
Log.error("[SettingsFile] File not found: " + ini.getAbsolutePath() + e.getMessage());
|
||||
view.onSettingsFileNotFound();
|
||||
if (view != null)
|
||||
view.onSettingsFileNotFound();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.error("[SettingsFile] Error reading from: " + ini.getAbsolutePath()+ e.getMessage());
|
||||
view.onSettingsFileNotFound();
|
||||
if (view != null)
|
||||
view.onSettingsFileNotFound();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -384,12 +389,14 @@ public final class SettingsFile
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.getMessage());
|
||||
view.showToastMessage("Error saving " + fileName + ".ini: " + e.getMessage());
|
||||
if (view != null)
|
||||
view.showToastMessage("Error saving " + fileName + ".ini: " + e.getMessage());
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
Log.error("[SettingsFile] Bad encoding; please file a bug report: " + fileName + ".ini: " + e.getMessage());
|
||||
view.showToastMessage("Error saving " + fileName + ".ini: " + e.getMessage());
|
||||
if (view != null)
|
||||
view.showToastMessage("Error saving " + fileName + ".ini: " + e.getMessage());
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -489,6 +496,23 @@ public final class SettingsFile
|
||||
sections.put(Settings.SECTION_INI_CORE, coreSection);
|
||||
}
|
||||
|
||||
public static void firstAnalyticsAdd(boolean enabled)
|
||||
{
|
||||
HashMap<String, SettingSection> dolphinSections = readFile(SettingsFile.FILE_NAME_DOLPHIN, null);
|
||||
SettingSection analyticsSection = dolphinSections.get(Settings.SECTION_ANALYTICS);
|
||||
|
||||
Setting analyticsEnabled = new StringSetting(KEY_ANALYTICS_ENABLED, Settings.SECTION_ANALYTICS, enabled ? "True" : "False");
|
||||
Setting analyticsFirstAsk = new StringSetting(KEY_ANALYTICS_PERMISSION_ASKED, Settings.SECTION_ANALYTICS, "True");
|
||||
|
||||
analyticsSection.putSetting(analyticsFirstAsk);
|
||||
analyticsSection.putSetting(analyticsEnabled);
|
||||
|
||||
dolphinSections.put(Settings.SECTION_ANALYTICS, analyticsSection);
|
||||
|
||||
TreeMap<String, SettingSection> saveSection = new TreeMap<>(dolphinSections);
|
||||
saveFile(SettingsFile.FILE_NAME_DOLPHIN, saveSection, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a line of text, determines what type of data is being represented, and returns
|
||||
* a Setting object containing this data.
|
||||
|
@ -16,6 +16,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.dolphinemu.dolphinemu.DolphinApplication;
|
||||
import org.dolphinemu.dolphinemu.NativeLibrary;
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
|
||||
@ -84,7 +85,12 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
||||
mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
|
||||
String gamePath = getArguments().getString(KEY_GAMEPATH);
|
||||
mEmulationState = new EmulationState(gamePath, getTemporaryStateFilePath());
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
boolean firstOpen = preferences.getBoolean(DolphinApplication.FIRST_OPEN, true);
|
||||
SharedPreferences.Editor sPrefsEditor = preferences.edit();
|
||||
sPrefsEditor.putBoolean(DolphinApplication.FIRST_OPEN, false);
|
||||
sPrefsEditor.apply();
|
||||
mEmulationState = new EmulationState(gamePath, getTemporaryStateFilePath(), firstOpen);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -264,10 +270,12 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
||||
private Surface mSurface;
|
||||
private boolean mRunWhenSurfaceIsValid;
|
||||
private boolean loadPreviousTemporaryState;
|
||||
private boolean firstOpen;
|
||||
private final String temporaryStatePath;
|
||||
|
||||
EmulationState(String gamePath, String temporaryStatePath)
|
||||
EmulationState(String gamePath, String temporaryStatePath, boolean firstOpen)
|
||||
{
|
||||
this.firstOpen = firstOpen;
|
||||
mGamePath = gamePath;
|
||||
this.temporaryStatePath = temporaryStatePath;
|
||||
// Starting state is stopped.
|
||||
@ -411,7 +419,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
||||
else
|
||||
{
|
||||
Log.debug("[EmulationFragment] Starting emulation thread.");
|
||||
NativeLibrary.Run(mGamePath);
|
||||
NativeLibrary.Run(mGamePath, firstOpen);
|
||||
}
|
||||
}, "NativeEmulation");
|
||||
mEmulationThread.start();
|
||||
|
@ -0,0 +1,125 @@
|
||||
package org.dolphinemu.dolphinemu.utils;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import com.android.volley.Request;
|
||||
import com.android.volley.toolbox.StringRequest;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
||||
import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile;
|
||||
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||
|
||||
public class Analytics
|
||||
{
|
||||
private static DirectoryStateReceiver directoryStateReceiver;
|
||||
|
||||
private static final String analyticsAsked = Settings.SECTION_ANALYTICS + "_" + SettingsFile.KEY_ANALYTICS_PERMISSION_ASKED;
|
||||
private static final String analyticsEnabled = Settings.SECTION_ANALYTICS + "_" + SettingsFile.KEY_ANALYTICS_ENABLED;
|
||||
|
||||
private static final String DEVICE_MANUFACTURER = "DEVICE_MANUFACTURER";
|
||||
private static final String DEVICE_OS = "DEVICE_OS";
|
||||
private static final String DEVICE_MODEL = "DEVICE_MODEL";
|
||||
private static final String DEVICE_TYPE = "DEVICE_TYPE";
|
||||
|
||||
private static String deviceType;
|
||||
|
||||
public static void checkAnalyticsInit(Context context)
|
||||
{
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (!preferences.getBoolean(analyticsAsked, false))
|
||||
{
|
||||
if (!DirectoryInitializationService.areDolphinDirectoriesReady())
|
||||
{
|
||||
// Wait for directories to get initialized
|
||||
IntentFilter statusIntentFilter = new IntentFilter(
|
||||
DirectoryInitializationService.BROADCAST_ACTION);
|
||||
|
||||
directoryStateReceiver = new DirectoryStateReceiver(directoryInitializationState ->
|
||||
{
|
||||
if (directoryInitializationState == DirectoryInitializationService.DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED)
|
||||
{
|
||||
LocalBroadcastManager.getInstance(context).unregisterReceiver(directoryStateReceiver);
|
||||
directoryStateReceiver = null;
|
||||
showMessage(context, preferences);
|
||||
}
|
||||
});
|
||||
// Registers the DirectoryStateReceiver and its intent filters
|
||||
LocalBroadcastManager.getInstance(context).registerReceiver(
|
||||
directoryStateReceiver,
|
||||
statusIntentFilter);
|
||||
}
|
||||
else
|
||||
{
|
||||
showMessage(context, preferences);
|
||||
}
|
||||
}
|
||||
// Get device type now since we have a context
|
||||
deviceType = TvUtil.isLeanback(context) ? "android-tv" : "android-mobile";
|
||||
}
|
||||
|
||||
private static void showMessage(Context context, SharedPreferences preferences)
|
||||
{
|
||||
// We asked, set to true regardless of answer
|
||||
SharedPreferences.Editor sPrefsEditor = preferences.edit();
|
||||
sPrefsEditor.putBoolean(analyticsAsked, true);
|
||||
sPrefsEditor.apply();
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(context.getString(R.string.analytics))
|
||||
.setMessage(context.getString(R.string.analytics_desc))
|
||||
.setPositiveButton(R.string.yes, (dialogInterface, i) ->
|
||||
{
|
||||
sPrefsEditor.putBoolean(analyticsEnabled, true);
|
||||
sPrefsEditor.apply();
|
||||
SettingsFile.firstAnalyticsAdd(true);
|
||||
})
|
||||
.setNegativeButton(R.string.no, (dialogInterface, i) ->
|
||||
{
|
||||
sPrefsEditor.putBoolean(analyticsEnabled, false);
|
||||
sPrefsEditor.apply();
|
||||
SettingsFile.firstAnalyticsAdd(false);
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
public static void sendReport(String endpoint, byte[] data)
|
||||
{
|
||||
StringRequest request = new StringRequest(Request.Method.POST, endpoint,
|
||||
null, error -> Log.debug("Send Report Failure code: "
|
||||
+ error.networkResponse.statusCode))
|
||||
{
|
||||
@Override
|
||||
public byte[] getBody()
|
||||
{
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
VolleyUtil.getQueue().add(request);
|
||||
}
|
||||
|
||||
public static String getValue(String key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case DEVICE_MODEL:
|
||||
return Build.MODEL;
|
||||
case DEVICE_MANUFACTURER:
|
||||
return Build.MANUFACTURER;
|
||||
case DEVICE_OS:
|
||||
return String.valueOf(Build.VERSION.SDK_INT);
|
||||
case DEVICE_TYPE:
|
||||
return deviceType;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,9 @@ public final class StartupHandler
|
||||
// Ask the user to grant write permission if it's not already granted
|
||||
PermissionsHandler.checkWritePermission(parent);
|
||||
|
||||
// Ask the user if he wants to enable analytics if we haven't yet.
|
||||
Analytics.checkAnalyticsInit(parent);
|
||||
|
||||
String start_file = "";
|
||||
Bundle extras = parent.getIntent().getExtras();
|
||||
if (extras != null)
|
||||
|
@ -0,0 +1,22 @@
|
||||
package org.dolphinemu.dolphinemu.utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.volley.RequestQueue;
|
||||
import com.android.volley.toolbox.Volley;
|
||||
|
||||
public class VolleyUtil
|
||||
{
|
||||
private static RequestQueue queue;
|
||||
|
||||
public static void init(Context context)
|
||||
{
|
||||
if (queue == null)
|
||||
queue = Volley.newRequestQueue(context);
|
||||
}
|
||||
|
||||
public static RequestQueue getQueue()
|
||||
{
|
||||
return queue;
|
||||
}
|
||||
}
|
@ -135,6 +135,8 @@
|
||||
<string name="wiimote_speaker_description">Enable sound output through the speaker on a real Wiimote (DolphinBar required).</string>
|
||||
<string name="audio_stretch">Audio Stretching</string>
|
||||
<string name="audio_stretch_description">Stretches audio to reduce stuttering. Increases latency.</string>
|
||||
<string name="analytics">Enable usage statistics reporting</string>
|
||||
<string name="analytics_desc">If authorized, Dolphin can collect data on its performance, feature usage, and configuration, as well as data on your system\'s hardware and operating system.\n\nNo private data is ever collected. This data helps us understand how people and emulated games use Dolphin and prioritize our efforts. It also helps us identify rare configurations that are causing bugs, performance and stability issues. This authorization can be revoked at any time through Dolphin\'s settings.</string>
|
||||
<string name="gametdb_thanks">Thanks to GameTDB.com for providing GameCube and Wii covers!</string>
|
||||
|
||||
<!-- Interface Preference Fragment -->
|
||||
|
Reference in New Issue
Block a user