Permalink
Browse files

Merge pull request #7350 from zackhow/android-analytics

Android: Add usage statistics to android
  • Loading branch information...
delroth committed Aug 27, 2018
2 parents b571d0c + a26cf8f commit 246b1f44598349e60ef040839ecdbd95f1de1d5f
Showing with 439 additions and 22 deletions.
  1. +3 −0 Source/Android/app/build.gradle
  2. +2 −0 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java
  3. +1 −1 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java
  4. +3 −1 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java
  5. +4 −0 ...d/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java
  6. +28 −4 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java
  7. +11 −5 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java
  8. +14 −0 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java
  9. +14 −0 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java
  10. +125 −0 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Analytics.java
  11. +41 −0 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/StartupHandler.java
  12. +22 −0 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/VolleyUtil.java
  13. +2 −0 Source/Android/app/src/main/res/values/strings.xml
  14. +26 −0 Source/Android/jni/AndroidCommon/IDCache.cpp
  15. +6 −0 Source/Android/jni/AndroidCommon/IDCache.h
  16. +58 −8 Source/Android/jni/MainAndroid.cpp
  17. +0 −1 Source/Core/Common/Analytics.cpp
  18. +0 −1 Source/Core/Common/Analytics.h
  19. +27 −0 Source/Core/Common/AndroidAnalytics.cpp
  20. +26 −0 Source/Core/Common/AndroidAnalytics.h
  21. +1 −0 Source/Core/Common/CMakeLists.txt
  22. +18 −0 Source/Core/Core/Analytics.cpp
  23. +7 −1 Source/Core/Core/Analytics.h
@@ -88,6 +88,9 @@ dependencies {
implementation "com.android.support:leanback-v17:$androidSupportVersion"
implementation "com.android.support:support-tv-provider:$androidSupportVersion"
// For REST calls
implementation 'com.android.volley:volley:1.1.0'
// For showing the banner as a circle a-la Material Design Guidelines
implementation 'de.hdodenhof:circleimageview:2.1.0'
@@ -4,6 +4,7 @@
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
import org.dolphinemu.dolphinemu.utils.VolleyUtil;
public class DolphinApplication extends Application
{
@@ -12,6 +13,7 @@ public void onCreate()
{
super.onCreate();
VolleyUtil.init(getApplicationContext());
System.loadLibrary("main");
if (PermissionsHandler.hasWriteAccess(getApplicationContext()))
@@ -322,7 +322,7 @@ private 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 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 @@ private void addGeneralSettings(ArrayList<SettingsItem> sl)
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 @@ else if (defaultCpuCore == 4) // AArch64
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 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 @@ else if ((current != null))
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 static void saveFile(final String fileName, TreeMap<String, SettingSectio
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
{
@@ -490,6 +497,23 @@ private static void addGcPadSettingsIfTheyDontExist(HashMap<String, SettingSecti
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.
@@ -24,11 +24,10 @@
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService.DirectoryInitializationState;
import org.dolphinemu.dolphinemu.utils.DirectoryStateReceiver;
import org.dolphinemu.dolphinemu.utils.Log;
import org.dolphinemu.dolphinemu.utils.StartupHandler;
import java.io.File;
import rx.functions.Action1;
public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback
{
private static final String KEY_GAMEPATH = "gamepath";
@@ -84,7 +83,12 @@ public void onCreate(Bundle savedInstanceState)
mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
String gamePath = getArguments().getString(KEY_GAMEPATH);
mEmulationState = new EmulationState(gamePath, getTemporaryStateFilePath());
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
boolean firstOpen = preferences.getBoolean(StartupHandler.NEW_SESSION, true);
SharedPreferences.Editor sPrefsEditor = preferences.edit();
sPrefsEditor.putBoolean(StartupHandler.NEW_SESSION, false);
sPrefsEditor.apply();
mEmulationState = new EmulationState(gamePath, getTemporaryStateFilePath(), firstOpen);
}
/**
@@ -264,10 +268,12 @@ public boolean isConfiguringControls()
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 +417,7 @@ private void runWithValidSurface()
else
{
Log.debug("[EmulationFragment] Starting emulation thread.");
NativeLibrary.Run(mGamePath);
NativeLibrary.Run(mGamePath, firstOpen);
}
}, "NativeEmulation");
mEmulationThread.start();
@@ -89,6 +89,20 @@ protected void onDestroy()
mPresenter.onDestroy();
}
@Override
protected void onStart()
{
super.onStart();
StartupHandler.checkSessionReset(this);
}
@Override
protected void onStop()
{
super.onStop();
StartupHandler.setSessionTime(this);
}
// TODO: Replace with a ButterKnife injection.
private void findViews()
{
@@ -74,6 +74,20 @@ protected void onDestroy()
mPresenter.onDestroy();
}
@Override
protected void onStart()
{
super.onStart();
StartupHandler.checkSessionReset(this);
}
@Override
protected void onStop()
{
super.onStop();
StartupHandler.setSessionTime(this);
}
void setupUI() {
final FragmentManager fragmentManager = getSupportFragmentManager();
mBrowseFragment = new BrowseSupportFragment();
@@ -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 "";
}
}
}
Oops, something went wrong.

0 comments on commit 246b1f4

Please sign in to comment.