diff options
| author | Pierre Bourdon <delroth@gmail.com> | 2018-08-27 04:37:17 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-08-27 04:37:17 +0200 |
| commit | 246b1f44598349e60ef040839ecdbd95f1de1d5f (patch) | |
| tree | 60557346f78ade02536eb2b5eafd5ffeb6214547 /Source/Android/app/src/main/java/org | |
| parent | b571d0c537625f3b34a4f2b98919a0fdd56d1c6f (diff) | |
| parent | a26cf8febcaf8d8f8d44a44ae834614bf89de2a8 (diff) | |
Merge pull request #7350 from zackhow/android-analytics
Android: Add usage statistics to android
Diffstat (limited to 'Source/Android/app/src/main/java/org')
11 files changed, 265 insertions, 11 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java index 22ee55cb70..251df3b117 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java @@ -4,6 +4,7 @@ import android.app.Application; 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 class DolphinApplication extends Application { super.onCreate(); + VolleyUtil.init(getApplicationContext()); System.loadLibrary("main"); if (PermissionsHandler.hasWriteAccess(getApplicationContext())) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index c986857e75..922c041a7b 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -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. diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java index 5d960a3b9f..599559030a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java @@ -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)); } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java index 1f6a0c3e5d..db533da7cb 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java @@ -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) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java index f19034cc16..fa9bd0add3 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java @@ -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 { @@ -490,6 +497,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. diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java index c7e13a9999..2fb4ad8440 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java @@ -24,11 +24,10 @@ import org.dolphinemu.dolphinemu.services.DirectoryInitializationService; 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 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(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 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 +417,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(); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java index a2d47444af..fe367276cf 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java @@ -89,6 +89,20 @@ public final class MainActivity extends AppCompatActivity implements MainView 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() { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java index 87ec24ff56..b1f8196d0d 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java @@ -74,6 +74,20 @@ public final class TvMainActivity extends FragmentActivity implements MainView 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(); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Analytics.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Analytics.java new file mode 100644 index 0000000000..158e7c2055 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Analytics.java @@ -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 ""; + } + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/StartupHandler.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/StartupHandler.java index 83d91c41ba..0d62de4074 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/StartupHandler.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/StartupHandler.java @@ -1,19 +1,31 @@ package org.dolphinemu.dolphinemu.utils; +import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; +import android.preference.PreferenceManager; import android.support.v4.app.FragmentActivity; import android.text.TextUtils; import org.dolphinemu.dolphinemu.activities.EmulationActivity; +import java.util.Date; + public final class StartupHandler { + public static final String NEW_SESSION = "NEW_SESSION"; + public static final String LAST_CLOSED = "LAST_CLOSED"; + public static final Long SESSION_TIMEOUT = 21600000L; // 6 hours in milliseconds + public static void HandleInit(FragmentActivity parent) { // 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) @@ -30,4 +42,33 @@ public final class StartupHandler parent.finish(); } } + + /** + * 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) + { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor sPrefsEditor = preferences.edit(); + sPrefsEditor.putLong(LAST_CLOSED, new Date(System.currentTimeMillis()).getTime()); + sPrefsEditor.apply(); + } + + /** + * Called to determine if we treat this activity start as a new session. + */ + public static void checkSessionReset(Context context) + { + Long currentTime = new Date(System.currentTimeMillis()).getTime(); + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + Long lastOpen = preferences.getLong(LAST_CLOSED, 0); + if (currentTime > (lastOpen + SESSION_TIMEOUT)) + { + // Passed at emulation start to trigger first open event. + SharedPreferences.Editor sPrefsEditor = preferences.edit(); + sPrefsEditor.putBoolean(NEW_SESSION, true); + sPrefsEditor.apply(); + } + } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/VolleyUtil.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/VolleyUtil.java new file mode 100644 index 0000000000..1e523bb45f --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/VolleyUtil.java @@ -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; + } +} |
