package net.hockeyapp.android.metrics; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.graphics.Point; import android.os.Build; import android.telephony.TelephonyManager; import android.view.Display; import android.view.WindowManager; import net.hockeyapp.android.BuildConfig; import net.hockeyapp.android.Constants; import net.hockeyapp.android.metrics.model.*; import net.hockeyapp.android.utils.HockeyLog; import net.hockeyapp.android.utils.Util; import java.lang.reflect.Method; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; /** * <h3>Description</h3> * * Class that manages the context in which telemetry items get sent. **/ class TelemetryContext { private static final String TAG = "HockeyApp-Metrics"; /** * Key needed to access the shared preferences of the SDK. */ private static final String SHARED_PREFERENCES_KEY = "HOCKEY_APP_TELEMETRY_CONTEXT"; /** * Key needed to determine, whether we have a new or existing user. */ private static final String SESSION_IS_FIRST_KEY = "SESSION_IS_FIRST"; /** * Device telemetryContext. */ protected final Device mDevice; /** * Session context. */ protected final Session mSession; /** * User context. */ protected final User mUser; /** * Internal context. */ protected final Internal mInternal; /** * Application context. */ protected final Application mApplication; /** * Synchronization LOCK for setting instrumentation key. */ private final Object IKEY_LOCK = new Object(); /** * The application context needed to update some context values. */ protected Context mContext; /** * The shared preferences INSTANCE for reading persistent context. */ private SharedPreferences mSettings; /** * Device context. */ private String mInstrumentationKey; /** * The app's package name. */ private String mPackageName; /** * Constructs a new INSTANCE of TelemetryContext. */ private TelemetryContext() { mDevice = new Device(); mSession = new Session(); mUser = new User(); mApplication = new Application(); mInternal = new Internal(); } /** * Constructs a new INSTANCE of TelemetryContext. * * @param context the context for this telemetryContext * @param appIdentifier the app identifier for this application */ protected TelemetryContext(Context context, String appIdentifier) { this(); mSettings = context.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE); mContext = context; mInstrumentationKey = Util.convertAppIdentifierToGuid(appIdentifier); configDeviceContext(); configUserId(); configInternalContext(); configApplicationContext(); } /** * Updates the session context. * * @param sessionId the current session Id */ protected void renewSessionContext(String sessionId) { configSessionContext(sessionId); } /** * Configure the session context. This is called for each new session. * * @param sessionId the current session Id */ protected void configSessionContext(String sessionId) { HockeyLog.debug(TAG, "Configuring session context"); setSessionId(sessionId); HockeyLog.debug(TAG, "Setting the isNew-flag to true, as we only count new sessions"); setIsNewSession("true"); SharedPreferences.Editor editor = mSettings.edit(); if (!mSettings.getBoolean(SESSION_IS_FIRST_KEY, false)) { editor.putBoolean(SESSION_IS_FIRST_KEY, true); editor.apply(); setIsFirstSession("true"); HockeyLog.debug(TAG, "It's our first session, writing true to SharedPreferences."); } else { setIsFirstSession("false"); HockeyLog.debug(TAG, "It's not their first session, writing false to SharedPreferences."); } } /** * Sets the application telemetryContext tags. */ protected void configApplicationContext() { HockeyLog.debug(TAG, "Configuring application context"); // App version String version = "unknown"; mPackageName = ""; if (Constants.APP_PACKAGE != null) { mPackageName = Constants.APP_PACKAGE; } version = String.format("%s (%S)", Constants.APP_VERSION_NAME, Constants.APP_VERSION); setAppVersion(version); // Hockey SDK version String sdkVersionString = BuildConfig.VERSION_NAME; setSdkVersion("android:" + sdkVersionString); } /** * Load the user context associated with telemetry data. */ protected void configUserId() { HockeyLog.debug(TAG, "Configuring user context"); HockeyLog.debug("Using pre-supplied anonymous device identifier."); setAnonymousUserId(Constants.CRASH_IDENTIFIER); } /** * Sets the device telemetryContext tags. */ protected void configDeviceContext() { HockeyLog.debug(TAG, "Configuring device context"); setOsVersion(Build.VERSION.RELEASE); setOsName("Android"); setDeviceModel(Build.MODEL); setDeviceOemName(Build.MANUFACTURER); setOsLocale(Locale.getDefault().toString()); setOsLanguage(Locale.getDefault().getLanguage()); updateScreenResolution(); setDeviceId(Constants.DEVICE_IDENTIFIER); // check device type final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); if (telephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE) { setDeviceType("Phone"); } else { setDeviceType("Tablet"); } // detect emulator if (Util.isEmulator()) { setDeviceModel("[Emulator]" + mDevice.getModel()); } } @SuppressWarnings("deprecation") @SuppressLint({"NewApi", "Deprecation"}) protected void updateScreenResolution() { String resolutionString; int width; int height; if (mContext != null) { WindowManager wm = (WindowManager) mContext.getSystemService( Context.WINDOW_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { Point size = new Point(); Display d = wm.getDefaultDisplay(); if (d != null) { d.getRealSize(size); width = size.x; height = size.y; } else { width = 0; height = 0; } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { try { //We have to use undocumented API here. Android 4.0 introduced soft buttons for //back, home and menu, but there's no API present to get the real display size //all available methods only return the size of the contentView. Method mGetRawW = Display.class.getMethod("getRawWidth"); Method mGetRawH = Display.class.getMethod("getRawHeight"); Display display = wm.getDefaultDisplay(); width = (Integer) mGetRawW.invoke(display); height = (Integer) mGetRawH.invoke(display); } catch (Exception ex) { Point size = new Point(); Display d = wm.getDefaultDisplay(); if (d != null) { d.getRealSize(size); width = size.x; height = size.y; } else { width = 0; height = 0; } HockeyLog.debug(TAG, "Couldn't determine screen resolution: " + ex.toString()); } } else { //Use old, and now deprecated API to get width and height of the display Display d = wm.getDefaultDisplay(); width = d.getWidth(); height = d.getHeight(); } resolutionString = String.valueOf(height) + "x" + String.valueOf(width); setScreenResolution(resolutionString); } } /** * Sets the internal package context. */ protected void configInternalContext() { String sdkVersionString = BuildConfig.VERSION_NAME; setSdkVersion("android:" + sdkVersionString); } /** * The package name. */ protected String getPackageName() { return mPackageName; } protected Map<String, String> getContextTags() { Map<String, String> contextTags = new LinkedHashMap<>(); synchronized (mApplication) { mApplication.addToHashMap(contextTags); } synchronized (mDevice) { mDevice.addToHashMap(contextTags); } synchronized (mSession) { mSession.addToHashMap(contextTags); } synchronized (mUser) { mUser.addToHashMap(contextTags); } synchronized (mInternal) { mInternal.addToHashMap(contextTags); } return contextTags; } public String getInstrumentationKey() { synchronized (IKEY_LOCK) { return mInstrumentationKey; } } public synchronized void setInstrumentationKey(String instrumentationKey) { synchronized (IKEY_LOCK) { mInstrumentationKey = instrumentationKey; } } public String getScreenResolution() { synchronized (mDevice) { return mDevice.getScreenResolution(); } } public void setScreenResolution(String screenResolution) { synchronized (mDevice) { mDevice.setScreenResolution(screenResolution); } } public String getAppVersion() { synchronized (mApplication) { return mApplication.getVer(); } } public void setAppVersion(String appVersion) { synchronized (mApplication) { mApplication.setVer(appVersion); } } public String getAnonymousUserId() { synchronized (mUser) { return mUser.getId(); } } public void setAnonymousUserId(String userId) { synchronized (mUser) { mUser.setId(userId); } } public String getSdkVersion() { synchronized (mInternal) { return mInternal.getSdkVersion(); } } public void setSdkVersion(String sdkVersion) { synchronized (mInternal) { mInternal.setSdkVersion(sdkVersion); } } public String getSessionId() { synchronized (mSession) { return mSession.getId(); } } public void setSessionId(String sessionId) { synchronized (mSession) { mSession.setId(sessionId); } } public String getIsFirstSession() { synchronized (mSession) { return mSession.getIsFirst(); } } public void setIsFirstSession(String isFirst) { synchronized (mSession) { mSession.setIsFirst(isFirst); } } public String getIsNewSession() { synchronized (mSession) { return mSession.getIsNew(); } } public void setIsNewSession(String isNewSession) { synchronized (mSession) { mSession.setIsNew(isNewSession); } } public String getOsVersion() { synchronized (mDevice) { return mDevice.getOsVersion(); } } public void setOsVersion(String osVersion) { synchronized (mDevice) { mDevice.setOsVersion(osVersion); } } public String getOsName() { synchronized (mDevice) { return mDevice.getOs(); } } public void setOsName(String osName) { synchronized (mDevice) { mDevice.setOs(osName); } } public String getDeviceModel() { synchronized (mDevice) { return mDevice.getModel(); } } public void setDeviceModel(String deviceModel) { synchronized (mDevice) { mDevice.setModel(deviceModel); } } public String getDeviceOemName() { synchronized (mDevice) { return mDevice.getOemName(); } } public void setDeviceOemName(String deviceOemName) { synchronized (mDevice) { mDevice.setOemName(deviceOemName); } } public String getOsLocale() { synchronized (mDevice) { return mDevice.getLocale(); } } public void setOsLocale(String osLocale) { synchronized (mDevice) { mDevice.setLocale(osLocale); } } public String getOSLanguage() { synchronized (mDevice) { return mDevice.getLanguage(); } } public void setOsLanguage(String osLanguage) { synchronized (mDevice) { mDevice.setLanguage(osLanguage); } } public String getDeviceId() { return mDevice.getId(); } public void setDeviceId(String deviceId) { synchronized (mDevice) { mDevice.setId(deviceId); } } public String getDeviceType() { return mDevice.getType(); } public void setDeviceType(String deviceType) { synchronized (mDevice) { mDevice.setType(deviceType); } } }