Merge "Add API to differentiate PiP content"
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index fb6eb97..3f3b202 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -106,7 +106,6 @@
import com.android.internal.power.MeasuredEnergyStats;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
@@ -122,7 +121,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -1080,16 +1078,6 @@
private long[] mCpuFreqs;
/**
- * Times spent by the system server process grouped by cluster and CPU speed.
- */
- private LongSamplingCounterArray mSystemServerCpuTimesUs;
-
- /**
- * Times spent by the system server threads grouped by cluster and CPU speed.
- */
- private LongSamplingCounterArray mSystemServerThreadCpuTimesUs;
-
- /**
* Times spent by the system server threads handling incoming binder requests.
*/
private LongSamplingCounterArray mBinderThreadCpuTimesUs;
@@ -10756,6 +10744,14 @@
}
}
+ /**
+ * Starts tracking CPU time-in-state for threads of the system server process,
+ * keeping a separate account of threads receiving incoming binder calls.
+ */
+ public void startTrackingSystemServerCpuTime() {
+ mSystemServerCpuThreadReader.startTrackingThreadCpuTime();
+ }
+
public void setCallback(BatteryCallback cb) {
mCallback = cb;
}
@@ -11411,8 +11407,6 @@
mExternalSync.scheduleSync("reset", ExternalStatsSync.UPDATE_ENERGY);
}
- resetIfNotNull(mSystemServerCpuTimesUs, false, elapsedRealtimeUs);
- resetIfNotNull(mSystemServerThreadCpuTimesUs, false, elapsedRealtimeUs);
resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs);
mLastHistoryStepDetails = null;
@@ -12511,27 +12505,17 @@
return;
}
- if (mSystemServerCpuTimesUs == null) {
- mSystemServerCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
- mSystemServerThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
+ if (mBinderThreadCpuTimesUs == null) {
mBinderThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
}
- mSystemServerCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.processCpuTimesUs);
- mSystemServerThreadCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.threadCpuTimesUs);
mBinderThreadCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
if (DEBUG_BINDER_STATS) {
- Slog.d(TAG, "System server threads per CPU cluster (binder threads/total threads/%)");
- long totalCpuTimeMs = 0;
- long totalThreadTimeMs = 0;
+ Slog.d(TAG, "System server threads per CPU cluster (incoming binder threads)");
long binderThreadTimeMs = 0;
int cpuIndex = 0;
- final long[] systemServerCpuTimesUs =
- mSystemServerCpuTimesUs.getCountsLocked(0);
- final long[] systemServerThreadCpuTimesUs =
- mSystemServerThreadCpuTimesUs.getCountsLocked(0);
- final long[] binderThreadCpuTimesUs =
- mBinderThreadCpuTimesUs.getCountsLocked(0);
+ final long[] binderThreadCpuTimesUs = mBinderThreadCpuTimesUs.getCountsLocked(
+ BatteryStats.STATS_SINCE_CHARGED);
int index = 0;
int numCpuClusters = mPowerProfile.getNumCpuClusters();
for (int cluster = 0; cluster < numCpuClusters; cluster++) {
@@ -12542,28 +12526,15 @@
if (speed != 0) {
sb.append(", ");
}
- long totalCountMs = systemServerThreadCpuTimesUs[index] / 1000;
long binderCountMs = binderThreadCpuTimesUs[index] / 1000;
- sb.append(String.format("%d/%d(%.1f%%)",
- binderCountMs,
- totalCountMs,
- totalCountMs != 0 ? (double) binderCountMs * 100 / totalCountMs : 0));
+ sb.append(TextUtils.formatSimple("%10d", binderCountMs));
- totalCpuTimeMs += systemServerCpuTimesUs[index] / 1000;
- totalThreadTimeMs += totalCountMs;
binderThreadTimeMs += binderCountMs;
index++;
}
cpuIndex += mPowerProfile.getNumCoresInCpuCluster(cluster);
Slog.d(TAG, sb.toString());
}
-
- Slog.d(TAG, "Total system server CPU time (ms): " + totalCpuTimeMs);
- Slog.d(TAG, "Total system server thread time (ms): " + totalThreadTimeMs);
- Slog.d(TAG, String.format("Total Binder thread time (ms): %d (%.1f%%)",
- binderThreadTimeMs,
- binderThreadTimeMs != 0
- ? (double) binderThreadTimeMs * 100 / totalThreadTimeMs : 0));
}
}
@@ -13853,60 +13824,16 @@
}
+ /**
+ * Estimates the time spent by the system server handling incoming binder requests.
+ */
@Override
public long[] getSystemServiceTimeAtCpuSpeeds() {
- // Estimates the time spent by the system server handling incoming binder requests.
- //
- // The data that we can get from the kernel is this:
- // - CPU duration for a (thread - cluster - CPU speed) combination
- // - CPU duration for a (UID - cluster - CPU speed) combination
- //
- // The configuration we have in the Power Profile is this:
- // - Average CPU power for a (cluster - CPU speed) combination.
- //
- // The model used by BatteryStats can be illustrated with this example:
- //
- // - Let's say the system server has 10 threads.
- // - These 10 threads spent 1000 ms of CPU time in aggregate
- // - Of the 10 threads 4 were execute exclusively incoming binder calls.
- // - These 4 "binder" threads consumed 600 ms of CPU time in aggregate
- // - The real time spent by the system server process doing all of this is, say, 200 ms.
- //
- // We will assume that power consumption is proportional to the time spent by the CPU
- // across all threads. This is a crude assumption, but we don't have more detailed data.
- // Thus,
- // binderRealTime = realTime * aggregateBinderThreadTime / aggregateAllThreadTime
- //
- // In our example,
- // binderRealTime = 200 * 600 / 1000 = 120ms
- //
- // We can then multiply this estimated time by the average power to obtain an estimate
- // of the total power consumed by incoming binder calls for the given cluster/speed
- // combination.
-
- if (mSystemServerCpuTimesUs == null) {
+ if (mBinderThreadCpuTimesUs == null) {
return null;
}
- final long[] systemServerCpuTimesUs = mSystemServerCpuTimesUs.getCountsLocked(
- BatteryStats.STATS_SINCE_CHARGED);
- final long [] systemServerThreadCpuTimesUs = mSystemServerThreadCpuTimesUs.getCountsLocked(
- BatteryStats.STATS_SINCE_CHARGED);
- final long[] binderThreadCpuTimesUs = mBinderThreadCpuTimesUs.getCountsLocked(
- BatteryStats.STATS_SINCE_CHARGED);
-
- final int size = systemServerCpuTimesUs.length;
- final long[] results = new long[size];
-
- for (int i = 0; i < size; i++) {
- if (systemServerThreadCpuTimesUs[i] == 0) {
- continue;
- }
-
- results[i] = systemServerCpuTimesUs[i] * binderThreadCpuTimesUs[i]
- / systemServerThreadCpuTimesUs[i];
- }
- return results;
+ return mBinderThreadCpuTimesUs.getCountsLocked(BatteryStats.STATS_SINCE_CHARGED);
}
/**
@@ -14306,7 +14233,7 @@
}
updateSystemServiceCallStats();
- if (mSystemServerThreadCpuTimesUs != null) {
+ if (mBinderThreadCpuTimesUs != null) {
pw.println("Per UID System server binder time in ms:");
long[] systemServiceTimeAtCpuSpeeds = getSystemServiceTimeAtCpuSpeeds();
for (int i = 0; i < size; i++) {
@@ -15872,9 +15799,6 @@
mUidStats.append(uid, u);
}
- mSystemServerCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase);
- mSystemServerThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in,
- mOnBatteryTimeBase);
mBinderThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase);
}
@@ -16083,8 +16007,6 @@
} else {
out.writeInt(0);
}
- LongSamplingCounterArray.writeToParcel(out, mSystemServerCpuTimesUs);
- LongSamplingCounterArray.writeToParcel(out, mSystemServerThreadCpuTimesUs);
LongSamplingCounterArray.writeToParcel(out, mBinderThreadCpuTimesUs);
}
diff --git a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
index e6a9623..4d2a08a 100644
--- a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
@@ -16,23 +16,12 @@
package com.android.internal.os;
-import static android.os.Process.PROC_OUT_LONG;
-import static android.os.Process.PROC_SPACE_TERM;
-
import android.annotation.Nullable;
-import android.os.Process;
-import android.system.Os;
-import android.system.OsConstants;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
-import java.nio.file.DirectoryIteratorException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Arrays;
/**
@@ -45,93 +34,65 @@
private static final String TAG = "KernelSingleProcCpuThreadRdr";
private static final boolean DEBUG = false;
- private static final boolean NATIVE_ENABLED = true;
-
- /**
- * The name of the file to read CPU statistics from, must be found in {@code
- * /proc/$PID/task/$TID}
- */
- private static final String CPU_STATISTICS_FILENAME = "time_in_state";
-
- private static final String PROC_STAT_FILENAME = "stat";
-
- /** Directory under /proc/$PID containing CPU stats files for threads */
- public static final String THREAD_CPU_STATS_DIRECTORY = "task";
-
- /** Default mount location of the {@code proc} filesystem */
- private static final Path DEFAULT_PROC_PATH = Paths.get("/proc");
-
- /** The initial {@code time_in_state} file for {@link ProcTimeInStateReader} */
- private static final Path INITIAL_TIME_IN_STATE_PATH = Paths.get("self/time_in_state");
-
- /** See https://man7.org/linux/man-pages/man5/proc.5.html */
- private static final int[] PROCESS_FULL_STATS_FORMAT = new int[] {
- PROC_SPACE_TERM,
- PROC_SPACE_TERM,
- PROC_SPACE_TERM,
- PROC_SPACE_TERM,
- PROC_SPACE_TERM,
- PROC_SPACE_TERM,
- PROC_SPACE_TERM,
- PROC_SPACE_TERM,
- PROC_SPACE_TERM,
- PROC_SPACE_TERM,
- PROC_SPACE_TERM,
- PROC_SPACE_TERM,
- PROC_SPACE_TERM,
- PROC_SPACE_TERM | PROC_OUT_LONG, // 14: utime
- PROC_SPACE_TERM | PROC_OUT_LONG, // 15: stime
- // Ignore remaining fields
- };
-
- private final long[] mProcessFullStatsData = new long[2];
-
- private static final int PROCESS_FULL_STAT_UTIME = 0;
- private static final int PROCESS_FULL_STAT_STIME = 1;
-
- /** Used to read and parse {@code time_in_state} files */
- private final ProcTimeInStateReader mProcTimeInStateReader;
private final int mPid;
- /** Where the proc filesystem is mounted */
- private final Path mProcPath;
+ private final CpuTimeInStateReader mCpuTimeInStateReader;
- // How long a CPU jiffy is in milliseconds.
- private final long mJiffyMillis;
-
- // Path: /proc/<pid>/stat
- private final String mProcessStatFilePath;
-
- // Path: /proc/<pid>/task
- private final Path mThreadsDirectoryPath;
+ private int[] mSelectedThreadNativeTids = new int[0]; // Sorted
/**
- * Count of frequencies read from the {@code time_in_state} file. Read from {@link
- * #mProcTimeInStateReader#getCpuFrequenciesKhz()}.
+ * Count of frequencies read from the {@code time_in_state} file.
*/
private int mFrequencyCount;
+ private boolean mIsTracking;
+
+ /**
+ * A CPU time-in-state provider for testing. Imitates the behavior of the corresponding
+ * methods in frameworks/native/libs/cputimeinstate/cputimeinstate.c
+ */
+ @VisibleForTesting
+ public interface CpuTimeInStateReader {
+ /**
+ * Returns the overall number of cluster-frequency combinations.
+ */
+ int getCpuFrequencyCount();
+
+ /**
+ * Returns true to indicate success.
+ *
+ * Called from native.
+ */
+ boolean startTrackingProcessCpuTimes(int tgid);
+
+ /**
+ * Returns true to indicate success.
+ *
+ * Called from native.
+ */
+ boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey);
+
+ /**
+ * Must return an array of strings formatted like this:
+ * "aggKey:t0_0 t0_1...:t1_0 t1_1..."
+ * Times should be provided in nanoseconds.
+ *
+ * Called from native.
+ */
+ String[] getAggregatedTaskCpuFreqTimes(int pid);
+ }
+
/**
* Create with a path where `proc` is mounted. Used primarily for testing
*
* @param pid PID of the process whose threads are to be read.
- * @param procPath where `proc` is mounted (to find, see {@code mount | grep ^proc})
*/
@VisibleForTesting
- public KernelSingleProcessCpuThreadReader(
- int pid,
- Path procPath) throws IOException {
+ public KernelSingleProcessCpuThreadReader(int pid,
+ @Nullable CpuTimeInStateReader cpuTimeInStateReader) throws IOException {
mPid = pid;
- mProcPath = procPath;
- mProcTimeInStateReader = new ProcTimeInStateReader(
- mProcPath.resolve(INITIAL_TIME_IN_STATE_PATH));
- long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
- mJiffyMillis = 1000 / jiffyHz;
- mProcessStatFilePath =
- mProcPath.resolve(String.valueOf(mPid)).resolve(PROC_STAT_FILENAME).toString();
- mThreadsDirectoryPath =
- mProcPath.resolve(String.valueOf(mPid)).resolve(THREAD_CPU_STATS_DIRECTORY);
+ mCpuTimeInStateReader = cpuTimeInStateReader;
}
/**
@@ -142,7 +103,7 @@
@Nullable
public static KernelSingleProcessCpuThreadReader create(int pid) {
try {
- return new KernelSingleProcessCpuThreadReader(pid, DEFAULT_PROC_PATH);
+ return new KernelSingleProcessCpuThreadReader(pid, null);
} catch (IOException e) {
Slog.e(TAG, "Failed to initialize KernelSingleProcessCpuThreadReader", e);
return null;
@@ -150,146 +111,98 @@
}
/**
- * Get the CPU frequencies that correspond to the times reported in {@link
- * ProcessCpuUsage#processCpuTimesMillis} etc.
+ * Starts tracking aggregated CPU time-in-state of all threads of the process with the PID
+ * supplied in the constructor.
+ */
+ public void startTrackingThreadCpuTimes() {
+ if (!mIsTracking) {
+ if (!startTrackingProcessCpuTimes(mPid, mCpuTimeInStateReader)) {
+ Slog.e(TAG, "Failed to start tracking process CPU times for " + mPid);
+ }
+ if (mSelectedThreadNativeTids.length > 0) {
+ if (!startAggregatingThreadCpuTimes(mSelectedThreadNativeTids,
+ mCpuTimeInStateReader)) {
+ Slog.e(TAG, "Failed to start tracking aggregated thread CPU times for "
+ + Arrays.toString(mSelectedThreadNativeTids));
+ }
+ }
+ mIsTracking = true;
+ }
+ }
+
+ /**
+ * @param nativeTids an array of native Thread IDs whose CPU times should
+ * be aggregated as a group. This is expected to be a subset
+ * of all thread IDs owned by the process.
+ */
+ public void setSelectedThreadIds(int[] nativeTids) {
+ mSelectedThreadNativeTids = nativeTids.clone();
+ if (mIsTracking) {
+ startAggregatingThreadCpuTimes(mSelectedThreadNativeTids, mCpuTimeInStateReader);
+ }
+ }
+
+ /**
+ * Get the CPU frequencies that correspond to the times reported in {@link ProcessCpuUsage}.
*/
public int getCpuFrequencyCount() {
if (mFrequencyCount == 0) {
- mFrequencyCount = mProcTimeInStateReader.getFrequenciesKhz().length;
+ mFrequencyCount = getCpuFrequencyCount(mCpuTimeInStateReader);
}
return mFrequencyCount;
}
/**
- * Get the total and per-thread CPU usage of the process with the PID specified in the
- * constructor.
- *
- * @param selectedThreadIds a SORTED array of native Thread IDs whose CPU times should
- * be aggregated as a group. This is expected to be a subset
- * of all thread IDs owned by the process.
+ * Get the total CPU usage of the process with the PID specified in the
+ * constructor. The CPU usage time is aggregated across all threads and may
+ * exceed the time the entire process has been running.
*/
@Nullable
- public ProcessCpuUsage getProcessCpuUsage(int[] selectedThreadIds) {
+ public ProcessCpuUsage getProcessCpuUsage() {
if (DEBUG) {
- Slog.d(TAG, "Reading CPU thread usages with directory " + mProcPath + " process ID "
- + mPid);
+ Slog.d(TAG, "Reading CPU thread usages for PID " + mPid);
}
- int cpuFrequencyCount = getCpuFrequencyCount();
- ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(cpuFrequencyCount);
+ ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(getCpuFrequencyCount());
- if (NATIVE_ENABLED) {
- boolean result = readProcessCpuUsage(mProcPath.toString(), mPid,
- selectedThreadIds, processCpuUsage.processCpuTimesMillis,
- processCpuUsage.threadCpuTimesMillis,
- processCpuUsage.selectedThreadCpuTimesMillis);
- if (!result) {
- return null;
- }
- return processCpuUsage;
- }
-
- if (!isSorted(selectedThreadIds)) {
- throw new IllegalArgumentException("selectedThreadIds is not sorted: "
- + Arrays.toString(selectedThreadIds));
- }
-
- if (!Process.readProcFile(mProcessStatFilePath, PROCESS_FULL_STATS_FORMAT, null,
- mProcessFullStatsData, null)) {
- Slog.e(TAG, "Failed to read process stat file " + mProcessStatFilePath);
+ boolean result = readProcessCpuUsage(mPid,
+ processCpuUsage.threadCpuTimesMillis,
+ processCpuUsage.selectedThreadCpuTimesMillis,
+ mCpuTimeInStateReader);
+ if (!result) {
return null;
}
- long utime = mProcessFullStatsData[PROCESS_FULL_STAT_UTIME];
- long stime = mProcessFullStatsData[PROCESS_FULL_STAT_STIME];
-
- long processCpuTimeMillis = (utime + stime) * mJiffyMillis;
-
- try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(mThreadsDirectoryPath)) {
- for (Path threadDirectory : threadPaths) {
- readThreadCpuUsage(processCpuUsage, selectedThreadIds, threadDirectory);
- }
- } catch (IOException | DirectoryIteratorException e) {
- // Expected when a process finishes
- return null;
- }
-
- // Estimate per cluster per frequency CPU time for the entire process
- // by distributing the total process CPU time proportionately to how much
- // CPU time its threads took on those clusters/frequencies. This algorithm
- // works more accurately when when we have equally distributed concurrency.
- // TODO(b/169279846): obtain actual process CPU times from the kernel
- long totalCpuTimeAllThreads = 0;
- for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
- totalCpuTimeAllThreads += processCpuUsage.threadCpuTimesMillis[i];
- }
-
- for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
- processCpuUsage.processCpuTimesMillis[i] =
- processCpuTimeMillis * processCpuUsage.threadCpuTimesMillis[i]
- / totalCpuTimeAllThreads;
+ if (DEBUG) {
+ Slog.d(TAG, "threadCpuTimesMillis = "
+ + Arrays.toString(processCpuUsage.threadCpuTimesMillis));
+ Slog.d(TAG, "selectedThreadCpuTimesMillis = "
+ + Arrays.toString(processCpuUsage.selectedThreadCpuTimesMillis));
}
return processCpuUsage;
}
- /**
- * Reads a thread's CPU usage and aggregates the per-cluster per-frequency CPU times.
- *
- * @param threadDirectory the {@code /proc} directory of the thread
- */
- private void readThreadCpuUsage(ProcessCpuUsage processCpuUsage, int[] selectedThreadIds,
- Path threadDirectory) {
- // Get the thread ID from the directory name
- final int threadId;
- try {
- final String directoryName = threadDirectory.getFileName().toString();
- threadId = Integer.parseInt(directoryName);
- } catch (NumberFormatException e) {
- Slog.w(TAG, "Failed to parse thread ID when iterating over /proc/*/task", e);
- return;
- }
-
- // Get the CPU statistics from the directory
- final Path threadCpuStatPath = threadDirectory.resolve(CPU_STATISTICS_FILENAME);
- final long[] cpuUsages = mProcTimeInStateReader.getUsageTimesMillis(threadCpuStatPath);
- if (cpuUsages == null) {
- return;
- }
-
- final int cpuFrequencyCount = getCpuFrequencyCount();
- final boolean isSelectedThread = Arrays.binarySearch(selectedThreadIds, threadId) >= 0;
- for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
- processCpuUsage.threadCpuTimesMillis[i] += cpuUsages[i];
- if (isSelectedThread) {
- processCpuUsage.selectedThreadCpuTimesMillis[i] += cpuUsages[i];
- }
- }
- }
-
/** CPU usage of a process, all of its threads and a selected subset of its threads */
public static class ProcessCpuUsage {
- public long[] processCpuTimesMillis;
public long[] threadCpuTimesMillis;
public long[] selectedThreadCpuTimesMillis;
public ProcessCpuUsage(int cpuFrequencyCount) {
- processCpuTimesMillis = new long[cpuFrequencyCount];
threadCpuTimesMillis = new long[cpuFrequencyCount];
selectedThreadCpuTimesMillis = new long[cpuFrequencyCount];
}
}
- private static boolean isSorted(int[] array) {
- for (int i = 0; i < array.length - 1; i++) {
- if (array[i] > array[i + 1]) {
- return false;
- }
- }
- return true;
- }
+ private native int getCpuFrequencyCount(CpuTimeInStateReader reader);
- private native boolean readProcessCpuUsage(String procPath, int pid, int[] selectedThreadIds,
- long[] processCpuTimesMillis, long[] threadCpuTimesMillis,
- long[] selectedThreadCpuTimesMillis);
+ private native boolean startTrackingProcessCpuTimes(int pid, CpuTimeInStateReader reader);
+
+ private native boolean startAggregatingThreadCpuTimes(int[] selectedThreadIds,
+ CpuTimeInStateReader reader);
+
+ private native boolean readProcessCpuUsage(int pid,
+ long[] threadCpuTimesMillis,
+ long[] selectedThreadCpuTimesMillis,
+ CpuTimeInStateReader reader);
}
diff --git a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
index fbbee94..fbad75e 100644
--- a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
+++ b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
@@ -22,8 +22,6 @@
import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Arrays;
/**
* Reads /proc/UID/task/TID/time_in_state files to obtain statistics on CPU usage
@@ -31,9 +29,7 @@
*/
public class SystemServerCpuThreadReader {
private final KernelSingleProcessCpuThreadReader mKernelCpuThreadReader;
- private int[] mBinderThreadNativeTids = new int[0]; // Sorted
- private long[] mLastProcessCpuTimeUs;
private long[] mLastThreadCpuTimesUs;
private long[] mLastBinderThreadCpuTimesUs;
@@ -41,8 +37,6 @@
* Times (in microseconds) spent by the system server UID.
*/
public static class SystemServiceCpuThreadTimes {
- // The entire process
- public long[] processCpuTimesUs;
// All threads
public long[] threadCpuTimesUs;
// Just the threads handling incoming binder calls
@@ -61,8 +55,10 @@
}
@VisibleForTesting
- public SystemServerCpuThreadReader(Path procPath, int pid) throws IOException {
- this(new KernelSingleProcessCpuThreadReader(pid, procPath));
+ public SystemServerCpuThreadReader(int pid,
+ KernelSingleProcessCpuThreadReader.CpuTimeInStateReader cpuTimeInStateReader)
+ throws IOException {
+ this(new KernelSingleProcessCpuThreadReader(pid, cpuTimeInStateReader));
}
@VisibleForTesting
@@ -70,9 +66,15 @@
mKernelCpuThreadReader = kernelCpuThreadReader;
}
+ /**
+ * Start tracking CPU time-in-state for the process specified in the constructor.
+ */
+ public void startTrackingThreadCpuTime() {
+ mKernelCpuThreadReader.startTrackingThreadCpuTimes();
+ }
+
public void setBinderThreadNativeTids(int[] nativeTids) {
- mBinderThreadNativeTids = nativeTids.clone();
- Arrays.sort(mBinderThreadNativeTids);
+ mKernelCpuThreadReader.setSelectedThreadIds(nativeTids);
}
/**
@@ -81,33 +83,27 @@
@Nullable
public SystemServiceCpuThreadTimes readDelta() {
final int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequencyCount();
- if (mLastProcessCpuTimeUs == null) {
- mLastProcessCpuTimeUs = new long[numCpuFrequencies];
+ if (mLastThreadCpuTimesUs == null) {
mLastThreadCpuTimesUs = new long[numCpuFrequencies];
mLastBinderThreadCpuTimesUs = new long[numCpuFrequencies];
- mDeltaCpuThreadTimes.processCpuTimesUs = new long[numCpuFrequencies];
mDeltaCpuThreadTimes.threadCpuTimesUs = new long[numCpuFrequencies];
mDeltaCpuThreadTimes.binderThreadCpuTimesUs = new long[numCpuFrequencies];
}
final KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage =
- mKernelCpuThreadReader.getProcessCpuUsage(mBinderThreadNativeTids);
+ mKernelCpuThreadReader.getProcessCpuUsage();
if (processCpuUsage == null) {
return null;
}
for (int i = numCpuFrequencies - 1; i >= 0; i--) {
- long processCpuTimesUs = processCpuUsage.processCpuTimesMillis[i] * 1000;
long threadCpuTimesUs = processCpuUsage.threadCpuTimesMillis[i] * 1000;
long binderThreadCpuTimesUs = processCpuUsage.selectedThreadCpuTimesMillis[i] * 1000;
- mDeltaCpuThreadTimes.processCpuTimesUs[i] =
- Math.max(0, processCpuTimesUs - mLastProcessCpuTimeUs[i]);
mDeltaCpuThreadTimes.threadCpuTimesUs[i] =
Math.max(0, threadCpuTimesUs - mLastThreadCpuTimesUs[i]);
mDeltaCpuThreadTimes.binderThreadCpuTimesUs[i] =
Math.max(0, binderThreadCpuTimesUs - mLastBinderThreadCpuTimesUs[i]);
- mLastProcessCpuTimeUs[i] = processCpuTimesUs;
mLastThreadCpuTimesUs[i] = threadCpuTimesUs;
mLastBinderThreadCpuTimesUs[i] = binderThreadCpuTimesUs;
}
diff --git a/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp b/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
index 52bed6b..dfae684 100644
--- a/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
+++ b/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
@@ -26,239 +26,230 @@
#include <android_runtime/Log.h>
#include <nativehelper/ScopedPrimitiveArray.h>
-#include <nativehelper/ScopedUtfChars.h>
namespace android {
+static constexpr uint16_t DEFAULT_THREAD_AGGREGATION_KEY = 0;
+static constexpr uint16_t SELECTED_THREAD_AGGREGATION_KEY = 1;
+
+static constexpr uint64_t NSEC_PER_MSEC = 1000000;
+
// Number of milliseconds in a jiffy - the unit of time measurement for processes and threads
static const uint32_t gJiffyMillis = (uint32_t)(1000 / sysconf(_SC_CLK_TCK));
-// Given a PID, returns a vector of all TIDs for the process' tasks. Thread IDs are
-// file names in the /proc/<pid>/task directory.
-static bool getThreadIds(const std::string &procPath, const pid_t pid,
- std::vector<pid_t> &outThreadIds) {
- std::string taskPath = android::base::StringPrintf("%s/%u/task", procPath.c_str(), pid);
+// Abstract class for readers of CPU time-in-state. There are two implementations of
+// this class: BpfCpuTimeInStateReader and MockCpuTimeInStateReader. The former is used
+// by the production code. The latter is used by unit tests to provide mock
+// CPU time-in-state data via a Java implementation.
+class ICpuTimeInStateReader {
+public:
+ virtual ~ICpuTimeInStateReader() {}
- struct dirent **dirlist;
- int threadCount = scandir(taskPath.c_str(), &dirlist, NULL, NULL);
- if (threadCount == -1) {
- ALOGE("Cannot read directory %s", taskPath.c_str());
- return false;
- }
+ // Returns the overall number of cluser-frequency combinations
+ virtual size_t getCpuFrequencyCount();
- outThreadIds.reserve(threadCount);
+ // Marks the CPU time-in-state tracking for threads of the specified TGID
+ virtual bool startTrackingProcessCpuTimes(pid_t) = 0;
- for (int i = 0; i < threadCount; i++) {
- pid_t tid;
- if (android::base::ParseInt<pid_t>(dirlist[i]->d_name, &tid)) {
- outThreadIds.push_back(tid);
+ // Marks the thread specified by its PID for CPU time-in-state tracking.
+ virtual bool startAggregatingTaskCpuTimes(pid_t, uint16_t) = 0;
+
+ // Retrieves the accumulated time-in-state data, which is organized as a map
+ // from aggregation keys to vectors of vectors using the format:
+ // { aggKey0 -> [[t0_0_0, t0_0_1, ...], [t0_1_0, t0_1_1, ...], ...],
+ // aggKey1 -> [[t1_0_0, t1_0_1, ...], [t1_1_0, t1_1_1, ...], ...], ... }
+ // where ti_j_k is the ns tid i spent running on the jth cluster at the cluster's kth lowest
+ // freq.
+ virtual std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
+ getAggregatedTaskCpuFreqTimes(pid_t, const std::vector<uint16_t> &);
+};
+
+// ICpuTimeInStateReader that uses eBPF to provide a map of aggregated CPU time-in-state values.
+// See cputtimeinstate.h/.cpp
+class BpfCpuTimeInStateReader : public ICpuTimeInStateReader {
+public:
+ size_t getCpuFrequencyCount() {
+ std::optional<std::vector<std::vector<uint32_t>>> cpuFreqs = android::bpf::getCpuFreqs();
+ if (!cpuFreqs) {
+ ALOGE("Cannot obtain CPU frequency count");
+ return 0;
}
- free(dirlist[i]);
- }
- free(dirlist);
- return true;
+ size_t freqCount = 0;
+ for (auto cluster : *cpuFreqs) {
+ freqCount += cluster.size();
+ }
+
+ return freqCount;
+ }
+
+ bool startTrackingProcessCpuTimes(pid_t tgid) {
+ return android::bpf::startTrackingProcessCpuTimes(tgid);
+ }
+
+ bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey) {
+ return android::bpf::startAggregatingTaskCpuTimes(pid, aggregationKey);
+ }
+
+ std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
+ getAggregatedTaskCpuFreqTimes(pid_t pid, const std::vector<uint16_t> &aggregationKeys) {
+ return android::bpf::getAggregatedTaskCpuFreqTimes(pid, aggregationKeys);
+ }
+};
+
+// ICpuTimeInStateReader that uses JNI to provide a map of aggregated CPU time-in-state
+// values.
+// This version of CpuTimeInStateReader is used exclusively for providing mock data in tests.
+class MockCpuTimeInStateReader : public ICpuTimeInStateReader {
+private:
+ JNIEnv *mEnv;
+ jobject mCpuTimeInStateReader;
+
+public:
+ MockCpuTimeInStateReader(JNIEnv *env, jobject cpuTimeInStateReader)
+ : mEnv(env), mCpuTimeInStateReader(cpuTimeInStateReader) {}
+
+ size_t getCpuFrequencyCount();
+
+ bool startTrackingProcessCpuTimes(pid_t tgid);
+
+ bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey);
+
+ std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
+ getAggregatedTaskCpuFreqTimes(pid_t tgid, const std::vector<uint16_t> &aggregationKeys);
+};
+
+static ICpuTimeInStateReader *getCpuTimeInStateReader(JNIEnv *env,
+ jobject cpuTimeInStateReaderObject) {
+ if (cpuTimeInStateReaderObject) {
+ return new MockCpuTimeInStateReader(env, cpuTimeInStateReaderObject);
+ } else {
+ return new BpfCpuTimeInStateReader();
+ }
}
-// Reads contents of a time_in_state file and returns times as a vector of times per frequency
-// A time_in_state file contains pairs of frequency - time (in jiffies):
-//
-// cpu0
-// 300000 30
-// 403200 0
-// cpu4
-// 710400 10
-// 825600 20
-// 940800 30
-//
-static bool getThreadTimeInState(const std::string &procPath, const pid_t pid, const pid_t tid,
- const size_t frequencyCount,
- std::vector<uint64_t> &outThreadTimeInState) {
- std::string timeInStateFilePath =
- android::base::StringPrintf("%s/%u/task/%u/time_in_state", procPath.c_str(), pid, tid);
- std::string data;
+static jint getCpuFrequencyCount(JNIEnv *env, jclass, jobject cpuTimeInStateReaderObject) {
+ std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
+ getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
+ return cpuTimeInStateReader->getCpuFrequencyCount();
+}
- if (!android::base::ReadFileToString(timeInStateFilePath, &data)) {
- ALOGE("Cannot read file: %s", timeInStateFilePath.c_str());
- return false;
- }
+static jboolean startTrackingProcessCpuTimes(JNIEnv *env, jclass, jint tgid,
+ jobject cpuTimeInStateReaderObject) {
+ std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
+ getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
+ return cpuTimeInStateReader->startTrackingProcessCpuTimes(tgid);
+}
- auto lines = android::base::Split(data, "\n");
- size_t index = 0;
- for (const auto &line : lines) {
- if (line.empty()) {
- continue;
- }
+static jboolean startAggregatingThreadCpuTimes(JNIEnv *env, jclass, jintArray selectedThreadIdArray,
+ jobject cpuTimeInStateReaderObject) {
+ ScopedIntArrayRO selectedThreadIds(env, selectedThreadIdArray);
+ std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
+ getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
- auto numbers = android::base::Split(line, " ");
- if (numbers.size() != 2) {
- continue;
- }
- uint64_t timeInState;
- if (!android::base::ParseUint<uint64_t>(numbers[1], &timeInState)) {
- ALOGE("Invalid time_in_state file format: %s", timeInStateFilePath.c_str());
+ for (int i = 0; i < selectedThreadIds.size(); i++) {
+ if (!cpuTimeInStateReader->startAggregatingTaskCpuTimes(selectedThreadIds[i],
+ SELECTED_THREAD_AGGREGATION_KEY)) {
return false;
}
- if (index < frequencyCount) {
- outThreadTimeInState[index] = timeInState;
- }
- index++;
}
+ return true;
+}
+// Converts time-in-state data from a vector of vectors to a flat array.
+// Also converts from nanoseconds to milliseconds.
+static bool flattenTimeInStateData(ScopedLongArrayRW &cpuTimesMillis,
+ const std::vector<std::vector<uint64_t>> &data) {
+ size_t frequencyCount = cpuTimesMillis.size();
+ size_t index = 0;
+ for (const auto &cluster : data) {
+ for (const uint64_t &timeNanos : cluster) {
+ if (index < frequencyCount) {
+ cpuTimesMillis[index] = timeNanos / NSEC_PER_MSEC;
+ }
+ index++;
+ }
+ }
if (index != frequencyCount) {
- ALOGE("Incorrect number of frequencies %u in %s. Expected %u",
- (uint32_t)outThreadTimeInState.size(), timeInStateFilePath.c_str(),
- (uint32_t)frequencyCount);
+ ALOGE("CPU time-in-state reader returned data for %zu frequencies; expected: %zu", index,
+ frequencyCount);
return false;
}
return true;
}
-static int pidCompare(const void *a, const void *b) {
- return (*(pid_t *)a - *(pid_t *)b);
-}
-
-static inline bool isSelectedThread(const pid_t tid, const pid_t *selectedThreadIds,
- const size_t selectedThreadCount) {
- return bsearch(&tid, selectedThreadIds, selectedThreadCount, sizeof(pid_t), pidCompare) != NULL;
-}
-
-// Reads all /proc/<pid>/task/*/time_in_state files and aggregates per-frequency
+// Reads all CPU time-in-state data accumulated by BPF and aggregates per-frequency
// time in state data for all threads. Also, separately aggregates time in state for
// selected threads whose TIDs are passes as selectedThreadIds.
-static void aggregateThreadCpuTimes(const std::string &procPath, const pid_t pid,
- const std::vector<pid_t> &threadIds,
- const size_t frequencyCount, const pid_t *selectedThreadIds,
- const size_t selectedThreadCount,
- uint64_t *threadCpuTimesMillis,
- uint64_t *selectedThreadCpuTimesMillis) {
- for (size_t j = 0; j < frequencyCount; j++) {
- threadCpuTimesMillis[j] = 0;
- selectedThreadCpuTimesMillis[j] = 0;
- }
-
- for (size_t i = 0; i < threadIds.size(); i++) {
- pid_t tid = threadIds[i];
- std::vector<uint64_t> timeInState(frequencyCount);
- if (!getThreadTimeInState(procPath, pid, tid, frequencyCount, timeInState)) {
- continue;
- }
-
- bool selectedThread = isSelectedThread(tid, selectedThreadIds, selectedThreadCount);
- for (size_t j = 0; j < frequencyCount; j++) {
- threadCpuTimesMillis[j] += timeInState[j];
- if (selectedThread) {
- selectedThreadCpuTimesMillis[j] += timeInState[j];
- }
- }
- }
- for (size_t i = 0; i < frequencyCount; i++) {
- threadCpuTimesMillis[i] *= gJiffyMillis;
- selectedThreadCpuTimesMillis[i] *= gJiffyMillis;
- }
-}
-
-// Reads process utime and stime from the /proc/<pid>/stat file.
-// Format of this file is described in https://man7.org/linux/man-pages/man5/proc.5.html.
-static bool getProcessCpuTime(const std::string &procPath, const pid_t pid,
- uint64_t &outTimeMillis) {
- std::string statFilePath = android::base::StringPrintf("%s/%u/stat", procPath.c_str(), pid);
- std::string data;
- if (!android::base::ReadFileToString(statFilePath, &data)) {
- return false;
- }
-
- auto fields = android::base::Split(data, " ");
- uint64_t utime, stime;
-
- // Field 14 (counting from 1) is utime - process time in user space, in jiffies
- // Field 15 (counting from 1) is stime - process time in system space, in jiffies
- if (fields.size() < 15 || !android::base::ParseUint(fields[13], &utime) ||
- !android::base::ParseUint(fields[14], &stime)) {
- ALOGE("Invalid file format %s", statFilePath.c_str());
- return false;
- }
-
- outTimeMillis = (utime + stime) * gJiffyMillis;
- return true;
-}
-
-// Estimates per cluster per frequency CPU time for the entire process
-// by distributing the total process CPU time proportionately to how much
-// CPU time its threads took on those clusters/frequencies. This algorithm
-// works more accurately when when we have equally distributed concurrency.
-// TODO(b/169279846): obtain actual process CPU times from the kernel
-static void estimateProcessTimeInState(const uint64_t processCpuTimeMillis,
- const uint64_t *threadCpuTimesMillis,
- const size_t frequencyCount,
- uint64_t *processCpuTimesMillis) {
- uint64_t totalCpuTimeAllThreads = 0;
- for (size_t i = 0; i < frequencyCount; i++) {
- totalCpuTimeAllThreads += threadCpuTimesMillis[i];
- }
-
- if (totalCpuTimeAllThreads != 0) {
- for (size_t i = 0; i < frequencyCount; i++) {
- processCpuTimesMillis[i] =
- processCpuTimeMillis * threadCpuTimesMillis[i] / totalCpuTimeAllThreads;
- }
- } else {
- for (size_t i = 0; i < frequencyCount; i++) {
- processCpuTimesMillis[i] = 0;
- }
- }
-}
-
-static jboolean readProcessCpuUsage(JNIEnv *env, jclass, jstring procPath, jint pid,
- jintArray selectedThreadIdArray,
- jlongArray processCpuTimesMillisArray,
+static jboolean readProcessCpuUsage(JNIEnv *env, jclass, jint pid,
jlongArray threadCpuTimesMillisArray,
- jlongArray selectedThreadCpuTimesMillisArray) {
- ScopedUtfChars procPathChars(env, procPath);
- ScopedIntArrayRO selectedThreadIds(env, selectedThreadIdArray);
- ScopedLongArrayRW processCpuTimesMillis(env, processCpuTimesMillisArray);
+ jlongArray selectedThreadCpuTimesMillisArray,
+ jobject cpuTimeInStateReaderObject) {
ScopedLongArrayRW threadCpuTimesMillis(env, threadCpuTimesMillisArray);
ScopedLongArrayRW selectedThreadCpuTimesMillis(env, selectedThreadCpuTimesMillisArray);
+ std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
+ getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
- std::string procPathStr(procPathChars.c_str());
-
- // Get all thread IDs for the process.
- std::vector<pid_t> threadIds;
- if (!getThreadIds(procPathStr, pid, threadIds)) {
- ALOGE("Could not obtain thread IDs from: %s", procPathStr.c_str());
- return false;
- }
-
- size_t frequencyCount = processCpuTimesMillis.size();
+ const size_t frequencyCount = cpuTimeInStateReader->getCpuFrequencyCount();
if (threadCpuTimesMillis.size() != frequencyCount) {
- ALOGE("Invalid array length: threadCpuTimesMillis");
+ ALOGE("Invalid threadCpuTimesMillis array length: %zu frequencies; expected: %zu",
+ threadCpuTimesMillis.size(), frequencyCount);
return false;
}
+
if (selectedThreadCpuTimesMillis.size() != frequencyCount) {
- ALOGE("Invalid array length: selectedThreadCpuTimesMillisArray");
+ ALOGE("Invalid selectedThreadCpuTimesMillis array length: %zu frequencies; expected: %zu",
+ selectedThreadCpuTimesMillis.size(), frequencyCount);
return false;
}
- aggregateThreadCpuTimes(procPathStr, pid, threadIds, frequencyCount, selectedThreadIds.get(),
- selectedThreadIds.size(),
- reinterpret_cast<uint64_t *>(threadCpuTimesMillis.get()),
- reinterpret_cast<uint64_t *>(selectedThreadCpuTimesMillis.get()));
-
- uint64_t processCpuTime;
- bool ret = getProcessCpuTime(procPathStr, pid, processCpuTime);
- if (ret) {
- estimateProcessTimeInState(processCpuTime,
- reinterpret_cast<uint64_t *>(threadCpuTimesMillis.get()),
- frequencyCount,
- reinterpret_cast<uint64_t *>(processCpuTimesMillis.get()));
+ for (size_t i = 0; i < frequencyCount; i++) {
+ threadCpuTimesMillis[i] = 0;
+ selectedThreadCpuTimesMillis[i] = 0;
}
- return ret;
+
+ std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>> data =
+ cpuTimeInStateReader->getAggregatedTaskCpuFreqTimes(pid,
+ {DEFAULT_THREAD_AGGREGATION_KEY,
+ SELECTED_THREAD_AGGREGATION_KEY});
+ if (!data) {
+ ALOGE("Cannot read thread CPU times for PID %d", pid);
+ return false;
+ }
+
+ if (!flattenTimeInStateData(threadCpuTimesMillis, (*data)[DEFAULT_THREAD_AGGREGATION_KEY])) {
+ return false;
+ }
+
+ if (!flattenTimeInStateData(selectedThreadCpuTimesMillis,
+ (*data)[SELECTED_THREAD_AGGREGATION_KEY])) {
+ return false;
+ }
+
+ // threadCpuTimesMillis returns CPU times for _all_ threads, including the selected ones
+ for (size_t i = 0; i < frequencyCount; i++) {
+ threadCpuTimesMillis[i] += selectedThreadCpuTimesMillis[i];
+ }
+
+ return true;
}
static const JNINativeMethod g_single_methods[] = {
- {"readProcessCpuUsage", "(Ljava/lang/String;I[I[J[J[J)Z", (void *)readProcessCpuUsage},
+ {"getCpuFrequencyCount",
+ "(Lcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)I",
+ (void *)getCpuFrequencyCount},
+ {"startTrackingProcessCpuTimes",
+ "(ILcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)Z",
+ (void *)startTrackingProcessCpuTimes},
+ {"startAggregatingThreadCpuTimes",
+ "([ILcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)Z",
+ (void *)startAggregatingThreadCpuTimes},
+ {"readProcessCpuUsage",
+ "(I[J[J"
+ "Lcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)Z",
+ (void *)readProcessCpuUsage},
};
int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv *env) {
@@ -266,4 +257,77 @@
g_single_methods, NELEM(g_single_methods));
}
+size_t MockCpuTimeInStateReader::getCpuFrequencyCount() {
+ jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
+ jmethodID mid = mEnv->GetMethodID(cls, "getCpuFrequencyCount", "()I");
+ if (mid == 0) {
+ ALOGE("Couldn't find the method getCpuFrequencyCount");
+ return false;
+ }
+ return (size_t)mEnv->CallIntMethod(mCpuTimeInStateReader, mid);
+}
+
+bool MockCpuTimeInStateReader::startTrackingProcessCpuTimes(pid_t tgid) {
+ jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
+ jmethodID mid = mEnv->GetMethodID(cls, "startTrackingProcessCpuTimes", "(I)Z");
+ if (mid == 0) {
+ ALOGE("Couldn't find the method startTrackingProcessCpuTimes");
+ return false;
+ }
+ return mEnv->CallBooleanMethod(mCpuTimeInStateReader, mid, tgid);
+}
+
+bool MockCpuTimeInStateReader::startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey) {
+ jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
+ jmethodID mid = mEnv->GetMethodID(cls, "startAggregatingTaskCpuTimes", "(II)Z");
+ if (mid == 0) {
+ ALOGE("Couldn't find the method startAggregatingTaskCpuTimes");
+ return false;
+ }
+ return mEnv->CallBooleanMethod(mCpuTimeInStateReader, mid, pid, aggregationKey);
+}
+
+std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
+MockCpuTimeInStateReader::getAggregatedTaskCpuFreqTimes(
+ pid_t pid, const std::vector<uint16_t> &aggregationKeys) {
+ jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
+ jmethodID mid =
+ mEnv->GetMethodID(cls, "getAggregatedTaskCpuFreqTimes", "(I)[Ljava/lang/String;");
+ if (mid == 0) {
+ ALOGE("Couldn't find the method getAggregatedTaskCpuFreqTimes");
+ return {};
+ }
+
+ std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>> map;
+
+ jobjectArray stringArray =
+ (jobjectArray)mEnv->CallObjectMethod(mCpuTimeInStateReader, mid, pid);
+ int size = mEnv->GetArrayLength(stringArray);
+ for (int i = 0; i < size; i++) {
+ ScopedUtfChars line(mEnv, (jstring)mEnv->GetObjectArrayElement(stringArray, i));
+ uint16_t aggregationKey;
+ std::vector<std::vector<uint64_t>> times;
+
+ // Each string is formatted like this: "aggKey:t0_0 t0_1...:t1_0 t1_1..."
+ auto fields = android::base::Split(line.c_str(), ":");
+ android::base::ParseUint(fields[0], &aggregationKey);
+
+ for (int j = 1; j < fields.size(); j++) {
+ auto numbers = android::base::Split(fields[j], " ");
+
+ std::vector<uint64_t> chunk;
+ for (int k = 0; k < numbers.size(); k++) {
+ uint64_t time;
+ android::base::ParseUint(numbers[k], &time);
+ chunk.emplace_back(time);
+ }
+ times.emplace_back(chunk);
+ }
+
+ map.emplace(aggregationKey, times);
+ }
+
+ return map;
+}
+
} // namespace android
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
index b5720a2..2de800b 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
@@ -19,122 +19,87 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.os.FileUtils;
-
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.File;
import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KernelSingleProcessCpuThreadReaderTest {
- private File mProcDirectory;
-
- @Before
- public void setUp() {
- Context context = InstrumentationRegistry.getContext();
- mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
- }
-
- @After
- public void tearDown() throws Exception {
- FileUtils.deleteContents(mProcDirectory);
- }
-
@Test
public void getProcessCpuUsage() throws IOException {
- setupDirectory(42,
- new int[] {42, 1, 2, 3},
- new int[] {1000, 2000},
- // Units are 10ms aka 10000Us
- new int[][] {{100, 200}, {0, 200}, {100, 300}, {0, 600}},
- new int[] {4500, 500});
+ // Units are nanoseconds
+ MockCpuTimeInStateReader mockReader = new MockCpuTimeInStateReader(4, new String[] {
+ "0:1000000000 2000000000 3000000000:4000000000",
+ "1:100000000 200000000 300000000:400000000",
+ });
KernelSingleProcessCpuThreadReader reader = new KernelSingleProcessCpuThreadReader(42,
- mProcDirectory.toPath());
+ mockReader);
+ reader.setSelectedThreadIds(new int[] {2, 3});
+ reader.startTrackingThreadCpuTimes();
KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage =
- reader.getProcessCpuUsage(new int[] {2, 3});
- assertThat(processCpuUsage.threadCpuTimesMillis).isEqualTo(new long[] {2000, 13000});
- assertThat(processCpuUsage.selectedThreadCpuTimesMillis).isEqualTo(new long[] {1000, 9000});
- assertThat(processCpuUsage.processCpuTimesMillis).isEqualTo(new long[] {6666, 43333});
+ reader.getProcessCpuUsage();
+ assertThat(mockReader.mTrackedTgid).isEqualTo(42);
+ // The strings are formatted as <TID TGID AGG_KEY>, where AGG_KEY is 1 for binder
+ // threads and 0 for all other threads.
+ assertThat(mockReader.mTrackedTasks).containsExactly(
+ "2 1",
+ "3 1");
+ assertThat(processCpuUsage.threadCpuTimesMillis).isEqualTo(
+ new long[] {1100, 2200, 3300, 4400});
+ assertThat(processCpuUsage.selectedThreadCpuTimesMillis).isEqualTo(
+ new long[] {100, 200, 300, 400});
}
@Test
public void getCpuFrequencyCount() throws IOException {
- setupDirectory(13,
- new int[] {13},
- new int[] {1000, 2000, 3000},
- new int[][] {{100, 200, 300}},
- new int[] {14, 15});
+ MockCpuTimeInStateReader mockReader = new MockCpuTimeInStateReader(3, new String[0]);
KernelSingleProcessCpuThreadReader reader = new KernelSingleProcessCpuThreadReader(13,
- mProcDirectory.toPath());
+ mockReader);
int cpuFrequencyCount = reader.getCpuFrequencyCount();
assertThat(cpuFrequencyCount).isEqualTo(3);
}
- private void setupDirectory(int pid, int[] threadIds, int[] cpuFrequencies,
- int[][] threadCpuTimes, int[] processCpuTimes)
- throws IOException {
+ public static class MockCpuTimeInStateReader implements
+ KernelSingleProcessCpuThreadReader.CpuTimeInStateReader {
+ private final int mCpuFrequencyCount;
+ private final String[] mAggregatedTaskCpuFreqTimes;
+ public int mTrackedTgid;
+ public List<String> mTrackedTasks = new ArrayList<>();
- assertTrue(mProcDirectory.toPath().resolve("self").toFile().mkdirs());
-
- try (OutputStream timeInStateStream =
- Files.newOutputStream(
- mProcDirectory.toPath().resolve("self").resolve("time_in_state"))) {
- for (int i = 0; i < cpuFrequencies.length; i++) {
- final String line = cpuFrequencies[i] + " 0\n";
- timeInStateStream.write(line.getBytes());
- }
+ public MockCpuTimeInStateReader(int cpuFrequencyCount,
+ String[] aggregatedTaskCpuFreqTimes) {
+ mCpuFrequencyCount = cpuFrequencyCount;
+ mAggregatedTaskCpuFreqTimes = aggregatedTaskCpuFreqTimes;
}
- Path processPath = mProcDirectory.toPath().resolve(String.valueOf(pid));
-
- // Make /proc/$PID
- assertTrue(processPath.toFile().mkdirs());
-
- // Write /proc/$PID/stat. Only the fields 14-17 matter.
- try (OutputStream timeInStateStream = Files.newOutputStream(processPath.resolve("stat"))) {
- timeInStateStream.write(
- (pid + " (test) S 4 5 6 7 8 9 10 11 12 13 "
- + processCpuTimes[0] + " "
- + processCpuTimes[1] + " "
- + "16 17 18 19 20 ...").getBytes());
+ @Override
+ public int getCpuFrequencyCount() {
+ return mCpuFrequencyCount;
}
- // Make /proc/$PID/task
- final Path selfThreadsPath = processPath.resolve("task");
- assertTrue(selfThreadsPath.toFile().mkdirs());
+ @Override
+ public boolean startTrackingProcessCpuTimes(int tgid) {
+ mTrackedTgid = tgid;
+ return true;
+ }
- // Make thread directories
- for (int i = 0; i < threadIds.length; i++) {
- // Make /proc/$PID/task/$TID
- final Path threadPath = selfThreadsPath.resolve(String.valueOf(threadIds[i]));
- assertTrue(threadPath.toFile().mkdirs());
+ public boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey) {
+ mTrackedTasks.add(pid + " " + aggregationKey);
+ return true;
+ }
- // Make /proc/$PID/task/$TID/time_in_state
- try (OutputStream timeInStateStream =
- Files.newOutputStream(threadPath.resolve("time_in_state"))) {
- for (int j = 0; j < cpuFrequencies.length; j++) {
- final String line = cpuFrequencies[j] + " " + threadCpuTimes[i][j] + "\n";
- timeInStateStream.write(line.getBytes());
- }
- }
+ public String[] getAggregatedTaskCpuFreqTimes(int pid) {
+ return mAggregatedTaskCpuFreqTimes;
}
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java
index 121c637..d116d4d 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java
@@ -16,146 +16,86 @@
package com.android.internal.os;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
-import android.content.Context;
-import android.os.FileUtils;
-
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.File;
import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SystemServerCpuThreadReaderTest {
- private File mProcDirectory;
-
- @Before
- public void setUp() {
- Context context = InstrumentationRegistry.getContext();
- mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
- }
-
- @After
- public void tearDown() throws Exception {
- FileUtils.deleteContents(mProcDirectory);
- }
@Test
- public void testReaderDelta_firstTime() throws IOException {
+ public void testReadDelta() throws IOException {
int pid = 42;
- setupDirectory(
- pid,
- new int[] {42, 1, 2, 3},
- new int[] {1000, 2000},
- // Units are 10ms aka 10000Us
- new int[][] {{100, 200}, {0, 200}, {0, 300}, {0, 400}},
- new int[] {1400, 1500});
- SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(
- mProcDirectory.toPath(), pid);
- reader.setBinderThreadNativeTids(new int[] {1, 3});
- SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
- reader.readDelta();
- assertArrayEquals(new long[] {100 * 10000, 1100 * 10000},
- systemServiceCpuThreadTimes.threadCpuTimesUs);
- assertArrayEquals(new long[] {0, 600 * 10000},
- systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
- }
+ MockCpuTimeInStateReader mockReader = new MockCpuTimeInStateReader(4);
+ // Units are nanoseconds
+ mockReader.setAggregatedTaskCpuFreqTimes(new String[] {
+ "0:1000000000 2000000000 3000000000:4000000000",
+ "1:100000000 200000000 300000000:400000000",
+ });
- @Test
- public void testReaderDelta_nextTime() throws IOException {
- int pid = 42;
- setupDirectory(
- pid,
- new int[] {42, 1, 2, 3},
- new int[] {1000, 2000},
- new int[][] {{100, 200}, {0, 200}, {0, 300}, {0, 400}},
- new int[] {1400, 1500});
-
- SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(
- mProcDirectory.toPath(), pid);
+ SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(pid, mockReader);
reader.setBinderThreadNativeTids(new int[] {1, 3});
- // First time, populate "last" snapshot
- reader.readDelta();
-
- FileUtils.deleteContents(mProcDirectory);
- setupDirectory(
- pid,
- new int[] {42, 1, 2, 3},
- new int[] {1000, 2000},
- new int[][] {{500, 600}, {700, 800}, {900, 1000}, {1100, 1200}},
- new int[] {2400, 2500});
-
- // Second time, get the actual delta
+ // The first invocation of readDelta populates the "last" snapshot
SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
reader.readDelta();
- assertArrayEquals(new long[] {3100 * 10000, 2500 * 10000},
- systemServiceCpuThreadTimes.threadCpuTimesUs);
- assertArrayEquals(new long[] {1800 * 10000, 1400 * 10000},
- systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
+ assertThat(systemServiceCpuThreadTimes.threadCpuTimesUs)
+ .isEqualTo(new long[] {1100000, 2200000, 3300000, 4400000});
+ assertThat(systemServiceCpuThreadTimes.binderThreadCpuTimesUs)
+ .isEqualTo(new long[] {100000, 200000, 300000, 400000});
+
+ mockReader.setAggregatedTaskCpuFreqTimes(new String[] {
+ "0:1010000000 2020000000 3030000000:4040000000",
+ "1:101000000 202000000 303000000:404000000",
+ });
+
+ // The second invocation gets the actual delta
+ systemServiceCpuThreadTimes = reader.readDelta();
+
+ assertThat(systemServiceCpuThreadTimes.threadCpuTimesUs)
+ .isEqualTo(new long[] {11000, 22000, 33000, 44000});
+ assertThat(systemServiceCpuThreadTimes.binderThreadCpuTimesUs)
+ .isEqualTo(new long[] {1000, 2000, 3000, 4000});
}
- private void setupDirectory(int pid, int[] threadIds, int[] cpuFrequencies, int[][] cpuTimes,
- int[] processCpuTimes)
- throws IOException {
+ public static class MockCpuTimeInStateReader implements
+ KernelSingleProcessCpuThreadReader.CpuTimeInStateReader {
+ private final int mCpuFrequencyCount;
+ private String[] mAggregatedTaskCpuFreqTimes;
- assertTrue(mProcDirectory.toPath().resolve("self").toFile().mkdirs());
-
- try (OutputStream timeInStateStream =
- Files.newOutputStream(
- mProcDirectory.toPath().resolve("self").resolve("time_in_state"))) {
- for (int i = 0; i < cpuFrequencies.length; i++) {
- final String line = cpuFrequencies[i] + " 0\n";
- timeInStateStream.write(line.getBytes());
- }
+ MockCpuTimeInStateReader(int frequencyCount) {
+ mCpuFrequencyCount = frequencyCount;
}
- Path processPath = mProcDirectory.toPath().resolve(String.valueOf(pid));
- // Make /proc/$PID
- assertTrue(processPath.toFile().mkdirs());
-
- // Write /proc/$PID/stat. Only the fields 14-17 matter.
- try (OutputStream timeInStateStream = Files.newOutputStream(processPath.resolve("stat"))) {
- timeInStateStream.write(
- (pid + " (test) S 4 5 6 7 8 9 10 11 12 13 "
- + processCpuTimes[0] + " "
- + processCpuTimes[1] + " "
- + "16 17 18 19 20 ...").getBytes());
+ @Override
+ public int getCpuFrequencyCount() {
+ return mCpuFrequencyCount;
}
- // Make /proc/$PID/task
- final Path selfThreadsPath = processPath.resolve("task");
- assertTrue(selfThreadsPath.toFile().mkdirs());
+ @Override
+ public boolean startTrackingProcessCpuTimes(int tgid) {
+ return true;
+ }
- // Make thread directories
- for (int i = 0; i < threadIds.length; i++) {
- // Make /proc/$PID/task/$TID
- final Path threadPath = selfThreadsPath.resolve(String.valueOf(threadIds[i]));
- assertTrue(threadPath.toFile().mkdirs());
+ public boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey) {
+ return true;
+ }
- // Make /proc/$PID/task/$TID/time_in_state
- try (OutputStream timeInStateStream =
- Files.newOutputStream(threadPath.resolve("time_in_state"))) {
- for (int j = 0; j < cpuFrequencies.length; j++) {
- final String line = cpuFrequencies[j] + " " + cpuTimes[i][j] + "\n";
- timeInStateStream.write(line.getBytes());
- }
- }
+ public void setAggregatedTaskCpuFreqTimes(String[] mAggregatedTaskCpuFreqTimes) {
+ this.mAggregatedTaskCpuFreqTimes = mAggregatedTaskCpuFreqTimes;
+ }
+
+ public String[] getAggregatedTaskCpuFreqTimes(int pid) {
+ return mAggregatedTaskCpuFreqTimes;
}
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
index dbb36fb..c8e8585 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
@@ -66,7 +66,6 @@
public void testCalculateApp() {
// Test Power Profile has two CPU clusters with 3 and 4 speeds, thus 7 freq times total
mMockSystemServerCpuThreadReader.setCpuTimes(
- new long[] {10000, 15000, 20000, 25000, 30000, 35000, 40000},
new long[] {30000, 40000, 50000, 60000, 70000, 80000, 90000},
new long[] {20000, 30000, 40000, 50000, 60000, 70000, 80000});
@@ -146,9 +145,7 @@
super(null);
}
- public void setCpuTimes(long[] processCpuTimesUs, long[] threadCpuTimesUs,
- long[] binderThreadCpuTimesUs) {
- mThreadTimes.processCpuTimesUs = processCpuTimesUs;
+ public void setCpuTimes(long[] threadCpuTimesUs, long[] binderThreadCpuTimesUs) {
mThreadTimes.threadCpuTimesUs = threadCpuTimesUs;
mThreadTimes.binderThreadCpuTimesUs = binderThreadCpuTimesUs;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/emergencynumber/EmergencyNumberUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/emergencynumber/EmergencyNumberUtilsTest.java
index 1403631..99d2ff7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/emergencynumber/EmergencyNumberUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/emergencynumber/EmergencyNumberUtilsTest.java
@@ -113,12 +113,13 @@
private void addEmergencyNumberToTelephony() {
final int subId = SubscriptionManager.getDefaultSubscriptionId();
EmergencyNumber emergencyNumber = mock(EmergencyNumber.class);
+ when(emergencyNumber.isInEmergencyServiceCategories(EMERGENCY_SERVICE_CATEGORY_POLICE))
+ .thenReturn(true);
Map<Integer, List<EmergencyNumber>> numbers = new ArrayMap<>();
List<EmergencyNumber> numbersForSubId = new ArrayList<>();
numbersForSubId.add(emergencyNumber);
numbers.put(subId, numbersForSubId);
- when(mTelephonyManager.getEmergencyNumberList(
- EMERGENCY_SERVICE_CATEGORY_POLICE)).thenReturn(numbers);
+ when(mTelephonyManager.getEmergencyNumberList()).thenReturn(numbers);
when(emergencyNumber.getNumber()).thenReturn(TELEPHONY_EMERGENCY_NUMBER);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 451bd42..eb86128 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -37,6 +37,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
+import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothUtils;
@@ -84,11 +85,14 @@
@VisibleForTesting
LocalMediaManager mLocalMediaManager;
+ private MediaOutputMetricLogger mMetricLogger;
+ private UiEventLogger mUiEventLogger;
+
@Inject
public MediaOutputController(@NonNull Context context, String packageName,
boolean aboveStatusbar, MediaSessionManager mediaSessionManager, LocalBluetoothManager
lbm, ShadeController shadeController, ActivityStarter starter,
- NotificationEntryManager notificationEntryManager) {
+ NotificationEntryManager notificationEntryManager, UiEventLogger uiEventLogger) {
mContext = context;
mPackageName = packageName;
mMediaSessionManager = mediaSessionManager;
@@ -98,6 +102,8 @@
mNotificationEntryManager = notificationEntryManager;
InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
+ mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
+ mUiEventLogger = uiEventLogger;
}
void start(@NonNull Callback cb) {
@@ -151,6 +157,7 @@
public void onSelectedDeviceStateChanged(MediaDevice device,
@LocalMediaManager.MediaDeviceState int state) {
mCallback.onRouteChanged();
+ mMetricLogger.logOutputSuccess(device.toString(), mMediaDevices);
}
@Override
@@ -161,6 +168,7 @@
@Override
public void onRequestFailed(int reason) {
mCallback.onRouteChanged();
+ mMetricLogger.logOutputFailure(mMediaDevices, reason);
}
CharSequence getHeaderTitle() {
@@ -311,6 +319,8 @@
}
void connectDevice(MediaDevice device) {
+ mMetricLogger.updateOutputEndPoints(getCurrentConnectedMediaDevice(), device);
+
ThreadUtils.postOnBackgroundThread(() -> {
mLocalMediaManager.connectDevice(device);
});
@@ -439,7 +449,7 @@
void launchMediaOutputDialog() {
mCallback.dismissDialog();
- new MediaOutputDialog(mContext, mAboveStatusbar, this);
+ new MediaOutputDialog(mContext, mAboveStatusbar, this, mUiEventLogger);
}
void launchMediaOutputGroupDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index a892a12..53029bd0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -23,6 +23,9 @@
import androidx.core.graphics.drawable.IconCompat;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
@@ -31,10 +34,12 @@
*/
@SysUISingleton
public class MediaOutputDialog extends MediaOutputBaseDialog {
+ final UiEventLogger mUiEventLogger;
MediaOutputDialog(Context context, boolean aboveStatusbar, MediaOutputController
- mediaOutputController) {
+ mediaOutputController, UiEventLogger uiEventLogger) {
super(context, mediaOutputController);
+ mUiEventLogger = uiEventLogger;
mAdapter = new MediaOutputAdapter(mMediaOutputController);
if (!aboveStatusbar) {
getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
@@ -45,6 +50,7 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ mUiEventLogger.log(MediaOutputEvent.MEDIA_OUTPUT_DIALOG_SHOW);
}
@Override
@@ -78,4 +84,21 @@
return mMediaOutputController.isActiveRemoteDevice(
mMediaOutputController.getCurrentConnectedMediaDevice()) ? View.VISIBLE : View.GONE;
}
+
+ @VisibleForTesting
+ public enum MediaOutputEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The MediaOutput dialog became visible on the screen.")
+ MEDIA_OUTPUT_DIALOG_SHOW(655);
+
+ private final int mId;
+
+ MediaOutputEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 7d1a7ce..0f340a5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.media.session.MediaSessionManager
+import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.notification.NotificationEntryManager
@@ -33,7 +34,8 @@
private val lbm: LocalBluetoothManager?,
private val shadeController: ShadeController,
private val starter: ActivityStarter,
- private val notificationEntryManager: NotificationEntryManager
+ private val notificationEntryManager: NotificationEntryManager,
+ private val uiEventLogger: UiEventLogger
) {
companion object {
var mediaOutputDialog: MediaOutputDialog? = null
@@ -43,8 +45,10 @@
fun create(packageName: String, aboveStatusBar: Boolean) {
mediaOutputDialog?.dismiss()
mediaOutputDialog = MediaOutputController(context, packageName, aboveStatusBar,
- mediaSessionManager, lbm, shadeController, starter, notificationEntryManager).run {
- MediaOutputDialog(context, aboveStatusBar, this) }
+ mediaSessionManager, lbm, shadeController, starter, notificationEntryManager,
+ uiEventLogger).run {
+ MediaOutputDialog(context, aboveStatusBar, this, uiEventLogger)
+ }
}
/** dismiss [MediaOutputDialog] if exist. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
new file mode 100644
index 0000000..ac0295e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dialog;
+
+import static android.media.MediaRoute2ProviderService.REASON_INVALID_COMMAND;
+import static android.media.MediaRoute2ProviderService.REASON_NETWORK_ERROR;
+import static android.media.MediaRoute2ProviderService.REASON_REJECTED;
+import static android.media.MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE;
+import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.util.Log;
+
+import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.shared.system.SysUiStatsLog;
+
+import java.util.List;
+
+/**
+ * Metric logger for media output features
+ */
+public class MediaOutputMetricLogger {
+
+ private static final String TAG = "MediaOutputMetricLogger";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Context mContext;
+ private final String mPackageName;
+ private MediaDevice mSourceDevice, mTargetDevice;
+ private int mWiredDeviceCount;
+ private int mConnectedBluetoothDeviceCount;
+ private int mRemoteDeviceCount;
+ private int mAppliedDeviceCountWithinRemoteGroup;
+
+ public MediaOutputMetricLogger(Context context, String packageName) {
+ mContext = context;
+ mPackageName = packageName;
+ }
+
+ /**
+ * Update the endpoints of a content switching operation.
+ * This method should be called before a switching operation, so the metric logger can track
+ * source and target devices.
+ * @param source the current connected media device
+ * @param target the target media device for content switching to
+ */
+ public void updateOutputEndPoints(MediaDevice source, MediaDevice target) {
+ mSourceDevice = source;
+ mTargetDevice = target;
+
+ if (DEBUG) {
+ Log.d(TAG, "updateOutputEndPoints -"
+ + " source:" + mSourceDevice.toString()
+ + " target:" + mTargetDevice.toString());
+ }
+ }
+
+ /**
+ * Do the metric logging of content switching success.
+ * @param selectedDeviceType string representation of the target media device
+ * @param deviceList media device list for device count updating
+ */
+ public void logOutputSuccess(String selectedDeviceType, List<MediaDevice> deviceList) {
+ if (DEBUG) {
+ Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType);
+ }
+
+ updateLoggingDeviceCount(deviceList);
+
+ SysUiStatsLog.write(
+ SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
+ getLoggingDeviceType(mSourceDevice, true),
+ getLoggingDeviceType(mTargetDevice, false),
+ SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__OK,
+ SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NO_ERROR,
+ getLoggingPackageName(),
+ mWiredDeviceCount,
+ mConnectedBluetoothDeviceCount,
+ mRemoteDeviceCount,
+ mAppliedDeviceCountWithinRemoteGroup);
+ }
+
+ /**
+ * Do the metric logging of content switching failure.
+ * @param deviceList media device list for device count updating
+ * @param reason the reason of content switching failure
+ */
+ public void logOutputFailure(List<MediaDevice> deviceList, int reason) {
+ if (DEBUG) {
+ Log.e(TAG, "logRequestFailed - " + reason);
+ }
+
+ updateLoggingDeviceCount(deviceList);
+
+ SysUiStatsLog.write(
+ SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
+ getLoggingDeviceType(mSourceDevice, true),
+ getLoggingDeviceType(mTargetDevice, false),
+ SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__ERROR,
+ getLoggingSwitchOpSubResult(reason),
+ getLoggingPackageName(),
+ mWiredDeviceCount,
+ mConnectedBluetoothDeviceCount,
+ mRemoteDeviceCount,
+ mAppliedDeviceCountWithinRemoteGroup);
+ }
+
+ private void updateLoggingDeviceCount(List<MediaDevice> deviceList) {
+ mWiredDeviceCount = mConnectedBluetoothDeviceCount = mRemoteDeviceCount = 0;
+ mAppliedDeviceCountWithinRemoteGroup = 0;
+
+ for (MediaDevice mediaDevice : deviceList) {
+ if (mediaDevice.isConnected()) {
+ switch (mediaDevice.getDeviceType()) {
+ case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
+ case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
+ mWiredDeviceCount++;
+ break;
+ case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
+ mConnectedBluetoothDeviceCount++;
+ break;
+ case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
+ case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
+ mRemoteDeviceCount++;
+ break;
+ default:
+ }
+ }
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "connected devices:" + " wired: " + mWiredDeviceCount
+ + " bluetooth: " + mConnectedBluetoothDeviceCount
+ + " remote: " + mRemoteDeviceCount);
+ }
+ }
+
+ private int getLoggingDeviceType(MediaDevice device, boolean isSourceDevice) {
+ switch (device.getDeviceType()) {
+ case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE:
+ return isSourceDevice
+ ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__BUILTIN_SPEAKER
+ : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__BUILTIN_SPEAKER;
+ case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
+ return isSourceDevice
+ ? SysUiStatsLog
+ .MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__WIRED_3POINT5_MM_AUDIO
+ : SysUiStatsLog
+ .MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__WIRED_3POINT5_MM_AUDIO;
+ case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
+ return isSourceDevice
+ ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__USB_C_AUDIO
+ : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__USB_C_AUDIO;
+ case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
+ return isSourceDevice
+ ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__BLUETOOTH
+ : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__BLUETOOTH;
+ case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
+ return isSourceDevice
+ ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__REMOTE_SINGLE
+ : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__REMOTE_SINGLE;
+ case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
+ return isSourceDevice
+ ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__REMOTE_GROUP
+ : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__REMOTE_GROUP;
+ default:
+ return isSourceDevice
+ ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__UNKNOWN_TYPE
+ : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__UNKNOWN_TYPE;
+ }
+ }
+
+ private int getLoggingSwitchOpSubResult(int reason) {
+ switch (reason) {
+ case REASON_REJECTED:
+ return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__REJECTED;
+ case REASON_NETWORK_ERROR:
+ return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NETWORK_ERROR;
+ case REASON_ROUTE_NOT_AVAILABLE:
+ return SysUiStatsLog
+ .MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__ROUTE_NOT_AVAILABLE;
+ case REASON_INVALID_COMMAND:
+ return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__INVALID_COMMAND;
+ case REASON_UNKNOWN_ERROR:
+ default:
+ return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__UNKNOWN_ERROR;
+ }
+ }
+
+ private String getLoggingPackageName() {
+ if (mPackageName != null && !mPackageName.isEmpty()) {
+ try {
+ final ApplicationInfo applicationInfo = mContext.getPackageManager()
+ .getApplicationInfo(mPackageName, /* default flag */ 0);
+ if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+ || (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+ return mPackageName;
+ }
+ } catch (Exception ex) {
+ Log.e(TAG, mPackageName + " is invalid.");
+ }
+ }
+
+ return "";
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index c897d8a..fd5d996 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -35,6 +35,7 @@
import androidx.core.graphics.drawable.IconCompat;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -61,6 +62,7 @@
private ActivityStarter mStarter = mock(ActivityStarter.class);
private NotificationEntryManager mNotificationEntryManager =
mock(NotificationEntryManager.class);
+ private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
private MediaOutputController mMediaOutputController;
@@ -73,7 +75,7 @@
public void setUp() {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager);
+ mNotificationEntryManager, mUiEventLogger);
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 0d352c1..d1a617b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -42,6 +42,7 @@
import androidx.core.graphics.drawable.IconCompat;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.LocalMediaManager;
@@ -89,6 +90,7 @@
private ActivityStarter mStarter = mock(ActivityStarter.class);
private NotificationEntryManager mNotificationEntryManager =
mock(NotificationEntryManager.class);
+ private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
private Context mSpyContext;
private MediaOutputController mMediaOutputController;
@@ -111,7 +113,7 @@
mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager);
+ mNotificationEntryManager, mUiEventLogger);
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -155,7 +157,7 @@
public void start_withoutPackageName_verifyMediaControllerInit() {
mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager);
+ mNotificationEntryManager, mUiEventLogger);
mMediaOutputController.start(mCb);
@@ -176,7 +178,7 @@
public void stop_withoutPackageName_verifyMediaControllerDeinit() {
mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager);
+ mNotificationEntryManager, mUiEventLogger);
mMediaOutputController.start(mCb);
@@ -200,8 +202,10 @@
@Test
public void onSelectedDeviceStateChanged_verifyCallback() {
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
mMediaOutputController.start(mCb);
reset(mCb);
+ mMediaOutputController.connectDevice(mMediaDevice1);
mMediaOutputController.onSelectedDeviceStateChanged(mMediaDevice1,
LocalMediaManager.MediaDeviceState.STATE_CONNECTED);
@@ -221,8 +225,10 @@
@Test
public void onRequestFailed_verifyCallback() {
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
mMediaOutputController.start(mCb);
reset(mCb);
+ mMediaOutputController.connectDevice(mMediaDevice2);
mMediaOutputController.onRequestFailed(0 /* reason */);
@@ -268,6 +274,8 @@
@Test
public void connectDevice_verifyConnect() {
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
+
mMediaOutputController.connectDevice(mMediaDevice1);
// Wait for background thread execution
@@ -441,7 +449,7 @@
public void getNotificationLargeIcon_withoutPackageName_returnsNull() {
mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager);
+ mNotificationEntryManager, mUiEventLogger);
assertThat(mMediaOutputController.getNotificationIcon()).isNull();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index c1e7db1..86f6bde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -19,6 +19,8 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.media.MediaRoute2Info;
@@ -29,6 +31,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
@@ -53,26 +56,28 @@
private static final String TEST_PACKAGE = "test_package";
// Mock
- private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
- private LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
- private ShadeController mShadeController = mock(ShadeController.class);
- private ActivityStarter mStarter = mock(ActivityStarter.class);
- private LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
- private MediaDevice mMediaDevice = mock(MediaDevice.class);
- private NotificationEntryManager mNotificationEntryManager =
+ private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
+ private final LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
+ private final ShadeController mShadeController = mock(ShadeController.class);
+ private final ActivityStarter mStarter = mock(ActivityStarter.class);
+ private final LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
+ private final MediaDevice mMediaDevice = mock(MediaDevice.class);
+ private final NotificationEntryManager mNotificationEntryManager =
mock(NotificationEntryManager.class);
+ private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
private MediaOutputDialog mMediaOutputDialog;
private MediaOutputController mMediaOutputController;
- private List<String> mFeatures = new ArrayList<>();
+ private final List<String> mFeatures = new ArrayList<>();
@Before
public void setUp() {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager);
+ mNotificationEntryManager, mUiEventLogger);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
- mMediaOutputDialog = new MediaOutputDialog(mContext, false, mMediaOutputController);
+ mMediaOutputDialog = new MediaOutputDialog(mContext, false,
+ mMediaOutputController, mUiEventLogger);
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice);
when(mMediaDevice.getFeatures()).thenReturn(mFeatures);
@@ -112,4 +117,16 @@
assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.GONE);
}
+ @Test
+ // Check the visibility metric logging by creating a new MediaOutput dialog,
+ // and verify if the calling times increases.
+ public void onCreate_ShouldLogVisibility() {
+ MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false,
+ mMediaOutputController, mUiEventLogger);
+
+ testDialog.dismissDialog();
+
+ verify(mUiEventLogger, times(2))
+ .log(MediaOutputDialog.MediaOutputEvent.MEDIA_OUTPUT_DIALOG_SHOW);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
index 5813350..c296ff5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
@@ -28,6 +28,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
@@ -62,6 +63,7 @@
private MediaDevice mMediaDevice1 = mock(MediaDevice.class);
private NotificationEntryManager mNotificationEntryManager =
mock(NotificationEntryManager.class);
+ private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
private MediaOutputGroupDialog mMediaOutputGroupDialog;
private MediaOutputController mMediaOutputController;
@@ -71,7 +73,7 @@
public void setUp() {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager);
+ mNotificationEntryManager, mUiEventLogger);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false,
mMediaOutputController);
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 48cbd54..fcf07f8 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -19,6 +19,7 @@
per-file *Storage* = file:/core/java/android/os/storage/OWNERS
per-file *TimeUpdate* = file:/core/java/android/app/timezone/OWNERS
per-file ConnectivityService.java = file:/services/core/java/com/android/server/net/OWNERS
+per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
per-file IpSecService.java = file:/services/core/java/com/android/server/net/OWNERS
per-file MmsServiceBroker.java = file:/telephony/OWNERS
per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 3b6f0ac..422ae68 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -261,6 +261,8 @@
mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
mStats.setPowerProfileLocked(new PowerProfile(context));
+ mStats.startTrackingSystemServerCpuTime();
+
mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8298dfd..5fed940 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4708,8 +4708,11 @@
.setContentTitle(text)
.setContentText(
mContext.getText(R.string.heavy_weight_notification_detail))
+ // TODO(b/175194709) Please replace FLAG_MUTABLE_UNAUDITED below
+ // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
.setContentIntent(PendingIntent.getActivityAsUser(mContext, 0,
- intent, PendingIntent.FLAG_CANCEL_CURRENT, null,
+ intent, PendingIntent.FLAG_CANCEL_CURRENT
+ | PendingIntent.FLAG_MUTABLE_UNAUDITED, null,
new UserHandle(userId)))
.build();
try {
diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS
index 6153db3..2463fc6 100644
--- a/services/tests/servicestests/src/com/android/server/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/OWNERS
@@ -4,3 +4,4 @@
per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
per-file *Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file GestureLauncherServiceTest.java = file:platform/packages/apps/EmergencyInfo:/OWNERS