/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ package com.facebook.battery.sample; import android.app.Activity; import android.app.Application; import android.os.Build; import android.os.Bundle; import android.util.Log; import com.facebook.battery.metrics.composite.CompositeMetrics; import com.facebook.battery.metrics.composite.CompositeMetricsCollector; import com.facebook.battery.metrics.core.StatefulSystemMetricsCollector; import com.facebook.battery.metrics.cpu.CpuFrequencyMetrics; import com.facebook.battery.metrics.cpu.CpuFrequencyMetricsCollector; import com.facebook.battery.metrics.cpu.CpuMetrics; import com.facebook.battery.metrics.cpu.CpuMetricsCollector; import com.facebook.battery.metrics.healthstats.HealthStatsMetrics; import com.facebook.battery.metrics.healthstats.HealthStatsMetricsCollector; import com.facebook.battery.metrics.network.NetworkMetrics; import com.facebook.battery.metrics.network.NetworkMetricsCollector; import com.facebook.battery.metrics.time.TimeMetrics; import com.facebook.battery.metrics.time.TimeMetricsCollector; import com.facebook.battery.reporter.composite.CompositeMetricsReporter; import com.facebook.battery.reporter.core.SystemMetricsReporter; import com.facebook.battery.reporter.cpu.CpuFrequencyMetricsReporter; import com.facebook.battery.reporter.cpu.CpuMetricsReporter; import com.facebook.battery.reporter.healthstats.HealthStatsMetricsReporter; import com.facebook.battery.reporter.network.NetworkMetricsReporter; import com.facebook.battery.reporter.time.TimeMetricsReporter; import com.facebook.battery.serializer.composite.CompositeMetricsSerializer; import com.facebook.battery.serializer.cpu.CpuFrequencyMetricsSerializer; import com.facebook.battery.serializer.cpu.CpuMetricsSerializer; import com.facebook.battery.serializer.healthstats.HealthStatsMetricsSerializer; import com.facebook.battery.serializer.network.NetworkMetricsSerializer; import com.facebook.battery.serializer.time.TimeMetricsSerializer; import java.io.*; /** * An application class that maintains a singleton instance of a system wide metrics collector and * demonstrates the battery metrics API as far as possible. Accordingly, this application class - * records cpu time, cpu frequencies, app uptime, and network transfer - creates an "event" every * time an activity is paused or resumed, as a very quick way of breaking down foreground/background * sessions - saves the last session's data to disk, which is loaded when the app is started (you * should really avoid IO on cold start -- I can get away with because this app is otherwise * pointless). * *

Interesting classes are commented with a "Note" -- you can simply look around for it. * *

If you're running the app live, you should run adb logcat -s "BatteryApplication:*" to see the * corresponding output. */ public class BatteryApplication extends Application implements Application.ActivityLifecycleCallbacks { private static final String LAST_SNAPSHOT = "lastsnapshot"; public static volatile BatteryApplication INSTANCE; private CompositeMetricsCollector mMetricsCollector; private StatefulSystemMetricsCollector mStatefulCollector; private CompositeMetricsReporter mMetricsReporter; private CompositeMetricsSerializer mMetricsSerializer; private final SystemMetricsReporter.Event mEvent = new Event(); private void init() { // Note -- Creating a collector instance that's shared across the application can be fairly // useful. You can set it up and hook up all the individual metrics collectors, // tweaking them once. CompositeMetricsCollector.Builder collectorBuilder = new CompositeMetricsCollector.Builder() .addMetricsCollector(TimeMetrics.class, new TimeMetricsCollector()) .addMetricsCollector(CpuFrequencyMetrics.class, new CpuFrequencyMetricsCollector()) .addMetricsCollector(CpuMetrics.class, new CpuMetricsCollector()) .addMetricsCollector(NetworkMetrics.class, new NetworkMetricsCollector(this)); if (Build.VERSION.SDK_INT >= 24) { collectorBuilder.addMetricsCollector( HealthStatsMetrics.class, new HealthStatsMetricsCollector(this)); } mMetricsCollector = collectorBuilder.build(); // Note -- The Reporter and Serializer mimic the collector; they were mainly split out into // separate modules to keep it simple to include only what you really needed. mMetricsReporter = new CompositeMetricsReporter() .addMetricsReporter(TimeMetrics.class, new TimeMetricsReporter()) .addMetricsReporter(CpuMetrics.class, new CpuMetricsReporter()) .addMetricsReporter(CpuFrequencyMetrics.class, new CpuFrequencyMetricsReporter()) .addMetricsReporter(NetworkMetrics.class, new NetworkMetricsReporter()); if (Build.VERSION.SDK_INT >= 24) { mMetricsReporter.addMetricsReporter( HealthStatsMetrics.class, new HealthStatsMetricsReporter()); } mMetricsSerializer = new CompositeMetricsSerializer() .addMetricsSerializer(TimeMetrics.class, new TimeMetricsSerializer()) .addMetricsSerializer(CpuMetrics.class, new CpuMetricsSerializer()) .addMetricsSerializer(CpuFrequencyMetrics.class, new CpuFrequencyMetricsSerializer()) .addMetricsSerializer(NetworkMetrics.class, new NetworkMetricsSerializer()); if (Build.VERSION.SDK_INT >= 24) { mMetricsSerializer.addMetricsSerializer( HealthStatsMetrics.class, new HealthStatsMetricsSerializer()); } // Note -- The stateful collector is a useful abstraction that maintains state about when it // was last triggered, making it simple to observe changes since the last call. // It's a very simple piece of code to reduce boilerplate, you should check out the // underlying source code. mStatefulCollector = new StatefulSystemMetricsCollector<>(mMetricsCollector); } public CompositeMetricsCollector getMetricsCollector() { return mMetricsCollector; } @Override public void onCreate() { super.onCreate(); INSTANCE = this; init(); registerActivityLifecycleCallbacks(this); try (DataInputStream input = new DataInputStream(new FileInputStream(openFile()))) { CompositeMetrics metrics = mMetricsCollector.createMetrics(); // Note -- this reads in from the last serialized value mMetricsSerializer.deserialize(metrics, input); // Note -- We've been careful to have good, readable `toString` implementations for metrics Log.i("BatteryApplication", "Last saved snapshot:\n" + metrics.toString()); } catch (IOException ioe) { Log.e("BatteryApplication", "Failed to deserialize", ioe); } } @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {} @Override public void onActivityStarted(Activity activity) {} @Override public void onActivityResumed(Activity activity) { // Note: Triggering an update / difference on transition logMetrics("background"); } @Override public void onActivityPaused(Activity activity) { // Note: Triggering an update on transition logMetrics("foreground"); } @Override public void onActivityStopped(Activity activity) {} @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} @Override public void onActivityDestroyed(Activity activity) {} private void logMetrics(String tag) { // Note -- this gets the difference from the last call / initialization of the StatefulCollector CompositeMetrics update = mStatefulCollector.getLatestDiffAndReset(); // Check out the Event class in this folder: it should be able to wrap most analytics // implementations comfortably; this one simply logs everything to logcat. mEvent.acquireEvent(null, "BatteryMetrics"); if (mEvent.isSampled()) { mEvent.add("dimension", tag); mMetricsReporter.reportTo(update, mEvent); mEvent.logAndRelease(); } try (DataOutputStream output = new DataOutputStream(new FileOutputStream(openFile()))) { // Save data as required, as cheaply as possible. mMetricsSerializer.serialize(update, output); } catch (IOException ioe) { Log.e("BatteryApplication", "Failed to serialize", ioe); } } private File openFile() { return new File(getFilesDir(), LAST_SNAPSHOT); } }