From 706b154329c02fc5adcabd73f6880311644b49a7 Mon Sep 17 00:00:00 2001 From: Yaron Budowski Date: Tue, 12 Dec 2023 19:59:33 +0100 Subject: [PATCH] Global analytics clients --- .../inaturalist/android/AnalyticsClient.java | 76 ++++++++++++++++--- .../android/BaseFragmentActivity.java | 1 + .../inaturalist/android/ExploreActivity.java | 19 +++++ .../inaturalist/android/TaxonActivity.java | 7 ++ .../org/inaturalist/android/UserProfile.java | 11 +++ 5 files changed, 103 insertions(+), 11 deletions(-) diff --git a/iNaturalist/src/main/java/org/inaturalist/android/AnalyticsClient.java b/iNaturalist/src/main/java/org/inaturalist/android/AnalyticsClient.java index 514d7aef7..2115fefdb 100644 --- a/iNaturalist/src/main/java/org/inaturalist/android/AnalyticsClient.java +++ b/iNaturalist/src/main/java/org/inaturalist/android/AnalyticsClient.java @@ -1,31 +1,35 @@ package org.inaturalist.android; import android.app.Activity; -import android.app.ActivityManager; import android.app.Application; -import android.content.ComponentName; -import android.content.Context; +import android.content.SharedPreferences; import android.os.Build; import android.os.Bundle; -import android.util.Log; import org.json.JSONException; import org.json.JSONObject; import org.tinylog.Logger; +import java.util.UUID; + /** * Encapsulates the analytics client and adds some extra functionality (singleton class) */ public class AnalyticsClient { + public static final String EVENT_PARAM_CURRENT_USER = "Current User"; + public static final String EVENT_PARAM_VIA = "Via"; public static final String EVENT_PARAM_VALUE_YES = "Yes"; public static final String EVENT_PARAM_VALUE_NO = "No"; + public static final String EVENT_PARAM_NEW_SCREEN = "New Screen"; public static final String EVENT_NAME_APP_LAUNCH = "AppLaunch"; public static final String EVENT_NAME_MENU = "Menu"; + + public static final String EVENT_NAME_CHANGED_SCREEN = "Changed Screen"; public static final String EVENT_NAME_ME = "Me"; public static final String EVENT_NAME_EXPLORE_MAP = "Explore - Map"; public static final String EVENT_NAME_USER_ACTIVITY = "User Activity"; @@ -170,6 +174,10 @@ public class AnalyticsClient { // Viewing obs details public static final String EVENT_NAME_NAVIGATE_OBS_DETAILS = "Navigate - Observations - Details"; + public static final String EVENT_NAME_EXPLORE_VIEW_SPECIES = "Explore - View - Species"; + public static final String EVENT_NAME_EXPLORE_VIEW_OBSERVER = "Explore - View - Observer"; + public static final String EVENT_NAME_EXPLORE_VIEW_IDENTIFIER = "Explore - View - Identifier"; + public static final String EVENT_NAME_EXPLORE_VIEW_OBSERVATION = "Explore - View - Observation"; public static final String EVENT_VALUE_EXPLORE_GRID = "Explore Grid"; public static final String EVENT_VALUE_EXPLORE_LIST = "Explore List"; public static final String EVENT_VALUE_UPDATES = "Updates"; @@ -177,19 +185,46 @@ public class AnalyticsClient { public static final String EVENT_VALUE_ME_TAB = "Me Tab"; public static final String EVENT_VALUE_IDENTIFICATIONS_TAB = "Identifications Tab"; public static final String EVENT_VALUE_PROJECT_DETAILS = "Project Details"; + public static final String EVENT_NAME_EXPLORE_TAB_OBSERVATIONS = "Explore - Tab - Observations"; + public static final String EVENT_NAME_EXPLORE_TAB_SPECIES = "Explore - Tab - Species"; + public static final String EVENT_NAME_EXPLORE_TAB_OBSERVERS = "Explore - Tab - Observers"; + public static final String EVENT_NAME_EXPLORE_TAB_IDENTIFIERS = "Explore - Tab - Identifiers"; + public static final String EVENT_NAME_TAXON_VIEW_MAP = "Taxon - View Map"; + public static final String EVENT_NAME_TAXON_VIEW_OBSERVATIONS = "Taxon - View Observations"; + public static final String EVENT_NAME_TAXON_VIEW_ON_INAT = "Taxon - View On iNat"; + public static final String EVENT_NAME_USER_PROFILE_SEND_MESSAGE = "User Profile - Send Message"; + public static final String EVENT_NAME_USER_PROFILE_VIEW_SPECIES = "User Profile - View Species"; + public static final String EVENT_NAME_USER_PROFILE_VIEW_TAXON = "User Profile - View Taxon"; + public static final String EVENT_NAME_USER_PROFILE_VIEW_OBSERVATIONS = "User Profile - View Observations"; + public static final String EVENT_NAME_USER_PROFILE_VIEW_IDENTIFICATIONS = "User Profile - View Identifications"; + public static final String EVENT_NAME_USER_PROFILE_VIEW_OBSERVATION = "User Profile - View Observation"; + public static final String EVENT_NAME_USER_PROFILE_VIEW_OBSERVATION_VIA_IDENTIFICATION = "User Profile - View Observation Via Identification"; + private static final String TAG = "AnalyticsClient"; // Singleton instance private static AnalyticsClient mAnalyticsClient = null; - private Application mApplication; + private INaturalistApp mApplication; private Activity mCurrentActivity; private boolean mDisabled; - private AnalyticsClient(Application application, boolean disabled) { + private String mAnalyticsID; + + private AnalyticsClient(INaturalistApp application, boolean disabled) { mApplication = application; mDisabled = disabled; + // Generate a random unique ID, to identify the user in an anonymous way + SharedPreferences settings = mApplication.getPrefs(); + if (!settings.contains("analytics_id")) { + String analyticsID = UUID.randomUUID().toString(); + settings.edit().putString("analytics_id", analyticsID).commit(); + } + + mAnalyticsID = settings.getString("analytics_id", null); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { // Modern way of getting current activity application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { @@ -205,6 +240,21 @@ public void onActivityStarted(Activity activity) { @Override public void onActivityResumed(Activity activity) { + + // Log every time the user switches screens (and log via which screen was it made) + if (mCurrentActivity != null) { + try { + JSONObject params = new JSONObject(); + String prevActivityName = getActivityName(activity); + String newActivityName = getActivityName(mCurrentActivity); + params.put(EVENT_PARAM_NEW_SCREEN, newActivityName); + params.put(EVENT_PARAM_VIA, prevActivityName); + logEvent(EVENT_NAME_CHANGED_SCREEN, params); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + mCurrentActivity = activity; } @@ -232,7 +282,7 @@ public void onActivityDestroyed(Activity activity) { } // Initializes the analytics client - should be called from the main activity or application class - public static void initAnalyticsClient(Application application, boolean disabled) { + public static void initAnalyticsClient(INaturalistApp application, boolean disabled) { mAnalyticsClient = new AnalyticsClient(application, disabled); } @@ -259,11 +309,15 @@ public void logEvent(String eventName, JSONObject parameters) { try { if (mCurrentActivity != null) { // Add the via parameter (which indicates the current screen the event was initiated from) - String currentActivityName = getCurrentActivityName(); + String currentActivityName = getActivityName(mCurrentActivity); if (!parameters.has(EVENT_PARAM_VIA)) parameters.put(EVENT_PARAM_VIA, currentActivityName); } - // Currently we've removed all analytics events (not using a tracker like Mixpanel, etc.) + parameters.put(EVENT_PARAM_CURRENT_USER, mAnalyticsID); + + // TODO - in the future, send this event forward + + Logger.tag(TAG).info(String.format("Event: %s - params: %s", eventName, parameters)); } catch (JSONException e) { Logger.tag(TAG).error(e); @@ -271,8 +325,8 @@ public void logEvent(String eventName, JSONObject parameters) { } // Returns current activity name - private String getCurrentActivityName() { - String className = mCurrentActivity.getClass().getName(); + private String getActivityName(Activity activity) { + String className = activity.getClass().getName(); // Extract just the activity class name (not including the package namespace) String[] parts = className.split("\\."); diff --git a/iNaturalist/src/main/java/org/inaturalist/android/BaseFragmentActivity.java b/iNaturalist/src/main/java/org/inaturalist/android/BaseFragmentActivity.java index 032805989..b5385182c 100644 --- a/iNaturalist/src/main/java/org/inaturalist/android/BaseFragmentActivity.java +++ b/iNaturalist/src/main/java/org/inaturalist/android/BaseFragmentActivity.java @@ -802,6 +802,7 @@ public static void signOut(Context context) { prefEditor.remove("user_unread_messages"); prefEditor.remove("user_privileges"); prefEditor.remove("muted_users"); + prefEditor.remove("analytics_id"); prefEditor.commit(); shouldRestart = !prevLocale.equals(""); diff --git a/iNaturalist/src/main/java/org/inaturalist/android/ExploreActivity.java b/iNaturalist/src/main/java/org/inaturalist/android/ExploreActivity.java index 077d0afde..6c4dd834b 100644 --- a/iNaturalist/src/main/java/org/inaturalist/android/ExploreActivity.java +++ b/iNaturalist/src/main/java/org/inaturalist/android/ExploreActivity.java @@ -508,6 +508,15 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse @Override public void onPageSelected(int position) { mActiveViewType = position; + if (mActiveViewType == VIEW_TYPE_IDENTIFIERS) { + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_EXPLORE_TAB_IDENTIFIERS); + } else if (mActiveViewType == VIEW_TYPE_OBSERVATIONS) { + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_EXPLORE_TAB_OBSERVATIONS); + } else if (mActiveViewType == VIEW_TYPE_OBSERVERS) { + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_EXPLORE_TAB_OBSERVERS); + } else if (mActiveViewType == VIEW_TYPE_SPECIES) { + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_EXPLORE_TAB_SPECIES); + } } @Override @@ -1046,6 +1055,8 @@ public void onItemClick(AdapterView arg0, View view, int position, long arg3) eventParams.put(AnalyticsClient.EVENT_PARAM_VIA, AnalyticsClient.EVENT_VALUE_EXPLORE_GRID); AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_NAVIGATE_OBS_DETAILS, eventParams); + + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_EXPLORE_VIEW_OBSERVATION); } catch (JSONException e) { Logger.tag(TAG).error(e); } @@ -1493,6 +1504,8 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { intent.putExtra(TaxonActivity.TAXON, new BetterJSONObject(item)); intent.putExtra(TaxonActivity.DOWNLOAD_TAXON, true); startActivity(intent); + + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_EXPLORE_VIEW_SPECIES); } }; @@ -1510,6 +1523,8 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { Intent intent = new Intent(ExploreActivity.this, UserProfile.class); intent.putExtra("user", new BetterJSONObject(item)); startActivity(intent); + + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_EXPLORE_VIEW_OBSERVER); } }; break; @@ -1527,6 +1542,8 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { Intent intent = new Intent(ExploreActivity.this, UserProfile.class); intent.putExtra("user", new BetterJSONObject(item)); startActivity(intent); + + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_EXPLORE_VIEW_IDENTIFIER); } }; break; @@ -1604,6 +1621,8 @@ public void run() { eventParams.put(AnalyticsClient.EVENT_PARAM_VIA, AnalyticsClient.EVENT_VALUE_EXPLORE_MAP); AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_NAVIGATE_OBS_DETAILS, eventParams); + + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_EXPLORE_VIEW_OBSERVATION); } } catch (IOException e) { Logger.tag(TAG).error(e); diff --git a/iNaturalist/src/main/java/org/inaturalist/android/TaxonActivity.java b/iNaturalist/src/main/java/org/inaturalist/android/TaxonActivity.java index e73f55630..011913eda 100644 --- a/iNaturalist/src/main/java/org/inaturalist/android/TaxonActivity.java +++ b/iNaturalist/src/main/java/org/inaturalist/android/TaxonActivity.java @@ -63,6 +63,7 @@ import com.viewpagerindicator.CirclePageIndicator; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import org.tinylog.Logger; @@ -727,6 +728,8 @@ public void onMapClick(LatLng latLng) { intent.putExtra(TaxonMapActivity.MAP_ZOOM, position.zoom); if (mObservation != null) intent.putExtra(TaxonMapActivity.OBSERVATION, new BetterJSONObject(ObservationUtils.getMinimalObservation(mObservation.getJSONObject()))); startActivity(intent); + + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_TAXON_VIEW_MAP); } }); @@ -791,6 +794,8 @@ public void onClick(View v) { intent1.putExtra(ExploreActivity.SEARCH_FILTERS, searchFilters); intent1.putExtra(ExploreActivity.ACTIVE_TAB, ExploreActivity.VIEW_TYPE_OBSERVATIONS); startActivity(intent1); + + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_TAXON_VIEW_OBSERVATIONS); }); @@ -804,6 +809,8 @@ public void onClick(View v) { Intent i = new Intent(Intent.ACTION_VIEW); i.setData(Uri.parse(taxonUrl)); startActivity(i); + + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_TAXON_VIEW_ON_INAT); } }); diff --git a/iNaturalist/src/main/java/org/inaturalist/android/UserProfile.java b/iNaturalist/src/main/java/org/inaturalist/android/UserProfile.java index ddf88efa8..6110f1793 100644 --- a/iNaturalist/src/main/java/org/inaturalist/android/UserProfile.java +++ b/iNaturalist/src/main/java/org/inaturalist/android/UserProfile.java @@ -177,6 +177,8 @@ protected void onCreate(Bundle savedInstanceState) { intent1.putExtra(NewMessageActivity.USER_ID, mUser.getInt("id")); intent1.putExtra(NewMessageActivity.USERNAME, mUser.getString("login")); startActivityForResult(intent1, NEW_MESSAGE_REQUEST_CODE); + + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_USER_PROFILE_SEND_MESSAGE); }); mSpeciesList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @@ -187,6 +189,8 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { intent.putExtra(TaxonActivity.TAXON, new BetterJSONObject(item)); intent.putExtra(TaxonActivity.DOWNLOAD_TAXON, true); startActivity(intent); + + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_USER_PROFILE_VIEW_TAXON); } }); @@ -205,6 +209,8 @@ public void onItemClick(AdapterView arg0, View view, int position, long arg3) intent.putExtra("read_only", true); intent.putExtra("reload", true); startActivity(intent); + + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_USER_PROFILE_VIEW_OBSERVATION); } }); @@ -259,10 +265,13 @@ public void onClick(View view) { if (view == mShowMoreObservations) { activeTab = ExploreActivity.VIEW_TYPE_OBSERVATIONS; + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_USER_PROFILE_VIEW_OBSERVATIONS); } else if (view == mShowMoreSpecies) { activeTab = ExploreActivity.VIEW_TYPE_SPECIES; + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_USER_PROFILE_VIEW_SPECIES); } else if (view == mShowMoreIdentifications) { activeTab = ExploreActivity.VIEW_TYPE_IDENTIFIERS; + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_USER_PROFILE_VIEW_IDENTIFICATIONS); } intent.putExtra(ExploreActivity.ACTIVE_TAB, activeTab); @@ -714,6 +723,8 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { intent.putExtra("read_only", true); intent.putExtra("reload", true); startActivity(intent); + + AnalyticsClient.getInstance().logEvent(AnalyticsClient.EVENT_NAME_USER_PROFILE_VIEW_OBSERVATION_VIA_IDENTIFICATION); } });