package com.bugsnag.android; import android.app.ActivityManager; import android.content.Context; import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import java.io.IOException; import java.util.List; /** * Information about the running Android app which can change over time, * including memory usage and active screen information. * <p/> * App information in this class is not cached, and is recalculated every * time toStream is called. */ class AppState implements JsonStream.Streamable { private static final Long startTime = SystemClock.elapsedRealtime(); private final Long duration; private final Boolean inForeground; private final String activeScreen; private final Long memoryUsage; private final Boolean lowMemory; static void init() { } AppState(@NonNull Context appContext) { duration = getDuration(); inForeground = isInForeground(appContext); activeScreen = getActiveScreen(appContext); memoryUsage = getMemoryUsage(); lowMemory = isLowMemory(appContext); } public void toStream(@NonNull JsonStream writer) throws IOException { writer.beginObject(); writer.name("duration").value(duration); writer.name("inForeground").value(inForeground); writer.name("activeScreen").value(activeScreen); writer.name("memoryUsage").value(memoryUsage); writer.name("lowMemory").value(lowMemory); writer.endObject(); } @Nullable public static String getActiveScreenClass(@Nullable String activeScreen) { if (activeScreen != null) { return activeScreen.substring(activeScreen.lastIndexOf('.') + 1); } else { return null; } } /** * Get the actual memory used by the VM (which may not be the total used * by the app in the case of NDK usage). */ @NonNull private static Long getMemoryUsage() { return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); } /** * Check if the device is currently running low on memory. */ @Nullable private static Boolean isLowMemory(Context appContext) { try { ActivityManager activityManager = (ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE); ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); activityManager.getMemoryInfo(memInfo); return memInfo.lowMemory; } catch (Exception e) { Logger.warn("Could not check lowMemory status"); } return null; } /** * Get the name of the top-most activity. Requires the GET_TASKS permission, * which defaults to true in Android 5.0+. */ @Nullable private static String getActiveScreen(Context appContext) { try { ActivityManager activityManager = (ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningTaskInfo> tasks = activityManager.getRunningTasks(1); ActivityManager.RunningTaskInfo runningTask = tasks.get(0); return runningTask.topActivity.getClassName(); } catch (Exception e) { Logger.warn("Could not get active screen information, we recommend granting the 'android.permission.GET_TASKS' permission"); } return null; } /** * Get the name of the top-most activity. Requires the GET_TASKS permission, * which defaults to true in Android 5.0+. */ @Nullable private static Boolean isInForeground(Context appContext) { try { ActivityManager activityManager = (ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningTaskInfo> tasks = activityManager.getRunningTasks(1); if (tasks.isEmpty()) { return false; } ActivityManager.RunningTaskInfo runningTask = tasks.get(0); return runningTask.topActivity.getPackageName().equalsIgnoreCase(appContext.getPackageName()); } catch (Exception e) { Logger.warn("Could not check if app is in the foreground, we recommend granting the 'android.permission.GET_TASKS' permission"); } return null; } /** * Get the time in milliseconds since Bugsnag was initialized, which is a * good approximation for how long the app has been running. */ @NonNull private static Long getDuration() { return SystemClock.elapsedRealtime() - startTime; } }