Add sources for API 35
Downloaded from https://dl.google.com/android/repository/source-35_r01.zip
using SdkManager in Studio
Test: None
Change-Id: I83f78aa820b66edfdc9f8594d17bc7b6cacccec1
diff --git a/android-35/com/android/internal/os/AndroidPrintStream.java b/android-35/com/android/internal/os/AndroidPrintStream.java
new file mode 100644
index 0000000..bb388bb
--- /dev/null
+++ b/android-35/com/android/internal/os/AndroidPrintStream.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2008 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.internal.os;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.util.Log;
+
+/**
+ * Print stream which log lines using Android's logging system.
+ *
+ * {@hide}
+ */
[email protected]
+class AndroidPrintStream extends LoggingPrintStream {
+
+ private final int priority;
+ private final String tag;
+
+ /**
+ * Constructs a new logging print stream.
+ *
+ * @param priority from {@link android.util.Log}
+ * @param tag to log
+ */
+ @UnsupportedAppUsage
+ public AndroidPrintStream(int priority, String tag) {
+ if (tag == null) {
+ throw new NullPointerException("tag");
+ }
+
+ this.priority = priority;
+ this.tag = tag;
+ }
+
+ protected void log(String line) {
+ Log.println(priority, tag, line);
+ }
+}
diff --git a/android-35/com/android/internal/os/AppFuseMount.java b/android-35/com/android/internal/os/AppFuseMount.java
new file mode 100644
index 0000000..5404fea
--- /dev/null
+++ b/android-35/com/android/internal/os/AppFuseMount.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 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.internal.os;
+
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.storage.IStorageManager;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Parcelable class representing AppFuse mount.
+ * This conveys the result for IStorageManager#openProxyFileDescriptor.
+ * @see IStorageManager#openProxyFileDescriptor
+ */
+public class AppFuseMount implements Parcelable {
+ final public int mountPointId;
+ final public ParcelFileDescriptor fd;
+
+ /**
+ * @param mountPointId Integer number for mount point that is unique in the lifetime of
+ * StorageManagerService.
+ * @param fd File descriptor pointing /dev/fuse and tagged with the mount point.
+ */
+ public AppFuseMount(int mountPointId, ParcelFileDescriptor fd) {
+ Preconditions.checkNotNull(fd);
+ this.mountPointId = mountPointId;
+ this.fd = fd;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(this.mountPointId);
+ dest.writeParcelable(fd, flags);
+ }
+
+ public static final Parcelable.Creator<AppFuseMount> CREATOR =
+ new Parcelable.Creator<AppFuseMount>() {
+ @Override
+ public AppFuseMount createFromParcel(Parcel in) {
+ return new AppFuseMount(in.readInt(), in.readParcelable(null, android.os.ParcelFileDescriptor.class));
+ }
+
+ @Override
+ public AppFuseMount[] newArray(int size) {
+ return new AppFuseMount[size];
+ }
+ };
+}
diff --git a/android-35/com/android/internal/os/AppIdToPackageMap.java b/android-35/com/android/internal/os/AppIdToPackageMap.java
new file mode 100644
index 0000000..98cced8
--- /dev/null
+++ b/android-35/com/android/internal/os/AppIdToPackageMap.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import android.app.AppGlobals;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+
+/** Maps AppIds to their package names. */
+public final class AppIdToPackageMap {
+ private final SparseArray<String> mAppIdToPackageMap;
+
+ @VisibleForTesting
+ public AppIdToPackageMap(SparseArray<String> appIdToPackageMap) {
+ mAppIdToPackageMap = appIdToPackageMap;
+ }
+
+ /** Creates a new {@link AppIdToPackageMap} for currently installed packages. */
+ public static AppIdToPackageMap getSnapshot() {
+ List<PackageInfo> packages;
+ try {
+ packages = AppGlobals.getPackageManager()
+ .getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE,
+ UserHandle.USER_SYSTEM).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ final SparseArray<String> map = new SparseArray<>();
+ for (PackageInfo pkg : packages) {
+ final int uid = pkg.applicationInfo.uid;
+ if (pkg.sharedUserId != null && map.indexOfKey(uid) >= 0) {
+ // Use sharedUserId string as package name if there are collisions
+ map.put(uid, "shared:" + pkg.sharedUserId);
+ } else {
+ map.put(uid, pkg.packageName);
+ }
+ }
+ return new AppIdToPackageMap(map);
+ }
+
+ /** Maps the AppId to a package name. */
+ public String mapAppId(int appId) {
+ String pkgName = mAppIdToPackageMap.get(appId);
+ return pkgName == null ? String.valueOf(appId) : pkgName;
+ }
+
+ /** Maps the UID to a package name. */
+ public String mapUid(int uid) {
+ final int appId = UserHandle.getAppId(uid);
+ final String pkgName = mAppIdToPackageMap.get(appId);
+ final String uidStr = UserHandle.formatUid(uid);
+ return pkgName == null ? uidStr : pkgName + '/' + uidStr;
+ }
+}
diff --git a/android-35/com/android/internal/os/AppZygoteInit.java b/android-35/com/android/internal/os/AppZygoteInit.java
new file mode 100644
index 0000000..f925afc
--- /dev/null
+++ b/android-35/com/android/internal/os/AppZygoteInit.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import android.app.LoadedApk;
+import android.app.ZygotePreload;
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.net.LocalSocket;
+import android.util.Log;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * Startup class for an Application zygote process.
+ *
+ * See {@link ZygoteInit} for generic zygote startup documentation.
+ *
+ * @hide
+ */
+class AppZygoteInit {
+ public static final String TAG = "AppZygoteInit";
+
+ private static ZygoteServer sServer;
+
+ private static class AppZygoteServer extends ZygoteServer {
+ @Override
+ protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
+ throws IOException {
+ return new AppZygoteConnection(socket, abiList);
+ }
+ }
+
+ private static class AppZygoteConnection extends ZygoteConnection {
+ AppZygoteConnection(LocalSocket socket, String abiList) throws IOException {
+ super(socket, abiList);
+ }
+
+ @Override
+ protected void preload() {
+ // Nothing to preload by default.
+ }
+
+ @Override
+ protected boolean isPreloadComplete() {
+ // App zygotes don't preload any classes or resources or defaults, all of their
+ // preloading is package specific.
+ return true;
+ }
+
+ @Override
+ protected boolean canPreloadApp() {
+ return true;
+ }
+
+ @Override
+ protected void handlePreloadApp(ApplicationInfo appInfo) {
+ Log.i(TAG, "Beginning application preload for " + appInfo.packageName);
+ LoadedApk loadedApk = new LoadedApk(null, appInfo, null, null, false, true, false);
+ ClassLoader loader = loadedApk.getClassLoader();
+
+ Zygote.allowAppFilesAcrossFork(appInfo);
+
+ if (appInfo.zygotePreloadName != null) {
+ Class<?> cl;
+ Method m;
+ try {
+ ComponentName preloadName = ComponentName.createRelative(appInfo.packageName,
+ appInfo.zygotePreloadName);
+ cl = Class.forName(preloadName.getClassName(), true, loader);
+ if (!ZygotePreload.class.isAssignableFrom(cl)) {
+ Log.e(TAG, preloadName.getClassName() + " does not implement "
+ + ZygotePreload.class.getName());
+ } else {
+ Constructor<?> ctor = cl.getConstructor();
+ ZygotePreload preloadObject = (ZygotePreload) ctor.newInstance();
+ Zygote.markOpenedFilesBeforePreload();
+ preloadObject.doPreload(appInfo);
+ Zygote.allowFilesOpenedByPreload();
+ }
+ } catch (ReflectiveOperationException e) {
+ Log.e(TAG, "AppZygote application preload failed for "
+ + appInfo.zygotePreloadName, e);
+ }
+ } else {
+ Log.i(TAG, "No zygotePreloadName attribute specified.");
+ }
+
+ try {
+ DataOutputStream socketOut = getSocketOutputStream();
+ socketOut.writeInt(loader != null ? 1 : 0);
+ } catch (IOException e) {
+ throw new IllegalStateException("Error writing to command socket", e);
+ }
+
+ Log.i(TAG, "Application preload done");
+ }
+ }
+
+ public static void main(String[] argv) {
+ AppZygoteServer server = new AppZygoteServer();
+ ChildZygoteInit.runZygoteServer(server, argv);
+ }
+}
diff --git a/android-35/com/android/internal/os/AtomicDirectory.java b/android-35/com/android/internal/os/AtomicDirectory.java
new file mode 100644
index 0000000..8ca7a71
--- /dev/null
+++ b/android-35/com/android/internal/os/AtomicDirectory.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import android.annotation.NonNull;
+import android.os.FileUtils;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Helper class for performing atomic operations on a directory, by creating a
+ * backup directory until a write has successfully completed.
+ * <p>
+ * Atomic directory guarantees directory integrity by ensuring that a directory has
+ * been completely written and sync'd to disk before removing its backup.
+ * As long as the backup directory exists, the original directory is considered
+ * to be invalid (leftover from a previous attempt to write).
+ * <p>
+ * Atomic directory does not confer any file locking semantics. Do not use this
+ * class when the directory may be accessed or modified concurrently
+ * by multiple threads or processes. The caller is responsible for ensuring
+ * appropriate mutual exclusion invariants whenever it accesses the directory.
+ * <p>
+ * To ensure atomicity you must always use this class to interact with the
+ * backing directory when checking existence, making changes, and deleting.
+ */
+public final class AtomicDirectory {
+
+ private static final String LOG_TAG = AtomicDirectory.class.getSimpleName();
+
+ private final @NonNull File mBaseDirectory;
+ private final @NonNull File mBackupDirectory;
+
+ private final @NonNull ArrayMap<File, FileOutputStream> mOpenFiles = new ArrayMap<>();
+
+ /**
+ * Creates a new instance.
+ *
+ * @param baseDirectory The base directory to treat atomically.
+ */
+ public AtomicDirectory(@NonNull File baseDirectory) {
+ Preconditions.checkNotNull(baseDirectory, "baseDirectory cannot be null");
+ mBaseDirectory = baseDirectory;
+ mBackupDirectory = new File(baseDirectory.getPath() + "_bak");
+ }
+
+ /**
+ * Gets the backup directory which may or may not exist. This could be
+ * useful if you are writing new state to the directory but need to access
+ * the last persisted state at the same time. This means that this call is
+ * useful in between {@link #startWrite()} and {@link #finishWrite()} or
+ * {@link #failWrite()}. You should not modify the content returned by this
+ * method.
+ *
+ * @see #startRead()
+ */
+ public @NonNull File getBackupDirectory() {
+ return mBackupDirectory;
+ }
+
+ /**
+ * Starts reading this directory. After calling this method you should
+ * not make any changes to its contents.
+ *
+ * @throws IOException If an error occurs.
+ *
+ * @see #finishRead()
+ * @see #startWrite()
+ */
+ public @NonNull File startRead() throws IOException {
+ restore();
+ ensureBaseDirectory();
+ return mBaseDirectory;
+ }
+
+ /**
+ * Finishes reading this directory.
+ *
+ * @see #startRead()
+ * @see #startWrite()
+ */
+ public void finishRead() {}
+
+ /**
+ * Starts editing this directory. After calling this method you should
+ * add content to the directory only via the APIs on this class. To open a
+ * file for writing in this directory you should use {@link #openWrite(File)}
+ * and to close the file {@link #closeWrite(FileOutputStream)}. Once all
+ * content has been written and all files closed you should commit via a
+ * call to {@link #finishWrite()} or discard via a call to {@link #failWrite()}.
+ *
+ * @throws IOException If an error occurs.
+ *
+ * @see #startRead()
+ * @see #openWrite(File)
+ * @see #finishWrite()
+ * @see #failWrite()
+ */
+ public @NonNull File startWrite() throws IOException {
+ backup();
+ ensureBaseDirectory();
+ return mBaseDirectory;
+ }
+
+ /**
+ * Opens a file in this directory for writing.
+ *
+ * @param file The file to open. Must be a file in the base directory.
+ * @return An input stream for reading.
+ *
+ * @throws IOException If an I/O error occurs.
+ *
+ * @see #closeWrite(FileOutputStream)
+ */
+ public @NonNull FileOutputStream openWrite(@NonNull File file) throws IOException {
+ if (file.isDirectory() || !file.getParentFile().equals(mBaseDirectory)) {
+ throw new IllegalArgumentException("Must be a file in " + mBaseDirectory);
+ }
+ if (mOpenFiles.containsKey(file)) {
+ throw new IllegalArgumentException("Already open file " + file.getAbsolutePath());
+ }
+ final FileOutputStream destination = new FileOutputStream(file);
+ mOpenFiles.put(file, destination);
+ return destination;
+ }
+
+ /**
+ * Closes a previously opened file.
+ *
+ * @param destination The stream to the file returned by {@link #openWrite(File)}.
+ *
+ * @see #openWrite(File)
+ */
+ public void closeWrite(@NonNull FileOutputStream destination) {
+ final int indexOfValue = mOpenFiles.indexOfValue(destination);
+ if (indexOfValue < 0) {
+ throw new IllegalArgumentException("Unknown file stream " + destination);
+ }
+ mOpenFiles.removeAt(indexOfValue);
+ FileUtils.sync(destination);
+ FileUtils.closeQuietly(destination);
+ }
+
+ public void failWrite(@NonNull FileOutputStream destination) {
+ final int indexOfValue = mOpenFiles.indexOfValue(destination);
+ if (indexOfValue < 0) {
+ throw new IllegalArgumentException("Unknown file stream " + destination);
+ }
+ mOpenFiles.removeAt(indexOfValue);
+ FileUtils.closeQuietly(destination);
+ }
+
+ /**
+ * Finishes the edit and commits all changes.
+ *
+ * @see #startWrite()
+ *
+ * @throws IllegalStateException if some files are not closed.
+ */
+ public void finishWrite() {
+ throwIfSomeFilesOpen();
+
+ syncDirectory(mBaseDirectory);
+ syncParentDirectory();
+ deleteDirectory(mBackupDirectory);
+ syncParentDirectory();
+ }
+
+ /**
+ * Finishes the edit and discards all changes.
+ *
+ * @see #startWrite()
+ */
+ public void failWrite() {
+ throwIfSomeFilesOpen();
+
+ try{
+ restore();
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Failed to restore in failWrite()", e);
+ }
+ }
+
+ /**
+ * @return Whether this directory exists.
+ */
+ public boolean exists() {
+ return mBaseDirectory.exists() || mBackupDirectory.exists();
+ }
+
+ /**
+ * Deletes this directory.
+ */
+ public void delete() {
+ boolean deleted = false;
+ if (mBaseDirectory.exists()) {
+ deleted |= deleteDirectory(mBaseDirectory);
+ }
+ if (mBackupDirectory.exists()) {
+ deleted |= deleteDirectory(mBackupDirectory);
+ }
+ if (deleted) {
+ syncParentDirectory();
+ }
+ }
+
+ private void ensureBaseDirectory() throws IOException {
+ if (mBaseDirectory.exists()) {
+ return;
+ }
+
+ if (!mBaseDirectory.mkdirs()) {
+ throw new IOException("Failed to create directory " + mBaseDirectory);
+ }
+ FileUtils.setPermissions(mBaseDirectory.getPath(),
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1);
+ }
+
+ private void throwIfSomeFilesOpen() {
+ if (!mOpenFiles.isEmpty()) {
+ throw new IllegalStateException("Unclosed files: "
+ + Arrays.toString(mOpenFiles.keySet().toArray()));
+ }
+ }
+
+ private void backup() throws IOException {
+ if (!mBaseDirectory.exists()) {
+ return;
+ }
+
+ if (mBackupDirectory.exists()) {
+ deleteDirectory(mBackupDirectory);
+ }
+ if (!mBaseDirectory.renameTo(mBackupDirectory)) {
+ throw new IOException("Failed to backup " + mBaseDirectory + " to " + mBackupDirectory);
+ }
+ syncParentDirectory();
+ }
+
+ private void restore() throws IOException {
+ if (!mBackupDirectory.exists()) {
+ return;
+ }
+
+ if (mBaseDirectory.exists()) {
+ deleteDirectory(mBaseDirectory);
+ }
+ if (!mBackupDirectory.renameTo(mBaseDirectory)) {
+ throw new IOException("Failed to restore " + mBackupDirectory + " to "
+ + mBaseDirectory);
+ }
+ syncParentDirectory();
+ }
+
+ private static boolean deleteDirectory(@NonNull File directory) {
+ return FileUtils.deleteContentsAndDir(directory);
+ }
+
+ private void syncParentDirectory() {
+ syncDirectory(mBaseDirectory.getParentFile());
+ }
+
+ // Standard Java IO doesn't allow opening a directory (will throw a FileNotFoundException
+ // instead), so we have to do it manually.
+ private static void syncDirectory(@NonNull File directory) {
+ String path = directory.getAbsolutePath();
+ FileDescriptor fd;
+ try {
+ fd = Os.open(path, OsConstants.O_RDONLY, 0);
+ } catch (ErrnoException e) {
+ Log.e(LOG_TAG, "Failed to open " + path, e);
+ return;
+ }
+ try {
+ Os.fsync(fd);
+ } catch (ErrnoException e) {
+ Log.e(LOG_TAG, "Failed to fsync " + path, e);
+ } finally {
+ FileUtils.closeQuietly(fd);
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/BackgroundThread.java b/android-35/com/android/internal/os/BackgroundThread.java
new file mode 100644
index 0000000..b75daed
--- /dev/null
+++ b/android-35/com/android/internal/os/BackgroundThread.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2013 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.internal.os;
+
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Trace;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Shared singleton background thread for each process.
+ */
[email protected]
+public final class BackgroundThread extends HandlerThread {
+ private static final long SLOW_DISPATCH_THRESHOLD_MS = 10_000;
+ private static final long SLOW_DELIVERY_THRESHOLD_MS = 30_000;
+ private static BackgroundThread sInstance;
+ private static Handler sHandler;
+ private static HandlerExecutor sHandlerExecutor;
+
+ private BackgroundThread() {
+ super("android.bg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ }
+
+ private static void ensureThreadLocked() {
+ if (sInstance == null) {
+ sInstance = new BackgroundThread();
+ sInstance.start();
+ final Looper looper = sInstance.getLooper();
+ looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER);
+ looper.setSlowLogThresholdMs(
+ SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
+ sHandler = new Handler(sInstance.getLooper(), /*callback=*/ null, /* async=*/ false,
+ /* shared=*/ true);
+ sHandlerExecutor = new HandlerExecutor(sHandler);
+ }
+ }
+
+ public static BackgroundThread get() {
+ synchronized (BackgroundThread.class) {
+ ensureThreadLocked();
+ return sInstance;
+ }
+ }
+
+ public static Handler getHandler() {
+ synchronized (BackgroundThread.class) {
+ ensureThreadLocked();
+ return sHandler;
+ }
+ }
+
+ public static Executor getExecutor() {
+ synchronized (BackgroundThread.class) {
+ ensureThreadLocked();
+ return sHandlerExecutor;
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/BaseCommand.java b/android-35/com/android/internal/os/BaseCommand.java
new file mode 100644
index 0000000..c85b5d7
--- /dev/null
+++ b/android-35/com/android/internal/os/BaseCommand.java
@@ -0,0 +1,138 @@
+/*
+**
+** Copyright 2013, 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.internal.os;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+
+import com.android.modules.utils.BasicShellCommandHandler;
+
+import java.io.PrintStream;
+
+public abstract class BaseCommand {
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ final protected BasicShellCommandHandler mArgs = new BasicShellCommandHandler() {
+ @Override public int onCommand(String cmd) {
+ return 0;
+ }
+ @Override public void onHelp() {
+ }
+ };
+
+ // These are magic strings understood by the Eclipse plugin.
+ public static final String FATAL_ERROR_CODE = "Error type 1";
+ public static final String NO_SYSTEM_ERROR_CODE = "Error type 2";
+ public static final String NO_CLASS_ERROR_CODE = "Error type 3";
+
+ private String[] mRawArgs;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public BaseCommand() {
+ }
+
+ /**
+ * Call to run the command.
+ */
+ public void run(String[] args) {
+ if (args.length < 1) {
+ onShowUsage(System.out);
+ return;
+ }
+
+ mRawArgs = args;
+ mArgs.init(null, null, null, null, args, 0);
+
+ try {
+ onRun();
+ } catch (IllegalArgumentException e) {
+ onShowUsage(System.err);
+ System.err.println();
+ System.err.println("Error: " + e.getMessage());
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ System.exit(1);
+ }
+ }
+
+ /**
+ * Convenience to show usage information to error output.
+ */
+ public void showUsage() {
+ onShowUsage(System.err);
+ }
+
+ /**
+ * Convenience to show usage information to error output along
+ * with an error message.
+ */
+ public void showError(String message) {
+ onShowUsage(System.err);
+ System.err.println();
+ System.err.println(message);
+ }
+
+ /**
+ * Implement the command.
+ */
+ public abstract void onRun() throws Exception;
+
+ /**
+ * Print help text for the command.
+ */
+ public abstract void onShowUsage(PrintStream out);
+
+ /**
+ * Return the next option on the command line -- that is an argument that
+ * starts with '-'. If the next argument is not an option, null is returned.
+ */
+ public String nextOption() {
+ return mArgs.getNextOption();
+ }
+
+ /**
+ * Return the next argument on the command line, whatever it is; if there are
+ * no arguments left, return null.
+ */
+ public String nextArg() {
+ return mArgs.getNextArg();
+ }
+
+ /**
+ * Peek the next argument on the command line, whatever it is; if there are
+ * no arguments left, return null.
+ */
+ public String peekNextArg() {
+ return mArgs.peekNextArg();
+ }
+
+ /**
+ * Return the next argument on the command line, whatever it is; if there are
+ * no arguments left, throws an IllegalArgumentException to report this to the user.
+ */
+ public String nextArgRequired() {
+ return mArgs.getNextArgRequired();
+ }
+
+ /**
+ * Return the original raw argument list supplied to the command.
+ */
+ public String[] getRawArgs() {
+ return mRawArgs;
+ }
+}
diff --git a/android-35/com/android/internal/os/BatteryStatsHistory.java b/android-35/com/android/internal/os/BatteryStatsHistory.java
new file mode 100644
index 0000000..e6af64a
--- /dev/null
+++ b/android-35/com/android/internal/os/BatteryStatsHistory.java
@@ -0,0 +1,2466 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.BatteryConsumer;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.BatteryStats.BitDescription;
+import android.os.BatteryStats.HistoryItem;
+import android.os.BatteryStats.HistoryStepDetails;
+import android.os.BatteryStats.HistoryTag;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.ParcelFormatException;
+import android.os.Process;
+import android.os.StatFs;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.ConcurrentModificationException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * BatteryStatsHistory encapsulates battery history files.
+ * Battery history record is appended into buffer {@link #mHistoryBuffer} and backed up into
+ * {@link #mActiveFile}.
+ * When {@link #mHistoryBuffer} size reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER},
+ * current mActiveFile is closed and a new mActiveFile is open.
+ * History files are under directory /data/system/battery-history/.
+ * History files have name battery-history-<num>.bin. The file number <num> starts from zero and
+ * grows sequentially.
+ * The mActiveFile is always the highest numbered history file.
+ * The lowest number file is always the oldest file.
+ * The highest number file is always the newest file.
+ * The file number grows sequentially and we never skip number.
+ * When count of history files exceeds {@link BatteryStatsImpl.Constants#MAX_HISTORY_FILES},
+ * the lowest numbered file is deleted and a new file is open.
+ *
+ * All interfaces in BatteryStatsHistory should only be called by BatteryStatsImpl and protected by
+ * locks on BatteryStatsImpl object.
+ */
[email protected]
+public class BatteryStatsHistory {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "BatteryStatsHistory";
+
+ // Current on-disk Parcel version. Must be updated when the format of the parcelable changes
+ private static final int VERSION = 210;
+
+ private static final String HISTORY_DIR = "battery-history";
+ private static final String FILE_SUFFIX = ".bh";
+ private static final int MIN_FREE_SPACE = 100 * 1024 * 1024;
+
+ // Part of initial delta int that specifies the time delta.
+ static final int DELTA_TIME_MASK = 0x7ffff;
+ static final int DELTA_TIME_LONG = 0x7ffff; // The delta is a following long
+ static final int DELTA_TIME_INT = 0x7fffe; // The delta is a following int
+ static final int DELTA_TIME_ABS = 0x7fffd; // Following is an entire abs update.
+ // Flag in delta int: a new battery level int follows.
+ static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000;
+ // Flag in delta int: a new full state and battery status int follows.
+ static final int DELTA_STATE_FLAG = 0x00100000;
+ // Flag in delta int: a new full state2 int follows.
+ static final int DELTA_STATE2_FLAG = 0x00200000;
+ // Flag in delta int: contains a wakelock or wakeReason tag.
+ static final int DELTA_WAKELOCK_FLAG = 0x00400000;
+ // Flag in delta int: contains an event description.
+ static final int DELTA_EVENT_FLAG = 0x00800000;
+ // Flag in delta int: contains the battery charge count in uAh.
+ static final int DELTA_BATTERY_CHARGE_FLAG = 0x01000000;
+ // These upper bits are the frequently changing state bits.
+ static final int DELTA_STATE_MASK = 0xfe000000;
+ // These are the pieces of battery state that are packed in to the upper bits of
+ // the state int that have been packed in to the first delta int. They must fit
+ // in STATE_BATTERY_MASK.
+ static final int STATE_BATTERY_MASK = 0xff000000;
+ static final int STATE_BATTERY_STATUS_MASK = 0x00000007;
+ static final int STATE_BATTERY_STATUS_SHIFT = 29;
+ static final int STATE_BATTERY_HEALTH_MASK = 0x00000007;
+ static final int STATE_BATTERY_HEALTH_SHIFT = 26;
+ static final int STATE_BATTERY_PLUG_MASK = 0x00000003;
+ static final int STATE_BATTERY_PLUG_SHIFT = 24;
+
+ // We use the low bit of the battery state int to indicate that we have full details
+ // from a battery level change.
+ static final int BATTERY_LEVEL_DETAILS_FLAG = 0x00000001;
+
+ // Flag in history tag index: indicates that this is the first occurrence of this tag,
+ // therefore the tag value is written in the parcel
+ static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000;
+
+ static final int EXTENSION_POWER_STATS_DESCRIPTOR_FLAG = 0x00000001;
+ static final int EXTENSION_POWER_STATS_FLAG = 0x00000002;
+ static final int EXTENSION_PROCESS_STATE_CHANGE_FLAG = 0x00000004;
+
+ // For state1, trace everything except the wakelock bit (which can race with
+ // suspend) and the running bit (which isn't meaningful in traces).
+ static final int STATE1_TRACE_MASK = ~(HistoryItem.STATE_WAKE_LOCK_FLAG
+ | HistoryItem.STATE_CPU_RUNNING_FLAG);
+ // For state2, trace all bit changes.
+ static final int STATE2_TRACE_MASK = ~0;
+
+ /**
+ * Number of overflow bytes that can be written into the history buffer if the history
+ * directory is locked. This is done to prevent a long lock contention and a potential
+ * kill by a watchdog.
+ */
+ private static final int EXTRA_BUFFER_SIZE_WHEN_DIR_LOCKED = 100_000;
+
+ private final Parcel mHistoryBuffer;
+ private final File mSystemDir;
+ private final HistoryStepDetailsCalculator mStepDetailsCalculator;
+ private final Clock mClock;
+
+ private int mMaxHistoryBufferSize;
+
+ /**
+ * The active history file that the history buffer is backed up into.
+ */
+ private AtomicFile mActiveFile;
+
+ /**
+ * A list of history files with increasing timestamps.
+ */
+ private final BatteryHistoryDirectory mHistoryDir;
+
+ /**
+ * A list of small history parcels, used when BatteryStatsImpl object is created from
+ * deserialization of a parcel, such as Settings app or checkin file.
+ */
+ private List<Parcel> mHistoryParcels = null;
+
+ /**
+ * When iterating history files, the current file index.
+ */
+ private BatteryHistoryFile mCurrentFile;
+
+ /**
+ * When iterating history files, the current file parcel.
+ */
+ private Parcel mCurrentParcel;
+ /**
+ * When iterating history file, the current parcel's Parcel.dataSize().
+ */
+ private int mCurrentParcelEnd;
+ /**
+ * Used when BatteryStatsImpl object is created from deserialization of a parcel,
+ * such as Settings app or checkin file, to iterate over history parcels.
+ */
+ private int mParcelIndex = 0;
+
+ private final ReentrantLock mWriteLock = new ReentrantLock();
+
+ private final HistoryItem mHistoryCur = new HistoryItem();
+
+ private boolean mHaveBatteryLevel;
+ private boolean mRecordingHistory;
+
+ static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe;
+ private static final int MAX_HISTORY_TAG_STRING_LENGTH = 1024;
+
+ private final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>();
+ private SparseArray<HistoryTag> mHistoryTags;
+ private final HistoryItem mHistoryLastWritten = new HistoryItem();
+ private final HistoryItem mHistoryLastLastWritten = new HistoryItem();
+ private final HistoryItem mHistoryAddTmp = new HistoryItem();
+ private int mNextHistoryTagIdx = 0;
+ private int mNumHistoryTagChars = 0;
+ private int mHistoryBufferLastPos = -1;
+ private long mTrackRunningHistoryElapsedRealtimeMs = 0;
+ private long mTrackRunningHistoryUptimeMs = 0;
+ private final MonotonicClock mMonotonicClock;
+ // Monotonic time when we started writing to the history buffer
+ private long mHistoryBufferStartTime;
+ private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();
+ private byte mLastHistoryStepLevel = 0;
+ private boolean mMutable = true;
+ private final BatteryStatsHistory mWritableHistory;
+
+ private static class BatteryHistoryFile implements Comparable<BatteryHistoryFile> {
+ public final long monotonicTimeMs;
+ public final AtomicFile atomicFile;
+
+ private BatteryHistoryFile(File directory, long monotonicTimeMs) {
+ this.monotonicTimeMs = monotonicTimeMs;
+ atomicFile = new AtomicFile(new File(directory, monotonicTimeMs + FILE_SUFFIX));
+ }
+
+ @Override
+ public int compareTo(BatteryHistoryFile o) {
+ return Long.compare(monotonicTimeMs, o.monotonicTimeMs);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return monotonicTimeMs == ((BatteryHistoryFile) o).monotonicTimeMs;
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(monotonicTimeMs);
+ }
+
+ @Override
+ public String toString() {
+ return atomicFile.getBaseFile().toString();
+ }
+ }
+
+ private static class BatteryHistoryDirectory {
+ private final File mDirectory;
+ private final MonotonicClock mMonotonicClock;
+ private int mMaxHistoryFiles;
+ private final List<BatteryHistoryFile> mHistoryFiles = new ArrayList<>();
+ private final ReentrantLock mLock = new ReentrantLock();
+ private boolean mCleanupNeeded;
+
+ BatteryHistoryDirectory(File directory, MonotonicClock monotonicClock,
+ int maxHistoryFiles) {
+ mDirectory = directory;
+ mMonotonicClock = monotonicClock;
+ mMaxHistoryFiles = maxHistoryFiles;
+ if (mMaxHistoryFiles == 0) {
+ Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
+ }
+ }
+
+ void setMaxHistoryFiles(int maxHistoryFiles) {
+ mMaxHistoryFiles = maxHistoryFiles;
+ cleanup();
+ }
+
+ void lock() {
+ mLock.lock();
+ }
+
+ boolean tryLock() {
+ return mLock.tryLock();
+ }
+
+ void unlock() {
+ mLock.unlock();
+ if (mCleanupNeeded) {
+ cleanup();
+ }
+ }
+
+ boolean isLocked() {
+ return mLock.isLocked();
+ }
+
+ void load() {
+ mDirectory.mkdirs();
+ if (!mDirectory.exists()) {
+ Slog.wtf(TAG, "HistoryDir does not exist:" + mDirectory.getPath());
+ }
+
+ final List<File> toRemove = new ArrayList<>();
+ final Set<BatteryHistoryFile> dedup = new ArraySet<>();
+ mDirectory.listFiles((dir, name) -> {
+ final int b = name.lastIndexOf(FILE_SUFFIX);
+ if (b <= 0) {
+ toRemove.add(new File(dir, name));
+ return false;
+ }
+ try {
+ long monotonicTime = Long.parseLong(name.substring(0, b));
+ dedup.add(new BatteryHistoryFile(mDirectory, monotonicTime));
+ } catch (NumberFormatException e) {
+ toRemove.add(new File(dir, name));
+ return false;
+ }
+ return true;
+ });
+ if (!dedup.isEmpty()) {
+ mHistoryFiles.addAll(dedup);
+ Collections.sort(mHistoryFiles);
+ }
+ if (!toRemove.isEmpty()) {
+ // Clear out legacy history files, which did not follow the X-Y.bin naming format.
+ BackgroundThread.getHandler().post(() -> {
+ lock();
+ try {
+ for (File file : toRemove) {
+ file.delete();
+ }
+ } finally {
+ unlock();
+ }
+ });
+ }
+ }
+
+ List<String> getFileNames() {
+ lock();
+ try {
+ List<String> names = new ArrayList<>();
+ for (BatteryHistoryFile historyFile : mHistoryFiles) {
+ names.add(historyFile.atomicFile.getBaseFile().getName());
+ }
+ return names;
+ } finally {
+ unlock();
+ }
+ }
+
+ @Nullable
+ BatteryHistoryFile getFirstFile() {
+ lock();
+ try {
+ if (!mHistoryFiles.isEmpty()) {
+ return mHistoryFiles.get(0);
+ }
+ return null;
+ } finally {
+ unlock();
+ }
+ }
+
+ @Nullable
+ BatteryHistoryFile getLastFile() {
+ lock();
+ try {
+ if (!mHistoryFiles.isEmpty()) {
+ return mHistoryFiles.get(mHistoryFiles.size() - 1);
+ }
+ return null;
+ } finally {
+ unlock();
+ }
+ }
+
+ @Nullable
+ BatteryHistoryFile getNextFile(BatteryHistoryFile current, long startTimeMs,
+ long endTimeMs) {
+ if (!mLock.isHeldByCurrentThread()) {
+ throw new IllegalStateException("Iterating battery history without a lock");
+ }
+
+ int nextFileIndex = 0;
+ int firstFileIndex = 0;
+ // skip the last file because its data is in history buffer.
+ int lastFileIndex = mHistoryFiles.size() - 2;
+ for (int i = lastFileIndex; i >= 0; i--) {
+ BatteryHistoryFile file = mHistoryFiles.get(i);
+ if (current != null && file.monotonicTimeMs == current.monotonicTimeMs) {
+ nextFileIndex = i + 1;
+ }
+ if (file.monotonicTimeMs > endTimeMs) {
+ lastFileIndex = i - 1;
+ }
+ if (file.monotonicTimeMs <= startTimeMs) {
+ firstFileIndex = i;
+ break;
+ }
+ }
+
+ if (nextFileIndex < firstFileIndex) {
+ nextFileIndex = firstFileIndex;
+ }
+
+ if (nextFileIndex <= lastFileIndex) {
+ return mHistoryFiles.get(nextFileIndex);
+ }
+
+ return null;
+ }
+
+ BatteryHistoryFile makeBatteryHistoryFile() {
+ BatteryHistoryFile file = new BatteryHistoryFile(mDirectory,
+ mMonotonicClock.monotonicTime());
+ lock();
+ try {
+ mHistoryFiles.add(file);
+ } finally {
+ unlock();
+ }
+ return file;
+ }
+
+ void writeToParcel(Parcel out, boolean useBlobs) {
+ lock();
+ try {
+ final long start = SystemClock.uptimeMillis();
+ out.writeInt(mHistoryFiles.size() - 1);
+ for (int i = 0; i < mHistoryFiles.size() - 1; i++) {
+ AtomicFile file = mHistoryFiles.get(i).atomicFile;
+ byte[] raw = new byte[0];
+ try {
+ raw = file.readFully();
+ } catch (Exception e) {
+ Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
+ }
+ if (useBlobs) {
+ out.writeBlob(raw);
+ } else {
+ // Avoiding blobs in the check-in file for compatibility
+ out.writeByteArray(raw);
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "writeToParcel duration ms:" + (SystemClock.uptimeMillis() - start));
+ }
+ } finally {
+ unlock();
+ }
+ }
+
+ int getFileCount() {
+ lock();
+ try {
+ return mHistoryFiles.size();
+ } finally {
+ unlock();
+ }
+ }
+
+ int getSize() {
+ lock();
+ try {
+ int ret = 0;
+ for (int i = 0; i < mHistoryFiles.size() - 1; i++) {
+ ret += (int) mHistoryFiles.get(i).atomicFile.getBaseFile().length();
+ }
+ return ret;
+ } finally {
+ unlock();
+ }
+ }
+
+ void reset() {
+ lock();
+ try {
+ if (DEBUG) Slog.i(TAG, "********** CLEARING HISTORY!");
+ for (BatteryHistoryFile file : mHistoryFiles) {
+ file.atomicFile.delete();
+ }
+ mHistoryFiles.clear();
+ } finally {
+ unlock();
+ }
+ }
+
+ private void cleanup() {
+ if (mDirectory == null) {
+ return;
+ }
+
+ if (!tryLock()) {
+ mCleanupNeeded = true;
+ return;
+ }
+
+ mCleanupNeeded = false;
+ try {
+ // if free disk space is less than 100MB, delete oldest history file.
+ if (!hasFreeDiskSpace(mDirectory)) {
+ BatteryHistoryFile oldest = mHistoryFiles.remove(0);
+ oldest.atomicFile.delete();
+ }
+
+ // if there are more history files than allowed, delete oldest history files.
+ // mMaxHistoryFiles comes from Constants.MAX_HISTORY_FILES and
+ // can be updated by DeviceConfig at run time.
+ while (mHistoryFiles.size() > mMaxHistoryFiles) {
+ BatteryHistoryFile oldest = mHistoryFiles.get(0);
+ oldest.atomicFile.delete();
+ mHistoryFiles.remove(0);
+ }
+ } finally {
+ unlock();
+ }
+ }
+ }
+
+ /**
+ * A delegate responsible for computing additional details for a step in battery history.
+ */
+ public interface HistoryStepDetailsCalculator {
+ /**
+ * Returns additional details for the current history step or null.
+ */
+ @Nullable
+ HistoryStepDetails getHistoryStepDetails();
+
+ /**
+ * Resets the calculator to get ready for a new battery session
+ */
+ void clear();
+ }
+
+ /**
+ * A delegate for android.os.Trace to allow testing static calls. Due to
+ * limitations in Android Tracing (b/153319140), the delegate also records
+ * counter values in system properties which allows reading the value at the
+ * start of a tracing session. This overhead is limited to userdebug builds.
+ * On user builds, tracing still occurs but the counter value will be missing
+ * until the first change occurs.
+ */
+ @VisibleForTesting
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
+ public static class TraceDelegate {
+ // Note: certain tests currently run as platform_app which is not allowed
+ // to set debug system properties. To ensure that system properties are set
+ // only when allowed, we check the current UID.
+ private final boolean mShouldSetProperty =
+ Build.IS_USERDEBUG && (Process.myUid() == Process.SYSTEM_UID);
+
+ /**
+ * Returns true if trace counters should be recorded.
+ */
+ public boolean tracingEnabled() {
+ return Trace.isTagEnabled(Trace.TRACE_TAG_POWER) || mShouldSetProperty;
+ }
+
+ /**
+ * Records the counter value with the given name.
+ */
+ public void traceCounter(@NonNull String name, int value) {
+ Trace.traceCounter(Trace.TRACE_TAG_POWER, name, value);
+ if (mShouldSetProperty) {
+ try {
+ SystemProperties.set("debug.tracing." + name, Integer.toString(value));
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Failed to set debug.tracing." + name, e);
+ }
+ }
+ }
+
+ /**
+ * Records an instant event (one with no duration).
+ */
+ public void traceInstantEvent(@NonNull String track, @NonNull String name) {
+ Trace.instantForTrack(Trace.TRACE_TAG_POWER, track, name);
+ }
+ }
+
+ public static class EventLogger {
+ /**
+ * Records a statsd event when the batterystats config file is written to disk.
+ */
+ public void writeCommitSysConfigFile(long startTimeMs) {
+ com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
+ "batterystats", SystemClock.uptimeMillis() - startTimeMs);
+ }
+ }
+
+ private TraceDelegate mTracer;
+ private int mTraceLastState = 0;
+ private int mTraceLastState2 = 0;
+ private final EventLogger mEventLogger;
+
+ /**
+ * Constructor
+ *
+ * @param systemDir typically /data/system
+ * @param maxHistoryFiles the largest number of history buffer files to keep
+ * @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps
+ */
+ public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
+ int maxHistoryFiles, int maxHistoryBufferSize,
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+ MonotonicClock monotonicClock, TraceDelegate tracer, EventLogger eventLogger) {
+ this(historyBuffer, systemDir, maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator,
+ clock, monotonicClock, tracer, eventLogger, null);
+ }
+
+ private BatteryStatsHistory(@Nullable Parcel historyBuffer, @Nullable File systemDir,
+ int maxHistoryFiles, int maxHistoryBufferSize,
+ @NonNull HistoryStepDetailsCalculator stepDetailsCalculator, @NonNull Clock clock,
+ @NonNull MonotonicClock monotonicClock, @NonNull TraceDelegate tracer,
+ @NonNull EventLogger eventLogger, @Nullable BatteryStatsHistory writableHistory) {
+ mSystemDir = systemDir;
+ mMaxHistoryBufferSize = maxHistoryBufferSize;
+ mStepDetailsCalculator = stepDetailsCalculator;
+ mTracer = tracer;
+ mClock = clock;
+ mMonotonicClock = monotonicClock;
+ mEventLogger = eventLogger;
+ mWritableHistory = writableHistory;
+ if (mWritableHistory != null) {
+ mMutable = false;
+ }
+
+ if (historyBuffer != null) {
+ mHistoryBuffer = historyBuffer;
+ } else {
+ mHistoryBuffer = Parcel.obtain();
+ initHistoryBuffer();
+ }
+
+ if (writableHistory != null) {
+ mHistoryDir = writableHistory.mHistoryDir;
+ } else if (systemDir != null) {
+ mHistoryDir = new BatteryHistoryDirectory(new File(systemDir, HISTORY_DIR),
+ monotonicClock, maxHistoryFiles);
+ mHistoryDir.load();
+ BatteryHistoryFile activeFile = mHistoryDir.getLastFile();
+ if (activeFile == null) {
+ activeFile = mHistoryDir.makeBatteryHistoryFile();
+ }
+ setActiveFile(activeFile);
+ } else {
+ mHistoryDir = null;
+ }
+ }
+
+ /**
+ * Used when BatteryStatsHistory object is created from deserialization of a BatteryUsageStats
+ * parcel.
+ */
+ private BatteryStatsHistory(Parcel parcel) {
+ mClock = Clock.SYSTEM_CLOCK;
+ mTracer = null;
+ mSystemDir = null;
+ mHistoryDir = null;
+ mStepDetailsCalculator = null;
+ mEventLogger = new EventLogger();
+ mWritableHistory = null;
+ mMutable = false;
+
+ final byte[] historyBlob = parcel.readBlob();
+
+ mHistoryBuffer = Parcel.obtain();
+ mHistoryBuffer.unmarshall(historyBlob, 0, historyBlob.length);
+
+ mMonotonicClock = null;
+ readFromParcel(parcel, true /* useBlobs */);
+ }
+
+ private void initHistoryBuffer() {
+ mTrackRunningHistoryElapsedRealtimeMs = 0;
+ mTrackRunningHistoryUptimeMs = 0;
+ mWrittenPowerStatsDescriptors.clear();
+
+ mHistoryBufferStartTime = mMonotonicClock.monotonicTime();
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+ mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
+ mHistoryLastLastWritten.clear();
+ mHistoryLastWritten.clear();
+ mHistoryTagPool.clear();
+ mNextHistoryTagIdx = 0;
+ mNumHistoryTagChars = 0;
+ mHistoryBufferLastPos = -1;
+ if (mStepDetailsCalculator != null) {
+ mStepDetailsCalculator.clear();
+ }
+ }
+
+ /**
+ * Changes the maximum number of history files to be kept.
+ */
+ public void setMaxHistoryFiles(int maxHistoryFiles) {
+ if (mHistoryDir != null) {
+ mHistoryDir.setMaxHistoryFiles(maxHistoryFiles);
+ }
+ }
+
+ /**
+ * Changes the maximum size of the history buffer, in bytes.
+ */
+ public void setMaxHistoryBufferSize(int maxHistoryBufferSize) {
+ mMaxHistoryBufferSize = maxHistoryBufferSize;
+ }
+
+ /**
+ * Creates a read-only copy of the battery history. Does not copy the files stored
+ * in the system directory, so it is not safe while actively writing history.
+ */
+ public BatteryStatsHistory copy() {
+ synchronized (this) {
+ // Make a copy of battery history to avoid concurrent modification.
+ Parcel historyBufferCopy = Parcel.obtain();
+ historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+
+ return new BatteryStatsHistory(historyBufferCopy, mSystemDir, 0, 0, null, null, null,
+ null, mEventLogger, this);
+ }
+ }
+
+ /**
+ * Returns true if this instance only supports reading history.
+ */
+ public boolean isReadOnly() {
+ return !mMutable || mActiveFile == null/* || mHistoryDir == null*/;
+ }
+
+ /**
+ * Set the active file that mHistoryBuffer is backed up into.
+ */
+ private void setActiveFile(BatteryHistoryFile file) {
+ mActiveFile = file.atomicFile;
+ if (DEBUG) {
+ Slog.d(TAG, "activeHistoryFile:" + mActiveFile.getBaseFile().getPath());
+ }
+ }
+
+ /**
+ * When {@link #mHistoryBuffer} reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER},
+ * create next history file.
+ */
+ public void startNextFile(long elapsedRealtimeMs) {
+ synchronized (this) {
+ startNextFileLocked(elapsedRealtimeMs);
+ }
+ }
+
+ @GuardedBy("this")
+ private void startNextFileLocked(long elapsedRealtimeMs) {
+ final long start = SystemClock.uptimeMillis();
+ writeHistory();
+ if (DEBUG) {
+ Slog.d(TAG, "writeHistory took ms:" + (SystemClock.uptimeMillis() - start));
+ }
+
+ setActiveFile(mHistoryDir.makeBatteryHistoryFile());
+ try {
+ mActiveFile.getBaseFile().createNewFile();
+ } catch (IOException e) {
+ Slog.e(TAG, "Could not create history file: " + mActiveFile.getBaseFile());
+ }
+
+ mHistoryBufferStartTime = mMonotonicClock.monotonicTime(elapsedRealtimeMs);
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+ mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
+ mHistoryBufferLastPos = -1;
+ mHistoryLastWritten.clear();
+ mHistoryLastLastWritten.clear();
+
+ // Mark every entry in the pool with a flag indicating that the tag
+ // has not yet been encountered while writing the current history buffer.
+ for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
+ entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
+ }
+
+ mWrittenPowerStatsDescriptors.clear();
+ mHistoryDir.cleanup();
+ }
+
+ /**
+ * Returns true if it is safe to reset history. It will return false if the history is
+ * currently being read.
+ */
+ public boolean isResetEnabled() {
+ return mHistoryDir == null || !mHistoryDir.isLocked();
+ }
+
+ /**
+ * Clear history buffer and delete all existing history files. Active history file start from
+ * number 0 again.
+ */
+ public void reset() {
+ synchronized (this) {
+ if (mHistoryDir != null) {
+ mHistoryDir.reset();
+ setActiveFile(mHistoryDir.makeBatteryHistoryFile());
+ }
+ initHistoryBuffer();
+ }
+ }
+
+ /**
+ * Returns the monotonic clock time when the available battery history collection started.
+ */
+ public long getStartTime() {
+ synchronized (this) {
+ BatteryHistoryFile file = mHistoryDir.getFirstFile();
+ if (file != null) {
+ return file.monotonicTimeMs;
+ } else {
+ return mHistoryBufferStartTime;
+ }
+ }
+ }
+
+ /**
+ * Start iterating history files and history buffer.
+ *
+ * @param startTimeMs monotonic time (the HistoryItem.time field) to start iterating from,
+ * inclusive
+ * @param endTimeMs monotonic time to stop iterating, exclusive.
+ * Pass {@link MonotonicClock#UNDEFINED} to indicate current time.
+ */
+ @NonNull
+ public BatteryStatsHistoryIterator iterate(long startTimeMs, long endTimeMs) {
+ if (mMutable) {
+ return copy().iterate(startTimeMs, endTimeMs);
+ }
+
+ if (mHistoryDir != null) {
+ mHistoryDir.lock();
+ }
+ mCurrentFile = null;
+ mCurrentParcel = null;
+ mCurrentParcelEnd = 0;
+ mParcelIndex = 0;
+ return new BatteryStatsHistoryIterator(this, startTimeMs, endTimeMs);
+ }
+
+ /**
+ * Finish iterating history files and history buffer.
+ */
+ void iteratorFinished() {
+ mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+ if (mHistoryDir != null) {
+ mHistoryDir.unlock();
+ }
+ }
+
+ /**
+ * When iterating history files and history buffer, always start from the lowest numbered
+ * history file, when reached the mActiveFile (highest numbered history file), do not read from
+ * mActiveFile, read from history buffer instead because the buffer has more updated data.
+ *
+ * @return The parcel that has next record. null if finished all history files and history
+ * buffer
+ */
+ @Nullable
+ public Parcel getNextParcel(long startTimeMs, long endTimeMs) {
+ checkImmutable();
+
+ // First iterate through all records in current parcel.
+ if (mCurrentParcel != null) {
+ if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) {
+ // There are more records in current parcel.
+ return mCurrentParcel;
+ } else if (mHistoryBuffer == mCurrentParcel) {
+ // finished iterate through all history files and history buffer.
+ return null;
+ } else if (mHistoryParcels == null
+ || !mHistoryParcels.contains(mCurrentParcel)) {
+ // current parcel is from history file.
+ mCurrentParcel.recycle();
+ }
+ }
+
+ if (mHistoryDir != null) {
+ BatteryHistoryFile nextFile = mHistoryDir.getNextFile(mCurrentFile, startTimeMs,
+ endTimeMs);
+ while (nextFile != null) {
+ mCurrentParcel = null;
+ mCurrentParcelEnd = 0;
+ final Parcel p = Parcel.obtain();
+ AtomicFile file = nextFile.atomicFile;
+ if (readFileToParcel(p, file)) {
+ int bufSize = p.readInt();
+ int curPos = p.dataPosition();
+ mCurrentParcelEnd = curPos + bufSize;
+ mCurrentParcel = p;
+ if (curPos < mCurrentParcelEnd) {
+ mCurrentFile = nextFile;
+ return mCurrentParcel;
+ }
+ } else {
+ p.recycle();
+ }
+ nextFile = mHistoryDir.getNextFile(nextFile, startTimeMs, endTimeMs);
+ }
+ }
+
+ // mHistoryParcels is created when BatteryStatsImpl object is created from deserialization
+ // of a parcel, such as Settings app or checkin file.
+ if (mHistoryParcels != null) {
+ while (mParcelIndex < mHistoryParcels.size()) {
+ final Parcel p = mHistoryParcels.get(mParcelIndex++);
+ if (!verifyVersion(p)) {
+ continue;
+ }
+ // skip monotonic time field.
+ p.readLong();
+
+ final int bufSize = p.readInt();
+ final int curPos = p.dataPosition();
+ mCurrentParcelEnd = curPos + bufSize;
+ mCurrentParcel = p;
+ if (curPos < mCurrentParcelEnd) {
+ return mCurrentParcel;
+ }
+ }
+ }
+
+ // finished iterator through history files (except the last one), now history buffer.
+ if (mHistoryBuffer.dataSize() <= 0) {
+ // buffer is empty.
+ return null;
+ }
+ mHistoryBuffer.setDataPosition(0);
+ mCurrentParcel = mHistoryBuffer;
+ mCurrentParcelEnd = mCurrentParcel.dataSize();
+ return mCurrentParcel;
+ }
+
+ private void checkImmutable() {
+ if (mMutable) {
+ throw new IllegalStateException("Iterating over a mutable battery history");
+ }
+ }
+
+ /**
+ * Read history file into a parcel.
+ *
+ * @param out the Parcel read into.
+ * @param file the File to read from.
+ * @return true if success, false otherwise.
+ */
+ public boolean readFileToParcel(Parcel out, AtomicFile file) {
+ byte[] raw = null;
+ try {
+ final long start = SystemClock.uptimeMillis();
+ raw = file.readFully();
+ if (DEBUG) {
+ Slog.d(TAG, "readFileToParcel:" + file.getBaseFile().getPath()
+ + " duration ms:" + (SystemClock.uptimeMillis() - start));
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
+ return false;
+ }
+ out.unmarshall(raw, 0, raw.length);
+ out.setDataPosition(0);
+ if (!verifyVersion(out)) {
+ return false;
+ }
+ // skip monotonic time field.
+ out.readLong();
+ return true;
+ }
+
+ /**
+ * Verify header part of history parcel.
+ *
+ * @return true if version match, false if not.
+ */
+ private boolean verifyVersion(Parcel p) {
+ p.setDataPosition(0);
+ final int version = p.readInt();
+ return version == VERSION;
+ }
+
+ /**
+ * Extracts the monotonic time, as per {@link MonotonicClock}, from the supplied battery history
+ * buffer.
+ */
+ public long getHistoryBufferStartTime(Parcel p) {
+ int pos = p.dataPosition();
+ p.setDataPosition(0);
+ p.readInt(); // Skip the version field
+ long monotonicTime = p.readLong();
+ p.setDataPosition(pos);
+ return monotonicTime;
+ }
+
+ /**
+ * Writes the battery history contents for persistence.
+ */
+ public void writeSummaryToParcel(Parcel out, boolean inclHistory) {
+ out.writeBoolean(inclHistory);
+ if (inclHistory) {
+ writeToParcel(out);
+ }
+
+ out.writeInt(mHistoryTagPool.size());
+ for (Map.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
+ HistoryTag tag = ent.getKey();
+ out.writeInt(ent.getValue());
+ out.writeString(tag.string);
+ out.writeInt(tag.uid);
+ }
+ }
+
+ /**
+ * Reads battery history contents from a persisted parcel.
+ */
+ public void readSummaryFromParcel(Parcel in) {
+ boolean inclHistory = in.readBoolean();
+ if (inclHistory) {
+ readFromParcel(in);
+ }
+
+ mHistoryTagPool.clear();
+ mNextHistoryTagIdx = 0;
+ mNumHistoryTagChars = 0;
+
+ int numTags = in.readInt();
+ for (int i = 0; i < numTags; i++) {
+ int idx = in.readInt();
+ String str = in.readString();
+ int uid = in.readInt();
+ HistoryTag tag = new HistoryTag();
+ tag.string = str;
+ tag.uid = uid;
+ tag.poolIdx = idx;
+ mHistoryTagPool.put(tag, idx);
+ if (idx >= mNextHistoryTagIdx) {
+ mNextHistoryTagIdx = idx + 1;
+ }
+ mNumHistoryTagChars += tag.string.length() + 1;
+ }
+ }
+
+ /**
+ * Read all history files and serialize into a big Parcel.
+ * Checkin file calls this method.
+ *
+ * @param out the output parcel
+ */
+ public void writeToParcel(Parcel out) {
+ synchronized (this) {
+ writeHistoryBuffer(out);
+ writeToParcel(out, false /* useBlobs */);
+ }
+ }
+
+ /**
+ * This is for Settings app, when Settings app receives big history parcel, it call
+ * this method to parse it into list of parcels.
+ *
+ * @param out the output parcel
+ */
+ public void writeToBatteryUsageStatsParcel(Parcel out) {
+ synchronized (this) {
+ out.writeBlob(mHistoryBuffer.marshall());
+ writeToParcel(out, true /* useBlobs */);
+ }
+ }
+
+ private void writeToParcel(Parcel out, boolean useBlobs) {
+ if (mHistoryDir != null) {
+ mHistoryDir.writeToParcel(out, useBlobs);
+ }
+ }
+
+ /**
+ * Reads a BatteryStatsHistory from a parcel written with
+ * the {@link #writeToBatteryUsageStatsParcel} method.
+ */
+ public static BatteryStatsHistory createFromBatteryUsageStatsParcel(Parcel in) {
+ return new BatteryStatsHistory(in);
+ }
+
+ /**
+ * Read history from a check-in file.
+ */
+ public boolean readSummary() {
+ if (mActiveFile == null) {
+ Slog.w(TAG, "readSummary: no history file associated with this instance");
+ return false;
+ }
+
+ Parcel parcel = Parcel.obtain();
+ try {
+ final long start = SystemClock.uptimeMillis();
+ if (mActiveFile.exists()) {
+ byte[] raw = mActiveFile.readFully();
+ if (raw.length > 0) {
+ parcel.unmarshall(raw, 0, raw.length);
+ parcel.setDataPosition(0);
+ readHistoryBuffer(parcel);
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "read history file::"
+ + mActiveFile.getBaseFile().getPath()
+ + " bytes:" + raw.length + " took ms:" + (SystemClock.uptimeMillis()
+ - start));
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Error reading battery history", e);
+ reset();
+ return false;
+ } finally {
+ parcel.recycle();
+ }
+ return true;
+ }
+
+ /**
+ * This is for the check-in file, which has all history files embedded.
+ *
+ * @param in the input parcel.
+ */
+ public void readFromParcel(Parcel in) {
+ readHistoryBuffer(in);
+ readFromParcel(in, false /* useBlobs */);
+ }
+
+ private void readFromParcel(Parcel in, boolean useBlobs) {
+ final long start = SystemClock.uptimeMillis();
+ mHistoryParcels = new ArrayList<>();
+ final int count = in.readInt();
+ for (int i = 0; i < count; i++) {
+ byte[] temp = useBlobs ? in.readBlob() : in.createByteArray();
+ if (temp == null || temp.length == 0) {
+ continue;
+ }
+ Parcel p = Parcel.obtain();
+ p.unmarshall(temp, 0, temp.length);
+ p.setDataPosition(0);
+ mHistoryParcels.add(p);
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "readFromParcel duration ms:" + (SystemClock.uptimeMillis() - start));
+ }
+ }
+
+ /**
+ * @return true if there is more than 100MB free disk space left.
+ */
+ @android.ravenwood.annotation.RavenwoodReplace
+ private static boolean hasFreeDiskSpace(File systemDir) {
+ final StatFs stats = new StatFs(systemDir.getAbsolutePath());
+ return stats.getAvailableBytes() > MIN_FREE_SPACE;
+ }
+
+ private static boolean hasFreeDiskSpace$ravenwood(File systemDir) {
+ return true;
+ }
+
+ @VisibleForTesting
+ public List<String> getFilesNames() {
+ return mHistoryDir.getFileNames();
+ }
+
+ @VisibleForTesting
+ public AtomicFile getActiveFile() {
+ return mActiveFile;
+ }
+
+ /**
+ * @return the total size of all history files and history buffer.
+ */
+ public int getHistoryUsedSize() {
+ int ret = mHistoryDir.getSize();
+ ret += mHistoryBuffer.dataSize();
+ if (mHistoryParcels != null) {
+ for (int i = 0; i < mHistoryParcels.size(); i++) {
+ ret += mHistoryParcels.get(i).dataSize();
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Enables/disables recording of history. When disabled, all "record*" calls are a no-op.
+ */
+ public void setHistoryRecordingEnabled(boolean enabled) {
+ synchronized (this) {
+ mRecordingHistory = enabled;
+ }
+ }
+
+ /**
+ * Returns true if history recording is enabled.
+ */
+ public boolean isRecordingHistory() {
+ synchronized (this) {
+ return mRecordingHistory;
+ }
+ }
+
+ /**
+ * Forces history recording regardless of charging state.
+ */
+ @VisibleForTesting
+ public void forceRecordAllHistory() {
+ synchronized (this) {
+ mHaveBatteryLevel = true;
+ mRecordingHistory = true;
+ }
+ }
+
+ /**
+ * Starts a history buffer by recording the current wall-clock time.
+ */
+ public void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs,
+ boolean reset) {
+ synchronized (this) {
+ mRecordingHistory = true;
+ mHistoryCur.currentTime = mClock.currentTimeMillis();
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur,
+ reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME);
+ mHistoryCur.currentTime = 0;
+ }
+ }
+
+ /**
+ * Prepares to continue recording after restoring previous history from persistent storage.
+ */
+ public void continueRecordingHistory() {
+ synchronized (this) {
+ if (mHistoryBuffer.dataPosition() <= 0 && mHistoryDir.getFileCount() <= 1) {
+ return;
+ }
+
+ mRecordingHistory = true;
+ final long elapsedRealtimeMs = mClock.elapsedRealtime();
+ final long uptimeMs = mClock.uptimeMillis();
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_START);
+ startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
+ }
+ }
+
+ /**
+ * Notes the current battery state to be reflected in the next written history item.
+ */
+ public void setBatteryState(boolean charging, int status, int level, int chargeUah) {
+ synchronized (this) {
+ mHaveBatteryLevel = true;
+ setChargingState(charging);
+ mHistoryCur.batteryStatus = (byte) status;
+ mHistoryCur.batteryLevel = (byte) level;
+ mHistoryCur.batteryChargeUah = chargeUah;
+ }
+ }
+
+ /**
+ * Notes the current battery state to be reflected in the next written history item.
+ */
+ public void setBatteryState(int status, int level, int health, int plugType, int temperature,
+ int voltageMv, int chargeUah) {
+ synchronized (this) {
+ mHaveBatteryLevel = true;
+ mHistoryCur.batteryStatus = (byte) status;
+ mHistoryCur.batteryLevel = (byte) level;
+ mHistoryCur.batteryHealth = (byte) health;
+ mHistoryCur.batteryPlugType = (byte) plugType;
+ mHistoryCur.batteryTemperature = (short) temperature;
+ mHistoryCur.batteryVoltage = (short) voltageMv;
+ mHistoryCur.batteryChargeUah = chargeUah;
+ }
+ }
+
+ /**
+ * Notes the current power plugged-in state to be reflected in the next written history item.
+ */
+ public void setPluggedInState(boolean pluggedIn) {
+ synchronized (this) {
+ if (pluggedIn) {
+ mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+ } else {
+ mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+ }
+ }
+ }
+
+ /**
+ * Notes the current battery charging state to be reflected in the next written history item.
+ */
+ public void setChargingState(boolean charging) {
+ synchronized (this) {
+ if (charging) {
+ mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
+ } else {
+ mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG;
+ }
+ }
+ }
+
+ /**
+ * Records a history event with the given code, name and UID.
+ */
+ public void recordEvent(long elapsedRealtimeMs, long uptimeMs, int code, String name,
+ int uid) {
+ synchronized (this) {
+ mHistoryCur.eventCode = code;
+ mHistoryCur.eventTag = mHistoryCur.localEventTag;
+ mHistoryCur.eventTag.string = name;
+ mHistoryCur.eventTag.uid = uid;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records a time change event.
+ */
+ public void recordCurrentTimeChange(long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
+ synchronized (this) {
+ if (!mRecordingHistory) {
+ return;
+ }
+
+ mHistoryCur.currentTime = currentTimeMs;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur,
+ HistoryItem.CMD_CURRENT_TIME);
+ mHistoryCur.currentTime = 0;
+ }
+ }
+
+ /**
+ * Records a system shutdown event.
+ */
+ public void recordShutdownEvent(long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
+ synchronized (this) {
+ if (!mRecordingHistory) {
+ return;
+ }
+
+ mHistoryCur.currentTime = currentTimeMs;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_SHUTDOWN);
+ mHistoryCur.currentTime = 0;
+ }
+ }
+
+ /**
+ * Records a battery state change event.
+ */
+ public void recordBatteryState(long elapsedRealtimeMs, long uptimeMs, int batteryLevel,
+ boolean isPlugged) {
+ synchronized (this) {
+ mHistoryCur.batteryLevel = (byte) batteryLevel;
+ setPluggedInState(isPlugged);
+ if (DEBUG) {
+ Slog.v(TAG, "Battery unplugged to: "
+ + Integer.toHexString(mHistoryCur.states));
+ }
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records a PowerStats snapshot.
+ */
+ public void recordPowerStats(long elapsedRealtimeMs, long uptimeMs,
+ PowerStats powerStats) {
+ synchronized (this) {
+ mHistoryCur.powerStats = powerStats;
+ mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records the change of a UID's proc state.
+ */
+ public void recordProcessStateChange(long elapsedRealtimeMs, long uptimeMs,
+ int uid, @BatteryConsumer.ProcessState int processState) {
+ synchronized (this) {
+ mHistoryCur.processStateChange = mHistoryCur.localProcessStateChange;
+ mHistoryCur.processStateChange.uid = uid;
+ mHistoryCur.processStateChange.processState = processState;
+ mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records a history item with the amount of charge consumed by WiFi. Used on certain devices
+ * equipped with on-device power metering.
+ */
+ public void recordWifiConsumedCharge(long elapsedRealtimeMs, long uptimeMs,
+ double monitoredRailChargeMah) {
+ synchronized (this) {
+ mHistoryCur.wifiRailChargeMah += monitoredRailChargeMah;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records a wakelock start event.
+ */
+ public void recordWakelockStartEvent(long elapsedRealtimeMs, long uptimeMs, String historyName,
+ int uid) {
+ synchronized (this) {
+ mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+ mHistoryCur.wakelockTag.string = historyName;
+ mHistoryCur.wakelockTag.uid = uid;
+ recordStateStartEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.STATE_WAKE_LOCK_FLAG);
+ }
+ }
+
+ /**
+ * Updates the previous history event with a wakelock name and UID.
+ */
+ public boolean maybeUpdateWakelockTag(long elapsedRealtimeMs, long uptimeMs, String historyName,
+ int uid) {
+ synchronized (this) {
+ if (mHistoryLastWritten.cmd != HistoryItem.CMD_UPDATE) {
+ return false;
+ }
+ if (mHistoryLastWritten.wakelockTag != null) {
+ // We'll try to update the last tag.
+ mHistoryLastWritten.wakelockTag = null;
+ mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+ mHistoryCur.wakelockTag.string = historyName;
+ mHistoryCur.wakelockTag.uid = uid;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Records a wakelock release event.
+ */
+ public void recordWakelockStopEvent(long elapsedRealtimeMs, long uptimeMs, String historyName,
+ int uid) {
+ synchronized (this) {
+ mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+ mHistoryCur.wakelockTag.string = historyName != null ? historyName : "";
+ mHistoryCur.wakelockTag.uid = uid;
+ recordStateStopEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.STATE_WAKE_LOCK_FLAG);
+ }
+ }
+
+ /**
+ * Records an event when some state flag changes to true.
+ */
+ public void recordStateStartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+ synchronized (this) {
+ mHistoryCur.states |= stateFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records an event when some state flag changes to false.
+ */
+ public void recordStateStopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+ synchronized (this) {
+ mHistoryCur.states &= ~stateFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records an event when some state flags change to true and some to false.
+ */
+ public void recordStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int stateStartFlags,
+ int stateStopFlags) {
+ synchronized (this) {
+ mHistoryCur.states = (mHistoryCur.states | stateStartFlags) & ~stateStopFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records an event when some state2 flag changes to true.
+ */
+ public void recordState2StartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+ synchronized (this) {
+ mHistoryCur.states2 |= stateFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records an event when some state2 flag changes to false.
+ */
+ public void recordState2StopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+ synchronized (this) {
+ mHistoryCur.states2 &= ~stateFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records an wakeup event.
+ */
+ public void recordWakeupEvent(long elapsedRealtimeMs, long uptimeMs, String reason) {
+ synchronized (this) {
+ mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
+ mHistoryCur.wakeReasonTag.string = reason;
+ mHistoryCur.wakeReasonTag.uid = 0;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records a screen brightness change event.
+ */
+ public void recordScreenBrightnessEvent(long elapsedRealtimeMs, long uptimeMs,
+ int brightnessBin) {
+ synchronized (this) {
+ mHistoryCur.states = setBitField(mHistoryCur.states, brightnessBin,
+ HistoryItem.STATE_BRIGHTNESS_SHIFT,
+ HistoryItem.STATE_BRIGHTNESS_MASK);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records a GNSS signal level change event.
+ */
+ public void recordGpsSignalQualityEvent(long elapsedRealtimeMs, long uptimeMs,
+ int signalLevel) {
+ synchronized (this) {
+ mHistoryCur.states2 = setBitField(mHistoryCur.states2, signalLevel,
+ HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT,
+ HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records a device idle mode change event.
+ */
+ public void recordDeviceIdleEvent(long elapsedRealtimeMs, long uptimeMs, int mode) {
+ synchronized (this) {
+ mHistoryCur.states2 = setBitField(mHistoryCur.states2, mode,
+ HistoryItem.STATE2_DEVICE_IDLE_SHIFT,
+ HistoryItem.STATE2_DEVICE_IDLE_MASK);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records a telephony state change event.
+ */
+ public void recordPhoneStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int addStateFlag,
+ int removeStateFlag, int state, int signalStrength) {
+ synchronized (this) {
+ mHistoryCur.states = (mHistoryCur.states | addStateFlag) & ~removeStateFlag;
+ if (state != -1) {
+ mHistoryCur.states =
+ setBitField(mHistoryCur.states, state,
+ HistoryItem.STATE_PHONE_STATE_SHIFT,
+ HistoryItem.STATE_PHONE_STATE_MASK);
+ }
+ if (signalStrength != -1) {
+ mHistoryCur.states =
+ setBitField(mHistoryCur.states, signalStrength,
+ HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT,
+ HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK);
+ }
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records a data connection type change event.
+ */
+ public void recordDataConnectionTypeChangeEvent(long elapsedRealtimeMs, long uptimeMs,
+ int dataConnectionType) {
+ synchronized (this) {
+ mHistoryCur.states = setBitField(mHistoryCur.states, dataConnectionType,
+ HistoryItem.STATE_DATA_CONNECTION_SHIFT,
+ HistoryItem.STATE_DATA_CONNECTION_MASK);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records a data connection type change event.
+ */
+ public void recordNrStateChangeEvent(long elapsedRealtimeMs, long uptimeMs,
+ int nrState) {
+ synchronized (this) {
+ mHistoryCur.states2 = setBitField(mHistoryCur.states2, nrState,
+ HistoryItem.STATE2_NR_STATE_SHIFT,
+ HistoryItem.STATE2_NR_STATE_MASK);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records a WiFi supplicant state change event.
+ */
+ public void recordWifiSupplicantStateChangeEvent(long elapsedRealtimeMs, long uptimeMs,
+ int supplState) {
+ synchronized (this) {
+ mHistoryCur.states2 =
+ setBitField(mHistoryCur.states2, supplState,
+ HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT,
+ HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Records a WiFi signal strength change event.
+ */
+ public void recordWifiSignalStrengthChangeEvent(long elapsedRealtimeMs, long uptimeMs,
+ int strengthBin) {
+ synchronized (this) {
+ mHistoryCur.states2 =
+ setBitField(mHistoryCur.states2, strengthBin,
+ HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT,
+ HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ }
+
+ /**
+ * Writes event details into Atrace.
+ */
+ private void recordTraceEvents(int code, HistoryTag tag) {
+ if (code == HistoryItem.EVENT_NONE) return;
+
+ final int idx = code & HistoryItem.EVENT_TYPE_MASK;
+ final String prefix = (code & HistoryItem.EVENT_FLAG_START) != 0 ? "+" :
+ (code & HistoryItem.EVENT_FLAG_FINISH) != 0 ? "-" : "";
+
+ final String[] names = BatteryStats.HISTORY_EVENT_NAMES;
+ if (idx < 0 || idx >= names.length) return;
+
+ final String track = "battery_stats." + names[idx];
+ final String name = prefix + names[idx] + "=" + tag.uid + ":\"" + tag.string + "\"";
+ mTracer.traceInstantEvent(track, name);
+ }
+
+ /**
+ * Writes changes to a HistoryItem state bitmap to Atrace.
+ */
+ private void recordTraceCounters(int oldval, int newval, int mask,
+ BitDescription[] descriptions) {
+ int diff = (oldval ^ newval) & mask;
+ if (diff == 0) return;
+
+ for (int i = 0; i < descriptions.length; i++) {
+ BitDescription bd = descriptions[i];
+ if ((diff & bd.mask) == 0) continue;
+
+ int value;
+ if (bd.shift < 0) {
+ value = (newval & bd.mask) != 0 ? 1 : 0;
+ } else {
+ value = (newval & bd.mask) >> bd.shift;
+ }
+ mTracer.traceCounter("battery_stats." + bd.name, value);
+ }
+ }
+
+ private int setBitField(int bits, int value, int shift, int mask) {
+ int shiftedValue = value << shift;
+ if ((shiftedValue & ~mask) != 0) {
+ Slog.wtfStack(TAG, "Value " + Integer.toHexString(value)
+ + " does not fit in the bit field: " + Integer.toHexString(mask));
+ shiftedValue &= mask;
+ }
+ return (bits & ~mask) | shiftedValue;
+ }
+
+ /**
+ * Writes the current history item to history.
+ */
+ public void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs) {
+ synchronized (this) {
+ if (mTrackRunningHistoryElapsedRealtimeMs != 0) {
+ final long diffElapsedMs =
+ elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtimeMs;
+ final long diffUptimeMs = uptimeMs - mTrackRunningHistoryUptimeMs;
+ if (diffUptimeMs < (diffElapsedMs - 20)) {
+ final long wakeElapsedTimeMs =
+ elapsedRealtimeMs - (diffElapsedMs - diffUptimeMs);
+ mHistoryAddTmp.setTo(mHistoryLastWritten);
+ mHistoryAddTmp.wakelockTag = null;
+ mHistoryAddTmp.wakeReasonTag = null;
+ mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE;
+ mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG;
+ writeHistoryItem(wakeElapsedTimeMs, uptimeMs, mHistoryAddTmp);
+ }
+ }
+ mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG;
+ mTrackRunningHistoryElapsedRealtimeMs = elapsedRealtimeMs;
+ mTrackRunningHistoryUptimeMs = uptimeMs;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur);
+ }
+ }
+
+ @GuardedBy("this")
+ private void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
+ if (mTracer != null && mTracer.tracingEnabled()) {
+ recordTraceEvents(cur.eventCode, cur.eventTag);
+ recordTraceCounters(mTraceLastState, cur.states, STATE1_TRACE_MASK,
+ BatteryStats.HISTORY_STATE_DESCRIPTIONS);
+ recordTraceCounters(mTraceLastState2, cur.states2, STATE2_TRACE_MASK,
+ BatteryStats.HISTORY_STATE2_DESCRIPTIONS);
+ mTraceLastState = cur.states;
+ mTraceLastState2 = cur.states2;
+ }
+
+ if ((!mHaveBatteryLevel || !mRecordingHistory)
+ && cur.powerStats == null
+ && cur.processStateChange == null) {
+ return;
+ }
+
+ if (!mMutable) {
+ throw new ConcurrentModificationException("Battery history is not writable");
+ }
+
+ final long timeDiffMs = mMonotonicClock.monotonicTime(elapsedRealtimeMs)
+ - mHistoryLastWritten.time;
+ final int diffStates = mHistoryLastWritten.states ^ cur.states;
+ final int diffStates2 = mHistoryLastWritten.states2 ^ cur.states2;
+ final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states;
+ final int lastDiffStates2 = mHistoryLastWritten.states2 ^ mHistoryLastLastWritten.states2;
+ if (DEBUG) {
+ Slog.i(TAG, "ADD: tdelta=" + timeDiffMs + " diff="
+ + Integer.toHexString(diffStates) + " lastDiff="
+ + Integer.toHexString(lastDiffStates) + " diff2="
+ + Integer.toHexString(diffStates2) + " lastDiff2="
+ + Integer.toHexString(lastDiffStates2));
+ }
+
+ if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
+ && timeDiffMs < 1000 && (diffStates & lastDiffStates) == 0
+ && (diffStates2 & lastDiffStates2) == 0
+ && (!mHistoryLastWritten.tagsFirstOccurrence && !cur.tagsFirstOccurrence)
+ && (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null)
+ && (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null)
+ && mHistoryLastWritten.stepDetails == null
+ && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE
+ || cur.eventCode == HistoryItem.EVENT_NONE)
+ && mHistoryLastWritten.batteryLevel == cur.batteryLevel
+ && mHistoryLastWritten.batteryStatus == cur.batteryStatus
+ && mHistoryLastWritten.batteryHealth == cur.batteryHealth
+ && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
+ && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
+ && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage
+ && mHistoryLastWritten.powerStats == null
+ && mHistoryLastWritten.processStateChange == null) {
+ // We can merge this new change in with the last one. Merging is
+ // allowed as long as only the states have changed, and within those states
+ // as long as no bit has changed both between now and the last entry, as
+ // well as the last entry and the one before it (so we capture any toggles).
+ if (DEBUG) Slog.i(TAG, "ADD: rewinding back to " + mHistoryBufferLastPos);
+ mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
+ mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
+ mHistoryBufferLastPos = -1;
+
+ elapsedRealtimeMs -= timeDiffMs;
+
+ // If the last written history had a wakelock tag, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have a wakelock tag.
+ if (mHistoryLastWritten.wakelockTag != null) {
+ cur.wakelockTag = cur.localWakelockTag;
+ cur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag);
+ }
+ // If the last written history had a wake reason tag, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have a wakelock tag.
+ if (mHistoryLastWritten.wakeReasonTag != null) {
+ cur.wakeReasonTag = cur.localWakeReasonTag;
+ cur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag);
+ }
+ // If the last written history had an event, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have an event.
+ if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) {
+ cur.eventCode = mHistoryLastWritten.eventCode;
+ cur.eventTag = cur.localEventTag;
+ cur.eventTag.setTo(mHistoryLastWritten.eventTag);
+ }
+ mHistoryLastWritten.setTo(mHistoryLastLastWritten);
+ }
+
+ if (maybeFlushBufferAndWriteHistoryItem(cur, elapsedRealtimeMs, uptimeMs)) {
+ return;
+ }
+
+ if (mHistoryBuffer.dataSize() == 0) {
+ // The history is currently empty; we need it to start with a time stamp.
+ HistoryItem copy = new HistoryItem();
+ copy.setTo(cur);
+ copy.currentTime = mClock.currentTimeMillis();
+ copy.wakelockTag = null;
+ copy.wakeReasonTag = null;
+ copy.eventCode = HistoryItem.EVENT_NONE;
+ copy.eventTag = null;
+ copy.tagsFirstOccurrence = false;
+ copy.powerStats = null;
+ copy.processStateChange = null;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, copy, HistoryItem.CMD_RESET);
+ }
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, cur, HistoryItem.CMD_UPDATE);
+ }
+
+ @GuardedBy("this")
+ private boolean maybeFlushBufferAndWriteHistoryItem(HistoryItem cur, long elapsedRealtimeMs,
+ long uptimeMs) {
+ int dataSize = mHistoryBuffer.dataSize();
+ if (dataSize < mMaxHistoryBufferSize) {
+ return false;
+ }
+
+ if (mMaxHistoryBufferSize == 0) {
+ Slog.wtf(TAG, "mMaxHistoryBufferSize should not be zero when writing history");
+ mMaxHistoryBufferSize = 1024;
+ }
+
+ boolean successfullyLocked = mHistoryDir.tryLock();
+ if (!successfullyLocked) { // Already locked by another thread
+ // If the buffer size is below the allowed overflow limit, just keep going
+ if (dataSize < mMaxHistoryBufferSize + EXTRA_BUFFER_SIZE_WHEN_DIR_LOCKED) {
+ return false;
+ }
+
+ // Report the long contention as a WTF and flush the buffer anyway, potentially
+ // triggering a watchdog kill, which is still better than spinning forever.
+ Slog.wtf(TAG, "History buffer overflow exceeds " + EXTRA_BUFFER_SIZE_WHEN_DIR_LOCKED
+ + " bytes");
+ }
+
+ // Make a copy of mHistoryCur before starting a new file
+ HistoryItem copy = new HistoryItem();
+ copy.setTo(cur);
+
+ try {
+ startNextFile(elapsedRealtimeMs);
+ } finally {
+ if (successfullyLocked) {
+ mHistoryDir.unlock();
+ }
+ }
+
+ // startRecordingHistory will reset mHistoryCur.
+ startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
+
+ // Add the copy into history buffer.
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, copy, HistoryItem.CMD_UPDATE);
+ return true;
+ }
+
+ @GuardedBy("this")
+ private void writeHistoryItem(long elapsedRealtimeMs,
+ @SuppressWarnings("UnusedVariable") long uptimeMs, HistoryItem cur, byte cmd) {
+ if (!mMutable) {
+ throw new ConcurrentModificationException("Battery history is not writable");
+ }
+ mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
+ mHistoryLastLastWritten.setTo(mHistoryLastWritten);
+ final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
+ mHistoryLastWritten.setTo(mMonotonicClock.monotonicTime(elapsedRealtimeMs), cmd, cur);
+ if (mHistoryLastWritten.time < mHistoryLastLastWritten.time - 60000) {
+ Slog.wtf(TAG, "Significantly earlier event written to battery history:"
+ + " time=" + mHistoryLastWritten.time
+ + " previous=" + mHistoryLastLastWritten.time);
+ }
+ mHistoryLastWritten.tagsFirstOccurrence = hasTags;
+ writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
+ cur.wakelockTag = null;
+ cur.wakeReasonTag = null;
+ cur.eventCode = HistoryItem.EVENT_NONE;
+ cur.eventTag = null;
+ cur.tagsFirstOccurrence = false;
+ cur.powerStats = null;
+ cur.processStateChange = null;
+ if (DEBUG) {
+ Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+ + " now " + mHistoryBuffer.dataPosition()
+ + " size is now " + mHistoryBuffer.dataSize());
+ }
+ }
+
+ /*
+ The history delta format uses flags to denote further data in subsequent ints in the parcel.
+
+ There is always the first token, which may contain the delta time, or an indicator of
+ the length of the time (int or long) following this token.
+
+ First token: always present,
+ 31 23 15 7 0
+ â–ˆM|L|K|J|I|H|G|Fâ–ˆE|D|C|B|A|T|T|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆ
+
+ T: the delta time if it is <= 0x7fffd. Otherwise 0x7fffe indicates an int immediately
+ follows containing the time, and 0x7ffff indicates a long immediately follows with the
+ delta time.
+ A: battery level changed and an int follows with battery data.
+ B: state changed and an int follows with state change data.
+ C: state2 has changed and an int follows with state2 change data.
+ D: wakelock/wakereason has changed and an wakelock/wakereason struct follows.
+ E: event data has changed and an event struct follows.
+ F: battery charge in coulombs has changed and an int with the charge follows.
+ G: state flag denoting that the mobile radio was active.
+ H: state flag denoting that the wifi radio was active.
+ I: state flag denoting that a wifi scan occurred.
+ J: state flag denoting that a wifi full lock was held.
+ K: state flag denoting that the gps was on.
+ L: state flag denoting that a wakelock was held.
+ M: state flag denoting that the cpu was running.
+
+ Time int/long: if T in the first token is 0x7ffff or 0x7fffe, then an int or long follows
+ with the time delta.
+
+ Battery level int: if A in the first token is set,
+ 31 23 15 7 0
+ â–ˆL|L|L|L|L|L|L|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆT|V|V|V|V|V|V|Vâ–ˆV|V|V|V|V|V|V|Dâ–ˆ
+
+ D: indicates that extra history details follow.
+ V: the battery voltage.
+ T: the battery temperature.
+ L: the battery level (out of 100).
+
+ State change int: if B in the first token is set,
+ 31 23 15 7 0
+ â–ˆS|S|S|H|H|H|P|Pâ–ˆF|E|D|C|B| | |Aâ–ˆ | | | | | | | â–ˆ | | | | | | | â–ˆ
+
+ A: wifi multicast was on.
+ B: battery was plugged in.
+ C: screen was on.
+ D: phone was scanning for signal.
+ E: audio was on.
+ F: a sensor was active.
+
+ State2 change int: if C in the first token is set,
+ 31 23 15 7 0
+ â–ˆM|L|K|J|I|H|H|Gâ–ˆF|E|D|C| | | | â–ˆ | | | | |O|O|Nâ–ˆN|B|B|B|A|A|A|Aâ–ˆ
+
+ A: 4 bits indicating the wifi supplicant state: {@link BatteryStats#WIFI_SUPPL_STATE_NAMES}.
+ B: 3 bits indicating the wifi signal strength: 0, 1, 2, 3, 4.
+ C: a bluetooth scan was active.
+ D: the camera was active.
+ E: bluetooth was on.
+ F: a phone call was active.
+ G: the device was charging.
+ H: 2 bits indicating the device-idle (doze) state: off, light, full
+ I: the flashlight was on.
+ J: wifi was on.
+ K: wifi was running.
+ L: video was playing.
+ M: power save mode was on.
+ N: 2 bits indicating the gps signal strength: poor, good, none.
+ O: 2 bits indicating nr state: none, restricted, not restricted, connected.
+
+ Wakelock/wakereason struct: if D in the first token is set,
+ Event struct: if E in the first token is set,
+ History step details struct: if D in the battery level int is set,
+
+ Battery charge int: if F in the first token is set, an int representing the battery charge
+ in coulombs follows.
+ */
+ /**
+ * Writes the delta between the previous and current history items into history buffer.
+ */
+ @GuardedBy("this")
+ private void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
+ if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
+ dest.writeInt(BatteryStatsHistory.DELTA_TIME_ABS);
+ cur.writeToParcel(dest, 0);
+ return;
+ }
+
+ int extensionFlags = 0;
+ final long deltaTime = cur.time - last.time;
+ final int lastBatteryLevelInt = buildBatteryLevelInt(last);
+ final int lastStateInt = buildStateInt(last);
+
+ int deltaTimeToken;
+ if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
+ deltaTimeToken = BatteryStatsHistory.DELTA_TIME_LONG;
+ } else if (deltaTime >= BatteryStatsHistory.DELTA_TIME_ABS) {
+ deltaTimeToken = BatteryStatsHistory.DELTA_TIME_INT;
+ } else {
+ deltaTimeToken = (int) deltaTime;
+ }
+ int firstToken = deltaTimeToken | (cur.states & BatteryStatsHistory.DELTA_STATE_MASK);
+ int batteryLevelInt = buildBatteryLevelInt(cur);
+
+ if (cur.batteryLevel < mLastHistoryStepLevel || mLastHistoryStepLevel == 0) {
+ cur.stepDetails = mStepDetailsCalculator.getHistoryStepDetails();
+ if (cur.stepDetails != null) {
+ batteryLevelInt |= BatteryStatsHistory.BATTERY_LEVEL_DETAILS_FLAG;
+ mLastHistoryStepLevel = cur.batteryLevel;
+ }
+ } else {
+ cur.stepDetails = null;
+ mLastHistoryStepLevel = cur.batteryLevel;
+ }
+
+ final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
+ if (batteryLevelIntChanged) {
+ firstToken |= BatteryStatsHistory.DELTA_BATTERY_LEVEL_FLAG;
+ }
+ final int stateInt = buildStateInt(cur);
+ final boolean stateIntChanged = stateInt != lastStateInt;
+ if (stateIntChanged) {
+ firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG;
+ }
+ if (cur.powerStats != null) {
+ extensionFlags |= BatteryStatsHistory.EXTENSION_POWER_STATS_FLAG;
+ if (!mWrittenPowerStatsDescriptors.contains(cur.powerStats.descriptor)) {
+ extensionFlags |= BatteryStatsHistory.EXTENSION_POWER_STATS_DESCRIPTOR_FLAG;
+ }
+ }
+ if (cur.processStateChange != null) {
+ extensionFlags |= BatteryStatsHistory.EXTENSION_PROCESS_STATE_CHANGE_FLAG;
+ }
+ if (extensionFlags != 0) {
+ cur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
+ } else {
+ cur.states2 &= ~HistoryItem.STATE2_EXTENSIONS_FLAG;
+ }
+ final boolean state2IntChanged = cur.states2 != last.states2 || extensionFlags != 0;
+ if (state2IntChanged) {
+ firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG;
+ }
+ if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+ firstToken |= BatteryStatsHistory.DELTA_WAKELOCK_FLAG;
+ }
+ if (cur.eventCode != HistoryItem.EVENT_NONE) {
+ firstToken |= BatteryStatsHistory.DELTA_EVENT_FLAG;
+ }
+
+ final boolean batteryChargeChanged = cur.batteryChargeUah != last.batteryChargeUah;
+ if (batteryChargeChanged) {
+ firstToken |= BatteryStatsHistory.DELTA_BATTERY_CHARGE_FLAG;
+ }
+ dest.writeInt(firstToken);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+ + " deltaTime=" + deltaTime);
+ }
+
+ if (deltaTimeToken >= BatteryStatsHistory.DELTA_TIME_INT) {
+ if (deltaTimeToken == BatteryStatsHistory.DELTA_TIME_INT) {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int) deltaTime);
+ dest.writeInt((int) deltaTime);
+ } else {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
+ dest.writeLong(deltaTime);
+ }
+ }
+ if (batteryLevelIntChanged) {
+ dest.writeInt(batteryLevelInt);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
+ + Integer.toHexString(batteryLevelInt)
+ + " batteryLevel=" + cur.batteryLevel
+ + " batteryTemp=" + cur.batteryTemperature
+ + " batteryVolt=" + (int) cur.batteryVoltage);
+ }
+ }
+ if (stateIntChanged) {
+ dest.writeInt(stateInt);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: stateToken=0x"
+ + Integer.toHexString(stateInt)
+ + " batteryStatus=" + cur.batteryStatus
+ + " batteryHealth=" + cur.batteryHealth
+ + " batteryPlugType=" + cur.batteryPlugType
+ + " states=0x" + Integer.toHexString(cur.states));
+ }
+ }
+ if (state2IntChanged) {
+ dest.writeInt(cur.states2);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: states2=0x"
+ + Integer.toHexString(cur.states2));
+ }
+ }
+ if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+ int wakeLockIndex;
+ int wakeReasonIndex;
+ if (cur.wakelockTag != null) {
+ wakeLockIndex = writeHistoryTag(cur.wakelockTag);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
+ + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
+ }
+ } else {
+ wakeLockIndex = 0xffff;
+ }
+ if (cur.wakeReasonTag != null) {
+ wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
+ + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
+ }
+ } else {
+ wakeReasonIndex = 0xffff;
+ }
+ dest.writeInt((wakeReasonIndex << 16) | wakeLockIndex);
+ if (cur.wakelockTag != null
+ && (wakeLockIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+ cur.wakelockTag.writeToParcel(dest, 0);
+ cur.tagsFirstOccurrence = true;
+ }
+ if (cur.wakeReasonTag != null
+ && (wakeReasonIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+ cur.wakeReasonTag.writeToParcel(dest, 0);
+ cur.tagsFirstOccurrence = true;
+ }
+ }
+ if (cur.eventCode != HistoryItem.EVENT_NONE) {
+ final int index = writeHistoryTag(cur.eventTag);
+ final int codeAndIndex = setBitField(cur.eventCode & 0xffff, index, 16, 0xFFFF0000);
+ dest.writeInt(codeAndIndex);
+ if ((index & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+ cur.eventTag.writeToParcel(dest, 0);
+ cur.tagsFirstOccurrence = true;
+ }
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#"
+ + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
+ + cur.eventTag.string);
+ }
+ }
+
+ if (cur.stepDetails != null) {
+ cur.stepDetails.writeToParcel(dest);
+ }
+
+ if (batteryChargeChanged) {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryChargeUah=" + cur.batteryChargeUah);
+ dest.writeInt(cur.batteryChargeUah);
+ }
+ dest.writeDouble(cur.modemRailChargeMah);
+ dest.writeDouble(cur.wifiRailChargeMah);
+ if (extensionFlags != 0) {
+ dest.writeInt(extensionFlags);
+ if (cur.powerStats != null) {
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_POWER_STATS_DESCRIPTOR_FLAG)
+ != 0) {
+ cur.powerStats.descriptor.writeSummaryToParcel(dest);
+ mWrittenPowerStatsDescriptors.add(cur.powerStats.descriptor);
+ }
+ cur.powerStats.writeToParcel(dest);
+ }
+ if (cur.processStateChange != null) {
+ cur.processStateChange.writeToParcel(dest);
+ }
+ }
+ }
+
+ private int buildBatteryLevelInt(HistoryItem h) {
+ int bits = 0;
+ bits = setBitField(bits, h.batteryLevel, 25, 0xfe000000 /* 7F << 25 */);
+ bits = setBitField(bits, h.batteryTemperature, 15, 0x01ff8000 /* 3FF << 15 */);
+ short voltage = (short) h.batteryVoltage;
+ if (voltage == -1) {
+ voltage = 0x3FFF;
+ }
+ bits = setBitField(bits, voltage, 1, 0x00007ffe /* 3FFF << 1 */);
+ return bits;
+ }
+
+ private int buildStateInt(HistoryItem h) {
+ int plugType = 0;
+ if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_AC) != 0) {
+ plugType = 1;
+ } else if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_USB) != 0) {
+ plugType = 2;
+ } else if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) {
+ plugType = 3;
+ }
+ return ((h.batteryStatus & BatteryStatsHistory.STATE_BATTERY_STATUS_MASK)
+ << BatteryStatsHistory.STATE_BATTERY_STATUS_SHIFT)
+ | ((h.batteryHealth & BatteryStatsHistory.STATE_BATTERY_HEALTH_MASK)
+ << BatteryStatsHistory.STATE_BATTERY_HEALTH_SHIFT)
+ | ((plugType & BatteryStatsHistory.STATE_BATTERY_PLUG_MASK)
+ << BatteryStatsHistory.STATE_BATTERY_PLUG_SHIFT)
+ | (h.states & (~BatteryStatsHistory.STATE_BATTERY_MASK));
+ }
+
+ /**
+ * Returns the index for the specified tag. If this is the first time the tag is encountered
+ * while writing the current history buffer, the method returns
+ * <code>(index | TAG_FIRST_OCCURRENCE_FLAG)</code>
+ */
+ @GuardedBy("this")
+ private int writeHistoryTag(HistoryTag tag) {
+ if (tag.string == null) {
+ Slog.wtfStack(TAG, "writeHistoryTag called with null name");
+ }
+
+ final int stringLength = tag.string.length();
+ if (stringLength > MAX_HISTORY_TAG_STRING_LENGTH) {
+ Slog.e(TAG, "Long battery history tag: " + tag.string);
+ tag.string = tag.string.substring(0, MAX_HISTORY_TAG_STRING_LENGTH);
+ }
+
+ Integer idxObj = mHistoryTagPool.get(tag);
+ int idx;
+ if (idxObj != null) {
+ idx = idxObj;
+ if ((idx & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+ mHistoryTagPool.put(tag, idx & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
+ }
+ return idx;
+ } else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) {
+ idx = mNextHistoryTagIdx;
+ HistoryTag key = new HistoryTag();
+ key.setTo(tag);
+ tag.poolIdx = idx;
+ mHistoryTagPool.put(key, idx);
+ mNextHistoryTagIdx++;
+
+ mNumHistoryTagChars += stringLength + 1;
+ if (mHistoryTags != null) {
+ mHistoryTags.put(idx, key);
+ }
+ return idx | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
+ } else {
+ tag.poolIdx = HistoryTag.HISTORY_TAG_POOL_OVERFLOW;
+ // Tag pool overflow: include the tag itself in the parcel
+ return HISTORY_TAG_INDEX_LIMIT | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
+ }
+ }
+
+ /**
+ * Don't allow any more batching in to the current history event.
+ */
+ public void commitCurrentHistoryBatchLocked() {
+ synchronized (this) {
+ mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+ }
+ }
+
+ /**
+ * Saves the accumulated history buffer in the active file, see {@link #getActiveFile()} .
+ */
+ public void writeHistory() {
+ synchronized (this) {
+ if (isReadOnly()) {
+ Slog.w(TAG, "writeHistory: this instance instance is read-only");
+ return;
+ }
+
+ // Save the monotonic time first, so that even if the history write below fails,
+ // we still wouldn't end up with overlapping history timelines.
+ mMonotonicClock.write();
+
+ Parcel p = Parcel.obtain();
+ try {
+ final long start = SystemClock.uptimeMillis();
+ writeHistoryBuffer(p);
+ if (DEBUG) {
+ Slog.d(TAG, "writeHistoryBuffer duration ms:"
+ + (SystemClock.uptimeMillis() - start) + " bytes:" + p.dataSize());
+ }
+ writeParcelToFileLocked(p, mActiveFile);
+ } finally {
+ p.recycle();
+ }
+ }
+ }
+
+ /**
+ * Reads history buffer from a persisted Parcel.
+ */
+ public void readHistoryBuffer(Parcel in) throws ParcelFormatException {
+ synchronized (this) {
+ final int version = in.readInt();
+ if (version != BatteryStatsHistory.VERSION) {
+ Slog.w("BatteryStats", "readHistoryBuffer: version got " + version
+ + ", expected " + BatteryStatsHistory.VERSION + "; erasing old stats");
+ return;
+ }
+
+ mHistoryBufferStartTime = in.readLong();
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+
+ int bufSize = in.readInt();
+ int curPos = in.dataPosition();
+ if (bufSize >= (mMaxHistoryBufferSize * 100)) {
+ throw new ParcelFormatException(
+ "File corrupt: history data buffer too large " + bufSize);
+ } else if ((bufSize & ~3) != bufSize) {
+ throw new ParcelFormatException(
+ "File corrupt: history data buffer not aligned " + bufSize);
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
+ + " bytes at " + curPos);
+ }
+ mHistoryBuffer.appendFrom(in, curPos, bufSize);
+ in.setDataPosition(curPos + bufSize);
+ }
+ }
+ }
+
+ @GuardedBy("this")
+ private void writeHistoryBuffer(Parcel out) {
+ out.writeInt(BatteryStatsHistory.VERSION);
+ out.writeLong(mHistoryBufferStartTime);
+ out.writeInt(mHistoryBuffer.dataSize());
+ if (DEBUG) {
+ Slog.i(TAG, "***************** WRITING HISTORY: "
+ + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
+ }
+ out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+ }
+
+ @GuardedBy("this")
+ private void writeParcelToFileLocked(Parcel p, AtomicFile file) {
+ FileOutputStream fos = null;
+ mWriteLock.lock();
+ try {
+ final long startTimeMs = SystemClock.uptimeMillis();
+ fos = file.startWrite();
+ fos.write(p.marshall());
+ fos.flush();
+ file.finishWrite(fos);
+ if (DEBUG) {
+ Slog.d(TAG, "writeParcelToFileLocked file:" + file.getBaseFile().getPath()
+ + " duration ms:" + (SystemClock.uptimeMillis() - startTimeMs)
+ + " bytes:" + p.dataSize());
+ }
+ mEventLogger.writeCommitSysConfigFile(startTimeMs);
+ } catch (IOException e) {
+ Slog.w(TAG, "Error writing battery statistics", e);
+ file.failWrite(fos);
+ } finally {
+ mWriteLock.unlock();
+ }
+ }
+
+
+ /**
+ * Returns the total number of history tags in the tag pool.
+ */
+ public int getHistoryStringPoolSize() {
+ synchronized (this) {
+ return mHistoryTagPool.size();
+ }
+ }
+
+ /**
+ * Returns the total number of bytes occupied by the history tag pool.
+ */
+ public int getHistoryStringPoolBytes() {
+ synchronized (this) {
+ return mNumHistoryTagChars;
+ }
+ }
+
+ /**
+ * Returns the string held by the requested history tag.
+ */
+ public String getHistoryTagPoolString(int index) {
+ synchronized (this) {
+ ensureHistoryTagArray();
+ HistoryTag historyTag = mHistoryTags.get(index);
+ return historyTag != null ? historyTag.string : null;
+ }
+ }
+
+ /**
+ * Returns the UID held by the requested history tag.
+ */
+ public int getHistoryTagPoolUid(int index) {
+ synchronized (this) {
+ ensureHistoryTagArray();
+ HistoryTag historyTag = mHistoryTags.get(index);
+ return historyTag != null ? historyTag.uid : Process.INVALID_UID;
+ }
+ }
+
+ @GuardedBy("this")
+ private void ensureHistoryTagArray() {
+ if (mHistoryTags != null) {
+ return;
+ }
+
+ mHistoryTags = new SparseArray<>(mHistoryTagPool.size());
+ for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
+ mHistoryTags.put(entry.getValue() & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG,
+ entry.getKey());
+ }
+ }
+
+ /**
+ * Writes/reads an array of longs into Parcel using a compact format, where small integers use
+ * fewer bytes. It is a bit more expensive than just writing the long into the parcel,
+ * but at scale saves a lot of storage and allows recording of longer battery history.
+ */
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
+ public static final class VarintParceler {
+ /**
+ * Writes an array of longs into Parcel using the varint format, see
+ * https://developers.google.com/protocol-buffers/docs/encoding#varints
+ */
+ public void writeLongArray(Parcel parcel, long[] values) {
+ if (values.length == 0) {
+ return;
+ }
+ int out = 0;
+ int shift = 0;
+ for (long value : values) {
+ boolean done = false;
+ while (!done) {
+ final byte b;
+ if ((value & ~0x7FL) == 0) {
+ b = (byte) value;
+ done = true;
+ } else {
+ b = (byte) (((int) value & 0x7F) | 0x80);
+ value >>>= 7;
+ }
+ if (shift == 32) {
+ parcel.writeInt(out);
+ shift = 0;
+ out = 0;
+ }
+ out |= (b & 0xFF) << shift;
+ shift += 8;
+ }
+ }
+ if (shift != 0) {
+ parcel.writeInt(out);
+ }
+ }
+
+ /**
+ * Reads a long written with {@link #writeLongArray}
+ */
+ public void readLongArray(Parcel parcel, long[] values) {
+ if (values.length == 0) {
+ return;
+ }
+ int in = parcel.readInt();
+ int available = 4;
+ for (int i = 0; i < values.length; i++) {
+ long result = 0;
+ int shift;
+ for (shift = 0; shift < 64; shift += 7) {
+ if (available == 0) {
+ in = parcel.readInt();
+ available = 4;
+ }
+ final byte b = (byte) in;
+ in >>= 8;
+ available--;
+
+ result |= (long) (b & 0x7F) << shift;
+ if ((b & 0x80) == 0) {
+ values[i] = result;
+ break;
+ }
+ }
+ if (shift >= 64) {
+ throw new ParcelFormatException("Invalid varint format");
+ }
+ }
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/BatteryStatsHistoryIterator.java b/android-35/com/android/internal/os/BatteryStatsHistoryIterator.java
new file mode 100644
index 0000000..e2005d7
--- /dev/null
+++ b/android-35/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2021 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.internal.os;
+
+import android.annotation.NonNull;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.Parcel;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.Iterator;
+
+/**
+ * An iterator for {@link BatteryStats.HistoryItem}'s.
+ */
[email protected]
+public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.HistoryItem>,
+ AutoCloseable {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "BatteryStatsHistoryItr";
+ private final BatteryStatsHistory mBatteryStatsHistory;
+ private final long mStartTimeMs;
+ private final long mEndTimeMs;
+ private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails =
+ new BatteryStats.HistoryStepDetails();
+ private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
+ private final PowerStats.DescriptorRegistry mDescriptorRegistry =
+ new PowerStats.DescriptorRegistry();
+ private BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
+ private boolean mNextItemReady;
+ private boolean mTimeInitialized;
+ private boolean mClosed;
+
+ public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs,
+ long endTimeMs) {
+ mBatteryStatsHistory = history;
+ mStartTimeMs = startTimeMs;
+ mEndTimeMs = (endTimeMs != MonotonicClock.UNDEFINED) ? endTimeMs : Long.MAX_VALUE;
+ mHistoryItem.clear();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (!mNextItemReady) {
+ advance();
+ }
+
+ return mHistoryItem != null;
+ }
+
+ /**
+ * Retrieves the next HistoryItem from battery history, if available. Returns null if there
+ * are no more items.
+ */
+ @Override
+ public BatteryStats.HistoryItem next() {
+ if (!mNextItemReady) {
+ advance();
+ }
+ mNextItemReady = false;
+ return mHistoryItem;
+ }
+
+ private void advance() {
+ while (true) {
+ Parcel p = mBatteryStatsHistory.getNextParcel(mStartTimeMs, mEndTimeMs);
+ if (p == null) {
+ break;
+ }
+
+ if (!mTimeInitialized) {
+ mHistoryItem.time = mBatteryStatsHistory.getHistoryBufferStartTime(p);
+ mTimeInitialized = true;
+ }
+
+ final long lastMonotonicTimeMs = mHistoryItem.time;
+ final long lastWalltimeMs = mHistoryItem.currentTime;
+ try {
+ readHistoryDelta(p, mHistoryItem);
+ } catch (Throwable t) {
+ Slog.wtf(TAG, "Corrupted battery history", t);
+ break;
+ }
+ if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
+ && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET
+ && lastWalltimeMs != 0) {
+ mHistoryItem.currentTime =
+ lastWalltimeMs + (mHistoryItem.time - lastMonotonicTimeMs);
+ }
+ if (mEndTimeMs != 0 && mHistoryItem.time >= mEndTimeMs) {
+ break;
+ }
+ if (mHistoryItem.time >= mStartTimeMs) {
+ mNextItemReady = true;
+ return;
+ }
+ }
+
+ mHistoryItem = null;
+ mNextItemReady = true;
+ close();
+ }
+
+ private void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) {
+ int firstToken = src.readInt();
+ int deltaTimeToken = firstToken & BatteryStatsHistory.DELTA_TIME_MASK;
+ cur.cmd = BatteryStats.HistoryItem.CMD_UPDATE;
+ cur.numReadInts = 1;
+ if (DEBUG) {
+ Slog.i(TAG, "READ DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+ + " deltaTimeToken=" + deltaTimeToken);
+ }
+
+ if (deltaTimeToken < BatteryStatsHistory.DELTA_TIME_ABS) {
+ cur.time += deltaTimeToken;
+ } else if (deltaTimeToken == BatteryStatsHistory.DELTA_TIME_ABS) {
+ cur.readFromParcel(src);
+ if (DEBUG) Slog.i(TAG, "READ DELTA: ABS time=" + cur.time);
+ return;
+ } else if (deltaTimeToken == BatteryStatsHistory.DELTA_TIME_INT) {
+ int delta = src.readInt();
+ cur.time += delta;
+ cur.numReadInts += 1;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + cur.time);
+ } else {
+ long delta = src.readLong();
+ if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + cur.time);
+ cur.time += delta;
+ cur.numReadInts += 2;
+ }
+
+ final int batteryLevelInt;
+ if ((firstToken & BatteryStatsHistory.DELTA_BATTERY_LEVEL_FLAG) != 0) {
+ batteryLevelInt = src.readInt();
+ readBatteryLevelInt(batteryLevelInt, cur);
+ cur.numReadInts += 1;
+ if (DEBUG) {
+ Slog.i(TAG, "READ DELTA: batteryToken=0x"
+ + Integer.toHexString(batteryLevelInt)
+ + " batteryLevel=" + cur.batteryLevel
+ + " batteryTemp=" + cur.batteryTemperature
+ + " batteryVolt=" + (int) cur.batteryVoltage);
+ }
+ } else {
+ batteryLevelInt = 0;
+ }
+
+ if ((firstToken & BatteryStatsHistory.DELTA_STATE_FLAG) != 0) {
+ int stateInt = src.readInt();
+ cur.states = (firstToken & BatteryStatsHistory.DELTA_STATE_MASK) | (stateInt
+ & (~BatteryStatsHistory.STATE_BATTERY_MASK));
+ cur.batteryStatus = (byte) ((stateInt >> BatteryStatsHistory.STATE_BATTERY_STATUS_SHIFT)
+ & BatteryStatsHistory.STATE_BATTERY_STATUS_MASK);
+ cur.batteryHealth = (byte) ((stateInt >> BatteryStatsHistory.STATE_BATTERY_HEALTH_SHIFT)
+ & BatteryStatsHistory.STATE_BATTERY_HEALTH_MASK);
+ cur.batteryPlugType = (byte) ((stateInt >> BatteryStatsHistory.STATE_BATTERY_PLUG_SHIFT)
+ & BatteryStatsHistory.STATE_BATTERY_PLUG_MASK);
+ switch (cur.batteryPlugType) {
+ case 1:
+ cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_AC;
+ break;
+ case 2:
+ cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_USB;
+ break;
+ case 3:
+ cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
+ break;
+ }
+ cur.numReadInts += 1;
+ if (DEBUG) {
+ Slog.i(TAG, "READ DELTA: stateToken=0x"
+ + Integer.toHexString(stateInt)
+ + " batteryStatus=" + cur.batteryStatus
+ + " batteryHealth=" + cur.batteryHealth
+ + " batteryPlugType=" + cur.batteryPlugType
+ + " states=0x" + Integer.toHexString(cur.states));
+ }
+ } else {
+ cur.states = (firstToken & BatteryStatsHistory.DELTA_STATE_MASK) | (cur.states
+ & (~BatteryStatsHistory.STATE_BATTERY_MASK));
+ }
+
+ if ((firstToken & BatteryStatsHistory.DELTA_STATE2_FLAG) != 0) {
+ cur.states2 = src.readInt();
+ if (DEBUG) {
+ Slog.i(TAG, "READ DELTA: states2=0x"
+ + Integer.toHexString(cur.states2));
+ }
+ }
+
+ if ((firstToken & BatteryStatsHistory.DELTA_WAKELOCK_FLAG) != 0) {
+ final int indexes = src.readInt();
+ final int wakeLockIndex = indexes & 0xffff;
+ final int wakeReasonIndex = (indexes >> 16) & 0xffff;
+ if (readHistoryTag(src, wakeLockIndex, cur.localWakelockTag)) {
+ cur.wakelockTag = cur.localWakelockTag;
+ } else {
+ cur.wakelockTag = null;
+ }
+ if (readHistoryTag(src, wakeReasonIndex, cur.localWakeReasonTag)) {
+ cur.wakeReasonTag = cur.localWakeReasonTag;
+ } else {
+ cur.wakeReasonTag = null;
+ }
+ cur.numReadInts += 1;
+ } else {
+ cur.wakelockTag = null;
+ cur.wakeReasonTag = null;
+ }
+
+ if ((firstToken & BatteryStatsHistory.DELTA_EVENT_FLAG) != 0) {
+ cur.eventTag = cur.localEventTag;
+ final int codeAndIndex = src.readInt();
+ cur.eventCode = (codeAndIndex & 0xffff);
+ final int index = ((codeAndIndex >> 16) & 0xffff);
+ if (readHistoryTag(src, index, cur.localEventTag)) {
+ cur.eventTag = cur.localEventTag;
+ } else {
+ cur.eventTag = null;
+ }
+ cur.numReadInts += 1;
+ if (DEBUG) {
+ Slog.i(TAG, "READ DELTA: event=" + cur.eventCode + " tag=#"
+ + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
+ + cur.eventTag.string);
+ }
+ } else {
+ cur.eventCode = BatteryStats.HistoryItem.EVENT_NONE;
+ }
+
+ if ((batteryLevelInt & BatteryStatsHistory.BATTERY_LEVEL_DETAILS_FLAG) != 0) {
+ cur.stepDetails = mReadHistoryStepDetails;
+ cur.stepDetails.readFromParcel(src);
+ } else {
+ cur.stepDetails = null;
+ }
+
+ if ((firstToken & BatteryStatsHistory.DELTA_BATTERY_CHARGE_FLAG) != 0) {
+ cur.batteryChargeUah = src.readInt();
+ }
+ cur.modemRailChargeMah = src.readDouble();
+ cur.wifiRailChargeMah = src.readDouble();
+ if ((cur.states2 & BatteryStats.HistoryItem.STATE2_EXTENSIONS_FLAG) != 0) {
+ final int extensionFlags = src.readInt();
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_POWER_STATS_DESCRIPTOR_FLAG) != 0) {
+ PowerStats.Descriptor descriptor = PowerStats.Descriptor.readSummaryFromParcel(src);
+ if (descriptor != null) {
+ mDescriptorRegistry.register(descriptor);
+ }
+ }
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_POWER_STATS_FLAG) != 0) {
+ cur.powerStats = PowerStats.readFromParcel(src, mDescriptorRegistry);
+ } else {
+ cur.powerStats = null;
+ }
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_PROCESS_STATE_CHANGE_FLAG) != 0) {
+ cur.processStateChange = cur.localProcessStateChange;
+ cur.processStateChange.readFromParcel(src);
+ } else {
+ cur.processStateChange = null;
+ }
+ } else {
+ cur.powerStats = null;
+ cur.processStateChange = null;
+ }
+ }
+
+ private boolean readHistoryTag(Parcel src, int index, BatteryStats.HistoryTag outTag) {
+ if (index == 0xffff) {
+ return false;
+ }
+
+ if ((index & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+ BatteryStats.HistoryTag tag = new BatteryStats.HistoryTag();
+ tag.readFromParcel(src);
+ tag.poolIdx = index & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
+ if (tag.poolIdx < BatteryStatsHistory.HISTORY_TAG_INDEX_LIMIT) {
+ mHistoryTags.put(tag.poolIdx, tag);
+ } else {
+ tag.poolIdx = BatteryStats.HistoryTag.HISTORY_TAG_POOL_OVERFLOW;
+ }
+
+ outTag.setTo(tag);
+ } else {
+ BatteryStats.HistoryTag historyTag = mHistoryTags.get(index);
+ if (historyTag != null) {
+ outTag.setTo(historyTag);
+ } else {
+ outTag.string = null;
+ outTag.uid = 0;
+ }
+ outTag.poolIdx = index;
+ }
+ return true;
+ }
+
+ private static void readBatteryLevelInt(int batteryLevelInt, BatteryStats.HistoryItem out) {
+ out.batteryLevel = (byte) ((batteryLevelInt & 0xfe000000) >>> 25);
+ out.batteryTemperature = (short) ((batteryLevelInt & 0x01ff8000) >>> 15);
+ int voltage = ((batteryLevelInt & 0x00007ffe) >>> 1);
+ if (voltage == 0x3FFF) {
+ voltage = -1;
+ }
+
+ out.batteryVoltage = (short) voltage;
+ }
+
+ /**
+ * Should be called when iteration is complete.
+ */
+ @Override
+ public void close() {
+ if (!mClosed) {
+ mClosed = true;
+ mBatteryStatsHistory.iteratorFinished();
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/BinderCallHeavyHitterWatcher.java b/android-35/com/android/internal/os/BinderCallHeavyHitterWatcher.java
new file mode 100644
index 0000000..00043cf
--- /dev/null
+++ b/android-35/com/android/internal/os/BinderCallHeavyHitterWatcher.java
@@ -0,0 +1,415 @@
+/*
+ * 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.internal.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.HeavyHitterSketch;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A watcher which makes stats on the incoming binder transaction, if the amount of some type of
+ * transactions exceeds the threshold, the listener will be notified.
+ */
[email protected]
+public final class BinderCallHeavyHitterWatcher {
+ private static final String TAG = "BinderCallHeavyHitterWatcher";
+
+ /**
+ * Whether or not this watcher is enabled.
+ */
+ @GuardedBy("mLock")
+ private boolean mEnabled;
+
+ /**
+ * The listener to be notified in case the amount of some type of transactions exceeds the
+ * threshold.
+ */
+ @GuardedBy("mLock")
+ private BinderCallHeavyHitterListener mListener;
+
+ /**
+ * The heavy hitter stats.
+ */
+ @GuardedBy("mLock")
+ private HeavyHitterSketch<Integer> mHeavyHitterSketch;
+
+ /**
+ * The candidates that could be the heavy hitters, so we track their hashcode and the actual
+ * containers in this map.
+ */
+ @GuardedBy("mLock")
+ private final SparseArray<HeavyHitterContainer> mHeavyHitterCandiates = new SparseArray<>();
+
+ /**
+ * The cache to receive the list of candidates (consists of the hashcode of heavy hitters).
+ */
+ @GuardedBy("mLock")
+ private final ArrayList<Integer> mCachedCandidateList = new ArrayList<>();
+
+ /**
+ * The cache to receive the frequencies of each items in {@link #mCachedCandidateList}.
+ */
+ @GuardedBy("mLock")
+ private final ArrayList<Float> mCachedCandidateFrequencies = new ArrayList<>();
+
+ /**
+ * The cache set to host the candidates.
+ */
+ @GuardedBy("mLock")
+ private ArraySet<Integer> mCachedCandidateSet = new ArraySet<>();
+
+ /**
+ * The cache set to host the containers of candidates.
+ */
+ @GuardedBy("mLock")
+ private HeavyHitterContainer[] mCachedCandidateContainers;
+
+ /**
+ * The index to the {@link #mCachedCandidateContainers}, denote the first available slot
+ */
+ @GuardedBy("mLock")
+ private int mCachedCandidateContainersIndex;
+
+ /**
+ * The input size, should be {@link #mTotalInputSize} - validation size.
+ */
+ @GuardedBy("mLock")
+ private int mInputSize;
+
+ /**
+ * The total input size.
+ */
+ @GuardedBy("mLock")
+ private int mTotalInputSize;
+
+ /**
+ * The number of inputs so far
+ */
+ @GuardedBy("mLock")
+ private int mCurrentInputSize;
+
+ /**
+ * The threshold to be considered as heavy hitters
+ */
+ @GuardedBy("mLock")
+ private float mThreshold;
+
+ /**
+ * The timestamp of the start of current tracing.
+ */
+ @GuardedBy("mLock")
+ private long mBatchStartTimeStamp;
+
+ /**
+ * The lock object
+ */
+ private final Object mLock = new Object();
+
+ /**
+ * The tolerance within which is approximately equal
+ */
+ private static final float EPSILON = 0.00001f;
+
+ /**
+ * Callback interface when the amount of some type of transactions exceeds the threshold.
+ */
+ public interface BinderCallHeavyHitterListener {
+ /**
+ * @param heavyHitters The list of binder call heavy hitters
+ * @param totalBinderCalls The total binder calls
+ * @param threshold The threshold to be considered as heavy hitters
+ * @param timeSpan The toal time span of all these binder calls
+ */
+ void onHeavyHit(List<HeavyHitterContainer> heavyHitters,
+ int totalBinderCalls, float threshold, long timeSpan);
+ }
+
+ /**
+ * Container to hold the potential heavy hitters
+ */
+ public static final class HeavyHitterContainer {
+ /**
+ * The caller UID
+ */
+ public int mUid;
+
+ /**
+ * The class of the Binder object which is being hit heavily
+ */
+ public Class mClass;
+
+ /**
+ * The transaction code within the Binder object which is being hit heavily
+ */
+ public int mCode;
+
+ /**
+ * The frequency of this being hit (a number between 0...1)
+ */
+ public float mFrequency;
+
+ /**
+ * Default constructor
+ */
+ public HeavyHitterContainer() {
+ }
+
+ /**
+ * Copy constructor
+ */
+ public HeavyHitterContainer(@NonNull final HeavyHitterContainer other) {
+ this.mUid = other.mUid;
+ this.mClass = other.mClass;
+ this.mCode = other.mCode;
+ this.mFrequency = other.mFrequency;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (other == null || !(other instanceof HeavyHitterContainer)) {
+ return false;
+ }
+ HeavyHitterContainer o = (HeavyHitterContainer) other;
+ return this.mUid == o.mUid && this.mClass == o.mClass && this.mCode == o.mCode
+ && Math.abs(this.mFrequency - o.mFrequency) < EPSILON;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode(mUid, mClass, mCode);
+ }
+
+ /**
+ * Compute the hashcode with given parameters.
+ */
+ static int hashCode(int uid, @NonNull Class clazz, int code) {
+ int hash = uid;
+ hash = 31 * hash + clazz.hashCode();
+ hash = 31 * hash + code;
+ return hash;
+ }
+ }
+
+ /**
+ * The static lock object
+ */
+ private static final Object sLock = new Object();
+
+ /**
+ * The default instance
+ */
+ @GuardedBy("sLock")
+ private static BinderCallHeavyHitterWatcher sInstance = null;
+
+ /**
+ * Return the instance of the watcher
+ */
+ public static BinderCallHeavyHitterWatcher getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new BinderCallHeavyHitterWatcher();
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Configure the parameters.
+ *
+ * @param enable Whether or not to enable the watcher
+ * @param batchSize The number of binder transactions it needs to receive before the conclusion
+ * @param threshold The threshold to determine if some type of transactions are too many, it
+ * should be a value between (0.0f, 1.0f]
+ * @param listener The callback interface
+ */
+ public void setConfig(final boolean enable, final int batchSize, final float threshold,
+ @Nullable final BinderCallHeavyHitterListener listener) {
+ synchronized (mLock) {
+ if (!enable) {
+ if (mEnabled) {
+ resetInternalLocked(null, null, 0, 0, 0.0f, 0);
+ mEnabled = false;
+ }
+ return;
+ }
+ mEnabled = true;
+ // Validate the threshold, which is expected to be within (0.0f, 1.0f]
+ if (threshold < EPSILON || threshold > 1.0f) {
+ return;
+ }
+
+ if (batchSize == mTotalInputSize && Math.abs(threshold - mThreshold) < EPSILON) {
+ // Shortcut: just update the listener, no need to reset the watcher itself.
+ mListener = listener;
+ return;
+ }
+
+ final int capacity = (int) (1.0f / threshold);
+ final HeavyHitterSketch<Integer> sketch = HeavyHitterSketch.<Integer>newDefault();
+ final float validationRatio = sketch.getRequiredValidationInputRatio();
+ int inputSize = batchSize;
+ if (!Float.isNaN(validationRatio)) {
+ inputSize = (int) (batchSize * (1 - validationRatio));
+ }
+ try {
+ sketch.setConfig(batchSize, capacity);
+ } catch (IllegalArgumentException e) {
+ // invalid parameter, ignore the config.
+ Log.w(TAG, "Invalid parameter to heavy hitter watcher: "
+ + batchSize + ", " + capacity);
+ return;
+ }
+ // Reset the watcher to start over with the new configuration.
+ resetInternalLocked(listener, sketch, inputSize, batchSize, threshold, capacity);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void resetInternalLocked(@Nullable final BinderCallHeavyHitterListener listener,
+ @Nullable final HeavyHitterSketch<Integer> sketch, final int inputSize,
+ final int batchSize, final float threshold, final int capacity) {
+ mListener = listener;
+ mHeavyHitterSketch = sketch;
+ mHeavyHitterCandiates.clear();
+ mCachedCandidateList.clear();
+ mCachedCandidateFrequencies.clear();
+ mCachedCandidateSet.clear();
+ mInputSize = inputSize;
+ mTotalInputSize = batchSize;
+ mCurrentInputSize = 0;
+ mThreshold = threshold;
+ mBatchStartTimeStamp = SystemClock.elapsedRealtime();
+ initCachedCandidateContainersLocked(capacity);
+ }
+
+ @GuardedBy("mLock")
+ private void initCachedCandidateContainersLocked(final int capacity) {
+ if (capacity > 0) {
+ mCachedCandidateContainers = new HeavyHitterContainer[capacity];
+ for (int i = 0; i < mCachedCandidateContainers.length; i++) {
+ mCachedCandidateContainers[i] = new HeavyHitterContainer();
+ }
+ } else {
+ mCachedCandidateContainers = null;
+ }
+ mCachedCandidateContainersIndex = 0;
+ }
+
+ @GuardedBy("mLock")
+ private @NonNull HeavyHitterContainer acquireHeavyHitterContainerLocked() {
+ return mCachedCandidateContainers[mCachedCandidateContainersIndex++];
+ }
+
+ @GuardedBy("mLock")
+ private void releaseHeavyHitterContainerLocked(@NonNull HeavyHitterContainer container) {
+ mCachedCandidateContainers[--mCachedCandidateContainersIndex] = container;
+ }
+
+ /**
+ * Called on incoming binder transaction
+ *
+ * @param callerUid The UID of the binder transaction's caller
+ * @param clazz The class of the Binder object serving the transaction
+ * @param code The binder transaction code
+ */
+ public void onTransaction(final int callerUid, @NonNull final Class clazz,
+ final int code) {
+ synchronized (mLock) {
+ if (!mEnabled) {
+ return;
+ }
+
+ final HeavyHitterSketch<Integer> sketch = mHeavyHitterSketch;
+ if (sketch == null) {
+ return;
+ }
+
+ // To reduce memory fragmentation, we only feed the hashcode to the sketch,
+ // and keep the mapping from the hashcode to the sketch locally.
+ // However, the mapping will not be built until the validation pass, by then
+ // we will know the potential heavy hitters, so the mapping can focus on
+ // those ones, which will significantly reduce the memory overhead.
+ final int hashCode = HeavyHitterContainer.hashCode(callerUid, clazz, code);
+
+ sketch.add(hashCode);
+ mCurrentInputSize++;
+ if (mCurrentInputSize == mInputSize) {
+ // Retrieve the candidates
+ sketch.getCandidates(mCachedCandidateList);
+ mCachedCandidateSet.addAll(mCachedCandidateList);
+ mCachedCandidateList.clear();
+ } else if (mCurrentInputSize > mInputSize && mCurrentInputSize < mTotalInputSize) {
+ // validation pass
+ if (mCachedCandidateSet.contains(hashCode)) {
+ // It's one of the candidates
+ final int index = mHeavyHitterCandiates.indexOfKey(hashCode);
+ if (index < 0) {
+ // We got another hit, now write down its information
+ final HeavyHitterContainer container =
+ acquireHeavyHitterContainerLocked();
+ container.mUid = callerUid;
+ container.mClass = clazz;
+ container.mCode = code;
+ mHeavyHitterCandiates.put(hashCode, container);
+ }
+ }
+ } else if (mCurrentInputSize == mTotalInputSize) {
+ // Reached the expected number of input, check top ones
+ if (mListener != null) {
+ final List<Integer> result = sketch.getTopHeavyHitters(0,
+ mCachedCandidateList, mCachedCandidateFrequencies);
+ if (result != null) {
+ final int size = result.size();
+ if (size > 0) {
+ final ArrayList<HeavyHitterContainer> hitters = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ final HeavyHitterContainer container = mHeavyHitterCandiates.get(
+ result.get(i));
+ if (container != null) {
+ final HeavyHitterContainer cont =
+ new HeavyHitterContainer(container);
+ cont.mFrequency = mCachedCandidateFrequencies.get(i);
+ hitters.add(cont);
+ }
+ }
+ mListener.onHeavyHit(hitters, mTotalInputSize, mThreshold,
+ SystemClock.elapsedRealtime() - mBatchStartTimeStamp);
+ }
+ }
+ }
+ // reset
+ mHeavyHitterSketch.reset();
+ mHeavyHitterCandiates.clear();
+ mCachedCandidateList.clear();
+ mCachedCandidateFrequencies.clear();
+ mCachedCandidateSet.clear();
+ mCachedCandidateContainersIndex = 0;
+ mCurrentInputSize = 0;
+ mBatchStartTimeStamp = SystemClock.elapsedRealtime();
+ }
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/BinderCallsStats.java b/android-35/com/android/internal/os/BinderCallsStats.java
new file mode 100644
index 0000000..eb62cb0
--- /dev/null
+++ b/android-35/com/android/internal/os/BinderCallsStats.java
@@ -0,0 +1,1290 @@
+/*
+ * Copyright (C) 2017 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.internal.os;
+
+import static com.android.internal.os.BinderLatencyProto.Dims.SYSTEM_SERVER;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.format.DateFormat;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IntArray;
+import android.util.KeyValueListParser;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BinderInternal.CallSession;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Queue;
+import java.util.Random;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.function.ToDoubleFunction;
+
+/**
+ * Collects statistics about CPU time spent per binder call across multiple dimensions, e.g.
+ * per thread, uid or call description.
+ */
+public class BinderCallsStats implements BinderInternal.Observer {
+ public static final boolean ENABLED_DEFAULT = true;
+ public static final boolean DETAILED_TRACKING_DEFAULT = true;
+ public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 1000;
+ public static final boolean DEFAULT_TRACK_SCREEN_INTERACTIVE = false;
+ public static final boolean DEFAULT_TRACK_DIRECT_CALLING_UID = true;
+ public static final boolean DEFAULT_IGNORE_BATTERY_STATUS = false;
+ public static final boolean DEFAULT_COLLECT_LATENCY_DATA = true;
+ public static final int MAX_BINDER_CALL_STATS_COUNT_DEFAULT = 1500;
+ public static final int SHARDING_MODULO_DEFAULT = 1;
+ private static final String DEBUG_ENTRY_PREFIX = "__DEBUG_";
+
+ private static class OverflowBinder extends Binder {}
+
+ private static final String TAG = "BinderCallsStats";
+ private static final int CALL_SESSIONS_POOL_SIZE = 100;
+ private static final int MAX_EXCEPTION_COUNT_SIZE = 50;
+ private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow";
+ // Default values for overflow entry. The work source uid does not use a default value in order
+ // to have on overflow entry per work source uid.
+ private static final Class<? extends Binder> OVERFLOW_BINDER = OverflowBinder.class;
+ private static final boolean OVERFLOW_SCREEN_INTERACTIVE = false;
+ private static final int OVERFLOW_DIRECT_CALLING_UID = -1;
+ private static final int OVERFLOW_TRANSACTION_CODE = -1;
+
+ // Whether to collect all the data: cpu + exceptions + reply/request sizes.
+ private boolean mDetailedTracking = DETAILED_TRACKING_DEFAULT;
+ // If set to true, indicates that all transactions for specific UIDs are being
+ // recorded, ignoring sampling. The UidEntry.recordAllTransactions flag is also set
+ // for the UIDs being tracked.
+ private boolean mRecordingAllTransactionsForUid;
+ // Sampling period to control how often to track CPU usage. 1 means all calls, 100 means ~1 out
+ // of 100 requests.
+ private int mPeriodicSamplingInterval = PERIODIC_SAMPLING_INTERVAL_DEFAULT;
+ private int mMaxBinderCallStatsCount = MAX_BINDER_CALL_STATS_COUNT_DEFAULT;
+ @GuardedBy("mLock")
+ private final SparseArray<UidEntry> mUidEntries = new SparseArray<>();
+ @GuardedBy("mLock")
+ private final ArrayMap<String, Integer> mExceptionCounts = new ArrayMap<>();
+ private final Queue<CallSession> mCallSessionsPool = new ConcurrentLinkedQueue<>();
+ private final Object mLock = new Object();
+ private final Random mRandom;
+ private long mStartCurrentTime = System.currentTimeMillis();
+ private long mStartElapsedTime = SystemClock.elapsedRealtime();
+ private long mCallStatsCount = 0;
+ private boolean mAddDebugEntries = false;
+ private boolean mTrackDirectCallingUid = DEFAULT_TRACK_DIRECT_CALLING_UID;
+ private boolean mTrackScreenInteractive = DEFAULT_TRACK_SCREEN_INTERACTIVE;
+ private boolean mIgnoreBatteryStatus = DEFAULT_IGNORE_BATTERY_STATUS;
+ private boolean mCollectLatencyData = DEFAULT_COLLECT_LATENCY_DATA;
+
+ // Controls how many APIs will be collected per device. 1 means all APIs, 10 means every 10th
+ // API will be collected.
+ private int mShardingModulo = SHARDING_MODULO_DEFAULT;
+ // Controls which shards will be collected on this device.
+ private int mShardingOffset;
+
+ private CachedDeviceState.Readonly mDeviceState;
+ private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch;
+
+ private static final int CALL_STATS_OBSERVER_DEBOUNCE_MILLIS = 5000;
+ private BinderLatencyObserver mLatencyObserver;
+ private BinderInternal.CallStatsObserver mCallStatsObserver;
+ private ArraySet<Integer> mSendUidsToObserver = new ArraySet<>(32);
+ private final Handler mCallStatsObserverHandler;
+ private Runnable mCallStatsObserverRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mCallStatsObserver == null) {
+ return;
+ }
+
+ noteCallsStatsDelayed();
+
+ synchronized (mLock) {
+ int size = mSendUidsToObserver.size();
+ for (int i = 0; i < size; i++) {
+ UidEntry uidEntry = mUidEntries.get(mSendUidsToObserver.valueAt(i));
+ if (uidEntry != null) {
+ ArrayMap<CallStatKey, CallStat> callStats = uidEntry.mCallStats;
+ final int csize = callStats.size();
+ final ArrayList<CallStat> tmpCallStats = new ArrayList<>(csize);
+ for (int j = 0; j < csize; j++) {
+ tmpCallStats.add(callStats.valueAt(j).clone());
+ }
+ mCallStatsObserver.noteCallStats(uidEntry.workSourceUid,
+ uidEntry.incrementalCallCount, tmpCallStats
+ );
+ uidEntry.incrementalCallCount = 0;
+ for (int j = callStats.size() - 1; j >= 0; j--) {
+ callStats.valueAt(j).incrementalCallCount = 0;
+ }
+ }
+ }
+ mSendUidsToObserver.clear();
+ }
+ }
+ };
+
+ private final Object mNativeTidsLock = new Object();
+ // @GuardedBy("mNativeTidsLock") // Cannot mark it as "GuardedBy" because it's read
+ // directly, as a volatile field.
+ private volatile IntArray mNativeTids = new IntArray(0);
+
+ /** Injector for {@link BinderCallsStats}. */
+ public static class Injector {
+ public Random getRandomGenerator() {
+ return new Random();
+ }
+
+ public Handler getHandler() {
+ return new Handler(Looper.getMainLooper());
+ }
+
+ /** Create a latency observer for the specified process. */
+ public BinderLatencyObserver getLatencyObserver(int processSource) {
+ return new BinderLatencyObserver(new BinderLatencyObserver.Injector(), processSource);
+ }
+ }
+
+ public BinderCallsStats(Injector injector) {
+ this(injector, SYSTEM_SERVER);
+ }
+
+ public BinderCallsStats(Injector injector, int processSource) {
+ this.mRandom = injector.getRandomGenerator();
+ this.mCallStatsObserverHandler = injector.getHandler();
+ this.mLatencyObserver = injector.getLatencyObserver(processSource);
+ this.mShardingOffset = mRandom.nextInt(mShardingModulo);
+ }
+
+ public void setDeviceState(@NonNull CachedDeviceState.Readonly deviceState) {
+ if (mBatteryStopwatch != null) {
+ mBatteryStopwatch.close();
+ }
+ mDeviceState = deviceState;
+ mBatteryStopwatch = deviceState.createTimeOnBatteryStopwatch();
+ }
+
+ /**
+ * Registers an observer for call stats, which is invoked periodically with accumulated
+ * binder call stats.
+ */
+ public void setCallStatsObserver(
+ BinderInternal.CallStatsObserver callStatsObserver) {
+ mCallStatsObserver = callStatsObserver;
+ noteBinderThreadNativeIds();
+ noteCallsStatsDelayed();
+ }
+
+ private void noteCallsStatsDelayed() {
+ mCallStatsObserverHandler.removeCallbacks(mCallStatsObserverRunnable);
+ if (mCallStatsObserver != null) {
+ mCallStatsObserverHandler.postDelayed(mCallStatsObserverRunnable,
+ CALL_STATS_OBSERVER_DEBOUNCE_MILLIS);
+ }
+ }
+
+ @Override
+ @Nullable
+ public CallSession callStarted(Binder binder, int code, int workSourceUid) {
+ noteNativeThreadId();
+
+ boolean collectCpu = canCollect();
+ // We always want to collect data for latency if it's enabled, regardless of device state.
+ if (!mCollectLatencyData && !collectCpu) {
+ return null;
+ }
+
+ final CallSession s = obtainCallSession();
+ s.binderClass = binder.getClass();
+ s.transactionCode = code;
+ s.exceptionThrown = false;
+ s.cpuTimeStarted = -1;
+ s.timeStarted = -1;
+ s.recordedCall = shouldRecordDetailedData();
+
+ if (collectCpu && (mRecordingAllTransactionsForUid || s.recordedCall)) {
+ s.cpuTimeStarted = getThreadTimeMicro();
+ s.timeStarted = getElapsedRealtimeMicro();
+ } else if (mCollectLatencyData) {
+ s.timeStarted = getElapsedRealtimeMicro();
+ }
+
+ return s;
+ }
+
+ private CallSession obtainCallSession() {
+ CallSession s = mCallSessionsPool.poll();
+ return s == null ? new CallSession() : s;
+ }
+
+ @Override
+ public void callEnded(@Nullable CallSession s, int parcelRequestSize,
+ int parcelReplySize, int workSourceUid) {
+ if (s == null) {
+ return;
+ }
+
+ processCallEnded(s, parcelRequestSize, parcelReplySize, workSourceUid);
+
+ if (mCallSessionsPool.size() < CALL_SESSIONS_POOL_SIZE) {
+ mCallSessionsPool.add(s);
+ }
+ }
+
+ private void processCallEnded(CallSession s,
+ int parcelRequestSize, int parcelReplySize, int workSourceUid) {
+ if (mCollectLatencyData) {
+ mLatencyObserver.callEnded(s);
+ }
+
+ // Latency collection has already been processed so check if the rest should be processed.
+ if (!canCollect()) {
+ return;
+ }
+
+ UidEntry uidEntry = null;
+ final boolean recordCall;
+ if (s.recordedCall) {
+ recordCall = true;
+ } else if (mRecordingAllTransactionsForUid) {
+ uidEntry = getUidEntry(workSourceUid);
+ recordCall = uidEntry.recordAllTransactions;
+ } else {
+ recordCall = false;
+ }
+
+ final long duration;
+ final long latencyDuration;
+ if (recordCall) {
+ duration = getThreadTimeMicro() - s.cpuTimeStarted;
+ latencyDuration = getElapsedRealtimeMicro() - s.timeStarted;
+ } else {
+ duration = 0;
+ latencyDuration = 0;
+ }
+ final boolean screenInteractive = mTrackScreenInteractive
+ ? mDeviceState.isScreenInteractive()
+ : OVERFLOW_SCREEN_INTERACTIVE;
+ final int callingUid = mTrackDirectCallingUid
+ ? getCallingUid()
+ : OVERFLOW_DIRECT_CALLING_UID;
+
+ synchronized (mLock) {
+ // This was already checked in #callStart but check again while synchronized.
+ if (!canCollect()) {
+ return;
+ }
+
+ if (uidEntry == null) {
+ uidEntry = getUidEntry(workSourceUid);
+ }
+
+ uidEntry.callCount++;
+ uidEntry.incrementalCallCount++;
+ if (recordCall) {
+ uidEntry.cpuTimeMicros += duration;
+ uidEntry.recordedCallCount++;
+
+ final CallStat callStat = uidEntry.getOrCreate(
+ callingUid, s.binderClass, s.transactionCode,
+ screenInteractive,
+ mCallStatsCount >= mMaxBinderCallStatsCount);
+ final boolean isNewCallStat = callStat.callCount == 0;
+ if (isNewCallStat) {
+ mCallStatsCount++;
+ }
+
+ callStat.callCount++;
+ callStat.incrementalCallCount++;
+ callStat.recordedCallCount++;
+ callStat.cpuTimeMicros += duration;
+ callStat.maxCpuTimeMicros = Math.max(callStat.maxCpuTimeMicros, duration);
+ callStat.latencyMicros += latencyDuration;
+ callStat.maxLatencyMicros =
+ Math.max(callStat.maxLatencyMicros, latencyDuration);
+ if (mDetailedTracking) {
+ callStat.exceptionCount += s.exceptionThrown ? 1 : 0;
+ callStat.maxRequestSizeBytes =
+ Math.max(callStat.maxRequestSizeBytes, parcelRequestSize);
+ callStat.maxReplySizeBytes =
+ Math.max(callStat.maxReplySizeBytes, parcelReplySize);
+ }
+ } else {
+ // Only record the total call count if we already track data for this key.
+ // It helps to keep the memory usage down when sampling is enabled.
+ final CallStat callStat = uidEntry.get(
+ callingUid, s.binderClass, s.transactionCode,
+ screenInteractive);
+ if (callStat != null) {
+ callStat.callCount++;
+ callStat.incrementalCallCount++;
+ }
+ }
+ if (mCallStatsObserver != null && !UserHandle.isCore(workSourceUid)) {
+ mSendUidsToObserver.add(workSourceUid);
+ }
+ }
+ }
+
+ private boolean shouldExport(ExportedCallStat e, boolean applySharding) {
+ if (!applySharding) {
+ return true;
+ }
+
+ int hash = e.binderClass.hashCode();
+ hash = 31 * hash + e.transactionCode;
+ hash = 31 * hash + e.callingUid;
+ hash = 31 * hash + (e.screenInteractive ? 1231 : 1237);
+
+ return (hash + mShardingOffset) % mShardingModulo == 0;
+ }
+
+ private UidEntry getUidEntry(int uid) {
+ UidEntry uidEntry = mUidEntries.get(uid);
+ if (uidEntry == null) {
+ uidEntry = new UidEntry(uid);
+ mUidEntries.put(uid, uidEntry);
+ }
+ return uidEntry;
+ }
+
+ @Override
+ public void callThrewException(@Nullable CallSession s, Exception exception) {
+ if (s == null) {
+ return;
+ }
+ s.exceptionThrown = true;
+ try {
+ String className = exception.getClass().getName();
+ synchronized (mLock) {
+ if (mExceptionCounts.size() >= MAX_EXCEPTION_COUNT_SIZE) {
+ className = EXCEPTION_COUNT_OVERFLOW_NAME;
+ }
+ final Integer count = mExceptionCounts.get(className);
+ mExceptionCounts.put(className, count == null ? 1 : count + 1);
+ }
+ } catch (RuntimeException e) {
+ // Do not propagate the exception. We do not want to swallow original exception.
+ Slog.wtf(TAG, "Unexpected exception while updating mExceptionCounts");
+ }
+ }
+
+ private void noteNativeThreadId() {
+ final int tid = getNativeTid();
+ int index = mNativeTids.binarySearch(tid);
+ if (index >= 0) {
+ return;
+ }
+
+ // Use the copy-on-write approach. The changes occur exceedingly infrequently, so
+ // this code path is exercised just a few times per boot
+ synchronized (mNativeTidsLock) {
+ IntArray nativeTids = mNativeTids;
+ index = nativeTids.binarySearch(tid);
+ if (index < 0) {
+ IntArray copyOnWriteArray = new IntArray(nativeTids.size() + 1);
+ copyOnWriteArray.addAll(nativeTids);
+ copyOnWriteArray.add(-index - 1, tid);
+ mNativeTids = copyOnWriteArray;
+ }
+ }
+
+ noteBinderThreadNativeIds();
+ }
+
+ private void noteBinderThreadNativeIds() {
+ if (mCallStatsObserver == null) {
+ return;
+ }
+
+ mCallStatsObserver.noteBinderThreadNativeIds(getNativeTids());
+ }
+
+ private boolean canCollect() {
+ if (mRecordingAllTransactionsForUid) {
+ return true;
+ }
+ if (mIgnoreBatteryStatus) {
+ return true;
+ }
+ if (mDeviceState == null) {
+ return false;
+ }
+ if (mDeviceState.isCharging()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * This method is expensive to call.
+ */
+ public ArrayList<ExportedCallStat> getExportedCallStats() {
+ return getExportedCallStats(false);
+ }
+
+ /**
+ * This method is expensive to call.
+ * Exports call stats and applies sharding if requested.
+ */
+ @VisibleForTesting
+ public ArrayList<ExportedCallStat> getExportedCallStats(boolean applySharding) {
+ // We do not collect all the data if detailed tracking is off.
+ if (!mDetailedTracking) {
+ return new ArrayList<>();
+ }
+
+ ArrayList<ExportedCallStat> resultCallStats = new ArrayList<>();
+ synchronized (mLock) {
+ final int uidEntriesSize = mUidEntries.size();
+ for (int entryIdx = 0; entryIdx < uidEntriesSize; entryIdx++) {
+ final UidEntry entry = mUidEntries.valueAt(entryIdx);
+ for (CallStat stat : entry.getCallStatsList()) {
+ ExportedCallStat e = getExportedCallStat(entry.workSourceUid, stat);
+ if (shouldExport(e, applySharding)) {
+ resultCallStats.add(e);
+ }
+ }
+ }
+ }
+
+ // Resolve codes outside of the lock since it can be slow.
+ resolveBinderMethodNames(resultCallStats);
+
+ // Debug entries added to help validate the data.
+ if (mAddDebugEntries && mBatteryStopwatch != null) {
+ resultCallStats.add(createDebugEntry("start_time_millis", mStartElapsedTime));
+ resultCallStats.add(createDebugEntry("end_time_millis", SystemClock.elapsedRealtime()));
+ resultCallStats.add(
+ createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis()));
+ resultCallStats.add(createDebugEntry("sampling_interval", mPeriodicSamplingInterval));
+ resultCallStats.add(createDebugEntry("sharding_modulo", mShardingModulo));
+ }
+
+ return resultCallStats;
+ }
+
+ /**
+ * This method is expensive to call.
+ */
+ public ArrayList<ExportedCallStat> getExportedCallStats(int workSourceUid) {
+ return getExportedCallStats(workSourceUid, false);
+ }
+
+ /**
+ * This method is expensive to call.
+ * Exports call stats and applies sharding if requested.
+ */
+ @VisibleForTesting
+ public ArrayList<ExportedCallStat> getExportedCallStats(
+ int workSourceUid, boolean applySharding) {
+ ArrayList<ExportedCallStat> resultCallStats = new ArrayList<>();
+ synchronized (mLock) {
+ final UidEntry entry = getUidEntry(workSourceUid);
+ for (CallStat stat : entry.getCallStatsList()) {
+ ExportedCallStat e = getExportedCallStat(workSourceUid, stat);
+ if (shouldExport(e, applySharding)) {
+ resultCallStats.add(e);
+ }
+ }
+ }
+
+ // Resolve codes outside of the lock since it can be slow.
+ resolveBinderMethodNames(resultCallStats);
+
+ return resultCallStats;
+ }
+
+ private ExportedCallStat getExportedCallStat(int workSourceUid, CallStat stat) {
+ ExportedCallStat exported = new ExportedCallStat();
+ exported.workSourceUid = workSourceUid;
+ exported.callingUid = stat.callingUid;
+ exported.className = stat.binderClass.getName();
+ exported.binderClass = stat.binderClass;
+ exported.transactionCode = stat.transactionCode;
+ exported.screenInteractive = stat.screenInteractive;
+ exported.cpuTimeMicros = stat.cpuTimeMicros;
+ exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
+ exported.latencyMicros = stat.latencyMicros;
+ exported.maxLatencyMicros = stat.maxLatencyMicros;
+ exported.recordedCallCount = stat.recordedCallCount;
+ exported.callCount = stat.callCount;
+ exported.maxRequestSizeBytes = stat.maxRequestSizeBytes;
+ exported.maxReplySizeBytes = stat.maxReplySizeBytes;
+ exported.exceptionCount = stat.exceptionCount;
+ return exported;
+ }
+
+ private void resolveBinderMethodNames(
+ ArrayList<ExportedCallStat> resultCallStats) {
+ // Resolve codes outside of the lock since it can be slow.
+ ExportedCallStat previous = null;
+ String previousMethodName = null;
+ resultCallStats.sort(BinderCallsStats::compareByBinderClassAndCode);
+ BinderTransactionNameResolver resolver = new BinderTransactionNameResolver();
+ for (ExportedCallStat exported : resultCallStats) {
+ final boolean isClassDifferent = previous == null
+ || !previous.className.equals(exported.className);
+ final boolean isCodeDifferent = previous == null
+ || previous.transactionCode != exported.transactionCode;
+ final String methodName;
+ if (isClassDifferent || isCodeDifferent) {
+ methodName = resolver.getMethodName(exported.binderClass, exported.transactionCode);
+ } else {
+ methodName = previousMethodName;
+ }
+ previousMethodName = methodName;
+ exported.methodName = methodName;
+ previous = exported;
+ }
+ }
+
+ private ExportedCallStat createDebugEntry(String variableName, long value) {
+ final int uid = Process.myUid();
+ final ExportedCallStat callStat = new ExportedCallStat();
+ callStat.className = "";
+ callStat.workSourceUid = uid;
+ callStat.callingUid = uid;
+ callStat.recordedCallCount = 1;
+ callStat.callCount = 1;
+ callStat.methodName = DEBUG_ENTRY_PREFIX + variableName;
+ callStat.latencyMicros = value;
+ return callStat;
+ }
+
+ /** @hide */
+ public ArrayMap<String, Integer> getExportedExceptionStats() {
+ synchronized (mLock) {
+ return new ArrayMap(mExceptionCounts);
+ }
+ }
+
+ /** Writes the collected statistics to the supplied {@link PrintWriter}.*/
+ public void dump(PrintWriter pw, AppIdToPackageMap packageMap, int workSourceUid,
+ boolean verbose) {
+ synchronized (mLock) {
+ dumpLocked(pw, packageMap, workSourceUid, verbose);
+ }
+ }
+
+ private void dumpLocked(PrintWriter pw, AppIdToPackageMap packageMap, int workSourceUid,
+ boolean verbose) {
+ if (workSourceUid != Process.INVALID_UID) {
+ verbose = true;
+ }
+ pw.print("Start time: ");
+ pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartCurrentTime));
+ pw.print("On battery time (ms): ");
+ pw.println(mBatteryStopwatch != null ? mBatteryStopwatch.getMillis() : 0);
+ pw.println("Sampling interval period: " + mPeriodicSamplingInterval);
+ pw.println("Sharding modulo: " + mShardingModulo);
+
+ final String datasetSizeDesc = verbose ? "" : "(top 90% by cpu time) ";
+ final StringBuilder sb = new StringBuilder();
+ pw.println("Per-UID raw data " + datasetSizeDesc
+ + "(package/uid, worksource, call_desc, screen_interactive, "
+ + "cpu_time_micros, max_cpu_time_micros, "
+ + "latency_time_micros, max_latency_time_micros, exception_count, "
+ + "max_request_size_bytes, max_reply_size_bytes, recorded_call_count, "
+ + "call_count):");
+ final List<ExportedCallStat> exportedCallStats;
+ if (workSourceUid != Process.INVALID_UID) {
+ exportedCallStats = getExportedCallStats(workSourceUid, true);
+ } else {
+ exportedCallStats = getExportedCallStats(true);
+ }
+ exportedCallStats.sort(BinderCallsStats::compareByCpuDesc);
+ for (ExportedCallStat e : exportedCallStats) {
+ if (e.methodName != null && e.methodName.startsWith(DEBUG_ENTRY_PREFIX)) {
+ // Do not dump debug entries.
+ continue;
+ }
+ sb.setLength(0);
+ sb.append(" ")
+ .append(packageMap.mapUid(e.callingUid))
+ .append(',')
+ .append(packageMap.mapUid(e.workSourceUid))
+ .append(',').append(e.className)
+ .append('#').append(e.methodName)
+ .append(',').append(e.screenInteractive)
+ .append(',').append(e.cpuTimeMicros)
+ .append(',').append(e.maxCpuTimeMicros)
+ .append(',').append(e.latencyMicros)
+ .append(',').append(e.maxLatencyMicros)
+ .append(',').append(mDetailedTracking ? e.exceptionCount : '_')
+ .append(',').append(mDetailedTracking ? e.maxRequestSizeBytes : '_')
+ .append(',').append(mDetailedTracking ? e.maxReplySizeBytes : '_')
+ .append(',').append(e.recordedCallCount)
+ .append(',').append(e.callCount);
+ pw.println(sb);
+ }
+ pw.println();
+ final List<UidEntry> entries = new ArrayList<>();
+ long totalCallsCount = 0;
+ long totalRecordedCallsCount = 0;
+ long totalCpuTime = 0;
+
+ if (workSourceUid != Process.INVALID_UID) {
+ UidEntry e = getUidEntry(workSourceUid);
+ entries.add(e);
+ totalCpuTime += e.cpuTimeMicros;
+ totalRecordedCallsCount += e.recordedCallCount;
+ totalCallsCount += e.callCount;
+ } else {
+ final int uidEntriesSize = mUidEntries.size();
+ for (int i = 0; i < uidEntriesSize; i++) {
+ UidEntry e = mUidEntries.valueAt(i);
+ entries.add(e);
+ totalCpuTime += e.cpuTimeMicros;
+ totalRecordedCallsCount += e.recordedCallCount;
+ totalCallsCount += e.callCount;
+ }
+ entries.sort(
+ Comparator.<UidEntry>comparingDouble(value -> value.cpuTimeMicros).reversed());
+ }
+
+ pw.println("Per-UID Summary " + datasetSizeDesc
+ + "(cpu_time, % of total cpu_time, recorded_call_count, call_count, package/uid):");
+ final List<UidEntry> summaryEntries = verbose ? entries
+ : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9);
+ for (UidEntry entry : summaryEntries) {
+ String uidStr = packageMap.mapUid(entry.workSourceUid);
+ pw.println(String.format(" %10d %3.0f%% %8d %8d %s",
+ entry.cpuTimeMicros, 100d * entry.cpuTimeMicros / totalCpuTime,
+ entry.recordedCallCount, entry.callCount, uidStr));
+ }
+ pw.println();
+ if (workSourceUid == Process.INVALID_UID) {
+ pw.println(String.format(" Summary: total_cpu_time=%d, "
+ + "calls_count=%d, avg_call_cpu_time=%.0f",
+ totalCpuTime, totalCallsCount,
+ (double) totalCpuTime / totalRecordedCallsCount));
+ pw.println();
+ }
+
+ pw.println("Exceptions thrown (exception_count, class_name):");
+ final List<Pair<String, Integer>> exceptionEntries = new ArrayList<>();
+ // We cannot use new ArrayList(Collection) constructor because MapCollections does not
+ // implement toArray method.
+ mExceptionCounts.entrySet().iterator().forEachRemaining(
+ (e) -> exceptionEntries.add(Pair.create(e.getKey(), e.getValue())));
+ exceptionEntries.sort((e1, e2) -> Integer.compare(e2.second, e1.second));
+ for (Pair<String, Integer> entry : exceptionEntries) {
+ pw.println(String.format(" %6d %s", entry.second, entry.first));
+ }
+
+ if (mPeriodicSamplingInterval != 1) {
+ pw.println("");
+ pw.println("/!\\ Displayed data is sampled. See sampling interval at the top.");
+ }
+ }
+
+ protected long getThreadTimeMicro() {
+ return SystemClock.currentThreadTimeMicro();
+ }
+
+ protected int getCallingUid() {
+ return Binder.getCallingUid();
+ }
+
+ protected int getNativeTid() {
+ return Process.myTid();
+ }
+
+ /**
+ * Returns known Linux TIDs for threads taking incoming binder calls.
+ */
+ public int[] getNativeTids() {
+ return mNativeTids.toArray();
+ }
+
+ protected long getElapsedRealtimeMicro() {
+ return SystemClock.elapsedRealtimeNanos() / 1000;
+ }
+
+ protected boolean shouldRecordDetailedData() {
+ return mRandom.nextInt(mPeriodicSamplingInterval) == 0;
+ }
+
+ /**
+ * Sets to true to collect all the data.
+ */
+ public void setDetailedTracking(boolean enabled) {
+ synchronized (mLock) {
+ if (enabled != mDetailedTracking) {
+ mDetailedTracking = enabled;
+ reset();
+ }
+ }
+ }
+
+ /**
+ * Whether to track the screen state.
+ */
+ public void setTrackScreenInteractive(boolean enabled) {
+ synchronized (mLock) {
+ if (enabled != mTrackScreenInteractive) {
+ mTrackScreenInteractive = enabled;
+ reset();
+ }
+ }
+ }
+
+ /**
+ * Whether to track direct caller uid.
+ */
+ public void setTrackDirectCallerUid(boolean enabled) {
+ synchronized (mLock) {
+ if (enabled != mTrackDirectCallingUid) {
+ mTrackDirectCallingUid = enabled;
+ reset();
+ }
+ }
+ }
+
+ /**
+ * Whether to ignore battery status when collecting stats
+ */
+ public void setIgnoreBatteryStatus(boolean ignored) {
+ synchronized (mLock) {
+ if (ignored != mIgnoreBatteryStatus) {
+ mIgnoreBatteryStatus = ignored;
+ reset();
+ }
+ }
+ }
+
+ /**
+ * Marks the specified work source UID for total binder call tracking: detailed information
+ * will be recorded for all calls from this source ID.
+ *
+ * This is expensive and can cause memory pressure, therefore this mode should only be used
+ * for debugging.
+ */
+ public void recordAllCallsForWorkSourceUid(int workSourceUid) {
+ setDetailedTracking(true);
+
+ Slog.i(TAG, "Recording all Binder calls for UID: " + workSourceUid);
+ UidEntry uidEntry = getUidEntry(workSourceUid);
+ uidEntry.recordAllTransactions = true;
+ mRecordingAllTransactionsForUid = true;
+ }
+
+ public void setAddDebugEntries(boolean addDebugEntries) {
+ mAddDebugEntries = addDebugEntries;
+ }
+
+ /**
+ * Sets the maximum number of items to track.
+ */
+ public void setMaxBinderCallStats(int maxKeys) {
+ if (maxKeys <= 0) {
+ Slog.w(TAG, "Ignored invalid max value (value must be positive): "
+ + maxKeys);
+ return;
+ }
+
+ synchronized (mLock) {
+ if (maxKeys != mMaxBinderCallStatsCount) {
+ mMaxBinderCallStatsCount = maxKeys;
+ reset();
+ }
+ }
+ }
+
+ public void setSamplingInterval(int samplingInterval) {
+ if (samplingInterval <= 0) {
+ Slog.w(TAG, "Ignored invalid sampling interval (value must be positive): "
+ + samplingInterval);
+ return;
+ }
+
+ synchronized (mLock) {
+ if (samplingInterval != mPeriodicSamplingInterval) {
+ mPeriodicSamplingInterval = samplingInterval;
+ reset();
+ }
+ }
+ }
+
+ /** Updates the sharding modulo. */
+ public void setShardingModulo(int shardingModulo) {
+ if (shardingModulo <= 0) {
+ Slog.w(TAG, "Ignored invalid sharding modulo (value must be positive): "
+ + shardingModulo);
+ return;
+ }
+
+ synchronized (mLock) {
+ if (shardingModulo != mShardingModulo) {
+ mShardingModulo = shardingModulo;
+ mShardingOffset = mRandom.nextInt(shardingModulo);
+ reset();
+ }
+ }
+ }
+
+ /** Whether to collect latency histograms. */
+ public void setCollectLatencyData(boolean collectLatencyData) {
+ mCollectLatencyData = collectLatencyData;
+ }
+
+ /** Whether to collect latency histograms. */
+ @VisibleForTesting
+ public boolean getCollectLatencyData() {
+ return mCollectLatencyData;
+ }
+
+ public void reset() {
+ synchronized (mLock) {
+ mCallStatsCount = 0;
+ mUidEntries.clear();
+ mExceptionCounts.clear();
+ mStartCurrentTime = System.currentTimeMillis();
+ mStartElapsedTime = SystemClock.elapsedRealtime();
+ if (mBatteryStopwatch != null) {
+ mBatteryStopwatch.reset();
+ }
+ mRecordingAllTransactionsForUid = false;
+ // Do not reset the latency observer as binder stats and latency will be pushed to WW
+ // at different intervals so the resets should not be coupled.
+ }
+ }
+
+ /**
+ * Aggregated data by uid/class/method to be sent through statsd.
+ */
+ public static class ExportedCallStat {
+ public int callingUid;
+ public int workSourceUid;
+ public String className;
+ public String methodName;
+ public boolean screenInteractive;
+ public long cpuTimeMicros;
+ public long maxCpuTimeMicros;
+ public long latencyMicros;
+ public long maxLatencyMicros;
+ public long callCount;
+ public long recordedCallCount;
+ public long maxRequestSizeBytes;
+ public long maxReplySizeBytes;
+ public long exceptionCount;
+
+ // Used internally.
+ Class<? extends Binder> binderClass;
+ int transactionCode;
+ }
+
+ @VisibleForTesting
+ public static class CallStat {
+ // The UID who executed the transaction (i.e. Binder#getCallingUid).
+ public final int callingUid;
+ public final Class<? extends Binder> binderClass;
+ public final int transactionCode;
+ // True if the screen was interactive when the call ended.
+ public final boolean screenInteractive;
+ // Number of calls for which we collected data for. We do not record data for all the calls
+ // when sampling is on.
+ public long recordedCallCount;
+ // Roughly the real number of total calls. We only track only track the API call count once
+ // at least one non-sampled count happened.
+ public long callCount;
+ // Total CPU of all for all the recorded calls.
+ // Approximate total CPU usage can be computed by
+ // cpuTimeMicros * callCount / recordedCallCount
+ public long cpuTimeMicros;
+ public long maxCpuTimeMicros;
+ // Total latency of all for all the recorded calls.
+ // Approximate average latency can be computed by
+ // latencyMicros * callCount / recordedCallCount
+ public long latencyMicros;
+ public long maxLatencyMicros;
+ // The following fields are only computed if mDetailedTracking is set.
+ public long maxRequestSizeBytes;
+ public long maxReplySizeBytes;
+ public long exceptionCount;
+ // Call count since reset
+ public long incrementalCallCount;
+
+ public CallStat(int callingUid, Class<? extends Binder> binderClass, int transactionCode,
+ boolean screenInteractive) {
+ this.callingUid = callingUid;
+ this.binderClass = binderClass;
+ this.transactionCode = transactionCode;
+ this.screenInteractive = screenInteractive;
+ }
+
+ @Override
+ public CallStat clone() {
+ CallStat clone = new CallStat(callingUid, binderClass, transactionCode,
+ screenInteractive);
+ clone.recordedCallCount = recordedCallCount;
+ clone.callCount = callCount;
+ clone.cpuTimeMicros = cpuTimeMicros;
+ clone.maxCpuTimeMicros = maxCpuTimeMicros;
+ clone.latencyMicros = latencyMicros;
+ clone.maxLatencyMicros = maxLatencyMicros;
+ clone.maxRequestSizeBytes = maxRequestSizeBytes;
+ clone.maxReplySizeBytes = maxReplySizeBytes;
+ clone.exceptionCount = exceptionCount;
+ clone.incrementalCallCount = incrementalCallCount;
+ return clone;
+ }
+
+ @Override
+ public String toString() {
+ // This is expensive, but CallStat.toString() is only used for debugging.
+ String methodName = new BinderTransactionNameResolver().getMethodName(binderClass,
+ transactionCode);
+ return "CallStat{"
+ + "callingUid=" + callingUid
+ + ", transaction=" + binderClass.getSimpleName() + '.' + methodName
+ + ", callCount=" + callCount
+ + ", incrementalCallCount=" + incrementalCallCount
+ + ", recordedCallCount=" + recordedCallCount
+ + ", cpuTimeMicros=" + cpuTimeMicros
+ + ", latencyMicros=" + latencyMicros
+ + '}';
+ }
+ }
+
+ /** Key used to store CallStat object in a Map. */
+ public static class CallStatKey {
+ public int callingUid;
+ public Class<? extends Binder> binderClass;
+ public int transactionCode;
+ private boolean screenInteractive;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ final CallStatKey key = (CallStatKey) o;
+ return callingUid == key.callingUid
+ && transactionCode == key.transactionCode
+ && screenInteractive == key.screenInteractive
+ && (binderClass.equals(key.binderClass));
+ }
+
+ @Override
+ public int hashCode() {
+ int result = binderClass.hashCode();
+ result = 31 * result + transactionCode;
+ result = 31 * result + callingUid;
+ result = 31 * result + (screenInteractive ? 1231 : 1237);
+ return result;
+ }
+ }
+
+
+ @VisibleForTesting
+ public static class UidEntry {
+ // The UID who is responsible for the binder transaction. If the bluetooth process execute a
+ // transaction on behalf of app foo, the workSourceUid will be the uid of app foo.
+ public int workSourceUid;
+ // Number of calls for which we collected data for. We do not record data for all the calls
+ // when sampling is on.
+ public long recordedCallCount;
+ // Real number of total calls.
+ public long callCount;
+ // Total CPU of all for all the recorded calls.
+ // Approximate total CPU usage can be computed by
+ // cpuTimeMicros * callCount / recordedCallCount
+ public long cpuTimeMicros;
+ // Call count that gets reset after delivery to BatteryStats
+ public long incrementalCallCount;
+ // Indicates that all transactions for the UID must be tracked
+ public boolean recordAllTransactions;
+
+ UidEntry(int uid) {
+ this.workSourceUid = uid;
+ }
+
+ // Aggregate time spent per each call name: call_desc -> cpu_time_micros
+ private ArrayMap<CallStatKey, CallStat> mCallStats = new ArrayMap<>();
+ private CallStatKey mTempKey = new CallStatKey();
+
+ @Nullable
+ CallStat get(int callingUid, Class<? extends Binder> binderClass, int transactionCode,
+ boolean screenInteractive) {
+ // Use a global temporary key to avoid creating new objects for every lookup.
+ mTempKey.callingUid = callingUid;
+ mTempKey.binderClass = binderClass;
+ mTempKey.transactionCode = transactionCode;
+ mTempKey.screenInteractive = screenInteractive;
+ return mCallStats.get(mTempKey);
+ }
+
+ CallStat getOrCreate(int callingUid, Class<? extends Binder> binderClass,
+ int transactionCode, boolean screenInteractive, boolean maxCallStatsReached) {
+ CallStat mapCallStat = get(callingUid, binderClass, transactionCode, screenInteractive);
+ // Only create CallStat if it's a new entry, otherwise update existing instance.
+ if (mapCallStat == null) {
+ if (maxCallStatsReached) {
+ mapCallStat = get(OVERFLOW_DIRECT_CALLING_UID, OVERFLOW_BINDER,
+ OVERFLOW_TRANSACTION_CODE, OVERFLOW_SCREEN_INTERACTIVE);
+ if (mapCallStat != null) {
+ return mapCallStat;
+ }
+
+ callingUid = OVERFLOW_DIRECT_CALLING_UID;
+ binderClass = OVERFLOW_BINDER;
+ transactionCode = OVERFLOW_TRANSACTION_CODE;
+ screenInteractive = OVERFLOW_SCREEN_INTERACTIVE;
+ }
+
+ mapCallStat = new CallStat(callingUid, binderClass, transactionCode,
+ screenInteractive);
+ CallStatKey key = new CallStatKey();
+ key.callingUid = callingUid;
+ key.binderClass = binderClass;
+ key.transactionCode = transactionCode;
+ key.screenInteractive = screenInteractive;
+ mCallStats.put(key, mapCallStat);
+ }
+ return mapCallStat;
+ }
+
+ /**
+ * Returns list of calls sorted by CPU time
+ */
+ public Collection<CallStat> getCallStatsList() {
+ return mCallStats.values();
+ }
+
+ @Override
+ public String toString() {
+ return "UidEntry{" +
+ "cpuTimeMicros=" + cpuTimeMicros +
+ ", callCount=" + callCount +
+ ", mCallStats=" + mCallStats +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ UidEntry uidEntry = (UidEntry) o;
+ return workSourceUid == uidEntry.workSourceUid;
+ }
+
+ @Override
+ public int hashCode() {
+ return workSourceUid;
+ }
+ }
+
+ @VisibleForTesting
+ public SparseArray<UidEntry> getUidEntries() {
+ return mUidEntries;
+ }
+
+ @VisibleForTesting
+ public ArrayMap<String, Integer> getExceptionCounts() {
+ return mExceptionCounts;
+ }
+
+ public BinderLatencyObserver getLatencyObserver() {
+ return mLatencyObserver;
+ }
+
+ @VisibleForTesting
+ public static <T> List<T> getHighestValues(List<T> list, ToDoubleFunction<T> toDouble,
+ double percentile) {
+ List<T> sortedList = new ArrayList<>(list);
+ sortedList.sort(Comparator.comparingDouble(toDouble).reversed());
+ double total = 0;
+ for (T item : list) {
+ total += toDouble.applyAsDouble(item);
+ }
+ List<T> result = new ArrayList<>();
+ double runningSum = 0;
+ for (T item : sortedList) {
+ if (runningSum > percentile * total) {
+ break;
+ }
+ result.add(item);
+ runningSum += toDouble.applyAsDouble(item);
+ }
+ return result;
+ }
+
+ private static int compareByCpuDesc(
+ ExportedCallStat a, ExportedCallStat b) {
+ return Long.compare(b.cpuTimeMicros, a.cpuTimeMicros);
+ }
+
+ private static int compareByBinderClassAndCode(
+ ExportedCallStat a, ExportedCallStat b) {
+ int result = a.className.compareTo(b.className);
+ return result != 0
+ ? result
+ : Integer.compare(a.transactionCode, b.transactionCode);
+ }
+
+ /** @hide */
+ public static void startForBluetooth(Context context) {
+ new BinderCallsStats.SettingsObserver(
+ context,
+ new BinderCallsStats(
+ new BinderCallsStats.Injector(),
+ com.android.internal.os.BinderLatencyProto.Dims.BLUETOOTH));
+
+ }
+
+
+
+ /**
+ * Settings observer for other processes (not system_server).
+ *
+ * We do not want to collect cpu data from other processes so only latency collection should be
+ * possible to enable.
+ */
+ public static class SettingsObserver extends ContentObserver {
+ // Settings for BinderCallsStats.
+ public static final String SETTINGS_ENABLED_KEY = "enabled";
+ public static final String SETTINGS_DETAILED_TRACKING_KEY = "detailed_tracking";
+ public static final String SETTINGS_UPLOAD_DATA_KEY = "upload_data";
+ public static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
+ public static final String SETTINGS_TRACK_SCREEN_INTERACTIVE_KEY = "track_screen_state";
+ public static final String SETTINGS_TRACK_DIRECT_CALLING_UID_KEY = "track_calling_uid";
+ public static final String SETTINGS_MAX_CALL_STATS_KEY = "max_call_stats_count";
+ public static final String SETTINGS_IGNORE_BATTERY_STATUS_KEY = "ignore_battery_status";
+ public static final String SETTINGS_SHARDING_MODULO_KEY = "sharding_modulo";
+ // Settings for BinderLatencyObserver.
+ public static final String SETTINGS_COLLECT_LATENCY_DATA_KEY = "collect_latency_data";
+ public static final String SETTINGS_LATENCY_OBSERVER_SAMPLING_INTERVAL_KEY =
+ "latency_observer_sampling_interval";
+ public static final String SETTINGS_LATENCY_OBSERVER_SHARDING_MODULO_KEY =
+ "latency_observer_sharding_modulo";
+ public static final String SETTINGS_LATENCY_OBSERVER_PUSH_INTERVAL_MINUTES_KEY =
+ "latency_observer_push_interval_minutes";
+ public static final String SETTINGS_LATENCY_HISTOGRAM_BUCKET_COUNT_KEY =
+ "latency_histogram_bucket_count";
+ public static final String SETTINGS_LATENCY_HISTOGRAM_FIRST_BUCKET_SIZE_KEY =
+ "latency_histogram_first_bucket_size";
+ public static final String SETTINGS_LATENCY_HISTOGRAM_BUCKET_SCALE_FACTOR_KEY =
+ "latency_histogram_bucket_scale_factor";
+
+ private boolean mEnabled;
+ private final Uri mUri = Settings.Global.getUriFor(Settings.Global.BINDER_CALLS_STATS);
+ private final Context mContext;
+ private final KeyValueListParser mParser = new KeyValueListParser(',');
+ private final BinderCallsStats mBinderCallsStats;
+
+ public SettingsObserver(Context context, BinderCallsStats binderCallsStats) {
+ super(BackgroundThread.getHandler());
+ mContext = context;
+ context.getContentResolver().registerContentObserver(mUri, false, this);
+ mBinderCallsStats = binderCallsStats;
+ // Always kick once to ensure that we match current state
+ onChange();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ if (mUri.equals(uri)) {
+ onChange();
+ }
+ }
+
+ void onChange() {
+ try {
+ mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.BINDER_CALLS_STATS));
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Bad binder call stats settings", e);
+ }
+
+ // Cpu data collection should always be disabled for other processes.
+ mBinderCallsStats.setDetailedTracking(false);
+ mBinderCallsStats.setTrackScreenInteractive(false);
+ mBinderCallsStats.setTrackDirectCallerUid(false);
+
+ mBinderCallsStats.setIgnoreBatteryStatus(
+ mParser.getBoolean(SETTINGS_IGNORE_BATTERY_STATUS_KEY,
+ BinderCallsStats.DEFAULT_IGNORE_BATTERY_STATUS));
+ mBinderCallsStats.setCollectLatencyData(
+ mParser.getBoolean(SETTINGS_COLLECT_LATENCY_DATA_KEY,
+ BinderCallsStats.DEFAULT_COLLECT_LATENCY_DATA));
+
+ // Binder latency observer settings.
+ configureLatencyObserver(mParser, mBinderCallsStats.getLatencyObserver());
+
+ final boolean enabled =
+ mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT);
+ if (mEnabled != enabled) {
+ if (enabled) {
+ Binder.setObserver(mBinderCallsStats);
+ } else {
+ Binder.setObserver(null);
+ }
+ mEnabled = enabled;
+ mBinderCallsStats.reset();
+ mBinderCallsStats.setAddDebugEntries(enabled);
+ mBinderCallsStats.getLatencyObserver().reset();
+ }
+ }
+
+ /** Configures the binder latency observer related settings. */
+ public static void configureLatencyObserver(
+ KeyValueListParser mParser, BinderLatencyObserver binderLatencyObserver) {
+ binderLatencyObserver.setSamplingInterval(mParser.getInt(
+ SETTINGS_LATENCY_OBSERVER_SAMPLING_INTERVAL_KEY,
+ BinderLatencyObserver.PERIODIC_SAMPLING_INTERVAL_DEFAULT));
+ binderLatencyObserver.setShardingModulo(mParser.getInt(
+ SETTINGS_LATENCY_OBSERVER_SHARDING_MODULO_KEY,
+ BinderLatencyObserver.SHARDING_MODULO_DEFAULT));
+ binderLatencyObserver.setHistogramBucketsParams(
+ mParser.getInt(
+ SETTINGS_LATENCY_HISTOGRAM_BUCKET_COUNT_KEY,
+ BinderLatencyObserver.BUCKET_COUNT_DEFAULT),
+ mParser.getInt(
+ SETTINGS_LATENCY_HISTOGRAM_FIRST_BUCKET_SIZE_KEY,
+ BinderLatencyObserver.FIRST_BUCKET_SIZE_DEFAULT),
+ mParser.getFloat(
+ SETTINGS_LATENCY_HISTOGRAM_BUCKET_SCALE_FACTOR_KEY,
+ BinderLatencyObserver.BUCKET_SCALE_FACTOR_DEFAULT));
+ binderLatencyObserver.setPushInterval(mParser.getInt(
+ SETTINGS_LATENCY_OBSERVER_PUSH_INTERVAL_MINUTES_KEY,
+ BinderLatencyObserver.STATSD_PUSH_INTERVAL_MINUTES_DEFAULT));
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/BinderDeathDispatcher.java b/android-35/com/android/internal/os/BinderDeathDispatcher.java
new file mode 100644
index 0000000..e7abe2a
--- /dev/null
+++ b/android-35/com/android/internal/os/BinderDeathDispatcher.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 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.internal.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.IInterface;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Multiplexes multiple binder death recipients on the same binder objects, so that at the native
+ * level, we only need to keep track of one death recipient reference. This will help reduce the
+ * number of JNI strong references.
+ *
+ * test with: atest FrameworksCoreTests:BinderDeathDispatcherTest
+ */
[email protected]
+public class BinderDeathDispatcher<T extends IInterface> {
+ private static final String TAG = "BinderDeathDispatcher";
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<IBinder, RecipientsInfo> mTargets = new ArrayMap<>();
+
+ @VisibleForTesting
+ class RecipientsInfo implements DeathRecipient {
+ final IBinder mTarget;
+
+ /**
+ * Recipient list. If it's null, {@link #mTarget} has already died, but in that case
+ * this RecipientsInfo instance is removed from {@link #mTargets}.
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ ArraySet<DeathRecipient> mRecipients = new ArraySet<>();
+
+ private RecipientsInfo(IBinder target) {
+ mTarget = target;
+ }
+
+ @Override
+ public void binderDied() {
+ }
+
+ @Override
+ public void binderDied(IBinder who) {
+ final ArraySet<DeathRecipient> copy;
+ synchronized (mLock) {
+ copy = mRecipients;
+ mRecipients = null;
+
+ // Also remove from the targets.
+ mTargets.remove(mTarget);
+ }
+ if (copy == null) {
+ return;
+ }
+ // Let's call it without holding the lock.
+ final int size = copy.size();
+ for (int i = 0; i < size; i++) {
+ copy.valueAt(i).binderDied(who);
+ }
+ }
+ }
+
+ /**
+ * Add a {@code recipient} to the death recipient list on {@code target}.
+ *
+ * @return # of recipients in the recipient list, including {@code recipient}. Or, -1
+ * if {@code target} is already dead, in which case recipient's
+ * {@link DeathRecipient#binderDied} won't be called.
+ */
+ public int linkToDeath(@NonNull T target, @NonNull DeathRecipient recipient) {
+ final IBinder ib = target.asBinder();
+ synchronized (mLock) {
+ RecipientsInfo info = mTargets.get(ib);
+ if (info == null) {
+ info = new RecipientsInfo(ib);
+
+ // First recipient; need to link to death.
+ try {
+ ib.linkToDeath(info, 0);
+ } catch (RemoteException e) {
+ return -1; // Already dead.
+ }
+ mTargets.put(ib, info);
+ }
+ info.mRecipients.add(recipient);
+ return info.mRecipients.size();
+ }
+ }
+
+ public void unlinkToDeath(@NonNull T target, @NonNull DeathRecipient recipient) {
+ final IBinder ib = target.asBinder();
+
+ synchronized (mLock) {
+ final RecipientsInfo info = mTargets.get(ib);
+ if (info == null) {
+ return;
+ }
+ if (info.mRecipients.remove(recipient) && info.mRecipients.size() == 0) {
+ info.mTarget.unlinkToDeath(info, 0);
+ mTargets.remove(info.mTarget);
+ }
+ }
+ }
+
+ /** Dump stats useful for debugging. Can be used from dump() methods of client services. */
+ public void dump(IndentingPrintWriter pw) {
+ synchronized (mLock) {
+ pw.print("# of watched binders: ");
+ pw.println(mTargets.size());
+
+ pw.print("# of death recipients: ");
+ int n = 0;
+ for (RecipientsInfo info : mTargets.values()) {
+ n += info.mRecipients.size();
+ }
+ pw.println(n);
+ }
+ }
+
+ @VisibleForTesting
+ public ArrayMap<IBinder, RecipientsInfo> getTargetsForTest() {
+ return mTargets;
+ }
+}
diff --git a/android-35/com/android/internal/os/BinderInternal.java b/android-35/com/android/internal/os/BinderInternal.java
new file mode 100644
index 0000000..8063be6
--- /dev/null
+++ b/android-35/com/android/internal/os/BinderInternal.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2008 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.internal.os;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.util.EventLog;
+import android.util.SparseIntArray;
+
+import com.android.internal.util.Preconditions;
+
+import dalvik.system.VMRuntime;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Private and debugging Binder APIs.
+ *
+ * @see IBinder
+ */
+public class BinderInternal {
+ private static final String TAG = "BinderInternal";
+ static WeakReference<GcWatcher> sGcWatcher
+ = new WeakReference<GcWatcher>(new GcWatcher());
+ static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
+ static Runnable[] sTmpWatchers = new Runnable[1];
+ static long sLastGcTime;
+ static final BinderProxyCountEventListenerDelegate sBinderProxyCountEventListenerDelegate =
+ new BinderProxyCountEventListenerDelegate();
+
+ static final class GcWatcher {
+ @Override
+ protected void finalize() throws Throwable {
+ handleGc();
+ sLastGcTime = SystemClock.uptimeMillis();
+ synchronized (sGcWatchers) {
+ sTmpWatchers = sGcWatchers.toArray(sTmpWatchers);
+ }
+ for (int i=0; i<sTmpWatchers.length; i++) {
+ if (sTmpWatchers[i] != null) {
+ sTmpWatchers[i].run();
+ }
+ }
+ sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
+ }
+ }
+
+ public static void addGcWatcher(Runnable watcher) {
+ synchronized (sGcWatchers) {
+ sGcWatchers.add(watcher);
+ }
+ }
+
+ /**
+ * A session used by {@link Observer} in order to keep track of some data.
+ */
+ public static class CallSession {
+ // Binder interface descriptor.
+ public Class<? extends Binder> binderClass;
+ // Binder transaction code.
+ public int transactionCode;
+ // CPU time at the beginning of the call.
+ long cpuTimeStarted;
+ // System time at the beginning of the call.
+ long timeStarted;
+ // Should be set to one when an exception is thrown.
+ boolean exceptionThrown;
+ // Detailed information should be recorded for this call when it ends.
+ public boolean recordedCall;
+ }
+
+ /**
+ * Responsible for resolving a work source.
+ */
+ @FunctionalInterface
+ public interface WorkSourceProvider {
+ /**
+ * <p>This method is called in a critical path of the binder transaction.
+ * <p>The implementation should never execute a binder call since it is called during a
+ * binder transaction.
+ *
+ * @param untrustedWorkSourceUid The work source set by the caller.
+ * @return the uid of the process to attribute the binder transaction to.
+ */
+ int resolveWorkSourceUid(int untrustedWorkSourceUid);
+ }
+
+ /**
+ * Allows to track various steps of an API call.
+ */
+ public interface Observer {
+ /**
+ * Called when a binder call starts.
+ *
+ * @return a CallSession to pass to the callEnded method.
+ */
+ CallSession callStarted(Binder binder, int code, int workSourceUid);
+
+ /**
+ * Called when a binder call stops.
+ *
+ * <li>This method will be called even when an exception is thrown by the binder stub
+ * implementation.
+ */
+ void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize,
+ int workSourceUid);
+
+ /**
+ * Called if an exception is thrown while executing the binder transaction.
+ *
+ * <li>BinderCallsStats#callEnded will be called afterwards.
+ * <li>Do not throw an exception in this method, it will swallow the original exception
+ * thrown by the binder transaction.
+ */
+ public void callThrewException(CallSession s, Exception exception);
+ }
+
+ /**
+ * Allows to track observe incoming binder call stats.
+ */
+ public interface CallStatsObserver {
+ /**
+ * Notes incoming binder call stats associated with this work source UID.
+ */
+ void noteCallStats(int workSourceUid, long incrementalCallCount,
+ Collection<BinderCallsStats.CallStat> callStats);
+
+ /**
+ * Notes the native IDs of threads taking incoming binder calls.
+ */
+ void noteBinderThreadNativeIds(int[] binderThreadNativeTids);
+ }
+
+ /**
+ * Add the calling thread to the IPC thread pool. This function does
+ * not return until the current process is exiting.
+ */
+ public static final native void joinThreadPool();
+
+ /**
+ * Return the system time (as reported by {@link SystemClock#uptimeMillis
+ * SystemClock.uptimeMillis()}) that the last garbage collection occurred
+ * in this process. This is not for general application use, and the
+ * meaning of "when a garbage collection occurred" will change as the
+ * garbage collector evolves.
+ *
+ * @return Returns the time as per {@link SystemClock#uptimeMillis
+ * SystemClock.uptimeMillis()} of the last garbage collection.
+ */
+ public static long getLastGcTime() {
+ return sLastGcTime;
+ }
+
+ /**
+ * Return the global "context object" of the system. This is usually
+ * an implementation of IServiceManager, which you can use to find
+ * other services.
+ */
+ @UnsupportedAppUsage
+ public static final native IBinder getContextObject();
+
+ /**
+ * Special for system process to not allow incoming calls to run at
+ * background scheduling priority.
+ * @hide
+ */
+ public static final native void disableBackgroundScheduling(boolean disable);
+
+ public static final native void setMaxThreads(int numThreads);
+
+ @UnsupportedAppUsage
+ static native final void handleGc();
+
+ public static void forceGc(String reason) {
+ EventLog.writeEvent(2741, reason);
+ VMRuntime.getRuntime().requestConcurrentGC();
+ }
+
+ static void forceBinderGc() {
+ forceGc("Binder");
+ }
+
+ /**
+ * Enable/disable Binder Proxy Instance Counting by Uid. While enabled, the set callback will
+ * be called if this process holds too many Binder Proxies on behalf of a Uid.
+ * @param enabled true to enable counting, false to disable
+ */
+ public static final native void nSetBinderProxyCountEnabled(boolean enabled);
+
+ /**
+ * Get the current number of Binder Proxies held for each uid.
+ * @return SparseIntArray mapping uids to the number of Binder Proxies currently held
+ */
+ public static final native SparseIntArray nGetBinderProxyPerUidCounts();
+
+ /**
+ * Get the current number of Binder Proxies held for an individual uid.
+ * @param uid Requested uid for Binder Proxy count
+ * @return int with the number of Binder proxies held for a uid
+ */
+ public static final native int nGetBinderProxyCount(int uid);
+
+ /**
+ * Set the Binder Proxy watermarks. Default high watermark = 2500. Default low watermark = 2000
+ * @param high The limit at which the BinderProxyListener callback will be called.
+ * @param low The threshold a binder count must drop below before the callback
+ * can be called again. (This is to avoid many repeated calls to the
+ * callback in a brief period of time)
+ * @param warning The threshold between {@code high} and {@code low} where if the binder count
+ * exceeds that, the warning callback would be triggered.
+ */
+ public static final native void nSetBinderProxyCountWatermarks(int high, int low, int warning);
+
+ /**
+ * Interface for callback invocation when the Binder Proxy limit is reached. onLimitReached will
+ * be called with the uid of the app causing too many Binder Proxies
+ */
+ public interface BinderProxyCountEventListener {
+ public void onLimitReached(int uid);
+
+ /**
+ * Call when the number of binder proxies from the uid of the app reaches
+ * the warning threshold.
+ */
+ default void onWarningThresholdReached(int uid) {
+ }
+ }
+
+ /**
+ * Callback used by native code to trigger a callback in java code. The callback will be
+ * triggered when too many binder proxies from a uid hits the allowed limit.
+ * @param uid The uid of the bad behaving app sending too many binders
+ */
+ public static void binderProxyLimitCallbackFromNative(int uid) {
+ sBinderProxyCountEventListenerDelegate.notifyLimitReached(uid);
+ }
+
+ /**
+ * Callback used by native code to trigger a callback in java code. The callback will be
+ * triggered when too many binder proxies from a uid hits the warning limit.
+ * @param uid The uid of the bad behaving app sending too many binders
+ */
+ @SuppressWarnings("unused")
+ public static void binderProxyWarningCallbackFromNative(int uid) {
+ sBinderProxyCountEventListenerDelegate.notifyWarningReached(uid);
+ }
+
+ /**
+ * Set a callback to be triggered when a uid's Binder Proxy limit is reached for this process.
+ * @param listener OnLimitReached of listener will be called in the thread provided by handler
+ * @param handler must not be null, callback will be posted through the handler;
+ *
+ */
+ public static void setBinderProxyCountCallback(BinderProxyCountEventListener listener,
+ @NonNull Handler handler) {
+ Preconditions.checkNotNull(handler,
+ "Must provide NonNull Handler to setBinderProxyCountCallback when setting "
+ + "BinderProxyCountEventListener");
+ sBinderProxyCountEventListenerDelegate.setListener(listener, handler);
+ }
+
+ /**
+ * Clear the Binder Proxy callback
+ */
+ public static void clearBinderProxyCountCallback() {
+ sBinderProxyCountEventListenerDelegate.setListener(null, null);
+ }
+
+ private static class BinderProxyCountEventListenerDelegate {
+ private BinderProxyCountEventListener mBinderProxyCountEventListener;
+ private Handler mHandler;
+
+ void setListener(BinderProxyCountEventListener listener, Handler handler) {
+ synchronized (this) {
+ mBinderProxyCountEventListener = listener;
+ mHandler = handler;
+ }
+ }
+
+ void notifyLimitReached(final int uid) {
+ synchronized (this) {
+ if (mBinderProxyCountEventListener != null) {
+ mHandler.post(() -> mBinderProxyCountEventListener.onLimitReached(uid));
+ }
+ }
+ }
+
+ void notifyWarningReached(final int uid) {
+ synchronized (this) {
+ if (mBinderProxyCountEventListener != null) {
+ mHandler.post(() ->
+ mBinderProxyCountEventListener.onWarningThresholdReached(uid));
+ }
+ }
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/BinderLatencyBuckets.java b/android-35/com/android/internal/os/BinderLatencyBuckets.java
new file mode 100644
index 0000000..5679bc7
--- /dev/null
+++ b/android-35/com/android/internal/os/BinderLatencyBuckets.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 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.internal.os;
+
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * Generates the bucket thresholds (with a custom logarithmic scale) for a histogram to store
+ * latency samples in.
+ */
[email protected]
+public class BinderLatencyBuckets {
+ private static final String TAG = "BinderLatencyBuckets";
+ private final int[] mBuckets;
+
+ /**
+ * @param bucketCount the number of buckets the histogram should have
+ * @param firstBucketSize the size of the first bucket (used to avoid excessive small buckets)
+ * @param scaleFactor the rate in which each consecutive bucket increases (before rounding)
+ */
+ public BinderLatencyBuckets(int bucketCount, int firstBucketSize, float scaleFactor) {
+ int[] buffer = new int[bucketCount - 1];
+ buffer[0] = firstBucketSize;
+
+ // Last value and the target are disjoint as we never want to create buckets smaller than 1.
+ double lastTarget = firstBucketSize;
+
+ // First bucket is already created and the last bucket is anything greater than the final
+ // bucket in the list, so create 'bucketCount' - 2 buckets.
+ for (int i = 1; i < bucketCount - 1; i++) {
+ // Increase the target bucket limit value by the scale factor.
+ double nextTarget = lastTarget * scaleFactor;
+
+ if (nextTarget > Integer.MAX_VALUE) {
+ // Do not throw an exception here as this should not affect binder calls.
+ Slog.w(TAG, "Attempted to create a bucket larger than maxint");
+ mBuckets = Arrays.copyOfRange(buffer, 0, i);
+ return;
+ }
+
+ if ((int) nextTarget > buffer[i - 1]) {
+ // Convert the target bucket limit value to an integer.
+ buffer[i] = (int) nextTarget;
+ } else {
+ // Avoid creating redundant buckets, so bucket size should be 1 at a minimum.
+ buffer[i] = buffer[i - 1] + 1;
+ }
+ lastTarget = nextTarget;
+ }
+ mBuckets = buffer;
+ }
+
+ /** Gets the bucket index to insert the provided sample in. */
+ public int sampleToBucket(int sample) {
+ if (sample >= mBuckets[mBuckets.length - 1]) {
+ return mBuckets.length;
+ }
+
+ // Binary search returns the element index if it is contained in the list - in this case the
+ // correct bucket is the index after as we use [minValue, maxValue) for bucket boundaries.
+ // Otherwise, it returns (-(insertion point) - 1), where insertion point is the point where
+ // to insert the element so that the array remains sorted - in this case the bucket index
+ // is the insertion point.
+ int searchResult = Arrays.binarySearch(mBuckets, sample);
+ return searchResult < 0 ? -(1 + searchResult) : searchResult + 1;
+ }
+
+ @VisibleForTesting
+ public int[] getBuckets() {
+ return mBuckets;
+ }
+}
diff --git a/android-35/com/android/internal/os/BinderLatencyObserver.java b/android-35/com/android/internal/os/BinderLatencyObserver.java
new file mode 100644
index 0000000..1276fb9
--- /dev/null
+++ b/android-35/com/android/internal/os/BinderLatencyObserver.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2017 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.internal.os;
+
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BinderInternal.CallSession;
+import com.android.internal.os.BinderLatencyProto.ApiStats;
+import com.android.internal.os.BinderLatencyProto.Dims;
+import com.android.internal.os.BinderLatencyProto.RepeatedApiStats;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.Random;
+
+/** Collects statistics about Binder call latency per calling API and method. */
+public class BinderLatencyObserver {
+ private static final String TAG = "BinderLatencyObserver";
+ private static final int MAX_ATOM_SIZE_BYTES = 4064;
+ // Be conservative and leave 1K space for the last histogram so we don't go over the size limit.
+ private static final int LAST_HISTOGRAM_BUFFER_SIZE_BYTES = 1000;
+
+ // Latency observer parameters.
+ public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 10;
+ public static final int SHARDING_MODULO_DEFAULT = 1;
+ public static final int STATSD_PUSH_INTERVAL_MINUTES_DEFAULT = 360;
+
+ // Histogram buckets parameters.
+ public static final int BUCKET_COUNT_DEFAULT = 100;
+ public static final int FIRST_BUCKET_SIZE_DEFAULT = 5;
+ public static final float BUCKET_SCALE_FACTOR_DEFAULT = 1.125f;
+
+ @GuardedBy("mLock")
+ private final ArrayMap<LatencyDims, int[]> mLatencyHistograms = new ArrayMap<>();
+ private final Object mLock = new Object();
+
+ // Sampling period to control how often to track CPU usage. 1 means all calls, 100 means ~1 out
+ // of 100 requests.
+ private int mPeriodicSamplingInterval = PERIODIC_SAMPLING_INTERVAL_DEFAULT;
+ // Controls how many APIs will be collected per device. 1 means all APIs, 10 means every 10th
+ // API will be collected.
+ private int mShardingModulo = SHARDING_MODULO_DEFAULT;
+ // Controls which shards will be collected on this device.
+ private int mShardingOffset;
+
+ private int mBucketCount = BUCKET_COUNT_DEFAULT;
+ private int mFirstBucketSize = FIRST_BUCKET_SIZE_DEFAULT;
+ private float mBucketScaleFactor = BUCKET_SCALE_FACTOR_DEFAULT;
+
+ private int mStatsdPushIntervalMinutes = STATSD_PUSH_INTERVAL_MINUTES_DEFAULT;
+
+ private final Random mRandom;
+ private final Handler mLatencyObserverHandler;
+ private final int mProcessSource;
+
+ private BinderLatencyBuckets mLatencyBuckets;
+
+ private Runnable mLatencyObserverRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // Schedule the next push.
+ noteLatencyDelayed();
+
+ ArrayMap<LatencyDims, int[]> histogramMap;
+ synchronized (mLock) {
+ // Copy the histograms map so we don't use the lock for longer than needed.
+ histogramMap = new ArrayMap<>(mLatencyHistograms);
+ mLatencyHistograms.clear();
+ }
+
+ BinderTransactionNameResolver resolver = new BinderTransactionNameResolver();
+ ProtoOutputStream proto = new ProtoOutputStream();
+ int histogramsWritten = 0;
+
+ for (LatencyDims dims : histogramMap.keySet()) {
+ // Start a new atom if the next histogram risks going over the atom size limit.
+ if (proto.getRawSize() + LAST_HISTOGRAM_BUFFER_SIZE_BYTES > getMaxAtomSizeBytes()) {
+ if (histogramsWritten > 0) {
+ writeAtomToStatsd(proto);
+ }
+ proto = new ProtoOutputStream();
+ histogramsWritten = 0;
+ }
+
+ String transactionName = resolver.getMethodName(
+ dims.getBinderClass(), dims.getTransactionCode());
+ fillApiStatsProto(proto, dims, transactionName, histogramMap.get(dims));
+ histogramsWritten++;
+ }
+ // Push the final atom.
+ if (histogramsWritten > 0) {
+ writeAtomToStatsd(proto);
+ }
+ }
+ };
+
+ private void fillApiStatsProto(
+ ProtoOutputStream proto, LatencyDims dims, String transactionName, int[] histogram) {
+ // Find the part of the histogram to write.
+ int firstNonEmptyBucket = 0;
+ for (int i = 0; i < mBucketCount; i++) {
+ if (histogram[i] != 0) {
+ firstNonEmptyBucket = i;
+ break;
+ }
+ }
+ int lastNonEmptyBucket = mBucketCount - 1;
+ for (int i = mBucketCount - 1; i >= 0; i--) {
+ if (histogram[i] != 0) {
+ lastNonEmptyBucket = i;
+ break;
+ }
+ }
+
+ // Start a new ApiStats proto.
+ long apiStatsToken = proto.start(RepeatedApiStats.API_STATS);
+
+ // Write the dims.
+ long dimsToken = proto.start(ApiStats.DIMS);
+ proto.write(Dims.PROCESS_SOURCE, mProcessSource);
+ proto.write(Dims.SERVICE_CLASS_NAME, dims.getBinderClass().getName());
+ proto.write(Dims.SERVICE_METHOD_NAME, transactionName);
+ proto.end(dimsToken);
+
+ // Write the histogram.
+ proto.write(ApiStats.FIRST_BUCKET_INDEX, firstNonEmptyBucket);
+ for (int i = firstNonEmptyBucket; i <= lastNonEmptyBucket; i++) {
+ proto.write(ApiStats.BUCKETS, histogram[i]);
+ }
+
+ proto.end(apiStatsToken);
+ }
+
+ protected int getMaxAtomSizeBytes() {
+ return MAX_ATOM_SIZE_BYTES;
+ }
+
+ protected void writeAtomToStatsd(ProtoOutputStream atom) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.BINDER_LATENCY_REPORTED,
+ atom.getBytes(),
+ mPeriodicSamplingInterval,
+ mShardingModulo,
+ mBucketCount,
+ mFirstBucketSize,
+ mBucketScaleFactor);
+ }
+
+ private void noteLatencyDelayed() {
+ mLatencyObserverHandler.removeCallbacks(mLatencyObserverRunnable);
+ mLatencyObserverHandler.postDelayed(mLatencyObserverRunnable,
+ mStatsdPushIntervalMinutes * 60 * 1000);
+ }
+
+ /** Injector for {@link BinderLatencyObserver}. */
+ public static class Injector {
+ public Random getRandomGenerator() {
+ return new Random();
+ }
+
+ public Handler getHandler() {
+ return BackgroundThread.getHandler();
+ }
+ }
+
+ public BinderLatencyObserver(Injector injector, int processSource) {
+ mRandom = injector.getRandomGenerator();
+ mLatencyObserverHandler = injector.getHandler();
+ mLatencyBuckets = new BinderLatencyBuckets(
+ mBucketCount, mFirstBucketSize, mBucketScaleFactor);
+ mProcessSource = processSource;
+ mShardingOffset = mRandom.nextInt(mShardingModulo);
+ noteLatencyDelayed();
+ }
+
+ /** Should be called when a Binder call completes, will store latency data. */
+ public void callEnded(@Nullable CallSession s) {
+ if (s == null || s.exceptionThrown || !shouldKeepSample()) {
+ return;
+ }
+
+ LatencyDims dims = LatencyDims.create(s.binderClass, s.transactionCode);
+
+ if (!shouldCollect(dims)) {
+ return;
+ }
+
+ long elapsedTimeMicro = getElapsedRealtimeMicro();
+ long callDuration = elapsedTimeMicro - s.timeStarted;
+
+ // Find the bucket this sample should go to.
+ int bucketIdx = mLatencyBuckets.sampleToBucket(
+ callDuration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) callDuration);
+
+ synchronized (mLock) {
+ int[] buckets = mLatencyHistograms.get(dims);
+ if (buckets == null) {
+ buckets = new int[mBucketCount];
+ mLatencyHistograms.put(dims, buckets);
+ }
+
+ // Increment the correct bucket.
+ if (buckets[bucketIdx] < Integer.MAX_VALUE) {
+ buckets[bucketIdx] += 1;
+ }
+ }
+ }
+
+ protected long getElapsedRealtimeMicro() {
+ return SystemClock.elapsedRealtimeNanos() / 1000;
+ }
+
+ protected boolean shouldCollect(LatencyDims dims) {
+ return (dims.hashCode() + mShardingOffset) % mShardingModulo == 0;
+ }
+
+ protected boolean shouldKeepSample() {
+ return mRandom.nextInt(mPeriodicSamplingInterval) == 0;
+ }
+
+ /** Updates the sampling interval. */
+ public void setSamplingInterval(int samplingInterval) {
+ if (samplingInterval <= 0) {
+ Slog.w(TAG, "Ignored invalid sampling interval (value must be positive): "
+ + samplingInterval);
+ return;
+ }
+
+ synchronized (mLock) {
+ if (samplingInterval != mPeriodicSamplingInterval) {
+ mPeriodicSamplingInterval = samplingInterval;
+ reset();
+ }
+ }
+ }
+
+ /** Updates the sharding modulo. */
+ public void setShardingModulo(int shardingModulo) {
+ if (shardingModulo <= 0) {
+ Slog.w(TAG, "Ignored invalid sharding modulo (value must be positive): "
+ + shardingModulo);
+ return;
+ }
+
+ synchronized (mLock) {
+ if (shardingModulo != mShardingModulo) {
+ mShardingModulo = shardingModulo;
+ mShardingOffset = mRandom.nextInt(shardingModulo);
+ reset();
+ }
+ }
+ }
+
+ /** Updates the statsd push interval. */
+ public void setPushInterval(int pushIntervalMinutes) {
+ if (pushIntervalMinutes <= 0) {
+ Slog.w(TAG, "Ignored invalid push interval (value must be positive): "
+ + pushIntervalMinutes);
+ return;
+ }
+
+ synchronized (mLock) {
+ if (pushIntervalMinutes != mStatsdPushIntervalMinutes) {
+ mStatsdPushIntervalMinutes = pushIntervalMinutes;
+ reset();
+ }
+ }
+ }
+
+ /** Updates the histogram buckets parameters. */
+ public void setHistogramBucketsParams(
+ int bucketCount, int firstBucketSize, float bucketScaleFactor) {
+ synchronized (mLock) {
+ if (bucketCount != mBucketCount || firstBucketSize != mFirstBucketSize
+ || bucketScaleFactor != mBucketScaleFactor) {
+ mBucketCount = bucketCount;
+ mFirstBucketSize = firstBucketSize;
+ mBucketScaleFactor = bucketScaleFactor;
+ mLatencyBuckets = new BinderLatencyBuckets(
+ mBucketCount, mFirstBucketSize, mBucketScaleFactor);
+ reset();
+ }
+ }
+ }
+
+ /** Resets the sample collection. */
+ public void reset() {
+ synchronized (mLock) {
+ mLatencyHistograms.clear();
+ }
+ noteLatencyDelayed();
+ }
+
+ /** Container for binder latency information. */
+ public static class LatencyDims {
+ // Binder interface descriptor.
+ private Class<? extends Binder> mBinderClass;
+ // Binder transaction code.
+ private int mTransactionCode;
+ // Cached hash code, 0 if not set yet.
+ private int mHashCode = 0;
+
+ /** Creates a new instance of LatencyDims. */
+ public static LatencyDims create(Class<? extends Binder> binderClass, int transactionCode) {
+ return new LatencyDims(binderClass, transactionCode);
+ }
+
+ private LatencyDims(Class<? extends Binder> binderClass, int transactionCode) {
+ this.mBinderClass = binderClass;
+ this.mTransactionCode = transactionCode;
+ }
+
+ public Class<? extends Binder> getBinderClass() {
+ return mBinderClass;
+ }
+
+ public int getTransactionCode() {
+ return mTransactionCode;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (other == null || !(other instanceof LatencyDims)) {
+ return false;
+ }
+ LatencyDims o = (LatencyDims) other;
+ return mTransactionCode == o.getTransactionCode() && mBinderClass == o.getBinderClass();
+ }
+
+ @Override
+ public int hashCode() {
+ if (mHashCode != 0) {
+ return mHashCode;
+ }
+ int hash = mTransactionCode;
+ hash = 31 * hash + mBinderClass.getName().hashCode();
+ mHashCode = hash;
+ return hash;
+ }
+ }
+
+ @VisibleForTesting
+ public ArrayMap<LatencyDims, int[]> getLatencyHistograms() {
+ return mLatencyHistograms;
+ }
+
+ @VisibleForTesting
+ public Runnable getStatsdPushRunnable() {
+ return mLatencyObserverRunnable;
+ }
+
+ @VisibleForTesting
+ public int getProcessSource() {
+ return mProcessSource;
+ }
+}
diff --git a/android-35/com/android/internal/os/BinderTransactionNameResolver.java b/android-35/com/android/internal/os/BinderTransactionNameResolver.java
new file mode 100644
index 0000000..5f6f427
--- /dev/null
+++ b/android-35/com/android/internal/os/BinderTransactionNameResolver.java
@@ -0,0 +1,89 @@
+/*
+ * 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.internal.os;
+
+import android.os.Binder;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+
+/**
+ * Maps a binder class and transaction code to the default transaction name. Since this
+ * resolution is class-based as opposed to instance-based, any custom implementation of
+ * {@link Binder#getTransactionName} will be ignored.
+ *
+ * The class is NOT thread safe
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class BinderTransactionNameResolver {
+ private static final Method NO_GET_DEFAULT_TRANSACTION_NAME_METHOD;
+
+ /**
+ * Generates the default transaction method name, which is just the transaction code.
+ * Used when the binder does not define a static "getDefaultTransactionName" method.
+ *
+ * @hide
+ */
+ public static String noDefaultTransactionName(int transactionCode) {
+ return String.valueOf(transactionCode);
+ }
+
+ static {
+ try {
+ NO_GET_DEFAULT_TRANSACTION_NAME_METHOD = BinderTransactionNameResolver.class.getMethod(
+ "noDefaultTransactionName", int.class);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final HashMap<Class<? extends Binder>, Method>
+ mGetDefaultTransactionNameMethods = new HashMap<>();
+
+ /**
+ * Given a binder class name and transaction code, returns the corresponding method name.
+ *
+ * @hide
+ */
+ public String getMethodName(Class<? extends Binder> binderClass, int transactionCode) {
+ Method method = mGetDefaultTransactionNameMethods.get(binderClass);
+ if (method == null) {
+ try {
+ method = binderClass.getMethod("getDefaultTransactionName", int.class);
+ } catch (NoSuchMethodException e) {
+ method = NO_GET_DEFAULT_TRANSACTION_NAME_METHOD;
+ }
+ if (method.getReturnType() != String.class
+ || !Modifier.isStatic(method.getModifiers())) {
+ method = NO_GET_DEFAULT_TRANSACTION_NAME_METHOD;
+ }
+ mGetDefaultTransactionNameMethods.put(binderClass, method);
+ }
+
+ try {
+ return (String) method.invoke(null, transactionCode);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/BinderfsStatsReader.java b/android-35/com/android/internal/os/BinderfsStatsReader.java
new file mode 100644
index 0000000..66f91e1
--- /dev/null
+++ b/android-35/com/android/internal/os/BinderfsStatsReader.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 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.internal.os;
+
+import com.android.internal.util.ProcFileReader;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * Reads and parses {@code binder_logs/stats} file in the {@code binderfs} filesystem.
+ * Reuse procFileReader as the contents are generated by Linux kernel in the same way.
+ *
+ * A typical example of binderfs stats log
+ *
+ * binder stats:
+ * BC_TRANSACTION: 378004
+ * BC_REPLY: 268352
+ * BC_FREE_BUFFER: 665854
+ * ...
+ * proc 12645
+ * context binder
+ * threads: 12
+ * requested threads: 0+5/15
+ * ready threads 0
+ * free async space 520192
+ * ...
+ */
[email protected]
+public class BinderfsStatsReader {
+ private final String mPath;
+
+ public BinderfsStatsReader() {
+ mPath = "/dev/binderfs/binder_logs/stats";
+ }
+
+ public BinderfsStatsReader(String path) {
+ mPath = path;
+ }
+
+ /**
+ * Read binderfs stats and call the consumer(pid, free) function for each valid process
+ *
+ * @param predicate Test if the pid is valid.
+ * @param biConsumer Callback function for each valid pid and its free async space
+ * @param consumer The error function to deal with exceptions
+ */
+ public void handleFreeAsyncSpace(Predicate<Integer> predicate,
+ BiConsumer<Integer, Integer> biConsumer, Consumer<Exception> consumer) {
+ try (ProcFileReader mReader = new ProcFileReader(new FileInputStream(mPath))) {
+ while (mReader.hasMoreData()) {
+ // find the next process
+ if (!mReader.nextString().equals("proc")) {
+ mReader.finishLine();
+ continue;
+ }
+
+ // read pid
+ int pid = mReader.nextInt();
+ mReader.finishLine();
+
+ // check if we have interest in this process
+ if (!predicate.test(pid)) {
+ continue;
+ }
+
+ // read free async space
+ mReader.finishLine(); // context binder
+ mReader.finishLine(); // threads:
+ mReader.finishLine(); // requested threads:
+ mReader.finishLine(); // ready threads
+ if (!mReader.nextString().equals("free")) {
+ mReader.finishLine();
+ continue;
+ }
+ if (!mReader.nextString().equals("async")) {
+ mReader.finishLine();
+ continue;
+ }
+ if (!mReader.nextString().equals("space")) {
+ mReader.finishLine();
+ continue;
+ }
+ int free = mReader.nextInt();
+ mReader.finishLine();
+ biConsumer.accept(pid, free);
+ }
+ } catch (IOException | NumberFormatException e) {
+ consumer.accept(e);
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/ByteTransferPipe.java b/android-35/com/android/internal/os/ByteTransferPipe.java
new file mode 100644
index 0000000..6489894
--- /dev/null
+++ b/android-35/com/android/internal/os/ByteTransferPipe.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 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.internal.os;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Helper class to get byte data through a pipe from a client app. Also {@see TransferPipe}.
+ */
+public class ByteTransferPipe extends TransferPipe {
+ static final String TAG = "ByteTransferPipe";
+
+ private ByteArrayOutputStream mOutputStream;
+
+ public ByteTransferPipe() throws IOException {
+ super();
+ }
+
+ public ByteTransferPipe(String bufferPrefix) throws IOException {
+ super(bufferPrefix, "ByteTransferPipe");
+ }
+
+ @Override
+ protected OutputStream getNewOutputStream() {
+ mOutputStream = new ByteArrayOutputStream();
+ return mOutputStream;
+ }
+
+ public byte[] get() throws IOException {
+ go(null);
+ return mOutputStream.toByteArray();
+ }
+}
diff --git a/android-35/com/android/internal/os/CachedDeviceState.java b/android-35/com/android/internal/os/CachedDeviceState.java
new file mode 100644
index 0000000..ac92f86
--- /dev/null
+++ b/android-35/com/android/internal/os/CachedDeviceState.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+
+import android.os.SystemClock;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * Stores the device state (e.g. charging/on battery, screen on/off) to be shared with
+ * the System Server telemetry services.
+ *
+ * @hide
+ */
[email protected]
+public class CachedDeviceState {
+ private volatile boolean mScreenInteractive;
+ private volatile boolean mCharging;
+ private final Object mStopwatchesLock = new Object();
+ @GuardedBy("mStopwatchLock")
+ private final ArrayList<TimeInStateStopwatch> mOnBatteryStopwatches = new ArrayList<>();
+
+ public CachedDeviceState() {
+ mCharging = true;
+ mScreenInteractive = false;
+ }
+
+ @VisibleForTesting
+ public CachedDeviceState(boolean isCharging, boolean isScreenInteractive) {
+ mCharging = isCharging;
+ mScreenInteractive = isScreenInteractive;
+ }
+
+ public void setScreenInteractive(boolean screenInteractive) {
+ mScreenInteractive = screenInteractive;
+ }
+
+ public void setCharging(boolean charging) {
+ if (mCharging != charging) {
+ mCharging = charging;
+ updateStopwatches(/* shouldStart= */ !charging);
+ }
+ }
+
+ private void updateStopwatches(boolean shouldStart) {
+ synchronized (mStopwatchesLock) {
+ final int size = mOnBatteryStopwatches.size();
+ for (int i = 0; i < size; i++) {
+ if (shouldStart) {
+ mOnBatteryStopwatches.get(i).start();
+ } else {
+ mOnBatteryStopwatches.get(i).stop();
+ }
+ }
+ }
+ }
+
+ public Readonly getReadonlyClient() {
+ return new CachedDeviceState.Readonly();
+ }
+
+ /**
+ * Allows for only a readonly access to the device state.
+ */
+ public class Readonly {
+ public boolean isCharging() {
+ return mCharging;
+ }
+
+ public boolean isScreenInteractive() {
+ return mScreenInteractive;
+ }
+
+ /** Creates a {@link TimeInStateStopwatch stopwatch} that tracks the time on battery. */
+ public TimeInStateStopwatch createTimeOnBatteryStopwatch() {
+ synchronized (mStopwatchesLock) {
+ final TimeInStateStopwatch stopwatch = new TimeInStateStopwatch();
+ mOnBatteryStopwatches.add(stopwatch);
+ if (!mCharging) {
+ stopwatch.start();
+ }
+ return stopwatch;
+ }
+ }
+ }
+
+ /** Tracks the time the device spent in a given state. */
+ public class TimeInStateStopwatch implements AutoCloseable {
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private long mStartTimeMillis;
+ @GuardedBy("mLock")
+ private long mTotalTimeMillis;
+
+ /** Returns the time in state since the last call to {@link TimeInStateStopwatch#reset}. */
+ public long getMillis() {
+ synchronized (mLock) {
+ return mTotalTimeMillis + elapsedTime();
+ }
+ }
+
+ /** Resets the time in state to 0 without stopping the timer if it's started. */
+ public void reset() {
+ synchronized (mLock) {
+ mTotalTimeMillis = 0;
+ mStartTimeMillis = isRunning() ? SystemClock.elapsedRealtime() : 0;
+ }
+ }
+
+ private void start() {
+ synchronized (mLock) {
+ if (!isRunning()) {
+ mStartTimeMillis = SystemClock.elapsedRealtime();
+ }
+ }
+ }
+
+ private void stop() {
+ synchronized (mLock) {
+ if (isRunning()) {
+ mTotalTimeMillis += elapsedTime();
+ mStartTimeMillis = 0;
+ }
+ }
+ }
+
+ private long elapsedTime() {
+ return isRunning() ? SystemClock.elapsedRealtime() - mStartTimeMillis : 0;
+ }
+
+ @VisibleForTesting
+ public boolean isRunning() {
+ return mStartTimeMillis > 0;
+ }
+
+ @Override
+ public void close() {
+ synchronized (mStopwatchesLock) {
+ mOnBatteryStopwatches.remove(this);
+ }
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/ChildZygoteInit.java b/android-35/com/android/internal/os/ChildZygoteInit.java
new file mode 100644
index 0000000..749ff84
--- /dev/null
+++ b/android-35/com/android/internal/os/ChildZygoteInit.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import android.os.Process;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+
+/**
+ * ChildZygoteInit is shared by both the Application and WebView zygote to initialize
+ * and run a (child) Zygote server.
+ *
+ * @hide
+ */
+public class ChildZygoteInit {
+ private static final String TAG = "ChildZygoteInit";
+
+ static String parseSocketNameFromArgs(String[] argv) {
+ for (String arg : argv) {
+ if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
+ return arg.substring(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG.length());
+ }
+ }
+
+ return null;
+ }
+
+ static String parseAbiListFromArgs(String[] argv) {
+ for (String arg : argv) {
+ if (arg.startsWith(Zygote.CHILD_ZYGOTE_ABI_LIST_ARG)) {
+ return arg.substring(Zygote.CHILD_ZYGOTE_ABI_LIST_ARG.length());
+ }
+ }
+
+ return null;
+ }
+
+ static int parseIntFromArg(String[] argv, String desiredArg) {
+ int value = -1;
+ for (String arg : argv) {
+ if (arg.startsWith(desiredArg)) {
+ String valueStr = arg.substring(arg.indexOf('=') + 1);
+ try {
+ value = Integer.parseInt(valueStr);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid int argument: "
+ + valueStr, e);
+ }
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Starts a ZygoteServer and listens for requests
+ *
+ * @param server An instance of a ZygoteServer to listen on
+ * @param args Passed in arguments for this ZygoteServer
+ */
+ static void runZygoteServer(ZygoteServer server, String[] args) {
+ String socketName = parseSocketNameFromArgs(args);
+ if (socketName == null) {
+ throw new NullPointerException("No socketName specified");
+ }
+
+ String abiList = parseAbiListFromArgs(args);
+ if (abiList == null) {
+ throw new NullPointerException("No abiList specified");
+ }
+
+ try {
+ Os.prctl(OsConstants.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+ } catch (ErrnoException ex) {
+ throw new RuntimeException("Failed to set PR_SET_NO_NEW_PRIVS", ex);
+ }
+
+ int uidGidMin = parseIntFromArg(args, Zygote.CHILD_ZYGOTE_UID_RANGE_START);
+ int uidGidMax = parseIntFromArg(args, Zygote.CHILD_ZYGOTE_UID_RANGE_END);
+ if (uidGidMin == -1 || uidGidMax == -1) {
+ throw new RuntimeException("Couldn't parse UID range start/end");
+ }
+ if (uidGidMin > uidGidMax) {
+ throw new RuntimeException("Passed in UID range is invalid, min > max.");
+ }
+
+ // Verify the UIDs at least do not include system UIDs; we can't easily verify there
+ // are just isolated UIDs in the range, because for the webview zygote, there is no
+ // single range that captures all possible isolated UIDs.
+ // TODO(b/123615476) narrow this down
+ if (uidGidMin < Process.FIRST_APP_ZYGOTE_ISOLATED_UID) {
+ throw new RuntimeException("Passed in UID range does not map to isolated processes.");
+ }
+
+ /**
+ * Install a seccomp filter that ensure this Zygote can only setuid()/setgid()
+ * to the passed in range.
+ */
+ Zygote.nativeInstallSeccompUidGidFilter(uidGidMin, uidGidMax);
+
+ final Runnable caller;
+ try {
+ server.registerServerSocketAtAbstractName(socketName);
+
+ // Add the abstract socket to the FD allow list so that the native zygote code
+ // can properly detach it after forking.
+ Zygote.nativeAllowFileAcrossFork("ABSTRACT/" + socketName);
+
+ // The select loop returns early in the child process after a fork and
+ // loops forever in the zygote.
+ caller = server.runSelectLoop(abiList);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Fatal exception:", e);
+ throw e;
+ } finally {
+ server.closeServerSocket();
+ }
+
+ // We're in the child process and have exited the select loop. Proceed to execute the
+ // command.
+ if (caller != null) {
+ caller.run();
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/ClassLoaderFactory.java b/android-35/com/android/internal/os/ClassLoaderFactory.java
new file mode 100644
index 0000000..8f5e97d
--- /dev/null
+++ b/android-35/com/android/internal/os/ClassLoaderFactory.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2016 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.internal.os;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Trace;
+
+import dalvik.system.DelegateLastClassLoader;
+import dalvik.system.DexClassLoader;
+import dalvik.system.PathClassLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Creates class loaders.
+ *
+ * @hide
+ */
+public class ClassLoaderFactory {
+ // Unconstructable
+ private ClassLoaderFactory() {}
+
+ private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName();
+ private static final String DEX_CLASS_LOADER_NAME = DexClassLoader.class.getName();
+ private static final String DELEGATE_LAST_CLASS_LOADER_NAME =
+ DelegateLastClassLoader.class.getName();
+
+ /**
+ * Returns the name of the class for PathClassLoader.
+ */
+ public static String getPathClassLoaderName() {
+ return PATH_CLASS_LOADER_NAME;
+ }
+
+ /**
+ * Returns true if {@code name} is a supported classloader. {@code name} must be a
+ * binary name of a class, as defined by {@code Class.getName}.
+ */
+ public static boolean isValidClassLoaderName(String name) {
+ // This method is used to parse package data and does not accept null names.
+ return name != null && (isPathClassLoaderName(name) || isDelegateLastClassLoaderName(name));
+ }
+
+ /**
+ * Returns true if {@code name} is the encoding for either PathClassLoader or DexClassLoader.
+ * The two class loaders are grouped together because they have the same behaviour.
+ */
+ public static boolean isPathClassLoaderName(String name) {
+ // For null values we default to PathClassLoader. This cover the case when packages
+ // don't specify any value for their class loaders.
+ return name == null || PATH_CLASS_LOADER_NAME.equals(name) ||
+ DEX_CLASS_LOADER_NAME.equals(name);
+ }
+
+ /**
+ * Returns true if {@code name} is the encoding for the DelegateLastClassLoader.
+ */
+ public static boolean isDelegateLastClassLoaderName(String name) {
+ return DELEGATE_LAST_CLASS_LOADER_NAME.equals(name);
+ }
+
+ /**
+ * Same as {@code createClassLoader} below, except that no associated namespace
+ * is created.
+ */
+ public static ClassLoader createClassLoader(String dexPath,
+ String librarySearchPath, ClassLoader parent, String classloaderName,
+ List<ClassLoader> sharedLibraries, List<ClassLoader> sharedLibrariesLoadedAfter) {
+ ClassLoader[] arrayOfSharedLibraries = (sharedLibraries == null)
+ ? null
+ : sharedLibraries.toArray(new ClassLoader[sharedLibraries.size()]);
+ ClassLoader[] arrayOfSharedLibrariesLoadedAfterApp = (sharedLibrariesLoadedAfter == null)
+ ? null
+ : sharedLibrariesLoadedAfter.toArray(
+ new ClassLoader[sharedLibrariesLoadedAfter.size()]);
+ if (isPathClassLoaderName(classloaderName)) {
+ return new PathClassLoader(dexPath, librarySearchPath, parent, arrayOfSharedLibraries,
+ arrayOfSharedLibrariesLoadedAfterApp);
+ } else if (isDelegateLastClassLoaderName(classloaderName)) {
+ return new DelegateLastClassLoader(dexPath, librarySearchPath, parent,
+ arrayOfSharedLibraries, arrayOfSharedLibrariesLoadedAfterApp);
+ }
+
+ throw new AssertionError("Invalid classLoaderName: " + classloaderName);
+ }
+
+ /**
+ * Same as {@code createClassLoader} below, but passes a null list of shared libraries. This
+ * method is used only to load platform classes (i.e. those in framework.jar or services.jar),
+ * and MUST NOT be used for loading untrusted classes, especially the app classes. For the
+ * latter case, use the below method which accepts list of shared libraries so that the classes
+ * don't have unlimited access to all shared libraries.
+ */
+ public static ClassLoader createClassLoader(String dexPath,
+ String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
+ int targetSdkVersion, boolean isNamespaceShared, String classLoaderName) {
+ // b/205164833: allow framework classes to have access to all public vendor libraries.
+ // This is because those classes are part of the platform and don't have an app manifest
+ // where required libraries can be specified using the <uses-native-library> tag.
+ // Note that this still does not give access to "private" vendor libraries.
+ List<String> nativeSharedLibraries = new ArrayList<>();
+ nativeSharedLibraries.add("ALL");
+
+ return createClassLoader(dexPath, librarySearchPath, libraryPermittedPath,
+ parent, targetSdkVersion, isNamespaceShared, classLoaderName, null,
+ nativeSharedLibraries, null);
+ }
+
+ /**
+ * Create a ClassLoader and initialize a linker-namespace for it.
+ */
+ public static ClassLoader createClassLoader(String dexPath,
+ String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
+ int targetSdkVersion, boolean isNamespaceShared, String classLoaderName,
+ List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries,
+ List<ClassLoader> sharedLibrariesAfter) {
+
+ final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent,
+ classLoaderName, sharedLibraries, sharedLibrariesAfter);
+
+ String sonameList = "";
+ if (nativeSharedLibraries != null) {
+ sonameList = String.join(":", nativeSharedLibraries);
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "createClassloaderNamespace");
+ String errorMessage = createClassloaderNamespace(classLoader,
+ targetSdkVersion,
+ librarySearchPath,
+ libraryPermittedPath,
+ isNamespaceShared,
+ dexPath,
+ sonameList);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ if (errorMessage != null) {
+ throw new UnsatisfiedLinkError("Unable to create namespace for the classloader " +
+ classLoader + ": " + errorMessage);
+ }
+
+ return classLoader;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private static native String createClassloaderNamespace(ClassLoader classLoader,
+ int targetSdkVersion,
+ String librarySearchPath,
+ String libraryPermittedPath,
+ boolean isNamespaceShared,
+ String dexPath,
+ String sonameList);
+}
diff --git a/android-35/com/android/internal/os/Clock.java b/android-35/com/android/internal/os/Clock.java
new file mode 100644
index 0000000..c2403d1
--- /dev/null
+++ b/android-35/com/android/internal/os/Clock.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 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.internal.os;
+
+import android.os.SystemClock;
+
+/**
+ * A wrapper for SystemClock, intended for mocking in unit tests.
+ */
[email protected]
+public abstract class Clock {
+ /** Elapsed Realtime, see SystemClock.elapsedRealtime() */
+ public long elapsedRealtime() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** Uptime, see SystemClock.uptimeMillis() */
+ public long uptimeMillis() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** Wall-clock time as per System.currentTimeMillis() */
+ public long currentTimeMillis() {
+ throw new UnsupportedOperationException();
+ }
+
+ public static final Clock SYSTEM_CLOCK = new Clock() {
+
+ @Override
+ public long elapsedRealtime() {
+ return SystemClock.elapsedRealtime();
+ }
+
+ @Override
+ public long uptimeMillis() {
+ return SystemClock.uptimeMillis();
+ }
+
+ @Override
+ public long currentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+ };
+}
diff --git a/android-35/com/android/internal/os/CpuScalingPolicies.java b/android-35/com/android/internal/os/CpuScalingPolicies.java
new file mode 100644
index 0000000..f61cf97
--- /dev/null
+++ b/android-35/com/android/internal/os/CpuScalingPolicies.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 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.internal.os;
+
+import android.annotation.NonNull;
+import android.util.SparseArray;
+
+import libcore.util.EmptyArray;
+
+import java.util.Arrays;
+
+/**
+ * CPU scaling policies: the policy IDs and corresponding supported scaling for those
+ * policies.
+ */
[email protected]
+public class CpuScalingPolicies {
+ private final SparseArray<int[]> mCpusByPolicy;
+ private final SparseArray<int[]> mFreqsByPolicy;
+ private final int[] mPolicies;
+ private final int mScalingStepCount;
+
+ public CpuScalingPolicies(@NonNull SparseArray<int[]> cpusByPolicy,
+ @NonNull SparseArray<int[]> freqsByPolicy) {
+ mCpusByPolicy = cpusByPolicy;
+ mFreqsByPolicy = freqsByPolicy;
+
+ mPolicies = new int[cpusByPolicy.size()];
+ for (int i = 0; i < mPolicies.length; i++) {
+ mPolicies[i] = cpusByPolicy.keyAt(i);
+ }
+
+ Arrays.sort(mPolicies);
+
+ int count = 0;
+ for (int i = freqsByPolicy.size() - 1; i >= 0; i--) {
+ count += freqsByPolicy.valueAt(i).length;
+ }
+ mScalingStepCount = count;
+ }
+
+ /**
+ * Returns available policies (aka clusters).
+ */
+ @NonNull
+ public int[] getPolicies() {
+ return mPolicies;
+ }
+
+ /**
+ * CPUs covered by the specified policy.
+ */
+ @NonNull
+ public int[] getRelatedCpus(int policy) {
+ return mCpusByPolicy.get(policy, EmptyArray.INT);
+ }
+
+ /**
+ * Scaling frequencies supported for the specified policy.
+ */
+ @NonNull
+ public int[] getFrequencies(int policy) {
+ return mFreqsByPolicy.get(policy, EmptyArray.INT);
+ }
+
+ /**
+ * Returns the overall number of supported scaling steps: grand total of available frequencies
+ * across all scaling policies.
+ */
+ public int getScalingStepCount() {
+ return mScalingStepCount;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int policy : mPolicies) {
+ sb.append("policy").append(policy)
+ .append("\n CPUs: ").append(Arrays.toString(mCpusByPolicy.get(policy)))
+ .append("\n freqs: ").append(Arrays.toString(mFreqsByPolicy.get(policy)))
+ .append("\n");
+ }
+ return sb.toString();
+ }
+}
diff --git a/android-35/com/android/internal/os/CpuScalingPolicyReader.java b/android-35/com/android/internal/os/CpuScalingPolicyReader.java
new file mode 100644
index 0000000..720577e
--- /dev/null
+++ b/android-35/com/android/internal/os/CpuScalingPolicyReader.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 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.internal.os;
+
+import android.annotation.NonNull;
+import android.os.FileUtils;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import libcore.util.EmptyArray;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Captures a CPU scaling policies such as available scaling frequencies as well as
+ * CPUs (cores) for each policy.
+ *
+ * See <a
+ * href="https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html
+ * #policy-interface-in-sysfs">Policy Interface in sysfs</a>
+ */
[email protected]
+public class CpuScalingPolicyReader {
+ private static final String TAG = "CpuScalingPolicyReader";
+ private static final String CPUFREQ_DIR = "/sys/devices/system/cpu/cpufreq";
+ private static final Pattern POLICY_PATTERN = Pattern.compile("policy(\\d+)");
+ private static final String FILE_NAME_RELATED_CPUS = "related_cpus";
+ private static final String FILE_NAME_SCALING_AVAILABLE_FREQUENCIES =
+ "scaling_available_frequencies";
+ private static final String FILE_NAME_SCALING_BOOST_FREQUENCIES = "scaling_boost_frequencies";
+ private static final String FILE_NAME_CPUINFO_CUR_FREQ = "cpuinfo_cur_freq";
+
+ private final String mCpuFreqDir;
+
+ public CpuScalingPolicyReader() {
+ this(CPUFREQ_DIR);
+ }
+
+ @VisibleForTesting
+ public CpuScalingPolicyReader(String cpuFreqDir) {
+ mCpuFreqDir = cpuFreqDir;
+ }
+
+ /**
+ * Reads scaling policy info from sysfs files in /sys/devices/system/cpu/cpufreq
+ */
+ @NonNull
+ public CpuScalingPolicies read() {
+ SparseArray<int[]> cpusByPolicy = new SparseArray<>();
+ SparseArray<int[]> freqsByPolicy = new SparseArray<>();
+
+ File cpuFreqDir = new File(mCpuFreqDir);
+ File[] policyDirs = cpuFreqDir.listFiles();
+ if (policyDirs != null) {
+ for (File policyDir : policyDirs) {
+ Matcher matcher = POLICY_PATTERN.matcher(policyDir.getName());
+ if (matcher.matches()) {
+ int[] relatedCpus = readIntsFromFile(
+ new File(policyDir, FILE_NAME_RELATED_CPUS));
+ if (relatedCpus.length == 0) {
+ continue;
+ }
+
+ int[] availableFreqs = readIntsFromFile(
+ new File(policyDir, FILE_NAME_SCALING_AVAILABLE_FREQUENCIES));
+ int[] boostFreqs = readIntsFromFile(
+ new File(policyDir, FILE_NAME_SCALING_BOOST_FREQUENCIES));
+ int[] freqs;
+ if (boostFreqs.length == 0) {
+ freqs = availableFreqs;
+ } else {
+ freqs = Arrays.copyOf(availableFreqs,
+ availableFreqs.length + boostFreqs.length);
+ System.arraycopy(boostFreqs, 0, freqs, availableFreqs.length,
+ boostFreqs.length);
+ }
+ if (freqs.length == 0) {
+ freqs = readIntsFromFile(new File(policyDir, FILE_NAME_CPUINFO_CUR_FREQ));
+ if (freqs.length == 0) {
+ freqs = new int[]{0}; // Unknown frequency
+ }
+ }
+ int policy = Integer.parseInt(matcher.group(1));
+ cpusByPolicy.put(policy, relatedCpus);
+ freqsByPolicy.put(policy, freqs);
+ }
+ }
+ }
+
+ if (cpusByPolicy.size() == 0) {
+ // There just has to be at least one CPU - otherwise, what's executing this code?
+ cpusByPolicy.put(0, new int[]{0});
+ freqsByPolicy.put(0, new int[]{0});
+ }
+
+ return new CpuScalingPolicies(cpusByPolicy, freqsByPolicy);
+ }
+
+ @NonNull
+ private static int[] readIntsFromFile(File file) {
+ if (!file.exists()) {
+ return EmptyArray.INT;
+ }
+
+ IntArray intArray = new IntArray(16);
+ try {
+ String contents = FileUtils.readTextFile(file, 0, null).trim();
+ String[] strings = contents.split(" ");
+ intArray.clear();
+ for (String s : strings) {
+ if (s.isBlank()) {
+ continue;
+ }
+ try {
+ intArray.add(Integer.parseInt(s));
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Unexpected file format " + file
+ + ": " + contents, e);
+ }
+ }
+ return intArray.toArray();
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot read " + file, e);
+ return EmptyArray.INT;
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/FuseAppLoop.java b/android-35/com/android/internal/os/FuseAppLoop.java
new file mode 100644
index 0000000..1c6c6a7
--- /dev/null
+++ b/android-35/com/android/internal/os/FuseAppLoop.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2016 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.internal.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.ProxyFileDescriptorCallback;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ThreadFactory;
+
+public class FuseAppLoop implements Handler.Callback {
+ private static final String TAG = "FuseAppLoop";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ public static final int ROOT_INODE = 1;
+ private static final int MIN_INODE = 2;
+ private static final ThreadFactory sDefaultThreadFactory = new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, TAG);
+ }
+ };
+ private static final int FUSE_OK = 0;
+ private static final int ARGS_POOL_SIZE = 50;
+
+ private final Object mLock = new Object();
+ private final int mMountPointId;
+ private final Thread mThread;
+
+ @GuardedBy("mLock")
+ private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>();
+
+ @GuardedBy("mLock")
+ private final BytesMap mBytesMap = new BytesMap();
+
+ @GuardedBy("mLock")
+ private final LinkedList<Args> mArgsPool = new LinkedList<>();
+
+ /**
+ * Sequential number can be used as file name and inode in AppFuse.
+ * 0 is regarded as an error, 1 is mount point. So we start the number from 2.
+ */
+ @GuardedBy("mLock")
+ private int mNextInode = MIN_INODE;
+
+ @GuardedBy("mLock")
+ private long mInstance;
+
+ public FuseAppLoop(
+ int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) {
+ mMountPointId = mountPointId;
+ if (factory == null) {
+ factory = sDefaultThreadFactory;
+ }
+ mInstance = native_new(fd.detachFd());
+ mThread = factory.newThread(() -> {
+ native_start(mInstance);
+ synchronized (mLock) {
+ native_delete(mInstance);
+ mInstance = 0;
+ mBytesMap.clear();
+ }
+ });
+ mThread.start();
+ }
+
+ public int registerCallback(@NonNull ProxyFileDescriptorCallback callback,
+ @NonNull Handler handler) throws FuseUnavailableMountException {
+ synchronized (mLock) {
+ Objects.requireNonNull(callback);
+ Objects.requireNonNull(handler);
+ Preconditions.checkState(
+ mCallbackMap.size() < Integer.MAX_VALUE - MIN_INODE, "Too many opened files.");
+ Preconditions.checkArgument(
+ Thread.currentThread().getId() != handler.getLooper().getThread().getId(),
+ "Handler must be different from the current thread");
+ if (mInstance == 0) {
+ throw new FuseUnavailableMountException(mMountPointId);
+ }
+ int id;
+ while (true) {
+ id = mNextInode;
+ mNextInode++;
+ if (mNextInode < 0) {
+ mNextInode = MIN_INODE;
+ }
+ if (mCallbackMap.get(id) == null) {
+ break;
+ }
+ }
+ mCallbackMap.put(id, new CallbackEntry(
+ callback, new Handler(handler.getLooper(), this)));
+ return id;
+ }
+ }
+
+ public void unregisterCallback(int id) {
+ synchronized (mLock) {
+ mCallbackMap.remove(id);
+ }
+ }
+
+ public int getMountPointId() {
+ return mMountPointId;
+ }
+
+ // Defined in fuse.h
+ private static final int FUSE_LOOKUP = 1;
+ private static final int FUSE_GETATTR = 3;
+ private static final int FUSE_OPEN = 14;
+ private static final int FUSE_READ = 15;
+ private static final int FUSE_WRITE = 16;
+ private static final int FUSE_RELEASE = 18;
+ private static final int FUSE_FSYNC = 20;
+
+ // Defined in FuseBuffer.h
+ private static final int FUSE_MAX_WRITE = 128 * 1024;
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ final Args args = (Args) msg.obj;
+ final CallbackEntry entry = args.entry;
+ final long inode = args.inode;
+ final long unique = args.unique;
+ final int size = args.size;
+ final long offset = args.offset;
+ final byte[] data = args.data;
+
+ try {
+ switch (msg.what) {
+ case FUSE_LOOKUP: {
+ final long fileSize = entry.callback.onGetSize();
+ synchronized (mLock) {
+ if (mInstance != 0) {
+ native_replyLookup(mInstance, unique, inode, fileSize);
+ }
+ recycleLocked(args);
+ }
+ break;
+ }
+ case FUSE_GETATTR: {
+ final long fileSize = entry.callback.onGetSize();
+ synchronized (mLock) {
+ if (mInstance != 0) {
+ native_replyGetAttr(mInstance, unique, inode, fileSize);
+ }
+ recycleLocked(args);
+ }
+ break;
+ }
+ case FUSE_READ:
+ final int readSize = entry.callback.onRead(
+ offset, size, data);
+ synchronized (mLock) {
+ if (mInstance != 0) {
+ native_replyRead(mInstance, unique, readSize, data);
+ }
+ recycleLocked(args);
+ }
+ break;
+ case FUSE_WRITE:
+ final int writeSize = entry.callback.onWrite(offset, size, data);
+ synchronized (mLock) {
+ if (mInstance != 0) {
+ native_replyWrite(mInstance, unique, writeSize);
+ }
+ recycleLocked(args);
+ }
+ break;
+ case FUSE_FSYNC:
+ entry.callback.onFsync();
+ synchronized (mLock) {
+ if (mInstance != 0) {
+ native_replySimple(mInstance, unique, FUSE_OK);
+ }
+ recycleLocked(args);
+ }
+ break;
+ case FUSE_RELEASE:
+ entry.callback.onRelease();
+ synchronized (mLock) {
+ if (mInstance != 0) {
+ native_replySimple(mInstance, unique, FUSE_OK);
+ }
+ mBytesMap.stopUsing(inode);
+ recycleLocked(args);
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown FUSE command: " + msg.what);
+ }
+ } catch (Exception error) {
+ synchronized (mLock) {
+ Log.e(TAG, "", error);
+ replySimpleLocked(unique, getError(error));
+ recycleLocked(args);
+ }
+ }
+
+ return true;
+ }
+
+ // Called by JNI.
+ @SuppressWarnings("unused")
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private void onCommand(int command, long unique, long inode, long offset, int size,
+ byte[] data) {
+ synchronized (mLock) {
+ try {
+ final Args args;
+ if (mArgsPool.size() == 0) {
+ args = new Args();
+ } else {
+ args = mArgsPool.pop();
+ }
+ args.unique = unique;
+ args.inode = inode;
+ args.offset = offset;
+ args.size = size;
+ args.data = data;
+ args.entry = getCallbackEntryOrThrowLocked(inode);
+ if (!args.entry.handler.sendMessage(
+ Message.obtain(args.entry.handler, command, 0, 0, args))) {
+ throw new ErrnoException("onCommand", OsConstants.EBADF);
+ }
+ } catch (Exception error) {
+ replySimpleLocked(unique, getError(error));
+ }
+ }
+ }
+
+ // Called by JNI.
+ @SuppressWarnings("unused")
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private byte[] onOpen(long unique, long inode) {
+ synchronized (mLock) {
+ try {
+ final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode);
+ if (entry.opened) {
+ throw new ErrnoException("onOpen", OsConstants.EMFILE);
+ }
+ if (mInstance != 0) {
+ native_replyOpen(mInstance, unique, /* fh */ inode);
+ entry.opened = true;
+ return mBytesMap.startUsing(inode);
+ }
+ } catch (ErrnoException error) {
+ replySimpleLocked(unique, getError(error));
+ }
+ return null;
+ }
+ }
+
+ private static int getError(@NonNull Exception error) {
+ if (error instanceof ErrnoException) {
+ final int errno = ((ErrnoException) error).errno;
+ if (errno != OsConstants.ENOSYS) {
+ return -errno;
+ }
+ }
+ return -OsConstants.EBADF;
+ }
+
+ @GuardedBy("mLock")
+ private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
+ final CallbackEntry entry = mCallbackMap.get(checkInode(inode));
+ if (entry == null) {
+ throw new ErrnoException("getCallbackEntryOrThrowLocked", OsConstants.ENOENT);
+ }
+ return entry;
+ }
+
+ @GuardedBy("mLock")
+ private void recycleLocked(Args args) {
+ if (mArgsPool.size() < ARGS_POOL_SIZE) {
+ mArgsPool.add(args);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void replySimpleLocked(long unique, int result) {
+ if (mInstance != 0) {
+ native_replySimple(mInstance, unique, result);
+ }
+ }
+
+ native long native_new(int fd);
+ native void native_delete(long ptr);
+ native void native_start(long ptr);
+
+ native void native_replySimple(long ptr, long unique, int result);
+ native void native_replyOpen(long ptr, long unique, long fh);
+ native void native_replyLookup(long ptr, long unique, long inode, long size);
+ native void native_replyGetAttr(long ptr, long unique, long inode, long size);
+ native void native_replyWrite(long ptr, long unique, int size);
+ native void native_replyRead(long ptr, long unique, int size, byte[] bytes);
+
+ private static int checkInode(long inode) {
+ Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode");
+ return (int) inode;
+ }
+
+ public static class UnmountedException extends Exception {}
+
+ private static class CallbackEntry {
+ final ProxyFileDescriptorCallback callback;
+ final Handler handler;
+ boolean opened;
+
+ CallbackEntry(ProxyFileDescriptorCallback callback, Handler handler) {
+ this.callback = Objects.requireNonNull(callback);
+ this.handler = Objects.requireNonNull(handler);
+ }
+
+ long getThreadId() {
+ return handler.getLooper().getThread().getId();
+ }
+ }
+
+ /**
+ * Entry for bytes map.
+ */
+ private static class BytesMapEntry {
+ int counter = 0;
+ byte[] bytes = new byte[FUSE_MAX_WRITE];
+ }
+
+ /**
+ * Map between inode and byte buffer.
+ */
+ private static class BytesMap {
+ final Map<Long, BytesMapEntry> mEntries = new HashMap<>();
+
+ byte[] startUsing(long inode) {
+ BytesMapEntry entry = mEntries.get(inode);
+ if (entry == null) {
+ entry = new BytesMapEntry();
+ mEntries.put(inode, entry);
+ }
+ entry.counter++;
+ return entry.bytes;
+ }
+
+ void stopUsing(long inode) {
+ final BytesMapEntry entry = mEntries.get(inode);
+ Objects.requireNonNull(entry);
+ entry.counter--;
+ if (entry.counter <= 0) {
+ mEntries.remove(inode);
+ }
+ }
+
+ void clear() {
+ mEntries.clear();
+ }
+ }
+
+ private static class Args {
+ long unique;
+ long inode;
+ long offset;
+ int size;
+ byte[] data;
+ CallbackEntry entry;
+ }
+}
diff --git a/android-35/com/android/internal/os/FuseUnavailableMountException.java b/android-35/com/android/internal/os/FuseUnavailableMountException.java
new file mode 100644
index 0000000..ca3cfb9
--- /dev/null
+++ b/android-35/com/android/internal/os/FuseUnavailableMountException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 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.internal.os;
+
+/**
+ * Exception occurred when the mount point has already been unavailable.
+ */
+public class FuseUnavailableMountException extends Exception {
+ public FuseUnavailableMountException(int mountId) {
+ super("AppFuse mount point " + mountId + " is unavailable");
+ }
+}
diff --git a/android-35/com/android/internal/os/HandlerCaller.java b/android-35/com/android/internal/os/HandlerCaller.java
new file mode 100644
index 0000000..a11c815
--- /dev/null
+++ b/android-35/com/android/internal/os/HandlerCaller.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2008 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.internal.os;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * @deprecated Use {@link com.android.internal.util.function.pooled.PooledLambda#obtainMessage}
+ * to achieve the same effect of storing multiple values in a message with the added typesafety
+ * and code continuity benefits.
+ */
+@Deprecated
+public class HandlerCaller {
+ final Looper mMainLooper;
+ final Handler mH;
+
+ final Callback mCallback;
+
+ class MyHandler extends Handler {
+ MyHandler(Looper looper, boolean async) {
+ super(looper, null, async);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ mCallback.executeMessage(msg);
+ }
+ }
+
+ public interface Callback {
+ public void executeMessage(Message msg);
+ }
+
+ public HandlerCaller(Context context, Looper looper, Callback callback,
+ boolean asyncHandler) {
+ mMainLooper = looper != null ? looper : context.getMainLooper();
+ mH = new MyHandler(mMainLooper, asyncHandler);
+ mCallback = callback;
+ }
+
+ public Handler getHandler() {
+ return mH;
+ }
+
+ public void executeOrSendMessage(Message msg) {
+ // If we are calling this from the main thread, then we can call
+ // right through. Otherwise, we need to send the message to the
+ // main thread.
+ if (Looper.myLooper() == mMainLooper) {
+ mCallback.executeMessage(msg);
+ msg.recycle();
+ return;
+ }
+
+ mH.sendMessage(msg);
+ }
+
+ public void sendMessageDelayed(Message msg, long delayMillis) {
+ mH.sendMessageDelayed(msg, delayMillis);
+ }
+
+ public boolean hasMessages(int what) {
+ return mH.hasMessages(what);
+ }
+
+ public void removeMessages(int what) {
+ mH.removeMessages(what);
+ }
+
+ public void removeMessages(int what, Object obj) {
+ mH.removeMessages(what, obj);
+ }
+
+ @UnsupportedAppUsage
+ public void sendMessage(Message msg) {
+ mH.sendMessage(msg);
+ }
+
+ public SomeArgs sendMessageAndWait(Message msg) {
+ if (Looper.myLooper() == mH.getLooper()) {
+ throw new IllegalStateException("Can't wait on same thread as looper");
+ }
+ SomeArgs args = (SomeArgs)msg.obj;
+ args.mWaitState = SomeArgs.WAIT_WAITING;
+ mH.sendMessage(msg);
+ synchronized (args) {
+ while (args.mWaitState == SomeArgs.WAIT_WAITING) {
+ try {
+ args.wait();
+ } catch (InterruptedException e) {
+ return null;
+ }
+ }
+ }
+ args.mWaitState = SomeArgs.WAIT_NONE;
+ return args;
+ }
+
+ @UnsupportedAppUsage
+ public Message obtainMessage(int what) {
+ return mH.obtainMessage(what);
+ }
+
+ public Message obtainMessageBO(int what, boolean arg1, Object arg2) {
+ return mH.obtainMessage(what, arg1 ? 1 : 0, 0, arg2);
+ }
+
+ public Message obtainMessageBOO(int what, boolean arg1, Object arg2, Object arg3) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg2;
+ args.arg2 = arg3;
+ return mH.obtainMessage(what, arg1 ? 1 : 0, 0, args);
+ }
+
+ @UnsupportedAppUsage
+ public Message obtainMessageO(int what, Object arg1) {
+ return mH.obtainMessage(what, 0, 0, arg1);
+ }
+
+ public Message obtainMessageI(int what, int arg1) {
+ return mH.obtainMessage(what, arg1, 0);
+ }
+
+ public Message obtainMessageII(int what, int arg1, int arg2) {
+ return mH.obtainMessage(what, arg1, arg2);
+ }
+
+ @UnsupportedAppUsage
+ public Message obtainMessageIO(int what, int arg1, Object arg2) {
+ return mH.obtainMessage(what, arg1, 0, arg2);
+ }
+
+ public Message obtainMessageIIO(int what, int arg1, int arg2, Object arg3) {
+ return mH.obtainMessage(what, arg1, arg2, arg3);
+ }
+
+ public Message obtainMessageIIOO(int what, int arg1, int arg2,
+ Object arg3, Object arg4) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg3;
+ args.arg2 = arg4;
+ return mH.obtainMessage(what, arg1, arg2, args);
+ }
+
+ @UnsupportedAppUsage
+ public Message obtainMessageIOO(int what, int arg1, Object arg2, Object arg3) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg2;
+ args.arg2 = arg3;
+ return mH.obtainMessage(what, arg1, 0, args);
+ }
+
+ public Message obtainMessageIOOO(int what, int arg1, Object arg2, Object arg3, Object arg4) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg2;
+ args.arg2 = arg3;
+ args.arg3 = arg4;
+ return mH.obtainMessage(what, arg1, 0, args);
+ }
+
+ public Message obtainMessageIIOOO(int what, int arg1, int arg2, Object arg3, Object arg4,
+ Object arg5) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg3;
+ args.arg2 = arg4;
+ args.arg3 = arg5;
+ return mH.obtainMessage(what, arg1, arg2, args);
+ }
+
+ public Message obtainMessageIIOOOO(int what, int arg1, int arg2, Object arg3, Object arg4,
+ Object arg5, Object arg6) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg3;
+ args.arg2 = arg4;
+ args.arg3 = arg5;
+ args.arg4 = arg6;
+ return mH.obtainMessage(what, arg1, arg2, args);
+ }
+
+ @UnsupportedAppUsage
+ public Message obtainMessageOO(int what, Object arg1, Object arg2) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg1;
+ args.arg2 = arg2;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
+ @UnsupportedAppUsage
+ public Message obtainMessageOOO(int what, Object arg1, Object arg2, Object arg3) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg1;
+ args.arg2 = arg2;
+ args.arg3 = arg3;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
+ public Message obtainMessageOOOO(int what, Object arg1, Object arg2,
+ Object arg3, Object arg4) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg1;
+ args.arg2 = arg2;
+ args.arg3 = arg3;
+ args.arg4 = arg4;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
+ public Message obtainMessageOOOOO(int what, Object arg1, Object arg2,
+ Object arg3, Object arg4, Object arg5) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg1;
+ args.arg2 = arg2;
+ args.arg3 = arg3;
+ args.arg4 = arg4;
+ args.arg5 = arg5;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
+ public Message obtainMessageOOOOII(int what, Object arg1, Object arg2,
+ Object arg3, Object arg4, int arg5, int arg6) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg1;
+ args.arg2 = arg2;
+ args.arg3 = arg3;
+ args.arg4 = arg4;
+ args.argi5 = arg5;
+ args.argi6 = arg6;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
+ public Message obtainMessageIIII(int what, int arg1, int arg2,
+ int arg3, int arg4) {
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = arg1;
+ args.argi2 = arg2;
+ args.argi3 = arg3;
+ args.argi4 = arg4;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
+ public Message obtainMessageIIIIII(int what, int arg1, int arg2,
+ int arg3, int arg4, int arg5, int arg6) {
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = arg1;
+ args.argi2 = arg2;
+ args.argi3 = arg3;
+ args.argi4 = arg4;
+ args.argi5 = arg5;
+ args.argi6 = arg6;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
+ public Message obtainMessageIIIIO(int what, int arg1, int arg2,
+ int arg3, int arg4, Object arg5) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg5;
+ args.argi1 = arg1;
+ args.argi2 = arg2;
+ args.argi3 = arg3;
+ args.argi4 = arg4;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+}
diff --git a/android-35/com/android/internal/os/KernelAllocationStats.java b/android-35/com/android/internal/os/KernelAllocationStats.java
new file mode 100644
index 0000000..58d51e3
--- /dev/null
+++ b/android-35/com/android/internal/os/KernelAllocationStats.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 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.internal.os;
+
+import android.annotation.Nullable;
+
+/** JNI wrapper around libmeminfo for kernel memory allocation stats (dmabufs, gpu driver). */
+public final class KernelAllocationStats {
+ private KernelAllocationStats() {}
+
+ /** Process dma-buf stats. */
+ public static final class ProcessDmabuf {
+ public final int uid;
+ public final String processName;
+ public final int oomScore;
+
+ /** Size of buffers retained by the process. */
+ public final int retainedSizeKb;
+ /** Number of buffers retained by the process. */
+ public final int retainedBuffersCount;
+ /** Size of buffers shared with Surface Flinger. */
+ public final int surfaceFlingerSizeKb;
+ /** Count of buffers shared with Surface Flinger. */
+ public final int surfaceFlingerCount;
+
+ ProcessDmabuf(int uid, String processName, int oomScore, int retainedSizeKb,
+ int retainedBuffersCount, int surfaceFlingerSizeKb,
+ int surfaceFlingerCount) {
+ this.uid = uid;
+ this.processName = processName;
+ this.oomScore = oomScore;
+ this.retainedSizeKb = retainedSizeKb;
+ this.retainedBuffersCount = retainedBuffersCount;
+ this.surfaceFlingerSizeKb = surfaceFlingerSizeKb;
+ this.surfaceFlingerCount = surfaceFlingerCount;
+ }
+ }
+
+ /**
+ * Return stats for DMA-BUFs retained by process pid or null if the DMA-BUF
+ * stats could not be read.
+ */
+ @Nullable
+ public static native ProcessDmabuf[] getDmabufAllocations();
+
+ /** Pid to gpu memory size. */
+ public static final class ProcessGpuMem {
+ public final int pid;
+ public final int gpuMemoryKb;
+
+ ProcessGpuMem(int pid, int gpuMemoryKb) {
+ this.pid = pid;
+ this.gpuMemoryKb = gpuMemoryKb;
+ }
+ }
+
+ /** Return list of pid to gpu memory size. */
+ public static native ProcessGpuMem[] getGpuAllocations();
+}
diff --git a/android-35/com/android/internal/os/KernelCpuBpfTracking.java b/android-35/com/android/internal/os/KernelCpuBpfTracking.java
new file mode 100644
index 0000000..387d327
--- /dev/null
+++ b/android-35/com/android/internal/os/KernelCpuBpfTracking.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 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.internal.os;
+
+import android.annotation.Nullable;
+
+/**
+ * CPU tracking using eBPF.
+ *
+ * The tracking state and data about available frequencies are cached to avoid JNI calls and
+ * creating temporary arrays. The data is stored in a format that is convenient for metrics
+ * computation.
+ *
+ * Synchronization is not needed because the underlying native library can be invoked concurrently
+ * and getters are idempotent.
+ */
+public final class KernelCpuBpfTracking {
+ private static boolean sTracking = false;
+
+ /** Cached mapping from frequency index to frequency in kHz. */
+ private static long[] sFreqs = null;
+
+ /** Cached mapping from frequency index to CPU cluster / policy. */
+ private static int[] sFreqsClusters = null;
+
+ private KernelCpuBpfTracking() {
+ }
+
+ /** Returns whether CPU tracking using eBPF is supported. */
+ public static native boolean isSupported();
+
+ /** Starts CPU tracking using eBPF. */
+ public static boolean startTracking() {
+ if (!sTracking) {
+ sTracking = startTrackingInternal();
+ }
+ return sTracking;
+ }
+
+ private static native boolean startTrackingInternal();
+
+ /** Returns frequencies in kHz on which CPU is tracked. Empty if not supported. */
+ public static long[] getFreqs() {
+ if (sFreqs == null) {
+ long[] freqs = getFreqsInternal();
+ if (freqs == null) {
+ return new long[0];
+ }
+ sFreqs = freqs;
+ }
+ return sFreqs;
+ }
+
+ @Nullable
+ static native long[] getFreqsInternal();
+
+ /**
+ * Returns the cluster (policy) number for each frequency on which CPU is tracked. Empty if
+ * not supported.
+ */
+ public static int[] getFreqsClusters() {
+ if (sFreqsClusters == null) {
+ int[] freqsClusters = getFreqsClustersInternal();
+ if (freqsClusters == null) {
+ return new int[0];
+ }
+ sFreqsClusters = freqsClusters;
+ }
+ return sFreqsClusters;
+ }
+
+ @Nullable
+ private static native int[] getFreqsClustersInternal();
+
+ /** Returns the number of clusters (policies). */
+ public static int getClusters() {
+ int[] freqClusters = getFreqsClusters();
+ return freqClusters.length > 0 ? freqClusters[freqClusters.length - 1] + 1 : 0;
+ }
+}
diff --git a/android-35/com/android/internal/os/KernelCpuProcStringReader.java b/android-35/com/android/internal/os/KernelCpuProcStringReader.java
new file mode 100644
index 0000000..b04fd47
--- /dev/null
+++ b/android-35/com/android/internal/os/KernelCpuProcStringReader.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import android.os.StrictMode;
+import android.util.Slog;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.CharBuffer;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Reads human-readable cpu time proc files.
+ *
+ * It is implemented as singletons for built-in kernel proc files. Get___Instance() method will
+ * return corresponding reader instance. In order to prevent frequent GC, it reuses the same char[]
+ * to store data read from proc files.
+ *
+ * A KernelCpuProcStringReader instance keeps an error counter. When the number of read errors
+ * within that instance accumulates to 5, this instance will reject all further read requests.
+ *
+ * Data fetched within last 500ms is considered fresh, since the reading lifecycle can take up to
+ * 100ms. KernelCpuProcStringReader always tries to use cache if it is fresh and valid, but it can
+ * be disabled through a parameter.
+ *
+ * A KernelCpuProcReader instance is thread-safe. It acquires a write lock when reading the proc
+ * file, releases it right after, then acquires a read lock before returning a ProcFileIterator.
+ * Caller is responsible for closing ProcFileIterator (also auto-closable) after reading, otherwise
+ * deadlock will occur.
+ */
+public class KernelCpuProcStringReader {
+ private static final String TAG = KernelCpuProcStringReader.class.getSimpleName();
+ private static final int ERROR_THRESHOLD = 5;
+ // Data read within the last 500ms is considered fresh.
+ private static final long FRESHNESS = 500L;
+ private static final int MAX_BUFFER_SIZE = 1024 * 1024;
+
+ private static final String PROC_UID_FREQ_TIME = "/proc/uid_time_in_state";
+ private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_concurrent_active_time";
+ private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_concurrent_policy_time";
+ private static final String PROC_UID_USER_SYS_TIME = "/proc/uid_cputime/show_uid_stat";
+
+ private static final KernelCpuProcStringReader FREQ_TIME_READER =
+ new KernelCpuProcStringReader(PROC_UID_FREQ_TIME);
+ private static final KernelCpuProcStringReader ACTIVE_TIME_READER =
+ new KernelCpuProcStringReader(PROC_UID_ACTIVE_TIME);
+ private static final KernelCpuProcStringReader CLUSTER_TIME_READER =
+ new KernelCpuProcStringReader(PROC_UID_CLUSTER_TIME);
+ private static final KernelCpuProcStringReader USER_SYS_TIME_READER =
+ new KernelCpuProcStringReader(PROC_UID_USER_SYS_TIME);
+
+ static KernelCpuProcStringReader getFreqTimeReaderInstance() {
+ return FREQ_TIME_READER;
+ }
+
+ static KernelCpuProcStringReader getActiveTimeReaderInstance() {
+ return ACTIVE_TIME_READER;
+ }
+
+ static KernelCpuProcStringReader getClusterTimeReaderInstance() {
+ return CLUSTER_TIME_READER;
+ }
+
+ static KernelCpuProcStringReader getUserSysTimeReaderInstance() {
+ return USER_SYS_TIME_READER;
+ }
+
+ private int mErrors = 0;
+ private final Path mFile;
+ private final Clock mClock;
+ private char[] mBuf;
+ private int mSize;
+ private long mLastReadTime = 0;
+ private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
+ private final ReentrantReadWriteLock.ReadLock mReadLock = mLock.readLock();
+ private final ReentrantReadWriteLock.WriteLock mWriteLock = mLock.writeLock();
+
+ public KernelCpuProcStringReader(String file) {
+ this(file, Clock.SYSTEM_CLOCK);
+ }
+
+ public KernelCpuProcStringReader(String file, Clock clock) {
+ mFile = Paths.get(file);
+ mClock = clock;
+ }
+
+ /**
+ * @see #open(boolean) Default behavior is trying to use cache.
+ */
+ public ProcFileIterator open() {
+ return open(false);
+ }
+
+ /**
+ * Opens the proc file and buffers all its content, which can be traversed through a
+ * ProcFileIterator.
+ *
+ * This method will tolerate at most 5 errors. After that, it will always return null. This is
+ * to save resources and to prevent log spam.
+ *
+ * This method is thread-safe. It first checks if there are other threads holding read/write
+ * lock. If there are, it assumes data is fresh and reuses the data.
+ *
+ * A read lock is automatically acquired when a valid ProcFileIterator is returned. Caller MUST
+ * call {@link ProcFileIterator#close()} when it is done to release the lock.
+ *
+ * @param ignoreCache If true, ignores the cache and refreshes the data anyway.
+ * @return A {@link ProcFileIterator} to iterate through the file content, or null if there is
+ * error.
+ */
+ public ProcFileIterator open(boolean ignoreCache) {
+ if (mErrors >= ERROR_THRESHOLD) {
+ return null;
+ }
+
+ if (ignoreCache) {
+ mWriteLock.lock();
+ } else {
+ mReadLock.lock();
+ if (dataValid()) {
+ return new ProcFileIterator(mSize);
+ }
+ mReadLock.unlock();
+ mWriteLock.lock();
+ if (dataValid()) {
+ // Recheck because another thread might have written data just before we did.
+ mReadLock.lock();
+ mWriteLock.unlock();
+ return new ProcFileIterator(mSize);
+ }
+ }
+
+ // At this point, write lock is held and data is invalid.
+ int total = 0;
+ int curr;
+ mSize = 0;
+ final int oldMask = StrictMode.allowThreadDiskReadsMask();
+ try (BufferedReader r = Files.newBufferedReader(mFile)) {
+ if (mBuf == null) {
+ mBuf = new char[1024];
+ }
+ while ((curr = r.read(mBuf, total, mBuf.length - total)) >= 0) {
+ total += curr;
+ if (total == mBuf.length) {
+ // Hit the limit. Resize buffer.
+ if (mBuf.length == MAX_BUFFER_SIZE) {
+ mErrors++;
+ Slog.e(TAG, "Proc file too large: " + mFile);
+ return null;
+ }
+ mBuf = Arrays.copyOf(mBuf, Math.min(mBuf.length << 1, MAX_BUFFER_SIZE));
+ }
+ }
+ mSize = total;
+ mLastReadTime = mClock.elapsedRealtime();
+ // ReentrantReadWriteLock allows lock downgrading.
+ mReadLock.lock();
+ return new ProcFileIterator(total);
+ } catch (FileNotFoundException | NoSuchFileException e) {
+ mErrors++;
+ Slog.w(TAG, "File not found. It's normal if not implemented: " + mFile);
+ } catch (IOException e) {
+ mErrors++;
+ Slog.e(TAG, "Error reading " + mFile, e);
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
+ mWriteLock.unlock();
+ }
+ return null;
+ }
+
+ private boolean dataValid() {
+ return mSize > 0 && (mClock.elapsedRealtime() - mLastReadTime < FRESHNESS);
+ }
+
+ /**
+ * An autoCloseable iterator to iterate through a string proc file line by line. User must call
+ * close() when finish using to prevent deadlock.
+ */
+ public class ProcFileIterator implements AutoCloseable {
+ private final int mSize;
+ private int mPos;
+
+ public ProcFileIterator(int size) {
+ mSize = size;
+ }
+
+ /** @return Whether there are more lines in the iterator. */
+ public boolean hasNextLine() {
+ return mPos < mSize;
+ }
+
+ /**
+ * Fetches the next line. Note that all subsequent return values share the same char[]
+ * under the hood.
+ *
+ * @return A {@link java.nio.CharBuffer} containing the next line without the new line
+ * symbol.
+ */
+ public CharBuffer nextLine() {
+ if (mPos >= mSize) {
+ return null;
+ }
+ int i = mPos;
+ // Move i to the next new line symbol, which is always '\n' in Android.
+ while (i < mSize && mBuf[i] != '\n') {
+ i++;
+ }
+ int start = mPos;
+ mPos = i + 1;
+ return CharBuffer.wrap(mBuf, start, i - start);
+ }
+
+ /** Total size of the proc file in chars. */
+ public int size() {
+ return mSize;
+ }
+
+ /** Must call close at the end to release the read lock! Or use try-with-resources. */
+ public void close() {
+ mReadLock.unlock();
+ }
+
+
+ }
+
+ /**
+ * Converts all numbers in the CharBuffer into longs, and puts into the given long[].
+ *
+ * Space and colon are treated as delimiters. All other chars are not allowed. All numbers
+ * are non-negative. To avoid GC, caller should try to use the same array for all calls.
+ *
+ * This method also resets the given buffer to the original position before return so that
+ * it can be read again.
+ *
+ * @param buf The char buffer to be converted.
+ * @param array An array to store the parsed numbers.
+ * @return The number of elements written to the given array. -1 if buf is null, -2 if buf
+ * contains invalid char, -3 if any number overflows.
+ */
+ public static int asLongs(CharBuffer buf, long[] array) {
+ if (buf == null) {
+ return -1;
+ }
+ final int initialPos = buf.position();
+ int count = 0;
+ long num = -1;
+ char c;
+
+ while (buf.remaining() > 0 && count < array.length) {
+ c = buf.get();
+ if (!(isNumber(c) || c == ' ' || c == ':')) {
+ buf.position(initialPos);
+ return -2;
+ }
+ if (num < 0) {
+ if (isNumber(c)) {
+ num = c - '0';
+ }
+ } else {
+ if (isNumber(c)) {
+ num = num * 10 + c - '0';
+ if (num < 0) {
+ buf.position(initialPos);
+ return -3;
+ }
+ } else {
+ array[count++] = num;
+ num = -1;
+ }
+ }
+ }
+ if (num >= 0) {
+ array[count++] = num;
+ }
+ buf.position(initialPos);
+ return count;
+ }
+
+ private static boolean isNumber(char c) {
+ return c >= '0' && c <= '9';
+ }
+}
diff --git a/android-35/com/android/internal/os/KernelCpuSpeedReader.java b/android-35/com/android/internal/os/KernelCpuSpeedReader.java
new file mode 100644
index 0000000..98fea01
--- /dev/null
+++ b/android-35/com/android/internal/os/KernelCpuSpeedReader.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2015 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.internal.os;
+
+import android.system.Os;
+import android.text.TextUtils;
+import android.os.StrictMode;
+import android.system.OsConstants;
+import android.util.Slog;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Reads CPU time of a specific core spent at various frequencies and provides a delta from the
+ * last call to {@link #readDelta}. Each line in the proc file has the format:
+ *
+ * freq time
+ *
+ * where time is measured in jiffies.
+ */
+public class KernelCpuSpeedReader {
+ private static final String TAG = "KernelCpuSpeedReader";
+
+ private final String mProcFile;
+ private final int mNumSpeedSteps;
+ private final long[] mLastSpeedTimesMs;
+ private final long[] mDeltaSpeedTimesMs;
+
+ // How long a CPU jiffy is in milliseconds.
+ private final long mJiffyMillis;
+
+ /**
+ * @param cpuNumber The cpu (cpu0, cpu1, etc) whose state to read.
+ */
+ public KernelCpuSpeedReader(int cpuNumber, int numSpeedSteps) {
+ mProcFile = String.format("/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state",
+ cpuNumber);
+ mNumSpeedSteps = numSpeedSteps;
+ mLastSpeedTimesMs = new long[numSpeedSteps];
+ mDeltaSpeedTimesMs = new long[numSpeedSteps];
+ long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
+ mJiffyMillis = 1000/jiffyHz;
+ }
+
+ /**
+ * The returned array is modified in subsequent calls to {@link #readDelta}.
+ * @return The time (in milliseconds) spent at different cpu speeds since the last call to
+ * {@link #readDelta}.
+ */
+ public long[] readDelta() {
+ StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
+ try (BufferedReader reader = new BufferedReader(new FileReader(mProcFile))) {
+ TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
+ String line;
+ int speedIndex = 0;
+ while (speedIndex < mLastSpeedTimesMs.length && (line = reader.readLine()) != null) {
+ splitter.setString(line);
+ splitter.next();
+
+ long time = Long.parseLong(splitter.next()) * mJiffyMillis;
+ if (time < mLastSpeedTimesMs[speedIndex]) {
+ // The stats reset when the cpu hotplugged. That means that the time
+ // we read is offset from 0, so the time is the delta.
+ mDeltaSpeedTimesMs[speedIndex] = time;
+ } else {
+ mDeltaSpeedTimesMs[speedIndex] = time - mLastSpeedTimesMs[speedIndex];
+ }
+ mLastSpeedTimesMs[speedIndex] = time;
+ speedIndex++;
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read cpu-freq: " + e.getMessage());
+ Arrays.fill(mDeltaSpeedTimesMs, 0);
+ } finally {
+ StrictMode.setThreadPolicy(policy);
+ }
+ return mDeltaSpeedTimesMs;
+ }
+
+ /**
+ * @return The time (in milliseconds) spent at different cpu speeds. The values should be
+ * monotonically increasing, unless the cpu was hotplugged.
+ */
+ public long[] readAbsolute() {
+ StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
+ long[] speedTimeMs = new long[mNumSpeedSteps];
+ try (BufferedReader reader = new BufferedReader(new FileReader(mProcFile))) {
+ TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
+ String line;
+ int speedIndex = 0;
+ while (speedIndex < mNumSpeedSteps && (line = reader.readLine()) != null) {
+ splitter.setString(line);
+ splitter.next();
+ long time = Long.parseLong(splitter.next()) * mJiffyMillis;
+ speedTimeMs[speedIndex] = time;
+ speedIndex++;
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read cpu-freq: " + e.getMessage());
+ Arrays.fill(speedTimeMs, 0);
+ } finally {
+ StrictMode.setThreadPolicy(policy);
+ }
+ return speedTimeMs;
+ }
+}
diff --git a/android-35/com/android/internal/os/KernelCpuThreadReader.java b/android-35/com/android/internal/os/KernelCpuThreadReader.java
new file mode 100644
index 0000000..5b6d1b6
--- /dev/null
+++ b/android-35/com/android/internal/os/KernelCpuThreadReader.java
@@ -0,0 +1,571 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import android.annotation.Nullable;
+import android.os.Process;
+import android.util.IntArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+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.ArrayList;
+import java.util.Arrays;
+import java.util.function.Predicate;
+
+/**
+ * Iterates over processes, and all threads owned by those processes, and return the CPU usage for
+ * each thread. The CPU usage statistics contain the amount of time spent in a frequency band. CPU
+ * usage is collected using {@link ProcTimeInStateReader}.
+ *
+ * <p>We only collect CPU data for processes and threads that are owned by certain UIDs. These UIDs
+ * are configured via {@link #setUidPredicate}.
+ *
+ * <p>Frequencies are bucketed together to reduce the amount of data created. This means that we
+ * return less frequencies than provided by {@link ProcTimeInStateReader}. The number of frequencies
+ * is configurable by {@link #setNumBuckets}. Frequencies are reported as the lowest frequency in
+ * that range. Frequencies are spread as evenly as possible across the buckets. The buckets do not
+ * cross over the little/big frequencies reported.
+ *
+ * <p>N.B.: In order to bucket across little/big frequencies correctly, we assume that the {@code
+ * time_in_state} file contains every little core frequency in ascending order, followed by every
+ * big core frequency in ascending order. This assumption might not hold for devices with different
+ * kernel implementations of the {@code time_in_state} file generation.
+ */
+public class KernelCpuThreadReader {
+
+ private static final String TAG = "KernelCpuThreadReader";
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * 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";
+
+ /**
+ * The name of the file to read process command line invocation from, must be found in {@code
+ * /proc/$PID/}
+ */
+ private static final String PROCESS_NAME_FILENAME = "cmdline";
+
+ /**
+ * The name of the file to read thread name from, must be found in {@code /proc/$PID/task/$TID}
+ */
+ private static final String THREAD_NAME_FILENAME = "comm";
+
+ /** Glob pattern for the process directory names under {@code proc} */
+ private static final String PROCESS_DIRECTORY_FILTER = "[0-9]*";
+
+ /** Default process name when the name can't be read */
+ private static final String DEFAULT_PROCESS_NAME = "unknown_process";
+
+ /** Default thread name when the name can't be read */
+ private static final String DEFAULT_THREAD_NAME = "unknown_thread";
+
+ /** 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 DEFAULT_INITIAL_TIME_IN_STATE_PATH =
+ DEFAULT_PROC_PATH.resolve("self/time_in_state");
+
+ /** Value returned when there was an error getting an integer ID value (e.g. PID, UID) */
+ private static final int ID_ERROR = -1;
+
+ /**
+ * When checking whether to report data for a thread, we check the UID of the thread's owner
+ * against this predicate
+ */
+ private Predicate<Integer> mUidPredicate;
+
+ /** Where the proc filesystem is mounted */
+ private final Path mProcPath;
+
+ /**
+ * Frequencies read from the {@code time_in_state} file. Read from {@link
+ * #mProcTimeInStateReader#getCpuFrequenciesKhz()} and cast to {@code int[]}
+ */
+ private int[] mFrequenciesKhz;
+
+ /** Used to read and parse {@code time_in_state} files */
+ private final ProcTimeInStateReader mProcTimeInStateReader;
+
+ /** Used to sort frequencies and usage times into buckets */
+ private FrequencyBucketCreator mFrequencyBucketCreator;
+
+ private final Injector mInjector;
+
+ /**
+ * Create with a path where `proc` is mounted. Used primarily for testing
+ *
+ * @param procPath where `proc` is mounted (to find, see {@code mount | grep ^proc})
+ * @param initialTimeInStatePath where the initial {@code time_in_state} file exists to define
+ * format
+ */
+ @VisibleForTesting
+ public KernelCpuThreadReader(
+ int numBuckets,
+ Predicate<Integer> uidPredicate,
+ Path procPath,
+ Path initialTimeInStatePath,
+ Injector injector)
+ throws IOException {
+ mUidPredicate = uidPredicate;
+ mProcPath = procPath;
+ mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath);
+ mInjector = injector;
+ setNumBuckets(numBuckets);
+ }
+
+ /**
+ * Create the reader and handle exceptions during creation
+ *
+ * @return the reader, null if an exception was thrown during creation
+ */
+ @Nullable
+ public static KernelCpuThreadReader create(int numBuckets, Predicate<Integer> uidPredicate) {
+ try {
+ return new KernelCpuThreadReader(
+ numBuckets,
+ uidPredicate,
+ DEFAULT_PROC_PATH,
+ DEFAULT_INITIAL_TIME_IN_STATE_PATH,
+ new Injector());
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to initialize KernelCpuThreadReader", e);
+ return null;
+ }
+ }
+
+ /**
+ * Get the per-thread CPU usage of all processes belonging to a set of UIDs
+ *
+ * <p>This function will crawl through all process {@code proc} directories found by the pattern
+ * {@code /proc/[0-9]*}, and then check the UID using {@code /proc/$PID/status}. This takes
+ * approximately 500ms on a 2017 device. Therefore, this method can be computationally
+ * expensive, and should not be called more than once an hour.
+ *
+ * <p>Data is only collected for UIDs passing the predicate supplied in {@link
+ * #setUidPredicate}.
+ */
+ @Nullable
+ public ArrayList<ProcessCpuUsage> getProcessCpuUsage() {
+ if (DEBUG) {
+ Slog.d(TAG, "Reading CPU thread usages for processes owned by UIDs");
+ }
+
+ final ArrayList<ProcessCpuUsage> processCpuUsages = new ArrayList<>();
+
+ try (DirectoryStream<Path> processPaths =
+ Files.newDirectoryStream(mProcPath, PROCESS_DIRECTORY_FILTER)) {
+ for (Path processPath : processPaths) {
+ final int processId = getProcessId(processPath);
+ final int uid = mInjector.getUidForPid(processId);
+ if (uid == ID_ERROR || processId == ID_ERROR) {
+ continue;
+ }
+ if (!mUidPredicate.test(uid)) {
+ continue;
+ }
+
+ final ProcessCpuUsage processCpuUsage =
+ getProcessCpuUsage(processPath, processId, uid);
+ if (processCpuUsage != null) {
+ processCpuUsages.add(processCpuUsage);
+ }
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to iterate over process paths", e);
+ return null;
+ }
+
+ if (processCpuUsages.isEmpty()) {
+ Slog.w(TAG, "Didn't successfully get any process CPU information for UIDs specified");
+ return null;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Read usage for " + processCpuUsages.size() + " processes");
+ }
+
+ return processCpuUsages;
+ }
+
+ /**
+ * Get the CPU frequencies that correspond to the times reported in {@link
+ * ThreadCpuUsage#usageTimesMillis}
+ */
+ @Nullable
+ public int[] getCpuFrequenciesKhz() {
+ return mFrequenciesKhz;
+ }
+
+ /** Set the number of frequency buckets to use */
+ void setNumBuckets(int numBuckets) {
+ // If `numBuckets` hasn't changed since the last set, do nothing
+ if (mFrequenciesKhz != null && mFrequenciesKhz.length == numBuckets) {
+ return;
+ }
+
+ final long[] frequenciesKhz = mProcTimeInStateReader.getFrequenciesKhz();
+ if (numBuckets != 0) {
+ mFrequencyBucketCreator = new FrequencyBucketCreator(frequenciesKhz, numBuckets);
+ mFrequenciesKhz = mFrequencyBucketCreator.bucketFrequencies(frequenciesKhz);
+ } else {
+ mFrequencyBucketCreator = null;
+ mFrequenciesKhz = new int[frequenciesKhz.length];
+ for (int i = 0; i < frequenciesKhz.length; i++) {
+ mFrequenciesKhz[i] = (int) frequenciesKhz[i];
+ }
+ }
+ }
+
+ /** Set the UID predicate for {@link #getProcessCpuUsage} */
+ @VisibleForTesting
+ public void setUidPredicate(Predicate<Integer> uidPredicate) {
+ mUidPredicate = uidPredicate;
+ }
+
+ /**
+ * Read all of the CPU usage statistics for each child thread of a process
+ *
+ * @param processPath the {@code /proc} path of the thread
+ * @param processId the ID of the process
+ * @param uid the ID of the user who owns the process
+ * @return process CPU usage containing usage of all child threads. Null if the process exited
+ * and its {@code proc} directory was removed while collecting information
+ */
+ @Nullable
+ private ProcessCpuUsage getProcessCpuUsage(Path processPath, int processId, int uid) {
+ if (DEBUG) {
+ Slog.d(
+ TAG,
+ "Reading CPU thread usages with directory "
+ + processPath
+ + " process ID "
+ + processId
+ + " and user ID "
+ + uid);
+ }
+
+ final Path allThreadsPath = processPath.resolve("task");
+ final ArrayList<ThreadCpuUsage> threadCpuUsages = new ArrayList<>();
+ try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(allThreadsPath)) {
+ for (Path threadDirectory : threadPaths) {
+ ThreadCpuUsage threadCpuUsage = getThreadCpuUsage(threadDirectory);
+ if (threadCpuUsage == null) {
+ continue;
+ }
+ threadCpuUsages.add(threadCpuUsage);
+ }
+ } catch (IOException | DirectoryIteratorException e) {
+ // Expected when a process finishes
+ return null;
+ }
+
+ // If we found no threads, then the process has exited while we were reading from it
+ if (threadCpuUsages.isEmpty()) {
+ return null;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Read CPU usage of " + threadCpuUsages.size() + " threads");
+ }
+ return new ProcessCpuUsage(processId, getProcessName(processPath), uid, threadCpuUsages);
+ }
+
+ /**
+ * Get a thread's CPU usage
+ *
+ * @param threadDirectory the {@code /proc} directory of the thread
+ * @return thread CPU usage. Null if the thread exited and its {@code proc} directory was
+ * removed while collecting information
+ */
+ @Nullable
+ private ThreadCpuUsage getThreadCpuUsage(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 null;
+ }
+
+ // Get the thread name from the thread directory
+ final String threadName = getThreadName(threadDirectory);
+
+ // Get the CPU statistics from the directory
+ final Path threadCpuStatPath = threadDirectory.resolve(CPU_STATISTICS_FILENAME);
+ final long[] cpuUsagesLong = mProcTimeInStateReader.getUsageTimesMillis(threadCpuStatPath);
+ if (cpuUsagesLong == null) {
+ return null;
+ }
+ final int[] cpuUsages;
+ if (mFrequencyBucketCreator != null) {
+ cpuUsages = mFrequencyBucketCreator.bucketValues(cpuUsagesLong);
+ } else {
+ cpuUsages = new int[cpuUsagesLong.length];
+ for (int i = 0; i < cpuUsagesLong.length; i++) {
+ cpuUsages[i] = (int) cpuUsagesLong[i];
+ }
+ }
+ return new ThreadCpuUsage(threadId, threadName, cpuUsages);
+ }
+
+ /** Get the command used to start a process */
+ private String getProcessName(Path processPath) {
+ final Path processNamePath = processPath.resolve(PROCESS_NAME_FILENAME);
+
+ final String processName = ProcStatsUtil.readSingleLineProcFile(processNamePath.toString());
+ if (processName != null) {
+ return processName;
+ }
+ return DEFAULT_PROCESS_NAME;
+ }
+
+ /** Get the name of a thread, given the {@code /proc} path of the thread */
+ private String getThreadName(Path threadPath) {
+ final Path threadNamePath = threadPath.resolve(THREAD_NAME_FILENAME);
+ final String threadName = ProcStatsUtil.readNullSeparatedFile(threadNamePath.toString());
+ if (threadName == null) {
+ return DEFAULT_THREAD_NAME;
+ }
+ return threadName;
+ }
+
+ /**
+ * Get the ID of a process from its path
+ *
+ * @param processPath {@code proc} path of the process
+ * @return the ID, {@link #ID_ERROR} if the path could not be parsed
+ */
+ private int getProcessId(Path processPath) {
+ String fileName = processPath.getFileName().toString();
+ try {
+ return Integer.parseInt(fileName);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Failed to parse " + fileName + " as process ID", e);
+ return ID_ERROR;
+ }
+ }
+
+ /**
+ * Quantizes a list of N frequencies into a list of M frequencies (where M<=N)
+ *
+ * <p>In order to reduce data sent from the device, we discard precise frequency information for
+ * an approximation. This is done by putting groups of adjacent frequencies into the same
+ * bucket, and then reporting that bucket under the minimum frequency in that bucket.
+ *
+ * <p>Many devices have multiple core clusters. We do not want to report frequencies from
+ * different clusters under the same bucket, so some complication arises.
+ *
+ * <p>Buckets are allocated evenly across all core clusters, i.e. they all have the same number
+ * of buckets regardless of how many frequencies they contain. This is done to reduce code
+ * complexity, and in practice the number of frequencies doesn't vary too much between core
+ * clusters.
+ *
+ * <p>If the number of buckets is not a factor of the number of frequencies, the remainder of
+ * the frequencies are placed into the last bucket.
+ *
+ * <p>It is possible to have less buckets than asked for, so any calling code can't assume that
+ * initializing with N buckets will use return N values. This happens in two scenarios:
+ *
+ * <ul>
+ * <li>There are less frequencies available than buckets asked for.
+ * <li>There are less frequencies in a core cluster than buckets allocated to that core
+ * cluster.
+ * </ul>
+ */
+ @VisibleForTesting
+ public static class FrequencyBucketCreator {
+ private final int mNumFrequencies;
+ private final int mNumBuckets;
+ private final int[] mBucketStartIndices;
+
+ @VisibleForTesting
+ public FrequencyBucketCreator(long[] frequencies, int targetNumBuckets) {
+ mNumFrequencies = frequencies.length;
+ int[] clusterStartIndices = getClusterStartIndices(frequencies);
+ mBucketStartIndices =
+ getBucketStartIndices(clusterStartIndices, targetNumBuckets, mNumFrequencies);
+ mNumBuckets = mBucketStartIndices.length;
+ }
+
+ /**
+ * Put an array of values into buckets. This takes a {@code long[]} and returns {@code
+ * int[]} as everywhere this method is used will have to do the conversion anyway, so we
+ * save time by doing it here instead
+ *
+ * @param values the values to bucket
+ * @return the bucketed usage times
+ */
+ @VisibleForTesting
+ public int[] bucketValues(long[] values) {
+ Preconditions.checkArgument(values.length == mNumFrequencies);
+ int[] buckets = new int[mNumBuckets];
+ for (int bucketIdx = 0; bucketIdx < mNumBuckets; bucketIdx++) {
+ final int bucketStartIdx = getLowerBound(bucketIdx, mBucketStartIndices);
+ final int bucketEndIdx =
+ getUpperBound(bucketIdx, mBucketStartIndices, values.length);
+ for (int valuesIdx = bucketStartIdx; valuesIdx < bucketEndIdx; valuesIdx++) {
+ buckets[bucketIdx] += values[valuesIdx];
+ }
+ }
+ return buckets;
+ }
+
+ /** Get the minimum frequency in each bucket */
+ @VisibleForTesting
+ public int[] bucketFrequencies(long[] frequencies) {
+ Preconditions.checkArgument(frequencies.length == mNumFrequencies);
+ int[] buckets = new int[mNumBuckets];
+ for (int i = 0; i < buckets.length; i++) {
+ buckets[i] = (int) frequencies[mBucketStartIndices[i]];
+ }
+ return buckets;
+ }
+
+ /**
+ * Get the index in frequencies where each core cluster starts
+ *
+ * <p>The frequencies for each cluster are given in ascending order, appended to each other.
+ * This means that every time there is a decrease in frequencies (instead of increase) a new
+ * cluster has started.
+ */
+ private static int[] getClusterStartIndices(long[] frequencies) {
+ IntArray indices = new IntArray();
+ indices.add(0);
+ for (int i = 0; i < frequencies.length - 1; i++) {
+ if (frequencies[i] >= frequencies[i + 1]) {
+ indices.add(i + 1);
+ }
+ }
+ return indices.toArray();
+ }
+
+ /** Get the index in frequencies where each bucket starts */
+ private static int[] getBucketStartIndices(
+ int[] clusterStartIndices, int targetNumBuckets, int numFrequencies) {
+ int numClusters = clusterStartIndices.length;
+
+ // If we haven't got enough buckets for every cluster, we instead have one bucket per
+ // cluster, with the last bucket containing the remaining clusters
+ if (numClusters > targetNumBuckets) {
+ return Arrays.copyOfRange(clusterStartIndices, 0, targetNumBuckets);
+ }
+
+ IntArray bucketStartIndices = new IntArray();
+ for (int clusterIdx = 0; clusterIdx < numClusters; clusterIdx++) {
+ final int clusterStartIdx = getLowerBound(clusterIdx, clusterStartIndices);
+ final int clusterEndIdx =
+ getUpperBound(clusterIdx, clusterStartIndices, numFrequencies);
+
+ final int numBucketsInCluster;
+ if (clusterIdx != numClusters - 1) {
+ numBucketsInCluster = targetNumBuckets / numClusters;
+ } else {
+ // If we're in the last cluster, the bucket will contain the remainder of the
+ // frequencies
+ int previousBucketsInCluster = targetNumBuckets / numClusters;
+ numBucketsInCluster =
+ targetNumBuckets - (previousBucketsInCluster * (numClusters - 1));
+ }
+
+ final int numFrequenciesInCluster = clusterEndIdx - clusterStartIdx;
+ // If there are less frequencies than buckets in a cluster, we have one bucket per
+ // frequency, and do not use the remaining buckets
+ final int numFrequenciesInBucket =
+ Math.max(1, numFrequenciesInCluster / numBucketsInCluster);
+ for (int bucketIdx = 0; bucketIdx < numBucketsInCluster; bucketIdx++) {
+ int bucketStartIdx = clusterStartIdx + bucketIdx * numFrequenciesInBucket;
+ // If we've gone over the end index, ignore the rest of the buckets for this
+ // cluster
+ if (bucketStartIdx >= clusterEndIdx) {
+ break;
+ }
+ bucketStartIndices.add(bucketStartIdx);
+ }
+ }
+ return bucketStartIndices.toArray();
+ }
+
+ private static int getLowerBound(int index, int[] startIndices) {
+ return startIndices[index];
+ }
+
+ private static int getUpperBound(int index, int[] startIndices, int max) {
+ if (index != startIndices.length - 1) {
+ return startIndices[index + 1];
+ } else {
+ return max;
+ }
+ }
+ }
+
+ /** CPU usage of a process */
+ public static class ProcessCpuUsage {
+ public final int processId;
+ public final String processName;
+ public final int uid;
+ public ArrayList<ThreadCpuUsage> threadCpuUsages;
+
+ @VisibleForTesting
+ public ProcessCpuUsage(
+ int processId,
+ String processName,
+ int uid,
+ ArrayList<ThreadCpuUsage> threadCpuUsages) {
+ this.processId = processId;
+ this.processName = processName;
+ this.uid = uid;
+ this.threadCpuUsages = threadCpuUsages;
+ }
+ }
+
+ /** CPU usage of a thread */
+ public static class ThreadCpuUsage {
+ public final int threadId;
+ public final String threadName;
+ public int[] usageTimesMillis;
+
+ @VisibleForTesting
+ public ThreadCpuUsage(int threadId, String threadName, int[] usageTimesMillis) {
+ this.threadId = threadId;
+ this.threadName = threadName;
+ this.usageTimesMillis = usageTimesMillis;
+ }
+ }
+
+ /** Used to inject static methods from {@link Process} */
+ @VisibleForTesting
+ public static class Injector {
+ /** Get the UID for the process with ID {@code pid} */
+ public int getUidForPid(int pid) {
+ return Process.getUidForPid(pid);
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/KernelCpuThreadReaderDiff.java b/android-35/com/android/internal/os/KernelCpuThreadReaderDiff.java
new file mode 100644
index 0000000..69ca992
--- /dev/null
+++ b/android-35/com/android/internal/os/KernelCpuThreadReaderDiff.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Delegates per-thread CPU collection to {@link KernelCpuThreadReader}, and calculates the
+ * difference between CPU usage at each call of {@link #getProcessCpuUsageDiffed()}.
+ *
+ * <p>Some notes on the diff calculation:
+ *
+ * <ul>
+ * <li>The diffing is done between each call of {@link #getProcessCpuUsageDiffed()}, i.e. call N
+ * of this method will return CPU used by threads between call N-1 and N.
+ * <li>The first call of {@link #getProcessCpuUsageDiffed()} will return no processes ("first
+ * call" is the first call in the lifetime of a {@link KernelCpuThreadReaderDiff} object).
+ * <li>If a thread does not exist at call N, but does exist at call N+1, the diff will assume that
+ * the CPU usage at call N was zero. Thus, the diff reported will be equivalent to the value
+ * returned by {@link KernelCpuThreadReader#getProcessCpuUsage()} at call N+1.
+ * <li>If an error occurs in {@link KernelCpuThreadReader} at call N, we will return no
+ * information for CPU usage between call N-1 and N (as we don't know the start value) and
+ * between N and N+1 (as we don't know the end value). Assuming all other calls are
+ * successful, the next call to return data will be N+2, for the period between N+1 and N+2.
+ * <li>If an error occurs in this class (but not in {@link KernelCpuThreadReader}) at call N, the
+ * data will only be dropped for call N, as we can still use the CPU data for the surrounding
+ * calls.
+ * </ul>
+ *
+ * <p>Additionally to diffing, this class also contains logic for thresholding reported threads. A
+ * thread will not be reported unless its total CPU usage is at least equal to the value set in
+ * {@link #setMinimumTotalCpuUsageMillis}. Filtered thread CPU usage is summed and reported under
+ * one "other threads" thread. This reduces the cardinality of the {@link
+ * #getProcessCpuUsageDiffed()} result.
+ *
+ * <p>Thresholding is done in this class, instead of {@link KernelCpuThreadReader}, and instead of
+ * statsd, because the thresholding should be done after diffing, not before. This is because of
+ * two issues with thresholding before diffing:
+ *
+ * <ul>
+ * <li>We would threshold less and less threads as thread uptime increases.
+ * <li>We would encounter errors as the filtered threads become unfiltered, as the "other threads"
+ * result could have negative diffs, and the newly unfiltered threads would have incorrect
+ * diffs that include CPU usage from when they were filtered.
+ * </ul>
+ *
+ * @hide Only for use within the system server
+ */
+@SuppressWarnings("ForLoopReplaceableByForEach")
+public class KernelCpuThreadReaderDiff {
+ private static final String TAG = "KernelCpuThreadReaderDiff";
+
+ /** Thread ID used when reporting CPU used by other threads */
+ private static final int OTHER_THREADS_ID = -1;
+
+ /** Thread name used when reporting CPU used by other threads */
+ private static final String OTHER_THREADS_NAME = "__OTHER_THREADS";
+
+ private final KernelCpuThreadReader mReader;
+
+ /**
+ * CPU usage from the previous call of {@link #getProcessCpuUsageDiffed()}. Null if there was no
+ * previous call, or if the previous call failed
+ *
+ * <p>Maps the thread's identifier to the per-frequency CPU usage for that thread. The
+ * identifier contains the minimal amount of information to identify a thread (see {@link
+ * ThreadKey} for more information), thus reducing memory consumption.
+ */
+ @Nullable private Map<ThreadKey, int[]> mPreviousCpuUsage;
+
+ /**
+ * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
+ * will not be reported
+ */
+ private int mMinimumTotalCpuUsageMillis;
+
+ @VisibleForTesting
+ public KernelCpuThreadReaderDiff(KernelCpuThreadReader reader, int minimumTotalCpuUsageMillis) {
+ mReader = checkNotNull(reader);
+ mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
+ mPreviousCpuUsage = null;
+ }
+
+ /**
+ * Returns the difference in CPU usage since the last time this method was called.
+ *
+ * @see KernelCpuThreadReader#getProcessCpuUsage()
+ */
+ @Nullable
+ public ArrayList<KernelCpuThreadReader.ProcessCpuUsage> getProcessCpuUsageDiffed() {
+ Map<ThreadKey, int[]> newCpuUsage = null;
+ try {
+ // Get the thread CPU usage and index them by ThreadKey
+ final ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages =
+ mReader.getProcessCpuUsage();
+ newCpuUsage = createCpuUsageMap(processCpuUsages);
+ // If there is no previous CPU usage, return nothing
+ if (mPreviousCpuUsage == null) {
+ return null;
+ }
+
+ // Do diffing and thresholding for each process
+ for (int i = 0; i < processCpuUsages.size(); i++) {
+ KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i);
+ changeToDiffs(mPreviousCpuUsage, processCpuUsage);
+ applyThresholding(processCpuUsage);
+ }
+ return processCpuUsages;
+ } finally {
+ // Always update the previous CPU usage. If we haven't got an update, it will be set to
+ // null, so the next call knows there no previous values
+ mPreviousCpuUsage = newCpuUsage;
+ }
+ }
+
+ /** @see KernelCpuThreadReader#getCpuFrequenciesKhz() */
+ @Nullable
+ public int[] getCpuFrequenciesKhz() {
+ return mReader.getCpuFrequenciesKhz();
+ }
+
+ /**
+ * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it
+ * will not be reported
+ */
+ void setMinimumTotalCpuUsageMillis(int minimumTotalCpuUsageMillis) {
+ if (minimumTotalCpuUsageMillis < 0) {
+ Slog.w(TAG, "Negative minimumTotalCpuUsageMillis: " + minimumTotalCpuUsageMillis);
+ return;
+ }
+ mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis;
+ }
+
+ /**
+ * Create a map of a thread's identifier to a thread's CPU usage. Used for fast indexing when
+ * calculating diffs
+ */
+ private static Map<ThreadKey, int[]> createCpuUsageMap(
+ List<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages) {
+ final Map<ThreadKey, int[]> cpuUsageMap = new ArrayMap<>();
+ for (int i = 0; i < processCpuUsages.size(); i++) {
+ KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i);
+ for (int j = 0; j < processCpuUsage.threadCpuUsages.size(); j++) {
+ KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
+ processCpuUsage.threadCpuUsages.get(j);
+ cpuUsageMap.put(
+ new ThreadKey(
+ processCpuUsage.processId,
+ threadCpuUsage.threadId,
+ processCpuUsage.processName,
+ threadCpuUsage.threadName),
+ threadCpuUsage.usageTimesMillis);
+ }
+ }
+ return cpuUsageMap;
+ }
+
+ /**
+ * Calculate the difference in per-frequency CPU usage for all threads in a process
+ *
+ * @param previousCpuUsage CPU usage from the last call, the base of the diff
+ * @param processCpuUsage CPU usage from the current call, this value is modified to contain the
+ * diffed values
+ */
+ private static void changeToDiffs(
+ Map<ThreadKey, int[]> previousCpuUsage,
+ KernelCpuThreadReader.ProcessCpuUsage processCpuUsage) {
+ for (int i = 0; i < processCpuUsage.threadCpuUsages.size(); i++) {
+ KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
+ processCpuUsage.threadCpuUsages.get(i);
+ final ThreadKey key =
+ new ThreadKey(
+ processCpuUsage.processId,
+ threadCpuUsage.threadId,
+ processCpuUsage.processName,
+ threadCpuUsage.threadName);
+ int[] previous = previousCpuUsage.get(key);
+ if (previous == null) {
+ // If there's no previous CPU usage, assume that it's zero
+ previous = new int[threadCpuUsage.usageTimesMillis.length];
+ }
+ threadCpuUsage.usageTimesMillis =
+ cpuTimeDiff(threadCpuUsage.usageTimesMillis, previous);
+ }
+ }
+
+ /**
+ * Filter out any threads with less than {@link #mMinimumTotalCpuUsageMillis} total CPU usage
+ *
+ * <p>The sum of the CPU usage of filtered threads is added under a single thread, labeled with
+ * {@link #OTHER_THREADS_ID} and {@link #OTHER_THREADS_NAME}.
+ *
+ * @param processCpuUsage CPU usage to apply thresholding to, this value is modified to change
+ * the threads it contains
+ */
+ private void applyThresholding(KernelCpuThreadReader.ProcessCpuUsage processCpuUsage) {
+ int[] filteredThreadsCpuUsage = null;
+ final ArrayList<KernelCpuThreadReader.ThreadCpuUsage> thresholded = new ArrayList<>();
+ for (int i = 0; i < processCpuUsage.threadCpuUsages.size(); i++) {
+ KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage =
+ processCpuUsage.threadCpuUsages.get(i);
+ if (mMinimumTotalCpuUsageMillis > totalCpuUsage(threadCpuUsage.usageTimesMillis)) {
+ if (filteredThreadsCpuUsage == null) {
+ filteredThreadsCpuUsage = new int[threadCpuUsage.usageTimesMillis.length];
+ }
+ addToCpuUsage(filteredThreadsCpuUsage, threadCpuUsage.usageTimesMillis);
+ continue;
+ }
+ thresholded.add(threadCpuUsage);
+ }
+ if (filteredThreadsCpuUsage != null) {
+ thresholded.add(
+ new KernelCpuThreadReader.ThreadCpuUsage(
+ OTHER_THREADS_ID, OTHER_THREADS_NAME, filteredThreadsCpuUsage));
+ }
+ processCpuUsage.threadCpuUsages = thresholded;
+ }
+
+ /** Get the sum of all CPU usage across all frequencies */
+ private static int totalCpuUsage(int[] cpuUsage) {
+ int total = 0;
+ for (int i = 0; i < cpuUsage.length; i++) {
+ total += cpuUsage[i];
+ }
+ return total;
+ }
+
+ /** Add two CPU frequency usages together */
+ private static void addToCpuUsage(int[] a, int[] b) {
+ for (int i = 0; i < a.length; i++) {
+ a[i] += b[i];
+ }
+ }
+
+ /** Subtract two CPU frequency usages from each other */
+ private static int[] cpuTimeDiff(int[] a, int[] b) {
+ int[] difference = new int[a.length];
+ for (int i = 0; i < a.length; i++) {
+ difference[i] = a[i] - b[i];
+ }
+ return difference;
+ }
+
+ /**
+ * Identifies a thread
+ *
+ * <p>Only stores the minimum amount of information to identify a thread. This includes the
+ * PID/TID, but as both are recycled as processes/threads end and begin, we also store the hash
+ * of the name of the process/thread.
+ */
+ private static class ThreadKey {
+ private final int mProcessId;
+ private final int mThreadId;
+ private final int mProcessNameHash;
+ private final int mThreadNameHash;
+
+ ThreadKey(int processId, int threadId, String processName, String threadName) {
+ this.mProcessId = processId;
+ this.mThreadId = threadId;
+ // Only store the hash to reduce memory consumption
+ this.mProcessNameHash = Objects.hash(processName);
+ this.mThreadNameHash = Objects.hash(threadName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mProcessId, mThreadId, mProcessNameHash, mThreadNameHash);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ThreadKey)) {
+ return false;
+ }
+ ThreadKey other = (ThreadKey) obj;
+ return mProcessId == other.mProcessId
+ && mThreadId == other.mThreadId
+ && mProcessNameHash == other.mProcessNameHash
+ && mThreadNameHash == other.mThreadNameHash;
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java b/android-35/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java
new file mode 100644
index 0000000..c908b8c
--- /dev/null
+++ b/android-35/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.KeyValueListParser;
+import android.util.Range;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Service that handles settings for {@link KernelCpuThreadReader}
+ *
+ * <p>N.B.: The `collected_uids` setting takes a string representation of what UIDs to collect data
+ * for. A string representation is used as we will want to express UID ranges, therefore an integer
+ * array could not be used. The format of the string representation is detailed here: {@link
+ * UidPredicate#fromString}.
+ *
+ * @hide Only for use within the system server
+ */
+public class KernelCpuThreadReaderSettingsObserver extends ContentObserver {
+ private static final String TAG = "KernelCpuThreadReaderSettingsObserver";
+
+ /** The number of frequency buckets to report */
+ private static final String NUM_BUCKETS_SETTINGS_KEY = "num_buckets";
+
+ private static final int NUM_BUCKETS_DEFAULT = 8;
+
+ /** List of UIDs to report data for */
+ private static final String COLLECTED_UIDS_SETTINGS_KEY = "collected_uids";
+
+ private static final String COLLECTED_UIDS_DEFAULT = "0-0;1000-1000";
+
+ /** Minimum total CPU usage to report */
+ private static final String MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY =
+ "minimum_total_cpu_usage_millis";
+
+ private static final int MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT = 10000;
+
+ private final Context mContext;
+
+ @Nullable private final KernelCpuThreadReader mKernelCpuThreadReader;
+
+ @Nullable private final KernelCpuThreadReaderDiff mKernelCpuThreadReaderDiff;
+
+ /**
+ * @return returns a created {@link KernelCpuThreadReader} that will be modified by any change
+ * in settings, returns null if creation failed
+ */
+ @Nullable
+ public static KernelCpuThreadReaderDiff getSettingsModifiedReader(Context context) {
+ // Create the observer
+ KernelCpuThreadReaderSettingsObserver settingsObserver =
+ new KernelCpuThreadReaderSettingsObserver(context);
+ // Register the observer to listen for setting changes
+ Uri settingsUri = Settings.Global.getUriFor(Settings.Global.KERNEL_CPU_THREAD_READER);
+ context.getContentResolver()
+ .registerContentObserver(
+ settingsUri, false, settingsObserver, UserHandle.USER_SYSTEM);
+ // Return the observer's reader
+ return settingsObserver.mKernelCpuThreadReaderDiff;
+ }
+
+ private KernelCpuThreadReaderSettingsObserver(Context context) {
+ super(BackgroundThread.getHandler());
+ mContext = context;
+ mKernelCpuThreadReader =
+ KernelCpuThreadReader.create(
+ NUM_BUCKETS_DEFAULT, UidPredicate.fromString(COLLECTED_UIDS_DEFAULT));
+ mKernelCpuThreadReaderDiff =
+ mKernelCpuThreadReader == null
+ ? null
+ : new KernelCpuThreadReaderDiff(
+ mKernelCpuThreadReader, MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) {
+ updateReader();
+ }
+
+ /** Update the reader with new settings */
+ private void updateReader() {
+ if (mKernelCpuThreadReader == null) {
+ return;
+ }
+
+ final KeyValueListParser parser = new KeyValueListParser(',');
+ try {
+ parser.setString(
+ Settings.Global.getString(
+ mContext.getContentResolver(),
+ Settings.Global.KERNEL_CPU_THREAD_READER));
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Bad settings", e);
+ return;
+ }
+
+ final UidPredicate uidPredicate;
+ try {
+ uidPredicate =
+ UidPredicate.fromString(
+ parser.getString(COLLECTED_UIDS_SETTINGS_KEY, COLLECTED_UIDS_DEFAULT));
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Failed to get UID predicate", e);
+ return;
+ }
+
+ mKernelCpuThreadReader.setNumBuckets(
+ parser.getInt(NUM_BUCKETS_SETTINGS_KEY, NUM_BUCKETS_DEFAULT));
+ mKernelCpuThreadReader.setUidPredicate(uidPredicate);
+ mKernelCpuThreadReaderDiff.setMinimumTotalCpuUsageMillis(
+ parser.getInt(
+ MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY,
+ MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT));
+ }
+
+ /** Check whether a UID belongs to a set of UIDs */
+ @VisibleForTesting
+ public static class UidPredicate implements Predicate<Integer> {
+ private static final Pattern UID_RANGE_PATTERN = Pattern.compile("([0-9]+)-([0-9]+)");
+ private static final String UID_SPECIFIER_DELIMITER = ";";
+ private final List<Range<Integer>> mAcceptedUidRanges;
+
+ /**
+ * Create a UID predicate from a string representing a list of UID ranges
+ *
+ * <p>UID ranges are a pair of integers separated by a '-'. If you want to specify a single
+ * UID (e.g. UID 1000), you can use {@code 1000-1000}. Lists of ranges are separated by a
+ * single ';'. For example, this would be a valid string representation: {@code
+ * "1000-1999;2003-2003;2004-2004;2050-2060"}.
+ *
+ * <p>We do not use ',' to delimit as it is already used in separating different setting
+ * arguments.
+ *
+ * @throws NumberFormatException if the input string is incorrectly formatted
+ * @throws IllegalArgumentException if an UID range has a lower end than start
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static UidPredicate fromString(String predicateString) throws NumberFormatException {
+ final List<Range<Integer>> acceptedUidRanges = new ArrayList<>();
+ for (String uidSpecifier : predicateString.split(UID_SPECIFIER_DELIMITER)) {
+ final Matcher uidRangeMatcher = UID_RANGE_PATTERN.matcher(uidSpecifier);
+ if (!uidRangeMatcher.matches()) {
+ throw new NumberFormatException(
+ "Failed to recognize as number range: " + uidSpecifier);
+ }
+ acceptedUidRanges.add(
+ Range.create(
+ Integer.parseInt(uidRangeMatcher.group(1)),
+ Integer.parseInt(uidRangeMatcher.group(2))));
+ }
+ return new UidPredicate(acceptedUidRanges);
+ }
+
+ private UidPredicate(List<Range<Integer>> acceptedUidRanges) {
+ mAcceptedUidRanges = acceptedUidRanges;
+ }
+
+ @Override
+ @SuppressWarnings("ForLoopReplaceableByForEach")
+ public boolean test(Integer uid) {
+ for (int i = 0; i < mAcceptedUidRanges.size(); i++) {
+ if (mAcceptedUidRanges.get(i).contains(uid)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/KernelCpuTotalBpfMapReader.java b/android-35/com/android/internal/os/KernelCpuTotalBpfMapReader.java
new file mode 100644
index 0000000..553eda4
--- /dev/null
+++ b/android-35/com/android/internal/os/KernelCpuTotalBpfMapReader.java
@@ -0,0 +1,37 @@
+/*
+ * 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.internal.os;
+
+import android.annotation.Nullable;
+
+/** Reads total CPU time bpf map. */
+public final class KernelCpuTotalBpfMapReader {
+ private KernelCpuTotalBpfMapReader() {
+ }
+
+ /** Reads total CPU times (excluding sleep) per frequency in milliseconds from bpf map. */
+ @Nullable
+ public static long[] read() {
+ if (!KernelCpuBpfTracking.startTracking()) {
+ return null;
+ }
+ return readInternal();
+ }
+
+ @Nullable
+ private static native long[] readInternal();
+}
diff --git a/android-35/com/android/internal/os/KernelCpuUidBpfMapReader.java b/android-35/com/android/internal/os/KernelCpuUidBpfMapReader.java
new file mode 100644
index 0000000..52c0c3f
--- /dev/null
+++ b/android-35/com/android/internal/os/KernelCpuUidBpfMapReader.java
@@ -0,0 +1,216 @@
+/*
+ * 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.internal.os;
+
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Reads cpu time bpf maps.
+ *
+ * It is implemented as singletons for each separate set of per-UID times. Get___Instance() method
+ * returns the corresponding reader instance. In order to prevent frequent GC, it reuses the same
+ * SparseArray to store data read from BPF maps.
+ *
+ * A KernelCpuUidBpfMapReader instance keeps an error counter. When the number of read errors within
+ * that instance accumulates to 5, this instance will reject all further read requests.
+ *
+ * Data fetched within last 500ms is considered fresh, since the reading lifecycle can take up to
+ * 25ms. KernelCpuUidBpfMapReader always tries to use cache if it is fresh and valid, but it can
+ * be disabled through a parameter.
+ *
+ * A KernelCpuUidBpfMapReader instance is thread-safe. It acquires a write lock when reading the bpf
+ * map, releases it right after, then acquires a read lock before returning a BpfMapIterator. Caller
+ * is responsible for closing BpfMapIterator (also auto-closable) after reading, otherwise deadlock
+ * will occur.
+ */
+public abstract class KernelCpuUidBpfMapReader {
+ private static final int ERROR_THRESHOLD = 5;
+ private static final long FRESHNESS_MS = 500L;
+
+ private static final KernelCpuUidBpfMapReader FREQ_TIME_READER =
+ new KernelCpuUidFreqTimeBpfMapReader();
+
+ private static final KernelCpuUidBpfMapReader ACTIVE_TIME_READER =
+ new KernelCpuUidActiveTimeBpfMapReader();
+
+ private static final KernelCpuUidBpfMapReader CLUSTER_TIME_READER =
+ new KernelCpuUidClusterTimeBpfMapReader();
+
+ static KernelCpuUidBpfMapReader getFreqTimeReaderInstance() {
+ return FREQ_TIME_READER;
+ }
+
+ static KernelCpuUidBpfMapReader getActiveTimeReaderInstance() {
+ return ACTIVE_TIME_READER;
+ }
+
+ static KernelCpuUidBpfMapReader getClusterTimeReaderInstance() {
+ return CLUSTER_TIME_READER;
+ }
+
+ final String mTag = this.getClass().getSimpleName();
+ private int mErrors = 0;
+ protected SparseArray<long[]> mData = new SparseArray<>();
+ private long mLastReadTime = 0;
+ protected final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
+ protected final ReentrantReadWriteLock.ReadLock mReadLock = mLock.readLock();
+ protected final ReentrantReadWriteLock.WriteLock mWriteLock = mLock.writeLock();
+
+ public boolean startTrackingBpfTimes() {
+ return KernelCpuBpfTracking.startTracking();
+ }
+
+ protected abstract boolean readBpfData();
+
+ /**
+ * Returns an array of metadata used to inform the caller of 1) the size of array required by
+ * getNextUid and 2) how to interpret the raw data copied to that array.
+ */
+ public abstract long[] getDataDimensions();
+
+ public void removeUidsInRange(int startUid, int endUid) {
+ if (mErrors > ERROR_THRESHOLD) {
+ return;
+ }
+ if (endUid < startUid || startUid < 0) {
+ return;
+ }
+
+ mWriteLock.lock();
+ int firstIndex = mData.indexOfKey(startUid);
+ if (firstIndex < 0) {
+ mData.put(startUid, null);
+ firstIndex = mData.indexOfKey(startUid);
+ }
+ int lastIndex = mData.indexOfKey(endUid);
+ if (lastIndex < 0) {
+ mData.put(endUid, null);
+ lastIndex = mData.indexOfKey(endUid);
+ }
+ mData.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+ mWriteLock.unlock();
+ }
+
+ public BpfMapIterator open() {
+ return open(false);
+ }
+
+ public BpfMapIterator open(boolean ignoreCache) {
+ if (mErrors > ERROR_THRESHOLD) {
+ return null;
+ }
+ if (!startTrackingBpfTimes()) {
+ Slog.w(mTag, "Failed to start tracking");
+ mErrors++;
+ return null;
+ }
+ if (ignoreCache) {
+ mWriteLock.lock();
+ } else {
+ mReadLock.lock();
+ if (dataValid()) {
+ return new BpfMapIterator();
+ }
+ mReadLock.unlock();
+ mWriteLock.lock();
+ if (dataValid()) {
+ mReadLock.lock();
+ mWriteLock.unlock();
+ return new BpfMapIterator();
+ }
+ }
+ if (readBpfData()) {
+ mLastReadTime = SystemClock.elapsedRealtime();
+ mReadLock.lock();
+ mWriteLock.unlock();
+ return new BpfMapIterator();
+ }
+
+ mWriteLock.unlock();
+ mErrors++;
+ Slog.w(mTag, "Failed to read bpf times");
+ return null;
+ }
+
+ private boolean dataValid() {
+ return mData.size() > 0 && (SystemClock.elapsedRealtime() - mLastReadTime < FRESHNESS_MS);
+ }
+
+ public class BpfMapIterator implements AutoCloseable {
+ private int mPos;
+
+ public BpfMapIterator() {
+ };
+
+ public boolean getNextUid(long[] buf) {
+ if (mPos >= mData.size()) {
+ return false;
+ }
+ buf[0] = mData.keyAt(mPos);
+ System.arraycopy(mData.valueAt(mPos), 0, buf, 1, mData.valueAt(mPos).length);
+ mPos++;
+ return true;
+ }
+
+ public void close() {
+ mReadLock.unlock();
+ }
+ }
+
+ public static class KernelCpuUidFreqTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+ private final native boolean removeUidRange(int startUid, int endUid);
+
+ @Override
+ protected final native boolean readBpfData();
+
+ @Override
+ public final long[] getDataDimensions() {
+ return KernelCpuBpfTracking.getFreqsInternal();
+ }
+
+ @Override
+ public void removeUidsInRange(int startUid, int endUid) {
+ mWriteLock.lock();
+ super.removeUidsInRange(startUid, endUid);
+ removeUidRange(startUid, endUid);
+ mWriteLock.unlock();
+ }
+ }
+
+ public static class KernelCpuUidActiveTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+ @Override
+ protected final native boolean readBpfData();
+
+ @Override
+ public final native long[] getDataDimensions();
+ }
+
+ public static class KernelCpuUidClusterTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+ @Override
+ protected final native boolean readBpfData();
+
+ @Override
+ public final native long[] getDataDimensions();
+ }
+}
diff --git a/android-35/com/android/internal/os/KernelCpuUidTimeReader.java b/android-35/com/android/internal/os/KernelCpuUidTimeReader.java
new file mode 100644
index 0000000..21e0dc5
--- /dev/null
+++ b/android-35/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -0,0 +1,960 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import static com.android.internal.os.KernelCpuProcStringReader.asLongs;
+
+import android.annotation.Nullable;
+import android.os.StrictMode;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.KernelCpuProcStringReader.ProcFileIterator;
+import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.CharBuffer;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Reads per-UID CPU time proc files. Concrete implementations are all nested inside.
+ *
+ * This class uses a throttler to reject any {@link #readDelta} or {@link #readAbsolute} call
+ * within {@link #mMinTimeBetweenRead}. The throttler can be enable / disabled via a param in
+ * the constructor.
+ *
+ * This class and its subclasses are NOT thread-safe and NOT designed to be accessed by more than
+ * one caller since each caller has its own view of delta.
+ *
+ * @param <T> The type of CPU time for the callback.
+ */
+public abstract class KernelCpuUidTimeReader<T> {
+ protected static final boolean DEBUG = false;
+ private static final long DEFAULT_MIN_TIME_BETWEEN_READ = 1000L; // In milliseconds
+
+ final String mTag = this.getClass().getSimpleName();
+ final SparseArray<T> mLastTimes = new SparseArray<>();
+ final KernelCpuProcStringReader mReader;
+ final boolean mThrottle;
+ protected boolean mBpfTimesAvailable;
+ final KernelCpuUidBpfMapReader mBpfReader;
+ private final Clock mClock;
+ private long mMinTimeBetweenRead = DEFAULT_MIN_TIME_BETWEEN_READ;
+ private long mLastReadTimeMs = 0;
+
+ /**
+ * Callback interface for processing each line of the proc file.
+ *
+ * @param <T> The type of CPU time for the callback function.
+ */
+ public interface Callback<T> {
+ /**
+ * @param uid UID of the app
+ * @param time Time spent. The exact data structure depends on subclass implementation.
+ */
+ void onUidCpuTime(int uid, T time);
+ }
+
+ KernelCpuUidTimeReader(KernelCpuProcStringReader reader,
+ @Nullable KernelCpuUidBpfMapReader bpfReader, boolean throttle, Clock clock) {
+ mReader = reader;
+ mThrottle = throttle;
+ mBpfReader = bpfReader;
+ mClock = clock;
+ mBpfTimesAvailable = (mBpfReader != null);
+ }
+
+ KernelCpuUidTimeReader(KernelCpuProcStringReader reader, boolean throttle, Clock clock) {
+ this(reader, null, throttle, clock);
+ }
+
+ /**
+ * Reads the proc file, calling into the callback with a delta of time for each UID.
+ *
+ * @param cb The callback to invoke for each line of the proc file. If null,the data is
+ */
+ public void readDelta(@Nullable Callback<T> cb) {
+ readDelta(false, cb);
+ }
+
+ /**
+ * Reads the proc file, calling into the callback with a delta of time for each UID.
+ *
+ * @param force Ignore the throttling and force read the delta.
+ * @param cb The callback to invoke for each line of the proc file. If null,the data is
+ */
+ public void readDelta(boolean force, @Nullable Callback<T> cb) {
+ if (!mThrottle) {
+ readDeltaImpl(cb, force);
+ return;
+ }
+ final long currTimeMs = mClock.elapsedRealtime();
+ if (!force && currTimeMs < mLastReadTimeMs + mMinTimeBetweenRead) {
+ if (DEBUG) {
+ Slog.d(mTag, "Throttle readDelta");
+ }
+ return;
+ }
+ readDeltaImpl(cb, force);
+ mLastReadTimeMs = currTimeMs;
+ }
+
+ /**
+ * Reads the proc file, calling into the callback with cumulative time for each UID.
+ *
+ * @param cb The callback to invoke for each line of the proc file. It cannot be null.
+ */
+ public void readAbsolute(Callback<T> cb) {
+ if (!mThrottle) {
+ readAbsoluteImpl(cb);
+ return;
+ }
+ final long currTimeMs = mClock.elapsedRealtime();
+ if (currTimeMs < mLastReadTimeMs + mMinTimeBetweenRead) {
+ if (DEBUG) {
+ Slog.d(mTag, "Throttle readAbsolute");
+ }
+ return;
+ }
+ readAbsoluteImpl(cb);
+ mLastReadTimeMs = currTimeMs;
+ }
+
+ abstract void readDeltaImpl(@Nullable Callback<T> cb, boolean forceRead);
+
+ abstract void readAbsoluteImpl(Callback<T> callback);
+
+ /**
+ * Removes the UID from internal accounting data. This method, overridden in
+ * {@link KernelCpuUidUserSysTimeReader}, also removes the UID from the kernel module.
+ *
+ * @param uid The UID to remove.
+ * @see KernelCpuUidUserSysTimeReader#removeUid(int)
+ */
+ public void removeUid(int uid) {
+ mLastTimes.delete(uid);
+
+ if (mBpfTimesAvailable) {
+ mBpfReader.removeUidsInRange(uid, uid);
+ }
+ }
+
+ /**
+ * Removes UIDs in a given range from internal accounting data. This method, overridden in
+ * {@link KernelCpuUidUserSysTimeReader}, also removes the UIDs from the kernel module.
+ *
+ * @param startUid the first uid to remove.
+ * @param endUid the last uid to remove.
+ * @see KernelCpuUidUserSysTimeReader#removeUidsInRange(int, int)
+ */
+ public void removeUidsInRange(int startUid, int endUid) {
+ if (endUid < startUid) {
+ Slog.e(mTag, "start UID " + startUid + " > end UID " + endUid);
+ return;
+ }
+ mLastTimes.put(startUid, null);
+ mLastTimes.put(endUid, null);
+ int firstIndex = mLastTimes.indexOfKey(startUid);
+ int lastIndex = mLastTimes.indexOfKey(endUid);
+ mLastTimes.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+
+ if (mBpfTimesAvailable) {
+ mBpfReader.removeUidsInRange(startUid, endUid);
+ }
+ }
+
+ /**
+ * Set the minimum time in milliseconds between reads. If throttle is not enabled, this method
+ * has no effect.
+ *
+ * @param minTimeBetweenRead The minimum time in milliseconds.
+ */
+ public void setThrottle(long minTimeBetweenRead) {
+ if (mThrottle && minTimeBetweenRead >= 0) {
+ mMinTimeBetweenRead = minTimeBetweenRead;
+ }
+ }
+
+ /**
+ * Reads /proc/uid_cputime/show_uid_stat which has the line format:
+ *
+ * uid: user_time_micro_seconds system_time_micro_seconds power_in_milli-amp-micro_seconds
+ *
+ * This provides the time a UID's processes spent executing in user-space and kernel-space.
+ * The file contains a monotonically increasing count of time for a single boot. This class
+ * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
+ * delta.
+ *
+ * The second parameter of the callback is a long[] with 2 elements, [user time in us, system
+ * time in us].
+ */
+ public static class KernelCpuUidUserSysTimeReader extends KernelCpuUidTimeReader<long[]> {
+ private static final String REMOVE_UID_PROC_FILE = "/proc/uid_cputime/remove_uid_range";
+
+ // [uid, user_time, system_time, (maybe) power_in_milli-amp-micro_seconds]
+ private final long[] mBuffer = new long[4];
+ // A reusable array to hold [user_time, system_time] for the callback.
+ private final long[] mUsrSysTime = new long[2];
+
+ public KernelCpuUidUserSysTimeReader(boolean throttle) {
+ this(throttle, Clock.SYSTEM_CLOCK);
+ }
+
+ public KernelCpuUidUserSysTimeReader(boolean throttle, Clock clock) {
+ super(KernelCpuProcStringReader.getUserSysTimeReaderInstance(), throttle, clock);
+ }
+
+ @VisibleForTesting
+ public KernelCpuUidUserSysTimeReader(KernelCpuProcStringReader reader, boolean throttle,
+ Clock clock) {
+ super(reader, throttle, clock);
+ }
+
+ @Override
+ void readDeltaImpl(@Nullable Callback<long[]> cb, boolean forceRead) {
+ try (ProcFileIterator iter = mReader.open(!mThrottle || forceRead)) {
+ if (iter == null) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) < 3) {
+ Slog.wtf(mTag, "Invalid line: " + buf.toString());
+ continue;
+ }
+ final int uid = (int) mBuffer[0];
+ long[] lastTimes = mLastTimes.get(uid);
+ if (lastTimes == null) {
+ lastTimes = new long[2];
+ mLastTimes.put(uid, lastTimes);
+ }
+ final long currUsrTimeUs = mBuffer[1];
+ final long currSysTimeUs = mBuffer[2];
+ mUsrSysTime[0] = currUsrTimeUs - lastTimes[0];
+ mUsrSysTime[1] = currSysTimeUs - lastTimes[1];
+
+ if (mUsrSysTime[0] < 0 || mUsrSysTime[1] < 0) {
+ Slog.e(mTag, "Negative user/sys time delta for UID=" + uid
+ + "\nPrev times: u=" + lastTimes[0] + " s=" + lastTimes[1]
+ + " Curr times: u=" + currUsrTimeUs + " s=" + currSysTimeUs);
+ } else if (mUsrSysTime[0] > 0 || mUsrSysTime[1] > 0) {
+ if (cb != null) {
+ cb.onUidCpuTime(uid, mUsrSysTime);
+ }
+ }
+ lastTimes[0] = currUsrTimeUs;
+ lastTimes[1] = currSysTimeUs;
+ }
+ }
+ }
+
+ @Override
+ void readAbsoluteImpl(Callback<long[]> cb) {
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (iter == null) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) < 3) {
+ Slog.wtf(mTag, "Invalid line: " + buf.toString());
+ continue;
+ }
+ mUsrSysTime[0] = mBuffer[1]; // User time in microseconds
+ mUsrSysTime[1] = mBuffer[2]; // System time in microseconds
+ cb.onUidCpuTime((int) mBuffer[0], mUsrSysTime);
+ }
+ }
+ }
+
+ @Override
+ public void removeUid(int uid) {
+ super.removeUid(uid);
+ removeUidsFromKernelModule(uid, uid);
+ }
+
+ @Override
+ public void removeUidsInRange(int startUid, int endUid) {
+ super.removeUidsInRange(startUid, endUid);
+ removeUidsFromKernelModule(startUid, endUid);
+ }
+
+ /**
+ * Removes UIDs in a given range from the kernel module and internal accounting data. Only
+ * {@link BatteryStatsImpl} and its child processes should call this, as the change on
+ * Kernel is
+ * visible system wide.
+ *
+ * @param startUid the first uid to remove
+ * @param endUid the last uid to remove
+ */
+ private void removeUidsFromKernelModule(int startUid, int endUid) {
+ Slog.d(mTag, "Removing uids " + startUid + "-" + endUid);
+ final int oldMask = StrictMode.allowThreadDiskWritesMask();
+ try (FileWriter writer = new FileWriter(REMOVE_UID_PROC_FILE)) {
+ writer.write(startUid + "-" + endUid);
+ writer.flush();
+ } catch (IOException e) {
+ Slog.e(mTag, "failed to remove uids " + startUid + " - " + endUid
+ + " from uid_cputime module", e);
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
+ }
+ }
+ }
+
+ /**
+ * Reads /proc/uid_time_in_state which has the format:
+ *
+ * uid: [freq1] [freq2] [freq3] ...
+ * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
+ * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
+ * ...
+ *
+ * This provides the times a UID's processes spent executing at each different cpu frequency.
+ * The file contains a monotonically increasing count of time for a single boot. This class
+ * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
+ * delta.
+ */
+ public static class KernelCpuUidFreqTimeReader extends KernelCpuUidTimeReader<long[]> {
+ private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
+ // We check the existence of proc file a few times (just in case it is not ready yet when we
+ // start reading) and if it is not available, we simply ignore further read requests.
+ private static final int MAX_ERROR_COUNT = 5;
+
+ private final Path mProcFilePath;
+ private long[] mBuffer;
+ private long[] mCurTimes;
+ private long[] mDeltaTimes;
+ private long[] mCpuFreqs;
+
+ private int mFreqCount = 0;
+ private int mErrors = 0;
+ private boolean mPerClusterTimesAvailable;
+ private boolean mAllUidTimesAvailable;
+
+ public KernelCpuUidFreqTimeReader(boolean throttle) {
+ this(throttle, Clock.SYSTEM_CLOCK);
+ }
+
+ public KernelCpuUidFreqTimeReader(boolean throttle, Clock clock) {
+ this(UID_TIMES_PROC_FILE, KernelCpuProcStringReader.getFreqTimeReaderInstance(),
+ KernelCpuUidBpfMapReader.getFreqTimeReaderInstance(), throttle, clock);
+ }
+
+ @VisibleForTesting
+ public KernelCpuUidFreqTimeReader(String procFile, KernelCpuProcStringReader reader,
+ KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+ this(procFile, reader, bpfReader, throttle, Clock.SYSTEM_CLOCK);
+ }
+
+ private KernelCpuUidFreqTimeReader(String procFile, KernelCpuProcStringReader reader,
+ KernelCpuUidBpfMapReader bpfReader, boolean throttle, Clock clock) {
+ super(reader, bpfReader, throttle, clock);
+ mProcFilePath = Paths.get(procFile);
+ }
+
+ /**
+ * Initializes the reader. Should be called during the system-ready boot phase.
+ */
+ public void onSystemReady() {
+ if (mBpfTimesAvailable && mCpuFreqs == null) {
+ readFreqsThroughBpf();
+ // By extension: if we can read CPU frequencies through eBPF, we can also
+ // read per-UID CPU time-in-state
+ mAllUidTimesAvailable = mCpuFreqs != null;
+ }
+ }
+
+ /**
+ * @return Whether per-cluster times are available.
+ */
+ public boolean perClusterTimesAvailable() {
+ return mBpfTimesAvailable;
+ }
+
+ /**
+ * @return Whether all-UID times are available.
+ */
+ public boolean allUidTimesAvailable() {
+ return mAllUidTimesAvailable;
+ }
+
+ /**
+ * @return A map of all UIDs to their CPU time-in-state array in milliseconds.
+ */
+ public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
+ return mLastTimes;
+ }
+
+ private long[] readFreqsThroughBpf() {
+ if (!mBpfTimesAvailable || mBpfReader == null) {
+ return null;
+ }
+ mCpuFreqs = mBpfReader.getDataDimensions();
+ if (mCpuFreqs == null) {
+ return null;
+ }
+ mFreqCount = mCpuFreqs.length;
+ mCurTimes = new long[mFreqCount];
+ mDeltaTimes = new long[mFreqCount];
+ mBuffer = new long[mFreqCount + 1];
+ return mCpuFreqs;
+ }
+
+ private long[] readFreqs(String line) {
+ if (line == null || line.trim().isEmpty()) {
+ return null;
+ }
+ final String[] lineArray = line.split(" ");
+ if (lineArray.length <= 1) {
+ Slog.wtf(mTag, "Malformed freq line: " + line);
+ return null;
+ }
+ mFreqCount = lineArray.length - 1;
+ mCpuFreqs = new long[mFreqCount];
+ mCurTimes = new long[mFreqCount];
+ mDeltaTimes = new long[mFreqCount];
+ mBuffer = new long[mFreqCount + 1];
+ for (int i = 0; i < mFreqCount; ++i) {
+ mCpuFreqs[i] = Long.parseLong(lineArray[i + 1], 10);
+ }
+ return mCpuFreqs;
+ }
+
+ private void processUidDelta(@Nullable Callback<long[]> cb) {
+ final int uid = (int) mBuffer[0];
+ long[] lastTimes = mLastTimes.get(uid);
+ if (lastTimes == null) {
+ lastTimes = new long[mFreqCount];
+ mLastTimes.put(uid, lastTimes);
+ }
+ copyToCurTimes();
+ boolean notify = false;
+ for (int i = 0; i < mFreqCount; i++) {
+ // Unit is 10ms.
+ mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
+ if (mDeltaTimes[i] < 0) {
+ Slog.e(mTag, "Negative delta from freq time for uid: " + uid
+ + ", delta: " + mDeltaTimes[i]);
+ return;
+ }
+ notify |= mDeltaTimes[i] > 0;
+ }
+ if (notify) {
+ System.arraycopy(mCurTimes, 0, lastTimes, 0, mFreqCount);
+ if (cb != null) {
+ cb.onUidCpuTime(uid, mDeltaTimes);
+ }
+ }
+ }
+
+ @Override
+ void readDeltaImpl(@Nullable Callback<long[]> cb, boolean forceRead) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ processUidDelta(cb);
+ }
+ return;
+ }
+ }
+ }
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (!checkPrecondition(iter)) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) != mBuffer.length) {
+ Slog.wtf(mTag, "Invalid line: " + buf.toString());
+ continue;
+ }
+ processUidDelta(cb);
+ }
+ }
+ }
+
+ @Override
+ void readAbsoluteImpl(Callback<long[]> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ copyToCurTimes();
+ cb.onUidCpuTime((int) mBuffer[0], mCurTimes);
+ }
+ return;
+ }
+ }
+ }
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (!checkPrecondition(iter)) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) != mBuffer.length) {
+ Slog.wtf(mTag, "Invalid line: " + buf.toString());
+ continue;
+ }
+ copyToCurTimes();
+ cb.onUidCpuTime((int) mBuffer[0], mCurTimes);
+ }
+ }
+ }
+
+ private void copyToCurTimes() {
+ long factor = mBpfTimesAvailable ? 1 : 10;
+ for (int i = 0; i < mFreqCount; i++) {
+ mCurTimes[i] = mBuffer[i + 1] * factor;
+ }
+ }
+
+ private boolean checkPrecondition(BpfMapIterator iter) {
+ if (iter == null) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ if (mCpuFreqs != null) {
+ return true;
+ }
+ mBpfTimesAvailable = (readFreqsThroughBpf() != null);
+ return mBpfTimesAvailable;
+ }
+
+ private boolean checkPrecondition(ProcFileIterator iter) {
+ if (iter == null || !iter.hasNextLine()) {
+ // Error logged in KernelCpuProcStringReader.
+ return false;
+ }
+ CharBuffer line = iter.nextLine();
+ if (mCpuFreqs != null) {
+ return true;
+ }
+ return readFreqs(line.toString()) != null;
+ }
+
+ /**
+ * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs
+ * read from the proc file.
+ *
+ * We need to assume that freqs in each cluster are strictly increasing.
+ * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means
+ * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52)
+ *
+ * @return an IntArray filled with no. of freqs in each cluster.
+ */
+ private IntArray extractClusterInfoFromProcFileFreqs() {
+ final IntArray numClusterFreqs = new IntArray();
+ int freqsFound = 0;
+ for (int i = 0; i < mFreqCount; ++i) {
+ freqsFound++;
+ if (i + 1 == mFreqCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) {
+ numClusterFreqs.add(freqsFound);
+ freqsFound = 0;
+ }
+ }
+ return numClusterFreqs;
+ }
+
+ public boolean isFastCpuTimesReader() {
+ return mBpfTimesAvailable;
+ }
+ }
+
+ /**
+ * Reads /proc/uid_concurrent_active_time and reports CPU active time to BatteryStats to
+ * compute {@link PowerProfile#POWER_CPU_ACTIVE}.
+ *
+ * /proc/uid_concurrent_active_time has the following format:
+ * cpus: n
+ * uid0: time0a, time0b, ..., time0n,
+ * uid1: time1a, time1b, ..., time1n,
+ * uid2: time2a, time2b, ..., time2n,
+ * ...
+ * where n is the total number of cpus (num_possible_cpus)
+ * timeXn means the CPU time that a UID X spent running concurrently with n other processes.
+ *
+ * The file contains a monotonically increasing count of time for a single boot. This class
+ * maintains the previous results of a call to {@link #readDelta} in order to provide a
+ * proper delta.
+ */
+ public static class KernelCpuUidActiveTimeReader extends KernelCpuUidTimeReader<Long> {
+ private int mCores = 0;
+ private long[] mBuffer;
+
+ public KernelCpuUidActiveTimeReader(boolean throttle) {
+ this(throttle, Clock.SYSTEM_CLOCK);
+ }
+
+ public KernelCpuUidActiveTimeReader(boolean throttle, Clock clock) {
+ super(KernelCpuProcStringReader.getActiveTimeReaderInstance(),
+ KernelCpuUidBpfMapReader.getActiveTimeReaderInstance(), throttle, clock);
+ }
+
+ @VisibleForTesting
+ public KernelCpuUidActiveTimeReader(KernelCpuProcStringReader reader,
+ KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+ super(reader, bpfReader, throttle, Clock.SYSTEM_CLOCK);
+ }
+
+ private void processUidDelta(@Nullable Callback<Long> cb) {
+ int uid = (int) mBuffer[0];
+ long cpuActiveTime = sumActiveTime(mBuffer, mBpfTimesAvailable ? 1 : 10);
+ if (cpuActiveTime > 0) {
+ long delta = cpuActiveTime - mLastTimes.get(uid, 0L);
+ if (delta > 0) {
+ mLastTimes.put(uid, cpuActiveTime);
+ if (cb != null) {
+ cb.onUidCpuTime(uid, delta);
+ }
+ } else if (delta < 0) {
+ Slog.e(mTag, "Negative delta from active time for uid: " + uid
+ + ", delta: " + delta);
+ }
+ }
+ }
+
+ @Override
+ void readDeltaImpl(@Nullable Callback<Long> cb, boolean forceRead) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ processUidDelta(cb);
+ }
+ return;
+ }
+ }
+ }
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (!checkPrecondition(iter)) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) != mBuffer.length) {
+ Slog.wtf(mTag, "Invalid line: " + buf.toString());
+ continue;
+ }
+ processUidDelta(cb);
+ }
+ }
+ }
+
+ private void processUidAbsolute(@Nullable Callback<Long> cb) {
+ long cpuActiveTime = sumActiveTime(mBuffer, mBpfTimesAvailable ? 1 : 10);
+ if (cpuActiveTime > 0) {
+ cb.onUidCpuTime((int) mBuffer[0], cpuActiveTime);
+ }
+ }
+
+ @Override
+ void readAbsoluteImpl(Callback<Long> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ processUidAbsolute(cb);
+ }
+ return;
+ }
+ }
+ }
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (!checkPrecondition(iter)) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) != mBuffer.length) {
+ Slog.wtf(mTag, "Invalid line: " + buf.toString());
+ continue;
+ }
+ processUidAbsolute(cb);
+ }
+ }
+ }
+
+ private static long sumActiveTime(long[] times, double factor) {
+ // UID is stored at times[0].
+ double sum = 0;
+ for (int i = 1; i < times.length; i++) {
+ sum += (double) times[i] * factor / i; // Unit is 10ms.
+ }
+ return (long) sum;
+ }
+
+ private boolean checkPrecondition(BpfMapIterator iter) {
+ if (iter == null) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ if (mCores > 0) {
+ return true;
+ }
+ long[] cores = mBpfReader.getDataDimensions();
+ if (cores == null || cores.length < 1) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ mCores = (int) cores[0];
+ mBuffer = new long[mCores + 1];
+ return true;
+ }
+
+ private boolean checkPrecondition(ProcFileIterator iter) {
+ if (iter == null || !iter.hasNextLine()) {
+ // Error logged in KernelCpuProcStringReader.
+ return false;
+ }
+ CharBuffer line = iter.nextLine();
+ if (mCores > 0) {
+ return true;
+ }
+
+ String str = line.toString().trim();
+ if (str.isEmpty()) {
+ Slog.w(mTag, "Empty uid_concurrent_active_time");
+ return false;
+ }
+ if (!str.startsWith("cpus:")) {
+ Slog.wtf(mTag, "Malformed uid_concurrent_active_time line: " + str);
+ return false;
+ }
+ int cores = Integer.parseInt(str.substring(5).trim(), 10);
+ if (cores <= 0) {
+ Slog.wtf(mTag, "Malformed uid_concurrent_active_time line: " + str);
+ return false;
+ }
+ mCores = cores;
+ mBuffer = new long[mCores + 1]; // UID is stored at mBuffer[0].
+ return true;
+ }
+ }
+
+
+ /**
+ * Reads /proc/uid_concurrent_policy_time and reports CPU cluster times to BatteryStats to
+ * compute cluster power. See {@link PowerProfile#getAveragePowerForCpuCluster(int)}.
+ *
+ * /proc/uid_concurrent_policy_time has the following format:
+ * policyX: x policyY: y policyZ: z...
+ * uid1, time1a, time1b, ..., time1n,
+ * uid2, time2a, time2b, ..., time2n,
+ * ...
+ * The first line lists all policies (i.e. clusters) followed by # cores in each policy.
+ * Each uid is followed by x time entries corresponding to the time it spent on clusterX
+ * running concurrently with 0, 1, 2, ..., x - 1 other processes, then followed by y, z, ...
+ * time entries.
+ *
+ * The file contains a monotonically increasing count of time for a single boot. This class
+ * maintains the previous results of a call to {@link #readDelta} in order to provide a
+ * proper delta.
+ */
+ public static class KernelCpuUidClusterTimeReader extends KernelCpuUidTimeReader<long[]> {
+ private int mNumClusters;
+ private int mNumCores;
+ private int[] mCoresOnClusters; // # cores on each cluster.
+ private long[] mBuffer; // To store data returned from ProcFileIterator.
+ private long[] mCurTime;
+ private long[] mDeltaTime;
+
+ public KernelCpuUidClusterTimeReader(boolean throttle) {
+ this(throttle, Clock.SYSTEM_CLOCK);
+ }
+
+ public KernelCpuUidClusterTimeReader(boolean throttle, Clock clock) {
+ super(KernelCpuProcStringReader.getClusterTimeReaderInstance(),
+ KernelCpuUidBpfMapReader.getClusterTimeReaderInstance(), throttle, clock);
+ }
+
+ @VisibleForTesting
+ public KernelCpuUidClusterTimeReader(KernelCpuProcStringReader reader,
+ KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+ super(reader, bpfReader, throttle, Clock.SYSTEM_CLOCK);
+ }
+
+ void processUidDelta(@Nullable Callback<long[]> cb) {
+ int uid = (int) mBuffer[0];
+ long[] lastTimes = mLastTimes.get(uid);
+ if (lastTimes == null) {
+ lastTimes = new long[mNumClusters];
+ mLastTimes.put(uid, lastTimes);
+ }
+ sumClusterTime();
+ boolean valid = true;
+ boolean notify = false;
+ for (int i = 0; i < mNumClusters; i++) {
+ mDeltaTime[i] = mCurTime[i] - lastTimes[i];
+ if (mDeltaTime[i] < 0) {
+ Slog.e(mTag, "Negative delta from cluster time for uid: " + uid
+ + ", delta: " + mDeltaTime[i]);
+ return;
+ }
+ notify |= mDeltaTime[i] > 0;
+ }
+ if (notify) {
+ System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
+ if (cb != null) {
+ cb.onUidCpuTime(uid, mDeltaTime);
+ }
+ }
+ }
+
+ @Override
+ void readDeltaImpl(@Nullable Callback<long[]> cb, boolean forceRead) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ processUidDelta(cb);
+ }
+ return;
+ }
+ }
+ }
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (!checkPrecondition(iter)) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) != mBuffer.length) {
+ Slog.wtf(mTag, "Invalid line: " + buf.toString());
+ continue;
+ }
+ processUidDelta(cb);
+ }
+ }
+ }
+
+ @Override
+ void readAbsoluteImpl(Callback<long[]> cb) {
+ if (mBpfTimesAvailable) {
+ try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+ if (checkPrecondition(iter)) {
+ while (iter.getNextUid(mBuffer)) {
+ sumClusterTime();
+ cb.onUidCpuTime((int) mBuffer[0], mCurTime);
+ }
+ return;
+ }
+ }
+ }
+ try (ProcFileIterator iter = mReader.open(!mThrottle)) {
+ if (!checkPrecondition(iter)) {
+ return;
+ }
+ CharBuffer buf;
+ while ((buf = iter.nextLine()) != null) {
+ if (asLongs(buf, mBuffer) != mBuffer.length) {
+ Slog.wtf(mTag, "Invalid line: " + buf.toString());
+ continue;
+ }
+ sumClusterTime();
+ cb.onUidCpuTime((int) mBuffer[0], mCurTime);
+ }
+ }
+ }
+
+ private void sumClusterTime() {
+ double factor = mBpfTimesAvailable ? 1 : 10;
+ // UID is stored at mBuffer[0].
+ int core = 1;
+ for (int i = 0; i < mNumClusters; i++) {
+ double sum = 0;
+ for (int j = 1; j <= mCoresOnClusters[i]; j++) {
+ sum += (double) mBuffer[core++] * factor / j; // Unit is 10ms.
+ }
+ mCurTime[i] = (long) sum;
+ }
+ }
+
+ private boolean checkPrecondition(BpfMapIterator iter) {
+ if (iter == null) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ if (mNumClusters > 0) {
+ return true;
+ }
+ long[] coresOnClusters = mBpfReader.getDataDimensions();
+ if (coresOnClusters == null || coresOnClusters.length < 1) {
+ mBpfTimesAvailable = false;
+ return false;
+ }
+ mNumClusters = coresOnClusters.length;
+ mCoresOnClusters = new int[mNumClusters];
+ int cores = 0;
+ for (int i = 0; i < mNumClusters; i++) {
+ mCoresOnClusters[i] = (int) coresOnClusters[i];
+ cores += mCoresOnClusters[i];
+ }
+ mNumCores = cores;
+ mBuffer = new long[cores + 1];
+ mCurTime = new long[mNumClusters];
+ mDeltaTime = new long[mNumClusters];
+ return true;
+ }
+
+ private boolean checkPrecondition(ProcFileIterator iter) {
+ if (iter == null || !iter.hasNextLine()) {
+ // Error logged in KernelCpuProcStringReader.
+ return false;
+ }
+ CharBuffer line = iter.nextLine();
+ if (mNumClusters > 0) {
+ return true;
+ }
+ String lineStr = line.toString().trim();
+ if (lineStr.isEmpty()) {
+ Slog.w(mTag, "Empty uid_concurrent_policy_time");
+ return false;
+ }
+ // Parse # cores in clusters.
+ String[] lineArray = lineStr.split(" ");
+ if (lineArray.length % 2 != 0) {
+ Slog.wtf(mTag, "Malformed uid_concurrent_policy_time line: " + lineStr);
+ return false;
+ }
+ int[] clusters = new int[lineArray.length / 2];
+ int cores = 0;
+ for (int i = 0; i < clusters.length; i++) {
+ if (!lineArray[i * 2].startsWith("policy")) {
+ Slog.wtf(mTag, "Malformed uid_concurrent_policy_time line: " + lineStr);
+ return false;
+ }
+ clusters[i] = Integer.parseInt(lineArray[i * 2 + 1], 10);
+ cores += clusters[i];
+ }
+ mNumClusters = clusters.length;
+ mNumCores = cores;
+ mCoresOnClusters = clusters;
+ mBuffer = new long[cores + 1];
+ mCurTime = new long[mNumClusters];
+ mDeltaTime = new long[mNumClusters];
+ return true;
+ }
+ }
+
+}
diff --git a/android-35/com/android/internal/os/KernelMemoryBandwidthStats.java b/android-35/com/android/internal/os/KernelMemoryBandwidthStats.java
new file mode 100644
index 0000000..15a5e3e
--- /dev/null
+++ b/android-35/com/android/internal/os/KernelMemoryBandwidthStats.java
@@ -0,0 +1,92 @@
+package com.android.internal.os;
+
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.LongSparseLongArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+
+/**
+ * Reads DDR time spent at various frequencies and stores the data. Supports diff comparison with
+ * other KernelMemoryBandwidthStats objects. The sysfs file has the format:
+ *
+ * freq time_in_bucket ... time_in_bucket
+ * ...
+ * freq time_in_bucket ... time_in_bucket
+ *
+ * where time is measured in nanoseconds.
+ */
+public class KernelMemoryBandwidthStats {
+ private static final String TAG = "KernelMemoryBandwidthStats";
+
+ private static final String mSysfsFile = "/sys/kernel/memory_state_time/show_stat";
+ private static final boolean DEBUG = false;
+
+ protected final LongSparseLongArray mBandwidthEntries = new LongSparseLongArray();
+ private boolean mStatsDoNotExist = false;
+
+ public void updateStats() {
+ if (mStatsDoNotExist) {
+ // Skip reading.
+ return;
+ }
+
+ final long startTime = SystemClock.uptimeMillis();
+
+ StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
+ try (BufferedReader reader = new BufferedReader(new FileReader(mSysfsFile))) {
+ parseStats(reader);
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, "No kernel memory bandwidth stats available");
+ mBandwidthEntries.clear();
+ mStatsDoNotExist = true;
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read memory bandwidth: " + e.getMessage());
+ mBandwidthEntries.clear();
+ } finally {
+ StrictMode.setThreadPolicy(policy);
+ }
+
+ final long readTime = SystemClock.uptimeMillis() - startTime;
+ if (DEBUG || readTime > 100) {
+ Slog.w(TAG, "Reading memory bandwidth file took " + readTime + "ms");
+ }
+ }
+
+ @VisibleForTesting
+ public void parseStats(BufferedReader reader) throws IOException {
+ String line;
+ TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
+ mBandwidthEntries.clear();
+ while ((line = reader.readLine()) != null) {
+ splitter.setString(line);
+ splitter.next();
+ int bandwidth = 0;
+ int index;
+ do {
+ if ((index = mBandwidthEntries.indexOfKey(bandwidth)) >= 0) {
+ mBandwidthEntries.put(bandwidth, mBandwidthEntries.valueAt(index)
+ + Long.parseLong(splitter.next()) / 1000000);
+ } else {
+ mBandwidthEntries.put(bandwidth, Long.parseLong(splitter.next()) / 1000000);
+ }
+ if (DEBUG) {
+ Slog.d(TAG, String.format("bandwidth: %s time: %s", bandwidth,
+ mBandwidthEntries.get(bandwidth)));
+ }
+ bandwidth++;
+ } while(splitter.hasNext());
+ }
+ }
+
+ public LongSparseLongArray getBandwidthEntries() {
+ return mBandwidthEntries;
+ }
+}
diff --git a/android-35/com/android/internal/os/KernelSingleProcessCpuThreadReader.java b/android-35/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
new file mode 100644
index 0000000..aa6b1c0
--- /dev/null
+++ b/android-35/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
@@ -0,0 +1,212 @@
+/*
+ * 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.internal.os;
+
+import android.annotation.Nullable;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.expresslog.Counter;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Iterates over all threads owned by a given process, and return the CPU usage for
+ * each thread. The CPU usage statistics contain the amount of time spent in a frequency band. CPU
+ * usage is collected using {@link ProcTimeInStateReader}.
+ */
+public class KernelSingleProcessCpuThreadReader {
+
+ private static final String TAG = "KernelSingleProcCpuThreadRdr";
+
+ private static final boolean DEBUG = false;
+
+ private final int mPid;
+
+ private final CpuTimeInStateReader mCpuTimeInStateReader;
+
+ private int[] mSelectedThreadNativeTids = new int[0]; // Sorted
+
+ /**
+ * 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.
+ */
+ @VisibleForTesting
+ public KernelSingleProcessCpuThreadReader(int pid,
+ @Nullable CpuTimeInStateReader cpuTimeInStateReader) throws IOException {
+ mPid = pid;
+ mCpuTimeInStateReader = cpuTimeInStateReader;
+ }
+
+ /**
+ * Create the reader and handle exceptions during creation
+ *
+ * @return the reader, null if an exception was thrown during creation
+ */
+ @Nullable
+ public static KernelSingleProcessCpuThreadReader create(int pid) {
+ try {
+ return new KernelSingleProcessCpuThreadReader(pid, null);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to initialize KernelSingleProcessCpuThreadReader", e);
+ return null;
+ }
+ }
+
+ /**
+ * 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.wtf(TAG, "Failed to start tracking process CPU times for " + mPid);
+ Counter.logIncrement("cpu.value_process_tracking_start_failure_count");
+ }
+ if (mSelectedThreadNativeTids.length > 0) {
+ if (!startAggregatingThreadCpuTimes(mSelectedThreadNativeTids,
+ mCpuTimeInStateReader)) {
+ Slog.wtf(TAG, "Failed to start tracking aggregated thread CPU times for "
+ + Arrays.toString(mSelectedThreadNativeTids));
+ Counter.logIncrement(
+ "cpu.value_aggregated_thread_tracking_start_failure_count");
+ }
+ }
+ 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 = getCpuFrequencyCount(mCpuTimeInStateReader);
+ }
+ return mFrequencyCount;
+ }
+
+ /**
+ * 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() {
+ if (DEBUG) {
+ Slog.d(TAG, "Reading CPU thread usages for PID " + mPid);
+ }
+
+ ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(getCpuFrequencyCount());
+
+ boolean result = readProcessCpuUsage(mPid,
+ processCpuUsage.threadCpuTimesMillis,
+ processCpuUsage.selectedThreadCpuTimesMillis,
+ mCpuTimeInStateReader);
+ if (!result) {
+ return null;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "threadCpuTimesMillis = "
+ + Arrays.toString(processCpuUsage.threadCpuTimesMillis));
+ Slog.d(TAG, "selectedThreadCpuTimesMillis = "
+ + Arrays.toString(processCpuUsage.selectedThreadCpuTimesMillis));
+ }
+
+ return processCpuUsage;
+ }
+
+ /** CPU usage of a process, all of its threads and a selected subset of its threads */
+ public static class ProcessCpuUsage {
+ public long[] threadCpuTimesMillis;
+ public long[] selectedThreadCpuTimesMillis;
+
+ public ProcessCpuUsage(int cpuFrequencyCount) {
+ threadCpuTimesMillis = new long[cpuFrequencyCount];
+ selectedThreadCpuTimesMillis = new long[cpuFrequencyCount];
+ }
+ }
+
+ private native int getCpuFrequencyCount(CpuTimeInStateReader reader);
+
+ 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/android-35/com/android/internal/os/KernelSingleUidTimeReader.java b/android-35/com/android/internal/os/KernelSingleUidTimeReader.java
new file mode 100644
index 0000000..de3edeb
--- /dev/null
+++ b/android-35/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2017 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.internal.os;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+public class KernelSingleUidTimeReader {
+ private static final String TAG = KernelSingleUidTimeReader.class.getName();
+ private static final boolean DBG = false;
+
+ private static final String PROC_FILE_DIR = "/proc/uid/";
+ private static final String PROC_FILE_NAME = "/time_in_state";
+ private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
+
+ @VisibleForTesting
+ public static final int TOTAL_READ_ERROR_COUNT = 5;
+
+ @GuardedBy("this")
+ private final int mCpuFreqsCount;
+
+ @GuardedBy("this")
+ private SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
+
+ @GuardedBy("this")
+ private int mReadErrorCounter;
+ @GuardedBy("this")
+ private boolean mSingleUidCpuTimesAvailable = true;
+ @GuardedBy("this")
+ private boolean mBpfTimesAvailable = true;
+ // We use the freq count obtained from /proc/uid_time_in_state to decide how many longs
+ // to read from each /proc/uid/<uid>/time_in_state. On the first read, verify if this is
+ // correct and if not, set {@link #mSingleUidCpuTimesAvailable} to false. This flag will
+ // indicate whether we checked for validity or not.
+ @GuardedBy("this")
+ private boolean mCpuFreqsCountVerified;
+
+ private final Injector mInjector;
+
+ private static final native boolean canReadBpfTimes();
+
+ public KernelSingleUidTimeReader(int cpuFreqsCount) {
+ this(cpuFreqsCount, new Injector());
+ }
+
+ public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) {
+ mInjector = injector;
+ mCpuFreqsCount = cpuFreqsCount;
+ if (mCpuFreqsCount == 0) {
+ mSingleUidCpuTimesAvailable = false;
+ }
+ }
+
+ public boolean singleUidCpuTimesAvailable() {
+ return mSingleUidCpuTimesAvailable;
+ }
+
+ public long[] readDeltaMs(int uid) {
+ synchronized (this) {
+ if (!mSingleUidCpuTimesAvailable) {
+ return null;
+ }
+ if (mBpfTimesAvailable) {
+ final long[] cpuTimesMs = mInjector.readBpfData(uid);
+ if (cpuTimesMs.length == 0) {
+ mBpfTimesAvailable = false;
+ } else if (!mCpuFreqsCountVerified && cpuTimesMs.length != mCpuFreqsCount) {
+ mSingleUidCpuTimesAvailable = false;
+ return null;
+ } else {
+ mCpuFreqsCountVerified = true;
+ return computeDelta(uid, cpuTimesMs);
+ }
+ }
+ // Read total cpu times from the proc file.
+ final String procFile = new StringBuilder(PROC_FILE_DIR)
+ .append(uid)
+ .append(PROC_FILE_NAME).toString();
+ final long[] cpuTimesMs;
+ try {
+ final byte[] data = mInjector.readData(procFile);
+ if (!mCpuFreqsCountVerified) {
+ verifyCpuFreqsCount(data.length, procFile);
+ }
+ final ByteBuffer buffer = ByteBuffer.wrap(data);
+ buffer.order(ByteOrder.nativeOrder());
+ cpuTimesMs = readCpuTimesFromByteBuffer(buffer);
+ } catch (Exception e) {
+ if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+ mSingleUidCpuTimesAvailable = false;
+ }
+ if (DBG) Slog.e(TAG, "Some error occured while reading " + procFile, e);
+ return null;
+ }
+
+ return computeDelta(uid, cpuTimesMs);
+ }
+ }
+
+ private void verifyCpuFreqsCount(int numBytes, String procFile) {
+ final int actualCount = (numBytes / Long.BYTES);
+ if (mCpuFreqsCount != actualCount) {
+ mSingleUidCpuTimesAvailable = false;
+ throw new IllegalStateException("Freq count didn't match,"
+ + "count from " + UID_TIMES_PROC_FILE + "=" + mCpuFreqsCount + ", but"
+ + "count from " + procFile + "=" + actualCount);
+ }
+ mCpuFreqsCountVerified = true;
+ }
+
+ private long[] readCpuTimesFromByteBuffer(ByteBuffer buffer) {
+ final long[] cpuTimesMs;
+ cpuTimesMs = new long[mCpuFreqsCount];
+ for (int i = 0; i < mCpuFreqsCount; ++i) {
+ // Times read will be in units of 10ms
+ cpuTimesMs[i] = buffer.getLong() * 10;
+ }
+ return cpuTimesMs;
+ }
+
+ /**
+ * Compute and return cpu times delta of an uid using previously read cpu times and
+ * {@param latestCpuTimesMs}.
+ *
+ * @return delta of cpu times if at least one of the cpu time at a freq is +ve, otherwise null.
+ */
+ public long[] computeDelta(int uid, @NonNull long[] latestCpuTimesMs) {
+ synchronized (this) {
+ if (!mSingleUidCpuTimesAvailable) {
+ return null;
+ }
+ // Subtract the last read cpu times to get deltas.
+ final long[] lastCpuTimesMs = mLastUidCpuTimeMs.get(uid);
+ final long[] deltaTimesMs = getDeltaLocked(lastCpuTimesMs, latestCpuTimesMs);
+ if (deltaTimesMs == null) {
+ if (DBG) Slog.e(TAG, "Malformed data read for uid=" + uid
+ + "; last=" + Arrays.toString(lastCpuTimesMs)
+ + "; latest=" + Arrays.toString(latestCpuTimesMs));
+ return null;
+ }
+ // If all elements are zero, return null to avoid unnecessary work on the caller side.
+ boolean hasNonZero = false;
+ for (int i = deltaTimesMs.length - 1; i >= 0; --i) {
+ if (deltaTimesMs[i] > 0) {
+ hasNonZero = true;
+ break;
+ }
+ }
+ if (hasNonZero) {
+ mLastUidCpuTimeMs.put(uid, latestCpuTimesMs);
+ return deltaTimesMs;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Returns null if the latest cpu times are not valid**, otherwise delta of
+ * {@param latestCpuTimesMs} and {@param lastCpuTimesMs}.
+ *
+ * **latest cpu times are considered valid if all the cpu times are +ve and
+ * greater than or equal to previously read cpu times.
+ */
+ @GuardedBy("this")
+ @VisibleForTesting(visibility = PACKAGE)
+ public long[] getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs) {
+ for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
+ if (latestCpuTimesMs[i] < 0) {
+ return null;
+ }
+ }
+ if (lastCpuTimesMs == null) {
+ return latestCpuTimesMs;
+ }
+ final long[] deltaTimesMs = new long[latestCpuTimesMs.length];
+ for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
+ deltaTimesMs[i] = latestCpuTimesMs[i] - lastCpuTimesMs[i];
+ if (deltaTimesMs[i] < 0) {
+ return null;
+ }
+ }
+ return deltaTimesMs;
+ }
+
+ public void setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs) {
+ synchronized (this) {
+ mLastUidCpuTimeMs.clear();
+ for (int i = allUidsCpuTimesMs.size() - 1; i >= 0; --i) {
+ final long[] cpuTimesMs = allUidsCpuTimesMs.valueAt(i);
+ if (cpuTimesMs != null) {
+ mLastUidCpuTimeMs.put(allUidsCpuTimesMs.keyAt(i), cpuTimesMs.clone());
+ }
+ }
+ }
+ }
+
+ public void removeUid(int uid) {
+ synchronized (this) {
+ mLastUidCpuTimeMs.delete(uid);
+ }
+ }
+
+ public void removeUidsInRange(int startUid, int endUid) {
+ if (endUid < startUid) {
+ return;
+ }
+ synchronized (this) {
+ mLastUidCpuTimeMs.put(startUid, null);
+ mLastUidCpuTimeMs.put(endUid, null);
+ final int startIdx = mLastUidCpuTimeMs.indexOfKey(startUid);
+ final int endIdx = mLastUidCpuTimeMs.indexOfKey(endUid);
+ mLastUidCpuTimeMs.removeAtRange(startIdx, endIdx - startIdx + 1);
+ }
+ }
+
+ /**
+ * Retrieves CPU time-in-state data for the specified UID and adds the accumulated
+ * delta to the supplied counter.
+ */
+ public void addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs) {
+ mInjector.addDelta(uid, counter, timestampMs, null);
+ }
+
+ /**
+ * Same as {@link #addDelta(int, LongArrayMultiStateCounter, long)}, also returning
+ * the delta in the supplied array container.
+ */
+ public void addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
+ LongArrayMultiStateCounter.LongArrayContainer deltaContainer) {
+ mInjector.addDelta(uid, counter, timestampMs, deltaContainer);
+ }
+
+ @VisibleForTesting
+ public static class Injector {
+ public byte[] readData(String procFile) throws IOException {
+ return Files.readAllBytes(Paths.get(procFile));
+ }
+
+ public native long[] readBpfData(int uid);
+
+ /**
+ * Reads CPU time-in-state data for the specified UID and adds the delta since the
+ * previous call to the current state stats in the LongArrayMultiStateCounter.
+ *
+ * The delta is also returned via the optional deltaOut parameter.
+ */
+ public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
+ LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
+ return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs,
+ deltaOut != null ? deltaOut.mNativeObject : 0);
+ }
+
+ @CriticalNative
+ private static native boolean addDeltaFromBpf(int uid,
+ long longArrayMultiStateCounterNativePointer, long timestampMs,
+ long longArrayContainerNativePointer);
+
+ /**
+ * Used for testing.
+ *
+ * Takes mock cpu-time-in-frequency data and uses it the same way eBPF data would be used.
+ */
+ public boolean addDeltaForTest(int uid, LongArrayMultiStateCounter counter,
+ long timestampMs, long[][] timeInFreqDataNanos,
+ LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
+ return addDeltaForTest(uid, counter.mNativeObject, timestampMs, timeInFreqDataNanos,
+ deltaOut != null ? deltaOut.mNativeObject : 0);
+ }
+
+ private static native boolean addDeltaForTest(int uid,
+ long longArrayMultiStateCounterNativePointer, long timestampMs,
+ long[][] timeInFreqDataNanos, long longArrayContainerNativePointer);
+ }
+
+ @VisibleForTesting
+ public SparseArray<long[]> getLastUidCpuTimeMs() {
+ return mLastUidCpuTimeMs;
+ }
+
+ @VisibleForTesting
+ public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) {
+ mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable;
+ }
+}
\ No newline at end of file
diff --git a/android-35/com/android/internal/os/LoggingPrintStream.java b/android-35/com/android/internal/os/LoggingPrintStream.java
new file mode 100644
index 0000000..4bf92bb
--- /dev/null
+++ b/android-35/com/android/internal/os/LoggingPrintStream.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2008 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.internal.os;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+import java.util.Formatter;
+import java.util.Locale;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A print stream which logs output line by line.
+ *
+ * {@hide}
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
[email protected]
+public abstract class LoggingPrintStream extends PrintStream {
+
+ private final StringBuilder builder = new StringBuilder();
+
+ /**
+ * A buffer that is initialized when raw bytes are first written to this
+ * stream. It may contain the leading bytes of multi-byte characters.
+ * Between writes this buffer is always ready to receive data; ie. the
+ * position is at the first unassigned byte and the limit is the capacity.
+ */
+ private ByteBuffer encodedBytes;
+
+ /**
+ * A buffer that is initialized when raw bytes are first written to this
+ * stream. Between writes this buffer is always clear; ie. the position is
+ * zero and the limit is the capacity.
+ */
+ private CharBuffer decodedChars;
+
+ /**
+ * Decodes bytes to characters using the system default charset. Initialized
+ * when raw bytes are first written to this stream.
+ */
+ private CharsetDecoder decoder;
+
+ protected LoggingPrintStream() {
+ super(new OutputStream() {
+ public void write(int oneByte) throws IOException {
+ throw new AssertionError();
+ }
+ });
+ }
+
+ /**
+ * Logs the given line.
+ */
+ protected abstract void log(String line);
+
+ @Override
+ public synchronized void flush() {
+ flush(true);
+ }
+
+ /**
+ * Searches buffer for line breaks and logs a message for each one.
+ *
+ * @param completely true if the ending chars should be treated as a line
+ * even though they don't end in a line break
+ */
+ private void flush(boolean completely) {
+ int length = builder.length();
+
+ int start = 0;
+ int nextBreak;
+
+ // Log one line for each line break.
+ while (start < length
+ && (nextBreak = builder.indexOf("\n", start)) != -1) {
+ log(builder.substring(start, nextBreak));
+ start = nextBreak + 1;
+ }
+
+ if (completely) {
+ // Log the remainder of the buffer.
+ if (start < length) {
+ log(builder.substring(start));
+ }
+ builder.setLength(0);
+ } else {
+ // Delete characters leading up to the next starting point.
+ builder.delete(0, start);
+ }
+ }
+
+ public void write(int oneByte) {
+ write(new byte[] { (byte) oneByte }, 0, 1);
+ }
+
+ @Override
+ public void write(byte[] buffer) {
+ write(buffer, 0, buffer.length);
+ }
+
+ @Override
+ public synchronized void write(byte bytes[], int start, int count) {
+ if (decoder == null) {
+ encodedBytes = ByteBuffer.allocate(80);
+ decodedChars = CharBuffer.allocate(80);
+ decoder = Charset.defaultCharset().newDecoder()
+ .onMalformedInput(CodingErrorAction.REPLACE)
+ .onUnmappableCharacter(CodingErrorAction.REPLACE);
+ }
+
+ int end = start + count;
+ while (start < end) {
+ // copy some bytes from the array to the long-lived buffer. This
+ // way, if we end with a partial character we don't lose it.
+ int numBytes = Math.min(encodedBytes.remaining(), end - start);
+ encodedBytes.put(bytes, start, numBytes);
+ start += numBytes;
+
+ encodedBytes.flip();
+ CoderResult coderResult;
+ do {
+ // decode bytes from the byte buffer into the char buffer
+ coderResult = decoder.decode(encodedBytes, decodedChars, false);
+
+ // copy chars from the char buffer into our string builder
+ decodedChars.flip();
+ builder.append(decodedChars);
+ decodedChars.clear();
+ } while (coderResult.isOverflow());
+ encodedBytes.compact();
+ }
+ flush(false);
+ }
+
+ /** Always returns false. */
+ @Override
+ public boolean checkError() {
+ return false;
+ }
+
+ /** Ignored. */
+ @Override
+ protected void setError() { /* ignored */ }
+
+ /** Ignored. */
+ @Override
+ public void close() { /* ignored */ }
+
+ @Override
+ public PrintStream format(String format, Object... args) {
+ return format(Locale.getDefault(), format, args);
+ }
+
+ @Override
+ public PrintStream printf(String format, Object... args) {
+ return format(format, args);
+ }
+
+ @Override
+ public PrintStream printf(Locale l, String format, Object... args) {
+ return format(l, format, args);
+ }
+
+ private final Formatter formatter = new Formatter(builder, null);
+
+ @Override
+ public synchronized PrintStream format(
+ Locale l, String format, Object... args) {
+ if (format == null) {
+ throw new NullPointerException("format");
+ }
+
+ formatter.format(l, format, args);
+ flush(false);
+ return this;
+ }
+
+ @Override
+ public synchronized void print(char[] charArray) {
+ builder.append(charArray);
+ flush(false);
+ }
+
+ @Override
+ public synchronized void print(char ch) {
+ builder.append(ch);
+ if (ch == '\n') {
+ flush(false);
+ }
+ }
+
+ @Override
+ public synchronized void print(double dnum) {
+ builder.append(dnum);
+ }
+
+ @Override
+ public synchronized void print(float fnum) {
+ builder.append(fnum);
+ }
+
+ @Override
+ public synchronized void print(int inum) {
+ builder.append(inum);
+ }
+
+ @Override
+ public synchronized void print(long lnum) {
+ builder.append(lnum);
+ }
+
+ @Override
+ public synchronized void print(Object obj) {
+ builder.append(obj);
+ flush(false);
+ }
+
+ @Override
+ public synchronized void print(String str) {
+ builder.append(str);
+ flush(false);
+ }
+
+ @Override
+ public synchronized void print(boolean bool) {
+ builder.append(bool);
+ }
+
+ @Override
+ public synchronized void println() {
+ flush(true);
+ }
+
+ @Override
+ public synchronized void println(char[] charArray) {
+ builder.append(charArray);
+ flush(true);
+ }
+
+ @Override
+ public synchronized void println(char ch) {
+ builder.append(ch);
+ flush(true);
+ }
+
+ @Override
+ public synchronized void println(double dnum) {
+ builder.append(dnum);
+ flush(true);
+ }
+
+ @Override
+ public synchronized void println(float fnum) {
+ builder.append(fnum);
+ flush(true);
+ }
+
+ @Override
+ public synchronized void println(int inum) {
+ builder.append(inum);
+ flush(true);
+ }
+
+ @Override
+ public synchronized void println(long lnum) {
+ builder.append(lnum);
+ flush(true);
+ }
+
+ @Override
+ public synchronized void println(Object obj) {
+ builder.append(obj);
+ flush(true);
+ }
+
+ @Override
+ public synchronized void println(String s) {
+ if (builder.length() == 0 && s != null) {
+ // Optimization for a simple println.
+ int length = s.length();
+
+ int start = 0;
+ int nextBreak;
+
+ // Log one line for each line break.
+ while (start < length
+ && (nextBreak = s.indexOf('\n', start)) != -1) {
+ log(s.substring(start, nextBreak));
+ start = nextBreak + 1;
+ }
+
+ if (start < length) {
+ log(s.substring(start));
+ }
+ } else {
+ builder.append(s);
+ flush(true);
+ }
+ }
+
+ @Override
+ public synchronized void println(boolean bool) {
+ builder.append(bool);
+ flush(true);
+ }
+
+ @Override
+ public synchronized PrintStream append(char c) {
+ print(c);
+ return this;
+ }
+
+ @Override
+ public synchronized PrintStream append(CharSequence csq) {
+ builder.append(csq);
+ flush(false);
+ return this;
+ }
+
+ @Override
+ public synchronized PrintStream append(
+ CharSequence csq, int start, int end) {
+ builder.append(csq, start, end);
+ flush(false);
+ return this;
+ }
+}
diff --git a/android-35/com/android/internal/os/LongArrayMultiStateCounter.java b/android-35/com/android/internal/os/LongArrayMultiStateCounter.java
new file mode 100644
index 0000000..07fa679
--- /dev/null
+++ b/android-35/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2021 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.internal.os;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Performs per-state counting of multi-element values over time. The class' behavior is illustrated
+ * by this example:
+ * <pre>
+ * // At 0 ms, the state of the tracked object is 0
+ * counter.setState(0, 0);
+ *
+ * // At 1000 ms, the state changes to 1
+ * counter.setState(1, 1000);
+ *
+ * // At 3000 ms, the tracked values are updated to {30, 300}
+ * arrayContainer.setValues(new long[]{{30, 300}};
+ * counter.updateValues(arrayContainer, 3000);
+ *
+ * // The values are distributed between states 0 and 1 according to the time
+ * // spent in those respective states. In this specific case, 1000 and 2000 ms.
+ * counter.getValues(arrayContainer, 0);
+ * // arrayContainer now has values {10, 100}
+ * counter.getValues(arrayContainer, 1);
+ * // arrayContainer now has values {20, 200}
+ * </pre>
+ *
+ * The tracked values are expected to increase monotonically.
+ *
+ * @hide
+ */
[email protected]
[email protected](
+ "com.android.platform.test.ravenwood.nativesubstitution.LongArrayMultiStateCounter_host")
+public final class LongArrayMultiStateCounter implements Parcelable {
+
+ /**
+ * Container for a native equivalent of a long[].
+ */
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
+ @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+ "com.android.platform.test.ravenwood.nativesubstitution"
+ + ".LongArrayMultiStateCounter_host$LongArrayContainer_host")
+ public static class LongArrayContainer {
+ private static NativeAllocationRegistry sRegistry;
+
+ // Visible to other objects in this package so that it can be passed to @CriticalNative
+ // methods.
+ final long mNativeObject;
+ private final int mLength;
+
+ public LongArrayContainer(int length) {
+ mLength = length;
+ mNativeObject = native_init(length);
+ registerNativeAllocation();
+ }
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private void registerNativeAllocation() {
+ if (sRegistry == null) {
+ synchronized (LongArrayMultiStateCounter.class) {
+ if (sRegistry == null) {
+ sRegistry = NativeAllocationRegistry.createMalloced(
+ LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());
+ }
+ }
+ }
+ sRegistry.registerNativeAllocation(this, mNativeObject);
+ }
+
+ private void registerNativeAllocation$ravenwood() {
+ // No-op under ravenwood
+ }
+
+ /**
+ * Copies the supplied values into the underlying native array.
+ */
+ public void setValues(long[] array) {
+ if (array.length != mLength) {
+ throw new IllegalArgumentException(
+ "Invalid array length: " + mLength + ", expected: " + mLength);
+ }
+ native_setValues(mNativeObject, array);
+ }
+
+ /**
+ * Copies the underlying native array values to the supplied array.
+ */
+ public void getValues(long[] array) {
+ if (array.length != mLength) {
+ throw new IllegalArgumentException(
+ "Invalid array length: " + mLength + ", expected: " + mLength);
+ }
+ native_getValues(mNativeObject, array);
+ }
+
+ /**
+ * Combines contained values into a smaller array by aggregating them
+ * according to an index map.
+ */
+ public boolean combineValues(long[] array, int[] indexMap) {
+ if (indexMap.length != mLength) {
+ throw new IllegalArgumentException(
+ "Wrong index map size " + indexMap.length + ", expected " + mLength);
+ }
+ return native_combineValues(mNativeObject, array, indexMap);
+ }
+
+ @Override
+ public String toString() {
+ final long[] array = new long[mLength];
+ getValues(array);
+ return Arrays.toString(array);
+ }
+
+ @CriticalNative
+ private static native long native_init(int length);
+
+ @CriticalNative
+ private static native long native_getReleaseFunc();
+
+ @FastNative
+ private static native void native_setValues(long nativeObject, long[] array);
+
+ @FastNative
+ private static native void native_getValues(long nativeObject, long[] array);
+
+ @FastNative
+ private static native boolean native_combineValues(long nativeObject, long[] array,
+ int[] indexMap);
+ }
+
+ private static volatile NativeAllocationRegistry sRegistry;
+ private static final AtomicReference<LongArrayContainer> sTmpArrayContainer =
+ new AtomicReference<>();
+
+ private final int mStateCount;
+ private final int mLength;
+
+ // Visible to other objects in this package so that it can be passed to @CriticalNative
+ // methods.
+ final long mNativeObject;
+
+ public LongArrayMultiStateCounter(int stateCount, int arrayLength) {
+ Preconditions.checkArgumentPositive(stateCount, "stateCount must be greater than 0");
+ mStateCount = stateCount;
+ mLength = arrayLength;
+ mNativeObject = native_init(stateCount, arrayLength);
+ registerNativeAllocation();
+ }
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private void registerNativeAllocation() {
+ if (sRegistry == null) {
+ synchronized (LongArrayMultiStateCounter.class) {
+ if (sRegistry == null) {
+ sRegistry = NativeAllocationRegistry.createMalloced(
+ LongArrayMultiStateCounter.class.getClassLoader(),
+ native_getReleaseFunc());
+ }
+ }
+ }
+ sRegistry.registerNativeAllocation(this, mNativeObject);
+ }
+
+ private void registerNativeAllocation$ravenwood() {
+ // No-op under ravenwood
+ }
+
+ private LongArrayMultiStateCounter(Parcel in) {
+ mNativeObject = native_initFromParcel(in);
+ registerNativeAllocation();
+
+ mStateCount = native_getStateCount(mNativeObject);
+ mLength = native_getArrayLength(mNativeObject);
+ }
+
+ public int getStateCount() {
+ return mStateCount;
+ }
+
+ public int getArrayLength() {
+ return mLength;
+ }
+
+ /**
+ * Enables or disables the counter. When the counter is disabled, it does not
+ * accumulate counts supplied by the {@link #updateValues} method.
+ */
+ public void setEnabled(boolean enabled, long timestampMs) {
+ native_setEnabled(mNativeObject, enabled, timestampMs);
+ }
+
+ /**
+ * Sets the current state to the supplied value.
+ */
+ public void setState(int state, long timestampMs) {
+ if (state < 0 || state >= mStateCount) {
+ throw new IllegalArgumentException(
+ "State: " + state + ", outside the range: [0-" + (mStateCount - 1) + "]");
+ }
+ native_setState(mNativeObject, state, timestampMs);
+ }
+
+ /**
+ * Copies time-in-state and timestamps from the supplied counter.
+ */
+ public void copyStatesFrom(LongArrayMultiStateCounter counter) {
+ if (mStateCount != counter.mStateCount) {
+ throw new IllegalArgumentException(
+ "State count is not the same: " + mStateCount + " vs. " + counter.mStateCount);
+ }
+ native_copyStatesFrom(mNativeObject, counter.mNativeObject);
+ }
+
+ /**
+ * Sets the new values for the given state.
+ */
+ public void setValues(int state, long[] values) {
+ if (state < 0 || state >= mStateCount) {
+ throw new IllegalArgumentException(
+ "State: " + state + ", outside the range: [0-" + (mStateCount - 1) + "]");
+ }
+ if (values.length != mLength) {
+ throw new IllegalArgumentException(
+ "Invalid array length: " + values.length + ", expected: " + mLength);
+ }
+ LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
+ if (container == null || container.mLength != values.length) {
+ container = new LongArrayContainer(values.length);
+ }
+ container.setValues(values);
+ native_setValues(mNativeObject, state, container.mNativeObject);
+ sTmpArrayContainer.set(container);
+ }
+
+ /**
+ * Sets the new values. The delta between the previously set values and these values
+ * is distributed among the state according to the time the object spent in those states
+ * since the previous call to updateValues.
+ */
+ public void updateValues(long[] values, long timestampMs) {
+ LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
+ if (container == null || container.mLength != values.length) {
+ container = new LongArrayContainer(values.length);
+ }
+ container.setValues(values);
+ updateValues(container, timestampMs);
+ sTmpArrayContainer.set(container);
+ }
+
+ /**
+ * Adds the supplied values to the current accumulated values in the counter.
+ */
+ public void incrementValues(long[] values, long timestampMs) {
+ LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
+ if (container == null || container.mLength != values.length) {
+ container = new LongArrayContainer(values.length);
+ }
+ container.setValues(values);
+ native_incrementValues(mNativeObject, container.mNativeObject, timestampMs);
+ sTmpArrayContainer.set(container);
+ }
+
+ /**
+ * Sets the new values. The delta between the previously set values and these values
+ * is distributed among the state according to the time the object spent in those states
+ * since the previous call to updateValues.
+ */
+ public void updateValues(LongArrayContainer longArrayContainer, long timestampMs) {
+ if (longArrayContainer.mLength != mLength) {
+ throw new IllegalArgumentException(
+ "Invalid array length: " + longArrayContainer.mLength + ", expected: "
+ + mLength);
+ }
+ native_updateValues(mNativeObject, longArrayContainer.mNativeObject, timestampMs);
+ }
+
+ /**
+ * Adds the supplied values to the current accumulated values in the counter.
+ */
+ public void addCounts(LongArrayContainer counts) {
+ if (counts.mLength != mLength) {
+ throw new IllegalArgumentException(
+ "Invalid array length: " + counts.mLength + ", expected: " + mLength);
+ }
+ native_addCounts(mNativeObject, counts.mNativeObject);
+ }
+
+ /**
+ * Resets the accumulated counts to 0.
+ */
+ public void reset() {
+ native_reset(mNativeObject);
+ }
+
+ /**
+ * Populates the array with the accumulated counts for the specified state.
+ */
+ public void getCounts(long[] counts, int state) {
+ LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
+ if (container == null || container.mLength != counts.length) {
+ container = new LongArrayContainer(counts.length);
+ }
+ getCounts(container, state);
+ container.getValues(counts);
+ sTmpArrayContainer.set(container);
+ }
+
+ /**
+ * Populates longArrayContainer with the accumulated counts for the specified state.
+ */
+ public void getCounts(LongArrayContainer longArrayContainer, int state) {
+ if (state < 0 || state >= mStateCount) {
+ throw new IllegalArgumentException(
+ "State: " + state + ", outside the range: [0-" + mStateCount + "]");
+ }
+ native_getCounts(mNativeObject, longArrayContainer.mNativeObject, state);
+ }
+
+ @Override
+ public String toString() {
+ return native_toString(mNativeObject);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ native_writeToParcel(mNativeObject, dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<LongArrayMultiStateCounter> CREATOR =
+ new Creator<LongArrayMultiStateCounter>() {
+ @Override
+ public LongArrayMultiStateCounter createFromParcel(Parcel in) {
+ return new LongArrayMultiStateCounter(in);
+ }
+
+ @Override
+ public LongArrayMultiStateCounter[] newArray(int size) {
+ return new LongArrayMultiStateCounter[size];
+ }
+ };
+
+
+ @CriticalNative
+ private static native long native_init(int stateCount, int arrayLength);
+
+ @CriticalNative
+ private static native long native_getReleaseFunc();
+
+ @CriticalNative
+ private static native void native_setEnabled(long nativeObject, boolean enabled,
+ long timestampMs);
+
+ @CriticalNative
+ private static native void native_setState(long nativeObject, int state, long timestampMs);
+
+ @CriticalNative
+ private static native void native_copyStatesFrom(long nativeObjectTarget,
+ long nativeObjectSource);
+
+ @CriticalNative
+ private static native void native_setValues(long nativeObject, int state,
+ long longArrayContainerNativeObject);
+
+ @CriticalNative
+ private static native void native_updateValues(long nativeObject,
+ long longArrayContainerNativeObject, long timestampMs);
+
+ @CriticalNative
+ private static native void native_incrementValues(long nativeObject,
+ long longArrayContainerNativeObject, long timestampMs);
+
+ @CriticalNative
+ private static native void native_addCounts(long nativeObject,
+ long longArrayContainerNativeObject);
+
+ @CriticalNative
+ private static native void native_reset(long nativeObject);
+
+ @CriticalNative
+ private static native void native_getCounts(long nativeObject,
+ long longArrayContainerNativeObject, int state);
+
+ @FastNative
+ private static native String native_toString(long nativeObject);
+
+ @FastNative
+ private static native void native_writeToParcel(long nativeObject, Parcel dest, int flags);
+
+ @FastNative
+ private static native long native_initFromParcel(Parcel parcel);
+
+ @CriticalNative
+ private static native int native_getStateCount(long nativeObject);
+
+ @CriticalNative
+ private static native int native_getArrayLength(long nativeObject);
+}
diff --git a/android-35/com/android/internal/os/LongMultiStateCounter.java b/android-35/com/android/internal/os/LongMultiStateCounter.java
new file mode 100644
index 0000000..e5662c7
--- /dev/null
+++ b/android-35/com/android/internal/os/LongMultiStateCounter.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2021 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.internal.os;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * Performs per-state counting of long integers over time. The tracked "value" is expected
+ * to increase monotonously. The counter keeps track of the current state. When the
+ * updateValue method is called, the delta from the previous invocation of this method
+ * and the new value is added to the counter corresponding to the current state. If the
+ * state changed in the interim, the delta is distributed proptionally.
+ *
+ * The class's behavior is illustrated by this example:
+ * <pre>
+ * // At 0 ms, the state of the tracked object is 0 and the initial tracked value is 100
+ * counter.setState(0, 0);
+ * counter.updateValue(100, 0);
+ *
+ * // At 1000 ms, the state changes to 1
+ * counter.setState(1, 1000);
+ *
+ * // At 3000 ms, the tracked value is updated to 130
+ * counter.updateValue(130, 3000);
+ *
+ * // The delta (130 - 100 = 30) is distributed between states 0 and 1 according to the time
+ * // spent in those respective states; in this specific case, 1000 and 2000 ms.
+ * long countForState0 == counter.getCount(0); // 10
+ * long countForState1 == counter.getCount(1); // 20
+ * </pre>
+ *
+ * The tracked values are expected to increase monotonically.
+ *
+ * @hide
+ */
[email protected]
[email protected](
+ "com.android.platform.test.ravenwood.nativesubstitution.LongMultiStateCounter_host")
+public final class LongMultiStateCounter implements Parcelable {
+
+ private static NativeAllocationRegistry sRegistry;
+
+ private final int mStateCount;
+
+ // Visible to other objects in this package so that it can be passed to @CriticalNative
+ // methods.
+ final long mNativeObject;
+
+ public LongMultiStateCounter(int stateCount) {
+ Preconditions.checkArgumentPositive(stateCount, "stateCount must be greater than 0");
+ mStateCount = stateCount;
+ mNativeObject = native_init(stateCount);
+ registerNativeAllocation();
+ }
+
+ private LongMultiStateCounter(Parcel in) {
+ mNativeObject = native_initFromParcel(in);
+ registerNativeAllocation();
+
+ mStateCount = native_getStateCount(mNativeObject);
+ }
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private void registerNativeAllocation() {
+ if (sRegistry == null) {
+ synchronized (LongMultiStateCounter.class) {
+ if (sRegistry == null) {
+ sRegistry = NativeAllocationRegistry.createMalloced(
+ LongMultiStateCounter.class.getClassLoader(), native_getReleaseFunc());
+ }
+ }
+ }
+ sRegistry.registerNativeAllocation(this, mNativeObject);
+ }
+
+ private void registerNativeAllocation$ravenwood() {
+ // No-op under ravenwood
+ }
+
+ public int getStateCount() {
+ return mStateCount;
+ }
+
+ /**
+ * Enables or disables the counter. When the counter is disabled, it does not
+ * accumulate counts supplied by the {@link #updateValue} method.
+ */
+ public void setEnabled(boolean enabled, long timestampMs) {
+ native_setEnabled(mNativeObject, enabled, timestampMs);
+ }
+
+ /**
+ * Sets the current state to the supplied value.
+ *
+ * @param state The new state
+ * @param timestampMs The time when the state change occurred, e.g.
+ * SystemClock.elapsedRealtime()
+ */
+ public void setState(int state, long timestampMs) {
+ if (state < 0 || state >= mStateCount) {
+ throw new IllegalArgumentException(
+ "State: " + state + ", outside the range: [0-" + (mStateCount - 1) + "]");
+ }
+ native_setState(mNativeObject, state, timestampMs);
+ }
+
+ /**
+ * Sets the new values. The delta between the previously set value and this value
+ * is distributed among the state according to the time the object spent in those states
+ * since the previous call to updateValue.
+ *
+ * @return The delta between the previous value and the new value.
+ */
+ public long updateValue(long value, long timestampMs) {
+ return native_updateValue(mNativeObject, value, timestampMs);
+ }
+
+ /**
+ * Adds the supplied values to the current accumulated values in the counter.
+ */
+ public void incrementValue(long count, long timestampMs) {
+ native_incrementValue(mNativeObject, count, timestampMs);
+ }
+
+ /**
+ * Adds the supplied values to the current accumulated values in the counter.
+ */
+ public void addCount(long count) {
+ native_addCount(mNativeObject, count);
+ }
+
+ /**
+ * Resets the accumulated counts to 0.
+ */
+ public void reset() {
+ native_reset(mNativeObject);
+ }
+
+ /**
+ * Returns the accumulated count for the specified state.
+ */
+ public long getCount(int state) {
+ if (state < 0 || state >= mStateCount) {
+ throw new IllegalArgumentException(
+ "State: " + state + ", outside the range: [0-" + mStateCount + "]");
+ }
+ return native_getCount(mNativeObject, state);
+ }
+
+ /**
+ * Returns the total accumulated count across all states.
+ */
+ public long getTotalCount() {
+ long total = 0;
+ for (int state = 0; state < mStateCount; state++) {
+ total += native_getCount(mNativeObject, state);
+ }
+ return total;
+ }
+
+ @Override
+ public String toString() {
+ return native_toString(mNativeObject);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ native_writeToParcel(mNativeObject, dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<LongMultiStateCounter> CREATOR =
+ new Creator<LongMultiStateCounter>() {
+ @Override
+ public LongMultiStateCounter createFromParcel(Parcel in) {
+ return new LongMultiStateCounter(in);
+ }
+
+ @Override
+ public LongMultiStateCounter[] newArray(int size) {
+ return new LongMultiStateCounter[size];
+ }
+ };
+
+
+ @CriticalNative
+ private static native long native_init(int stateCount);
+
+ @CriticalNative
+ private static native long native_getReleaseFunc();
+
+ @CriticalNative
+ private static native void native_setEnabled(long nativeObject, boolean enabled,
+ long timestampMs);
+
+ @CriticalNative
+ private static native void native_setState(long nativeObject, int state, long timestampMs);
+
+ @CriticalNative
+ private static native long native_updateValue(long nativeObject, long value, long timestampMs);
+
+ @CriticalNative
+ private static native void native_incrementValue(long nativeObject, long increment,
+ long timestampMs);
+
+ @CriticalNative
+ private static native void native_addCount(long nativeObject, long count);
+
+ @CriticalNative
+ private static native void native_reset(long nativeObject);
+
+ @CriticalNative
+ private static native long native_getCount(long nativeObject, int state);
+
+ @FastNative
+ private static native String native_toString(long nativeObject);
+
+ @FastNative
+ private static native void native_writeToParcel(long nativeObject, Parcel dest, int flags);
+
+ @FastNative
+ private static native long native_initFromParcel(Parcel parcel);
+
+ @CriticalNative
+ private static native int native_getStateCount(long nativeObject);
+}
diff --git a/android-35/com/android/internal/os/LooperStats.java b/android-35/com/android/internal/os/LooperStats.java
new file mode 100644
index 0000000..bbcea8a
--- /dev/null
+++ b/android-35/com/android/internal/os/LooperStats.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * Collects aggregated telemetry data about Looper message dispatching.
+ *
+ * @hide Only for use within the system server.
+ */
[email protected]
+public class LooperStats implements Looper.Observer {
+ public static final String DEBUG_ENTRY_PREFIX = "__DEBUG_";
+ private static final int SESSION_POOL_SIZE = 50;
+ private static final boolean DISABLED_SCREEN_STATE_TRACKING_VALUE = false;
+ public static final boolean DEFAULT_IGNORE_BATTERY_STATUS = false;
+
+ @GuardedBy("mLock")
+ private final SparseArray<Entry> mEntries = new SparseArray<>(512);
+ private final Object mLock = new Object();
+ private final Entry mOverflowEntry = new Entry("OVERFLOW");
+ private final Entry mHashCollisionEntry = new Entry("HASH_COLLISION");
+ private final ConcurrentLinkedQueue<DispatchSession> mSessionPool =
+ new ConcurrentLinkedQueue<>();
+ private final int mEntriesSizeCap;
+ private int mSamplingInterval;
+ private CachedDeviceState.Readonly mDeviceState;
+ private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch;
+ private long mStartCurrentTime = System.currentTimeMillis();
+ private long mStartElapsedTime = SystemClock.elapsedRealtime();
+ private boolean mAddDebugEntries = false;
+ private boolean mTrackScreenInteractive = false;
+ private boolean mIgnoreBatteryStatus = DEFAULT_IGNORE_BATTERY_STATUS;
+
+ public LooperStats(int samplingInterval, int entriesSizeCap) {
+ this.mSamplingInterval = samplingInterval;
+ this.mEntriesSizeCap = entriesSizeCap;
+ }
+
+ public void setDeviceState(@NonNull CachedDeviceState.Readonly deviceState) {
+ if (mBatteryStopwatch != null) {
+ mBatteryStopwatch.close();
+ }
+
+ mDeviceState = deviceState;
+ mBatteryStopwatch = deviceState.createTimeOnBatteryStopwatch();
+ }
+
+ public void setAddDebugEntries(boolean addDebugEntries) {
+ mAddDebugEntries = addDebugEntries;
+ }
+
+ @Override
+ public Object messageDispatchStarting() {
+ if (deviceStateAllowsCollection() && shouldCollectDetailedData()) {
+ DispatchSession session = mSessionPool.poll();
+ session = session == null ? new DispatchSession() : session;
+ session.startTimeMicro = getElapsedRealtimeMicro();
+ session.cpuStartMicro = getThreadTimeMicro();
+ session.systemUptimeMillis = getSystemUptimeMillis();
+ return session;
+ }
+
+ return DispatchSession.NOT_SAMPLED;
+ }
+
+ @Override
+ public void messageDispatched(Object token, Message msg) {
+ if (!deviceStateAllowsCollection()) {
+ return;
+ }
+
+ DispatchSession session = (DispatchSession) token;
+ Entry entry = findEntry(msg, /* allowCreateNew= */session != DispatchSession.NOT_SAMPLED);
+ if (entry != null) {
+ synchronized (entry) {
+ entry.messageCount++;
+ if (session != DispatchSession.NOT_SAMPLED) {
+ entry.recordedMessageCount++;
+ final long latency = getElapsedRealtimeMicro() - session.startTimeMicro;
+ final long cpuUsage = getThreadTimeMicro() - session.cpuStartMicro;
+ entry.totalLatencyMicro += latency;
+ entry.maxLatencyMicro = Math.max(entry.maxLatencyMicro, latency);
+ entry.cpuUsageMicro += cpuUsage;
+ entry.maxCpuUsageMicro = Math.max(entry.maxCpuUsageMicro, cpuUsage);
+ if (msg.getWhen() > 0) {
+ final long delay = Math.max(0L, session.systemUptimeMillis - msg.getWhen());
+ entry.delayMillis += delay;
+ entry.maxDelayMillis = Math.max(entry.maxDelayMillis, delay);
+ entry.recordedDelayMessageCount++;
+ }
+ }
+ }
+ }
+
+ recycleSession(session);
+ }
+
+ @Override
+ public void dispatchingThrewException(Object token, Message msg, Exception exception) {
+ if (!deviceStateAllowsCollection()) {
+ return;
+ }
+
+ DispatchSession session = (DispatchSession) token;
+ Entry entry = findEntry(msg, /* allowCreateNew= */session != DispatchSession.NOT_SAMPLED);
+ if (entry != null) {
+ synchronized (entry) {
+ entry.exceptionCount++;
+ }
+ }
+
+ recycleSession(session);
+ }
+
+ private boolean deviceStateAllowsCollection() {
+ if (mIgnoreBatteryStatus) {
+ return true;
+ }
+ if (mDeviceState == null) {
+ return false;
+ }
+ if (mDeviceState.isCharging()) {
+ return false;
+ }
+ return true;
+ }
+
+ /** Returns an array of {@link ExportedEntry entries} with the aggregated statistics. */
+ public List<ExportedEntry> getEntries() {
+ final ArrayList<ExportedEntry> exportedEntries;
+ synchronized (mLock) {
+ final int size = mEntries.size();
+ exportedEntries = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ Entry entry = mEntries.valueAt(i);
+ synchronized (entry) {
+ exportedEntries.add(new ExportedEntry(entry));
+ }
+ }
+ }
+ // Add the overflow and collision entries only if they have any data.
+ maybeAddSpecialEntry(exportedEntries, mOverflowEntry);
+ maybeAddSpecialEntry(exportedEntries, mHashCollisionEntry);
+ // Debug entries added to help validate the data.
+ if (mAddDebugEntries && mBatteryStopwatch != null) {
+ exportedEntries.add(createDebugEntry("start_time_millis", mStartElapsedTime));
+ exportedEntries.add(createDebugEntry("end_time_millis", SystemClock.elapsedRealtime()));
+ exportedEntries.add(
+ createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis()));
+ exportedEntries.add(createDebugEntry("sampling_interval", mSamplingInterval));
+ }
+ return exportedEntries;
+ }
+
+ private ExportedEntry createDebugEntry(String variableName, long value) {
+ final Entry entry = new Entry(DEBUG_ENTRY_PREFIX + variableName);
+ entry.messageCount = 1;
+ entry.recordedMessageCount = 1;
+ entry.totalLatencyMicro = value;
+ return new ExportedEntry(entry);
+ }
+
+ /** Returns a timestamp indicating when the statistics were last reset. */
+ public long getStartTimeMillis() {
+ return mStartCurrentTime;
+ }
+
+ public long getStartElapsedTimeMillis() {
+ return mStartElapsedTime;
+ }
+
+ public long getBatteryTimeMillis() {
+ return mBatteryStopwatch != null ? mBatteryStopwatch.getMillis() : 0;
+ }
+
+ private void maybeAddSpecialEntry(List<ExportedEntry> exportedEntries, Entry specialEntry) {
+ synchronized (specialEntry) {
+ if (specialEntry.messageCount > 0 || specialEntry.exceptionCount > 0) {
+ exportedEntries.add(new ExportedEntry(specialEntry));
+ }
+ }
+ }
+
+ /** Removes all collected data. */
+ public void reset() {
+ synchronized (mLock) {
+ mEntries.clear();
+ }
+ synchronized (mHashCollisionEntry) {
+ mHashCollisionEntry.reset();
+ }
+ synchronized (mOverflowEntry) {
+ mOverflowEntry.reset();
+ }
+ mStartCurrentTime = System.currentTimeMillis();
+ mStartElapsedTime = SystemClock.elapsedRealtime();
+ if (mBatteryStopwatch != null) {
+ mBatteryStopwatch.reset();
+ }
+ }
+
+ public void setSamplingInterval(int samplingInterval) {
+ mSamplingInterval = samplingInterval;
+ }
+
+ public void setTrackScreenInteractive(boolean enabled) {
+ mTrackScreenInteractive = enabled;
+ }
+
+ public void setIgnoreBatteryStatus(boolean ignore) {
+ mIgnoreBatteryStatus = ignore;
+ }
+
+ @Nullable
+ private Entry findEntry(Message msg, boolean allowCreateNew) {
+ final boolean isInteractive = mTrackScreenInteractive
+ ? mDeviceState.isScreenInteractive()
+ : DISABLED_SCREEN_STATE_TRACKING_VALUE;
+ final int id = Entry.idFor(msg, isInteractive);
+ Entry entry;
+ synchronized (mLock) {
+ entry = mEntries.get(id);
+ if (entry == null) {
+ if (!allowCreateNew) {
+ return null;
+ } else if (mEntries.size() >= mEntriesSizeCap) {
+ // If over the size cap track totals under OVERFLOW entry.
+ return mOverflowEntry;
+ } else {
+ entry = new Entry(msg, isInteractive);
+ mEntries.put(id, entry);
+ }
+ }
+ }
+
+ if (entry.workSourceUid != msg.workSourceUid
+ || entry.handler.getClass() != msg.getTarget().getClass()
+ || entry.handler.getLooper().getThread() != msg.getTarget().getLooper().getThread()
+ || entry.isInteractive != isInteractive) {
+ // If a hash collision happened, track totals under a single entry.
+ return mHashCollisionEntry;
+ }
+ return entry;
+ }
+
+ private void recycleSession(DispatchSession session) {
+ if (session != DispatchSession.NOT_SAMPLED && mSessionPool.size() < SESSION_POOL_SIZE) {
+ mSessionPool.add(session);
+ }
+ }
+
+ protected long getThreadTimeMicro() {
+ return SystemClock.currentThreadTimeMicro();
+ }
+
+ protected long getElapsedRealtimeMicro() {
+ return SystemClock.elapsedRealtimeNanos() / 1000;
+ }
+
+ protected long getSystemUptimeMillis() {
+ return SystemClock.uptimeMillis();
+ }
+
+ protected boolean shouldCollectDetailedData() {
+ return ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0;
+ }
+
+ private static class DispatchSession {
+ static final DispatchSession NOT_SAMPLED = new DispatchSession();
+ public long startTimeMicro;
+ public long cpuStartMicro;
+ public long systemUptimeMillis;
+ }
+
+ private static class Entry {
+ public final int workSourceUid;
+ public final Handler handler;
+ public final String messageName;
+ public final boolean isInteractive;
+ public long messageCount;
+ public long recordedMessageCount;
+ public long exceptionCount;
+ public long totalLatencyMicro;
+ public long maxLatencyMicro;
+ public long cpuUsageMicro;
+ public long maxCpuUsageMicro;
+ public long recordedDelayMessageCount;
+ public long delayMillis;
+ public long maxDelayMillis;
+
+ Entry(Message msg, boolean isInteractive) {
+ this.workSourceUid = msg.workSourceUid;
+ this.handler = msg.getTarget();
+ this.messageName = handler.getMessageName(msg);
+ this.isInteractive = isInteractive;
+ }
+
+ Entry(String specialEntryName) {
+ this.workSourceUid = Message.UID_NONE;
+ this.messageName = specialEntryName;
+ this.handler = null;
+ this.isInteractive = false;
+ }
+
+ void reset() {
+ messageCount = 0;
+ recordedMessageCount = 0;
+ exceptionCount = 0;
+ totalLatencyMicro = 0;
+ maxLatencyMicro = 0;
+ cpuUsageMicro = 0;
+ maxCpuUsageMicro = 0;
+ delayMillis = 0;
+ maxDelayMillis = 0;
+ recordedDelayMessageCount = 0;
+ }
+
+ static int idFor(Message msg, boolean isInteractive) {
+ int result = 7;
+ result = 31 * result + msg.workSourceUid;
+ result = 31 * result + msg.getTarget().getLooper().getThread().hashCode();
+ result = 31 * result + msg.getTarget().getClass().hashCode();
+ result = 31 * result + (isInteractive ? 1231 : 1237);
+ if (msg.getCallback() != null) {
+ return 31 * result + msg.getCallback().getClass().hashCode();
+ } else {
+ return 31 * result + msg.what;
+ }
+ }
+ }
+
+ /** Aggregated data of Looper message dispatching in the in the current process. */
+ public static class ExportedEntry {
+ public final int workSourceUid;
+ public final String handlerClassName;
+ public final String threadName;
+ public final String messageName;
+ public final boolean isInteractive;
+ public final long messageCount;
+ public final long recordedMessageCount;
+ public final long exceptionCount;
+ public final long totalLatencyMicros;
+ public final long maxLatencyMicros;
+ public final long cpuUsageMicros;
+ public final long maxCpuUsageMicros;
+ public final long maxDelayMillis;
+ public final long delayMillis;
+ public final long recordedDelayMessageCount;
+
+ ExportedEntry(Entry entry) {
+ this.workSourceUid = entry.workSourceUid;
+ if (entry.handler != null) {
+ this.handlerClassName = entry.handler.getClass().getName();
+ this.threadName = entry.handler.getLooper().getThread().getName();
+ } else {
+ // Overflow/collision entries do not have a handler set.
+ this.handlerClassName = "";
+ this.threadName = "";
+ }
+ this.isInteractive = entry.isInteractive;
+ this.messageName = entry.messageName;
+ this.messageCount = entry.messageCount;
+ this.recordedMessageCount = entry.recordedMessageCount;
+ this.exceptionCount = entry.exceptionCount;
+ this.totalLatencyMicros = entry.totalLatencyMicro;
+ this.maxLatencyMicros = entry.maxLatencyMicro;
+ this.cpuUsageMicros = entry.cpuUsageMicro;
+ this.maxCpuUsageMicros = entry.maxCpuUsageMicro;
+ this.delayMillis = entry.delayMillis;
+ this.maxDelayMillis = entry.maxDelayMillis;
+ this.recordedDelayMessageCount = entry.recordedDelayMessageCount;
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/MonotonicClock.java b/android-35/com/android/internal/os/MonotonicClock.java
new file mode 100644
index 0000000..eca6f58
--- /dev/null
+++ b/android-35/com/android/internal/os/MonotonicClock.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2023 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.internal.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * A clock that is similar to SystemClock#elapsedRealtime(), except that it is not reset
+ * on reboot, but keeps going.
+ */
[email protected]
+public class MonotonicClock {
+ private static final String TAG = "MonotonicClock";
+
+ private static final String XML_TAG_MONOTONIC_TIME = "monotonic_time";
+ private static final String XML_ATTR_TIMESHIFT = "timeshift";
+
+ private final AtomicFile mFile;
+ private final Clock mClock;
+ private final long mTimeshift;
+
+ public static final long UNDEFINED = -1;
+
+ public MonotonicClock(File file) {
+ this (file, Clock.SYSTEM_CLOCK.elapsedRealtime(), Clock.SYSTEM_CLOCK);
+ }
+
+ public MonotonicClock(long monotonicTime, @NonNull Clock clock) {
+ this(null, monotonicTime, clock);
+ }
+
+ public MonotonicClock(@Nullable File file, long monotonicTime, @NonNull Clock clock) {
+ mClock = clock;
+ if (file != null) {
+ mFile = new AtomicFile(file);
+ mTimeshift = read(monotonicTime - mClock.elapsedRealtime());
+ } else {
+ mFile = null;
+ mTimeshift = monotonicTime - mClock.elapsedRealtime();
+ }
+ }
+
+ /**
+ * Returns time in milliseconds, based on SystemClock.elapsedTime, adjusted so that
+ * after a device reboot the time keeps increasing.
+ */
+ public long monotonicTime() {
+ return monotonicTime(mClock.elapsedRealtime());
+ }
+
+ /**
+ * Like {@link #monotonicTime()}, except the elapsed time is supplied as an argument instead
+ * of being read from the Clock.
+ */
+ public long monotonicTime(long elapsedRealtimeMs) {
+ return mTimeshift + elapsedRealtimeMs;
+ }
+
+ private long read(long defaultTimeshift) {
+ if (!mFile.exists()) {
+ return defaultTimeshift;
+ }
+
+ try {
+ return readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e);
+ return defaultTimeshift;
+ }
+ }
+
+ /**
+ * Saves the timeshift into a file. Call this method just before system shutdown, after
+ * writing the last battery history event.
+ */
+ public void write() {
+ if (mFile == null) {
+ return;
+ }
+
+ FileOutputStream out = null;
+ try {
+ out = mFile.startWrite();
+ writeXml(out, Xml.newBinarySerializer());
+ mFile.finishWrite(out);
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
+ mFile.failWrite(out);
+ }
+ }
+
+ /**
+ * Parses an XML file containing the persistent state of the monotonic clock.
+ */
+ private long readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
+ long savedTimeshift = 0;
+ try {
+ parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG
+ && parser.getName().equals(XML_TAG_MONOTONIC_TIME)) {
+ savedTimeshift = parser.getAttributeLong(null, XML_ATTR_TIMESHIFT);
+ }
+ eventType = parser.next();
+ }
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ return savedTimeshift - mClock.elapsedRealtime();
+ }
+
+ /**
+ * Creates an XML file containing the persistent state of the monotonic clock.
+ */
+ private void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+ serializer.setOutput(out, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.startTag(null, XML_TAG_MONOTONIC_TIME);
+ serializer.attributeLong(null, XML_ATTR_TIMESHIFT, monotonicTime());
+ serializer.endTag(null, XML_TAG_MONOTONIC_TIME);
+ serializer.endDocument();
+ }
+}
diff --git a/android-35/com/android/internal/os/PowerProfile.java b/android-35/com/android/internal/os/PowerProfile.java
new file mode 100644
index 0000000..9646ae9
--- /dev/null
+++ b/android-35/com/android/internal/os/PowerProfile.java
@@ -0,0 +1,1214 @@
+/*
+ * Copyright (C) 2009 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.internal.os;
+
+
+import android.annotation.LongDef;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.annotation.XmlRes;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.power.ModemPowerProfile;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Reports power consumption values for various device activities. Reads values from an XML file.
+ * Customize the XML file for different devices.
+ * [hidden]
+ */
[email protected]
+public class PowerProfile {
+
+ public static final String TAG = "PowerProfile";
+
+ /*
+ * POWER_CPU_SUSPEND: Power consumption when CPU is in power collapse mode.
+ * POWER_CPU_IDLE: Power consumption when CPU is awake (when a wake lock is held). This should
+ * be zero on devices that can go into full CPU power collapse even when a wake
+ * lock is held. Otherwise, this is the power consumption in addition to
+ * POWER_CPU_SUSPEND due to a wake lock being held but with no CPU activity.
+ * POWER_CPU_ACTIVE: Power consumption when CPU is running, excluding power consumed by clusters
+ * and cores.
+ *
+ * CPU Power Equation (assume two clusters):
+ * Total power = POWER_CPU_SUSPEND (always added)
+ * + POWER_CPU_IDLE (skip this and below if in power collapse mode)
+ * + POWER_CPU_ACTIVE (skip this and below if CPU is not running, but a wakelock
+ * is held)
+ * + cluster_power.cluster0 + cluster_power.cluster1 (skip cluster not running)
+ * + core_power.cluster0 * num running cores in cluster 0
+ * + core_power.cluster1 * num running cores in cluster 1
+ */
+ public static final String POWER_CPU_SUSPEND = "cpu.suspend";
+ @UnsupportedAppUsage
+ public static final String POWER_CPU_IDLE = "cpu.idle";
+ @UnsupportedAppUsage
+ public static final String POWER_CPU_ACTIVE = "cpu.active";
+
+ /**
+ * Power consumption when WiFi driver is scanning for networks.
+ */
+ @UnsupportedAppUsage
+ public static final String POWER_WIFI_SCAN = "wifi.scan";
+
+ /**
+ * Power consumption when WiFi driver is on.
+ */
+ @UnsupportedAppUsage
+ public static final String POWER_WIFI_ON = "wifi.on";
+
+ /**
+ * Power consumption when WiFi driver is transmitting/receiving.
+ */
+ @UnsupportedAppUsage
+ public static final String POWER_WIFI_ACTIVE = "wifi.active";
+
+ //
+ // Updated power constants. These are not estimated, they are real world
+ // currents and voltages for the underlying bluetooth and wifi controllers.
+ //
+ public static final String POWER_WIFI_CONTROLLER_IDLE = "wifi.controller.idle";
+ public static final String POWER_WIFI_CONTROLLER_RX = "wifi.controller.rx";
+ public static final String POWER_WIFI_CONTROLLER_TX = "wifi.controller.tx";
+ public static final String POWER_WIFI_CONTROLLER_TX_LEVELS = "wifi.controller.tx_levels";
+ public static final String POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE = "wifi.controller.voltage";
+
+ public static final String POWER_BLUETOOTH_CONTROLLER_IDLE = "bluetooth.controller.idle";
+ public static final String POWER_BLUETOOTH_CONTROLLER_RX = "bluetooth.controller.rx";
+ public static final String POWER_BLUETOOTH_CONTROLLER_TX = "bluetooth.controller.tx";
+ public static final String POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE =
+ "bluetooth.controller.voltage";
+
+ public static final String POWER_MODEM_CONTROLLER_SLEEP = "modem.controller.sleep";
+ public static final String POWER_MODEM_CONTROLLER_IDLE = "modem.controller.idle";
+ public static final String POWER_MODEM_CONTROLLER_RX = "modem.controller.rx";
+ public static final String POWER_MODEM_CONTROLLER_TX = "modem.controller.tx";
+ public static final String POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE =
+ "modem.controller.voltage";
+
+ /**
+ * Power consumption when GPS is on.
+ */
+ @UnsupportedAppUsage
+ public static final String POWER_GPS_ON = "gps.on";
+
+ /**
+ * GPS power parameters based on signal quality
+ */
+ public static final String POWER_GPS_SIGNAL_QUALITY_BASED = "gps.signalqualitybased";
+ public static final String POWER_GPS_OPERATING_VOLTAGE = "gps.voltage";
+
+ /**
+ * Power consumption when Bluetooth driver is on.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static final String POWER_BLUETOOTH_ON = "bluetooth.on";
+
+ /**
+ * Power consumption when Bluetooth driver is transmitting/receiving.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ public static final String POWER_BLUETOOTH_ACTIVE = "bluetooth.active";
+
+ /**
+ * Power consumption when Bluetooth driver gets an AT command.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static final String POWER_BLUETOOTH_AT_CMD = "bluetooth.at";
+
+ /**
+ * Power consumption when screen is in doze/ambient/always-on mode, including backlight power.
+ *
+ * @deprecated Use {@link #POWER_GROUP_DISPLAY_AMBIENT} instead.
+ */
+ @Deprecated
+ public static final String POWER_AMBIENT_DISPLAY = "ambient.on";
+
+ /**
+ * Power consumption when screen is on, not including the backlight power.
+ *
+ * @deprecated Use {@link #POWER_GROUP_DISPLAY_SCREEN_ON} instead.
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static final String POWER_SCREEN_ON = "screen.on";
+
+ /**
+ * Power consumption when cell radio is on but not on a call.
+ */
+ @UnsupportedAppUsage
+ public static final String POWER_RADIO_ON = "radio.on";
+
+ /**
+ * Power consumption when cell radio is hunting for a signal.
+ */
+ @UnsupportedAppUsage
+ public static final String POWER_RADIO_SCANNING = "radio.scanning";
+
+ /**
+ * Power consumption when talking on the phone.
+ */
+ @UnsupportedAppUsage
+ public static final String POWER_RADIO_ACTIVE = "radio.active";
+
+ /**
+ * Power consumption at full backlight brightness. If the backlight is at
+ * 50% brightness, then this should be multiplied by 0.5
+ *
+ * @deprecated Use {@link #POWER_GROUP_DISPLAY_SCREEN_FULL} instead.
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static final String POWER_SCREEN_FULL = "screen.full";
+
+ /**
+ * Power consumed by the audio hardware when playing back audio content. This is in addition
+ * to the CPU power, probably due to a DSP and / or amplifier.
+ */
+ public static final String POWER_AUDIO = "audio";
+
+ /**
+ * Power consumed by any media hardware when playing back video content. This is in addition
+ * to the CPU power, probably due to a DSP.
+ */
+ public static final String POWER_VIDEO = "video";
+
+ /**
+ * Average power consumption when camera flashlight is on.
+ */
+ public static final String POWER_FLASHLIGHT = "camera.flashlight";
+
+ /**
+ * Power consumption when DDR is being used.
+ */
+ public static final String POWER_MEMORY = "memory.bandwidths";
+
+ /**
+ * Average power consumption when the camera is on over all standard use cases.
+ *
+ * TODO: Add more fine-grained camera power metrics.
+ */
+ public static final String POWER_CAMERA = "camera.avg";
+
+ /**
+ * Power consumed by wif batched scaning. Broken down into bins by
+ * Channels Scanned per Hour. May do 1-720 scans per hour of 1-100 channels
+ * for a range of 1-72,000. Going logrithmic (1-8, 9-64, 65-512, 513-4096, 4097-)!
+ */
+ public static final String POWER_WIFI_BATCHED_SCAN = "wifi.batchedscan";
+
+ /**
+ * Battery capacity in milliAmpHour (mAh).
+ */
+ public static final String POWER_BATTERY_CAPACITY = "battery.capacity";
+
+ /**
+ * Power consumption when a screen is in doze/ambient/always-on mode, including backlight power.
+ */
+ public static final String POWER_GROUP_DISPLAY_AMBIENT = "ambient.on.display";
+
+ /**
+ * Power consumption when a screen is on, not including the backlight power.
+ */
+ public static final String POWER_GROUP_DISPLAY_SCREEN_ON = "screen.on.display";
+
+ /**
+ * Power consumption of a screen at full backlight brightness.
+ */
+ public static final String POWER_GROUP_DISPLAY_SCREEN_FULL = "screen.full.display";
+
+ @StringDef(prefix = { "POWER_GROUP_" }, value = {
+ POWER_GROUP_DISPLAY_AMBIENT,
+ POWER_GROUP_DISPLAY_SCREEN_ON,
+ POWER_GROUP_DISPLAY_SCREEN_FULL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PowerGroup {}
+
+ /**
+ * Constants for generating a 64bit power constant key.
+ *
+ * The bitfields of a key describes what its corresponding power constant represents:
+ * [63:40] - RESERVED
+ * [39:32] - {@link Subsystem} (max count = 16).
+ * [31:0] - per Subsystem fields, see {@link ModemPowerProfile}.
+ *
+ */
+ private static final long SUBSYSTEM_MASK = 0xF_0000_0000L;
+ /**
+ * Power constant not associated with a subsystem.
+ */
+ public static final long SUBSYSTEM_NONE = 0x0_0000_0000L;
+ /**
+ * Modem power constant.
+ */
+ public static final long SUBSYSTEM_MODEM = 0x1_0000_0000L;
+
+ @LongDef(prefix = { "SUBSYSTEM_" }, value = {
+ SUBSYSTEM_NONE,
+ SUBSYSTEM_MODEM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Subsystem {}
+
+ private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFF_FFFF;
+
+ public static final int POWER_BRACKETS_UNSPECIFIED = -1;
+
+ /**
+ * A map from Power Use Item to its power consumption.
+ */
+ static final HashMap<String, Double> sPowerItemMap = new HashMap<>();
+ /**
+ * A map from Power Use Item to an array of its power consumption
+ * (for items with variable power e.g. CPU).
+ */
+ static final HashMap<String, Double[]> sPowerArrayMap = new HashMap<>();
+
+ static final ModemPowerProfile sModemPowerProfile = new ModemPowerProfile();
+
+ private static final String TAG_DEVICE = "device";
+ private static final String TAG_ITEM = "item";
+ private static final String TAG_ARRAY = "array";
+ private static final String TAG_ARRAYITEM = "value";
+ private static final String ATTR_NAME = "name";
+
+ private static final String TAG_MODEM = "modem";
+
+ private static final Object sLock = new Object();
+
+ private int mCpuPowerBracketCount;
+
+ @VisibleForTesting
+ public PowerProfile() {
+ synchronized (sLock) {
+ initLocked();
+ }
+ }
+
+ @VisibleForTesting
+ @UnsupportedAppUsage
+ public PowerProfile(Context context) {
+ this(context, false);
+ }
+
+ /**
+ * For PowerProfileTest
+ */
+ @VisibleForTesting
+ public PowerProfile(Context context, boolean forTest) {
+ // Read the XML file for the given profile (normally only one per device)
+ synchronized (sLock) {
+ final int xmlId = forTest ? com.android.internal.R.xml.power_profile_test
+ : com.android.internal.R.xml.power_profile;
+ initLocked(context, xmlId);
+ }
+ }
+
+ /**
+ * Reinitialize the PowerProfile with the provided XML.
+ * WARNING: use only for testing!
+ */
+ @VisibleForTesting
+ public void initForTesting(XmlPullParser parser) {
+ initForTesting(parser, null);
+ }
+
+ /**
+ * Reinitialize the PowerProfile with the provided XML, using optional Resources for fallback
+ * configuration settings.
+ * WARNING: use only for testing!
+ */
+ @VisibleForTesting
+ public void initForTesting(XmlPullParser parser, @Nullable Resources resources) {
+ synchronized (sLock) {
+ sPowerItemMap.clear();
+ sPowerArrayMap.clear();
+ sModemPowerProfile.clear();
+
+ try {
+ readPowerValuesFromXml(parser, resources);
+ } finally {
+ if (parser instanceof XmlResourceParser) {
+ ((XmlResourceParser) parser).close();
+ }
+ }
+ initLocked();
+ }
+ }
+
+ @GuardedBy("sLock")
+ private void initLocked(Context context, @XmlRes int xmlId) {
+ if (sPowerItemMap.size() == 0 && sPowerArrayMap.size() == 0) {
+ final Resources resources = context.getResources();
+ XmlResourceParser parser = resources.getXml(xmlId);
+ readPowerValuesFromXml(parser, resources);
+ }
+ initLocked();
+ }
+
+ private void initLocked() {
+ initCpuClusters();
+ initCpuScalingPolicies();
+ initCpuPowerBrackets();
+ initDisplays();
+ initModem();
+ }
+
+ private static void readPowerValuesFromXml(XmlPullParser parser,
+ @Nullable Resources resources) {
+ boolean parsingArray = false;
+ ArrayList<Double> array = new ArrayList<>();
+ String arrayName = null;
+
+ try {
+ XmlUtils.beginDocument(parser, TAG_DEVICE);
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+
+ String element = parser.getName();
+ if (element == null) break;
+
+ if (parsingArray && !element.equals(TAG_ARRAYITEM)) {
+ // Finish array
+ sPowerArrayMap.put(arrayName, array.toArray(new Double[array.size()]));
+ parsingArray = false;
+ }
+ if (element.equals(TAG_ARRAY)) {
+ parsingArray = true;
+ array.clear();
+ arrayName = parser.getAttributeValue(null, ATTR_NAME);
+ } else if (element.equals(TAG_ITEM) || element.equals(TAG_ARRAYITEM)) {
+ String name = null;
+ if (!parsingArray) name = parser.getAttributeValue(null, ATTR_NAME);
+ if (parser.next() == XmlPullParser.TEXT) {
+ String power = parser.getText();
+ double value = 0;
+ try {
+ value = Double.valueOf(power);
+ } catch (NumberFormatException nfe) {
+ }
+ if (element.equals(TAG_ITEM)) {
+ sPowerItemMap.put(name, value);
+ } else if (parsingArray) {
+ array.add(value);
+ }
+ }
+ } else if (element.equals(TAG_MODEM)) {
+ sModemPowerProfile.parseFromXml(parser);
+ }
+ }
+ if (parsingArray) {
+ sPowerArrayMap.put(arrayName, array.toArray(new Double[array.size()]));
+ }
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ if (parser instanceof XmlResourceParser) {
+ ((XmlResourceParser) parser).close();
+ }
+ }
+
+ if (resources != null) {
+ getDefaultValuesFromConfig(resources);
+ }
+ }
+
+ private static void getDefaultValuesFromConfig(Resources resources) {
+ // Now collect other config variables.
+ int[] configResIds = new int[]{
+ com.android.internal.R.integer.config_bluetooth_idle_cur_ma,
+ com.android.internal.R.integer.config_bluetooth_rx_cur_ma,
+ com.android.internal.R.integer.config_bluetooth_tx_cur_ma,
+ com.android.internal.R.integer.config_bluetooth_operating_voltage_mv,
+ };
+
+ String[] configResIdKeys = new String[]{
+ POWER_BLUETOOTH_CONTROLLER_IDLE,
+ POWER_BLUETOOTH_CONTROLLER_RX,
+ POWER_BLUETOOTH_CONTROLLER_TX,
+ POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE,
+ };
+
+ for (int i = 0; i < configResIds.length; i++) {
+ String key = configResIdKeys[i];
+ // if we already have some of these parameters in power_profile.xml, ignore the
+ // value in config.xml
+ if ((sPowerItemMap.containsKey(key) && sPowerItemMap.get(key) > 0)) {
+ continue;
+ }
+ int value = resources.getInteger(configResIds[i]);
+ if (value > 0) {
+ sPowerItemMap.put(key, (double) value);
+ }
+ }
+ }
+
+ private CpuClusterKey[] mCpuClusters;
+
+ private static final String CPU_PER_CLUSTER_CORE_COUNT = "cpu.clusters.cores";
+ private static final String CPU_CLUSTER_POWER_COUNT = "cpu.cluster_power.cluster";
+ private static final String CPU_CORE_SPEED_PREFIX = "cpu.core_speeds.cluster";
+ private static final String CPU_CORE_POWER_PREFIX = "cpu.core_power.cluster";
+ private static final String CPU_POWER_BRACKETS_PREFIX = "cpu.power_brackets.policy";
+
+ private void initCpuClusters() {
+ if (sPowerArrayMap.containsKey(CPU_PER_CLUSTER_CORE_COUNT)) {
+ final Double[] data = sPowerArrayMap.get(CPU_PER_CLUSTER_CORE_COUNT);
+ mCpuClusters = new CpuClusterKey[data.length];
+ for (int cluster = 0; cluster < data.length; cluster++) {
+ int numCpusInCluster = (int) Math.round(data[cluster]);
+ mCpuClusters[cluster] = new CpuClusterKey(
+ CPU_CORE_SPEED_PREFIX + cluster, CPU_CLUSTER_POWER_COUNT + cluster,
+ CPU_CORE_POWER_PREFIX + cluster, numCpusInCluster);
+ }
+ } else {
+ // Default to single.
+ mCpuClusters = new CpuClusterKey[1];
+ int numCpus = 1;
+ if (sPowerItemMap.containsKey(CPU_PER_CLUSTER_CORE_COUNT)) {
+ numCpus = (int) Math.round(sPowerItemMap.get(CPU_PER_CLUSTER_CORE_COUNT));
+ }
+ mCpuClusters[0] = new CpuClusterKey(CPU_CORE_SPEED_PREFIX + 0,
+ CPU_CLUSTER_POWER_COUNT + 0, CPU_CORE_POWER_PREFIX + 0, numCpus);
+ }
+ }
+
+ private SparseArray<CpuScalingPolicyPower> mCpuScalingPolicies;
+ private static final String CPU_SCALING_POLICY_POWER_POLICY = "cpu.scaling_policy_power.policy";
+ private static final String CPU_SCALING_STEP_POWER_POLICY = "cpu.scaling_step_power.policy";
+
+ private void initCpuScalingPolicies() {
+ int policyCount = 0;
+ for (String key : sPowerItemMap.keySet()) {
+ if (key.startsWith(CPU_SCALING_POLICY_POWER_POLICY)) {
+ int policy =
+ Integer.parseInt(key.substring(CPU_SCALING_POLICY_POWER_POLICY.length()));
+ policyCount = Math.max(policyCount, policy + 1);
+ }
+ }
+ for (String key : sPowerArrayMap.keySet()) {
+ if (key.startsWith(CPU_SCALING_STEP_POWER_POLICY)) {
+ int policy =
+ Integer.parseInt(key.substring(CPU_SCALING_STEP_POWER_POLICY.length()));
+ policyCount = Math.max(policyCount, policy + 1);
+ }
+ }
+
+ if (policyCount > 0) {
+ mCpuScalingPolicies = new SparseArray<>(policyCount);
+ for (int policy = 0; policy < policyCount; policy++) {
+ Double policyPower = sPowerItemMap.get(CPU_SCALING_POLICY_POWER_POLICY + policy);
+ Double[] stepPower = sPowerArrayMap.get(CPU_SCALING_STEP_POWER_POLICY + policy);
+ if (policyPower != null || stepPower != null) {
+ double[] primitiveStepPower;
+ if (stepPower != null) {
+ primitiveStepPower = new double[stepPower.length];
+ for (int i = 0; i < stepPower.length; i++) {
+ primitiveStepPower[i] = stepPower[i];
+ }
+ } else {
+ primitiveStepPower = new double[0];
+ }
+ mCpuScalingPolicies.put(policy, new CpuScalingPolicyPower(
+ policyPower != null ? policyPower : 0, primitiveStepPower));
+ }
+ }
+ } else {
+ // Legacy power_profile.xml
+ int cpuId = 0;
+ for (CpuClusterKey cpuCluster : mCpuClusters) {
+ policyCount = cpuId + 1;
+ cpuId += cpuCluster.numCpus;
+ }
+
+ if (policyCount > 0) {
+ mCpuScalingPolicies = new SparseArray<>(policyCount);
+ cpuId = 0;
+ for (CpuClusterKey cpuCluster : mCpuClusters) {
+ double clusterPower = getAveragePower(cpuCluster.clusterPowerKey);
+ double[] stepPower;
+ int numSteps = getNumElements(cpuCluster.corePowerKey);
+ if (numSteps != 0) {
+ stepPower = new double[numSteps];
+ for (int step = 0; step < numSteps; step++) {
+ stepPower[step] = getAveragePower(cpuCluster.corePowerKey, step);
+ }
+ } else {
+ stepPower = new double[1];
+ }
+ mCpuScalingPolicies.put(cpuId,
+ new CpuScalingPolicyPower(clusterPower, stepPower));
+ cpuId += cpuCluster.numCpus;
+ }
+ } else {
+ mCpuScalingPolicies = new SparseArray<>(1);
+ mCpuScalingPolicies.put(0,
+ new CpuScalingPolicyPower(getAveragePower(POWER_CPU_ACTIVE),
+ new double[]{0}));
+ }
+ }
+ }
+
+ /**
+ * Parses or computes CPU power brackets: groups of states with similar power requirements.
+ */
+ private void initCpuPowerBrackets() {
+ boolean anyBracketsSpecified = false;
+ boolean allBracketsSpecified = true;
+ for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
+ int policy = mCpuScalingPolicies.keyAt(i);
+ CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
+ final int steps = cpuScalingPolicyPower.stepPower.length;
+ cpuScalingPolicyPower.powerBrackets = new int[steps];
+ if (sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + policy) != null) {
+ anyBracketsSpecified = true;
+ } else {
+ allBracketsSpecified = false;
+ }
+ }
+ if (anyBracketsSpecified && !allBracketsSpecified) {
+ throw new RuntimeException(
+ "Power brackets should be specified for all scaling policies or none");
+ }
+
+ if (!allBracketsSpecified) {
+ mCpuPowerBracketCount = POWER_BRACKETS_UNSPECIFIED;
+ return;
+ }
+
+ mCpuPowerBracketCount = 0;
+ for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
+ int policy = mCpuScalingPolicies.keyAt(i);
+ CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
+ final Double[] data = sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + policy);
+ if (data.length != cpuScalingPolicyPower.powerBrackets.length) {
+ throw new RuntimeException(
+ "Wrong number of items in " + CPU_POWER_BRACKETS_PREFIX + policy
+ + ", expected: "
+ + cpuScalingPolicyPower.powerBrackets.length);
+ }
+
+ for (int j = 0; j < data.length; j++) {
+ final int bracket = (int) Math.round(data[j]);
+ cpuScalingPolicyPower.powerBrackets[j] = bracket;
+ if (bracket > mCpuPowerBracketCount) {
+ mCpuPowerBracketCount = bracket;
+ }
+ }
+ }
+ mCpuPowerBracketCount++;
+ }
+
+ private static class CpuScalingPolicyPower {
+ public final double policyPower;
+ public final double[] stepPower;
+ public int[] powerBrackets;
+
+ private CpuScalingPolicyPower(double policyPower, double[] stepPower) {
+ this.policyPower = policyPower;
+ this.stepPower = stepPower;
+ }
+ }
+
+ /**
+ * Returns the average additional power in (mA) when the CPU scaling policy <code>policy</code>
+ * is used.
+ *
+ * @param policy Policy ID as per <code>ls /sys/devices/system/cpu/cpufreq</code>. Typically,
+ * policy ID corresponds to the index of the first related CPU, e.g. for "policy6"
+ * <code>/sys/devices/system/cpu/cpufreq/policy6/related_cpus</code> will
+ * contain CPU IDs like <code>6, 7</code>
+ */
+ public double getAveragePowerForCpuScalingPolicy(int policy) {
+ CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.get(policy);
+ if (cpuScalingPolicyPower != null) {
+ return cpuScalingPolicyPower.policyPower;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the average additional power in (mA) when the CPU scaling policy <code>policy</code>
+ * is used at the <code>step</code> frequency step (this is not the frequency itself, but the
+ * integer index of the frequency step).
+ */
+ public double getAveragePowerForCpuScalingStep(int policy, int step) {
+ CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.get(policy);
+ if (cpuScalingPolicyPower != null
+ && step >= 0 && step < cpuScalingPolicyPower.stepPower.length) {
+ return cpuScalingPolicyPower.stepPower[step];
+ }
+ return 0;
+ }
+
+ private static class CpuClusterKey {
+ public final String freqKey;
+ public final String clusterPowerKey;
+ public final String corePowerKey;
+ public final int numCpus;
+
+ private CpuClusterKey(String freqKey, String clusterPowerKey,
+ String corePowerKey, int numCpus) {
+ this.freqKey = freqKey;
+ this.clusterPowerKey = clusterPowerKey;
+ this.corePowerKey = corePowerKey;
+ this.numCpus = numCpus;
+ }
+ }
+
+ /**
+ * @deprecated Use CpuScalingPolicy instead
+ */
+ @UnsupportedAppUsage
+ @Deprecated
+ public int getNumCpuClusters() {
+ return mCpuClusters.length;
+ }
+
+ /**
+ * @deprecated Use CpuScalingPolicy instead
+ */
+ @Deprecated
+ public int getNumCoresInCpuCluster(int cluster) {
+ if (cluster < 0 || cluster >= mCpuClusters.length) {
+ return 0; // index out of bound
+ }
+ return mCpuClusters[cluster].numCpus;
+ }
+
+ /**
+ * @deprecated Use CpuScalingPolicy instead
+ */
+ @UnsupportedAppUsage
+ @Deprecated
+ public int getNumSpeedStepsInCpuCluster(int cluster) {
+ if (cluster < 0 || cluster >= mCpuClusters.length) {
+ return 0; // index out of bound
+ }
+ if (sPowerArrayMap.containsKey(mCpuClusters[cluster].freqKey)) {
+ return sPowerArrayMap.get(mCpuClusters[cluster].freqKey).length;
+ }
+ return 1; // Only one speed
+ }
+
+ /**
+ * @deprecated Use getAveragePowerForCpuScalingPolicy
+ */
+ @Deprecated
+ public double getAveragePowerForCpuCluster(int cluster) {
+ if (cluster >= 0 && cluster < mCpuClusters.length) {
+ return getAveragePower(mCpuClusters[cluster].clusterPowerKey);
+ }
+ return 0;
+ }
+
+ /**
+ * @deprecated Use getAveragePowerForCpuScalingStep
+ */
+ @Deprecated
+ public double getAveragePowerForCpuCore(int cluster, int step) {
+ if (cluster >= 0 && cluster < mCpuClusters.length) {
+ return getAveragePower(mCpuClusters[cluster].corePowerKey, step);
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the number of CPU power brackets: groups of states with similar power requirements.
+ * If power brackets are not specified, returns {@link #POWER_BRACKETS_UNSPECIFIED}
+ */
+ public int getCpuPowerBracketCount() {
+ return mCpuPowerBracketCount;
+ }
+
+ /**
+ * Returns the CPU power bracket corresponding to the specified scaling policy and frequency
+ * step
+ */
+ public int getCpuPowerBracketForScalingStep(int policy, int step) {
+ CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.get(policy);
+ if (cpuScalingPolicyPower != null
+ && step >= 0 && step < cpuScalingPolicyPower.powerBrackets.length) {
+ return cpuScalingPolicyPower.powerBrackets[step];
+ }
+ return 0;
+ }
+
+ private int mNumDisplays;
+
+ private void initDisplays() {
+ // Figure out how many displays are listed in the power profile.
+ mNumDisplays = 0;
+ while (!Double.isNaN(
+ getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, mNumDisplays, Double.NaN))
+ || !Double.isNaN(
+ getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, mNumDisplays, Double.NaN))
+ || !Double.isNaN(
+ getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, mNumDisplays,
+ Double.NaN))) {
+ mNumDisplays++;
+ }
+
+ // Handle legacy display power constants.
+ final Double deprecatedAmbientDisplay = sPowerItemMap.get(POWER_AMBIENT_DISPLAY);
+ boolean legacy = false;
+ if (deprecatedAmbientDisplay != null && mNumDisplays == 0) {
+ final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_AMBIENT, 0);
+ Slog.w(TAG, POWER_AMBIENT_DISPLAY + " is deprecated! Use " + key + " instead.");
+ sPowerItemMap.put(key, deprecatedAmbientDisplay);
+ legacy = true;
+ }
+
+ final Double deprecatedScreenOn = sPowerItemMap.get(POWER_SCREEN_ON);
+ if (deprecatedScreenOn != null && mNumDisplays == 0) {
+ final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_SCREEN_ON, 0);
+ Slog.w(TAG, POWER_SCREEN_ON + " is deprecated! Use " + key + " instead.");
+ sPowerItemMap.put(key, deprecatedScreenOn);
+ legacy = true;
+ }
+
+ final Double deprecatedScreenFull = sPowerItemMap.get(POWER_SCREEN_FULL);
+ if (deprecatedScreenFull != null && mNumDisplays == 0) {
+ final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_SCREEN_FULL, 0);
+ Slog.w(TAG, POWER_SCREEN_FULL + " is deprecated! Use " + key + " instead.");
+ sPowerItemMap.put(key, deprecatedScreenFull);
+ legacy = true;
+ }
+ if (legacy) {
+ mNumDisplays = 1;
+ }
+ }
+
+ /**
+ * Returns the number built in displays on the device as defined in the power_profile.xml.
+ */
+ public int getNumDisplays() {
+ return mNumDisplays;
+ }
+
+ private void initModem() {
+ handleDeprecatedModemConstant(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP,
+ POWER_MODEM_CONTROLLER_SLEEP, 0);
+ handleDeprecatedModemConstant(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE,
+ POWER_MODEM_CONTROLLER_IDLE, 0);
+ handleDeprecatedModemConstant(
+ ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX,
+ POWER_MODEM_CONTROLLER_RX, 0);
+ handleDeprecatedModemConstant(
+ ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+ | ModemPowerProfile.MODEM_TX_LEVEL_0, POWER_MODEM_CONTROLLER_TX, 0);
+ handleDeprecatedModemConstant(
+ ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+ | ModemPowerProfile.MODEM_TX_LEVEL_1, POWER_MODEM_CONTROLLER_TX, 1);
+ handleDeprecatedModemConstant(
+ ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+ | ModemPowerProfile.MODEM_TX_LEVEL_2, POWER_MODEM_CONTROLLER_TX, 2);
+ handleDeprecatedModemConstant(
+ ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+ | ModemPowerProfile.MODEM_TX_LEVEL_3, POWER_MODEM_CONTROLLER_TX, 3);
+ handleDeprecatedModemConstant(
+ ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+ | ModemPowerProfile.MODEM_TX_LEVEL_4, POWER_MODEM_CONTROLLER_TX, 4);
+ }
+
+ private void handleDeprecatedModemConstant(int key, String deprecatedKey, int level) {
+ final double drain = sModemPowerProfile.getAverageBatteryDrainMa(key);
+ if (!Double.isNaN(drain)) return; // Value already set, don't overwrite it.
+
+ final double deprecatedDrain = getAveragePower(deprecatedKey, level);
+ sModemPowerProfile.setPowerConstant(key, Double.toString(deprecatedDrain));
+ }
+
+ /**
+ * Returns the number of memory bandwidth buckets defined in power_profile.xml, or a
+ * default value if the subsystem has no recorded value.
+ *
+ * @return the number of memory bandwidth buckets.
+ */
+ public int getNumElements(String key) {
+ if (sPowerItemMap.containsKey(key)) {
+ return 1;
+ } else if (sPowerArrayMap.containsKey(key)) {
+ return sPowerArrayMap.get(key).length;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the average current in mA consumed by the subsystem, or the given
+ * default value if the subsystem has no recorded value.
+ *
+ * @param type the subsystem type
+ * @param defaultValue the value to return if the subsystem has no recorded value.
+ * @return the average current in milliAmps.
+ */
+ public double getAveragePowerOrDefault(String type, double defaultValue) {
+ if (sPowerItemMap.containsKey(type)) {
+ return sPowerItemMap.get(type);
+ } else if (sPowerArrayMap.containsKey(type)) {
+ return sPowerArrayMap.get(type)[0];
+ } else {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Returns the average current in mA consumed by the subsystem
+ *
+ * @param type the subsystem type
+ * @return the average current in milliAmps.
+ */
+ @UnsupportedAppUsage
+ public double getAveragePower(String type) {
+ return getAveragePowerOrDefault(type, 0);
+ }
+
+ /**
+ * Returns the average current in mA consumed by a subsystem's specified operation, or the given
+ * default value if the subsystem has no recorded value.
+ *
+ * @param key that describes a subsystem's battery draining operation
+ * The key is built from multiple constant, see {@link Subsystem} and
+ * {@link ModemPowerProfile}.
+ * @param defaultValue the value to return if the subsystem has no recorded value.
+ * @return the average current in milliAmps.
+ */
+ public double getAverageBatteryDrainOrDefaultMa(long key, double defaultValue) {
+ final long subsystemType = key & SUBSYSTEM_MASK;
+ final int subsystemFields = (int) (key & SUBSYSTEM_FIELDS_MASK);
+
+ final double value;
+ if (subsystemType == SUBSYSTEM_MODEM) {
+ value = sModemPowerProfile.getAverageBatteryDrainMa(subsystemFields);
+ } else {
+ value = Double.NaN;
+ }
+
+ if (Double.isNaN(value)) return defaultValue;
+ return value;
+ }
+
+ /**
+ * Returns the average current in mA consumed by a subsystem's specified operation.
+ *
+ * @param key that describes a subsystem's battery draining operation
+ * The key is built from multiple constant, see {@link Subsystem} and
+ * {@link ModemPowerProfile}.
+ * @return the average current in milliAmps.
+ */
+ public double getAverageBatteryDrainMa(long key) {
+ return getAverageBatteryDrainOrDefaultMa(key, 0);
+ }
+
+ /**
+ * Returns the average current in mA consumed by the subsystem for the given level.
+ *
+ * @param type the subsystem type
+ * @param level the level of power at which the subsystem is running. For instance, the
+ * signal strength of the cell network between 0 and 4 (if there are 4 bars max.)
+ * If there is no data for multiple levels, the level is ignored.
+ * @return the average current in milliAmps.
+ */
+ @UnsupportedAppUsage
+ public double getAveragePower(String type, int level) {
+ if (sPowerItemMap.containsKey(type)) {
+ return sPowerItemMap.get(type);
+ } else if (sPowerArrayMap.containsKey(type)) {
+ final Double[] values = sPowerArrayMap.get(type);
+ if (values.length > level && level >= 0) {
+ return values[level];
+ } else if (level < 0 || values.length == 0) {
+ return 0;
+ } else {
+ return values[values.length - 1];
+ }
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Returns the average current in mA consumed by an ordinaled subsystem, or the given
+ * default value if the subsystem has no recorded value.
+ *
+ * @param group the subsystem {@link PowerGroup}.
+ * @param ordinal which entity in the {@link PowerGroup}.
+ * @param defaultValue the value to return if the subsystem has no recorded value.
+ * @return the average current in milliAmps.
+ */
+ public double getAveragePowerForOrdinal(@PowerGroup String group, int ordinal,
+ double defaultValue) {
+ final String type = getOrdinalPowerType(group, ordinal);
+ return getAveragePowerOrDefault(type, defaultValue);
+ }
+
+ /**
+ * Returns the average current in mA consumed by an ordinaled subsystem.
+ *
+ * @param group the subsystem {@link PowerGroup}.
+ * @param ordinal which entity in the {@link PowerGroup}.
+ * @return the average current in milliAmps.
+ */
+ public double getAveragePowerForOrdinal(@PowerGroup String group, int ordinal) {
+ return getAveragePowerForOrdinal(group, ordinal, 0);
+ }
+
+ /**
+ * Returns the battery capacity, if available, in milli Amp Hours. If not available,
+ * it returns zero.
+ *
+ * @return the battery capacity in mAh
+ */
+ @UnsupportedAppUsage
+ public double getBatteryCapacity() {
+ return getAveragePower(POWER_BATTERY_CAPACITY);
+ }
+
+ /**
+ * Dump power constants into PowerProfileProto
+ */
+ public void dumpDebug(ProtoOutputStream proto) {
+ // cpu.suspend
+ writePowerConstantToProto(proto, POWER_CPU_SUSPEND, PowerProfileProto.CPU_SUSPEND);
+
+ // cpu.idle
+ writePowerConstantToProto(proto, POWER_CPU_IDLE, PowerProfileProto.CPU_IDLE);
+
+ // cpu.active
+ writePowerConstantToProto(proto, POWER_CPU_ACTIVE, PowerProfileProto.CPU_ACTIVE);
+
+ // cpu.clusters.cores
+ // cpu.cluster_power.cluster
+ // cpu.core_speeds.cluster
+ // cpu.core_power.cluster
+ for (int cluster = 0; cluster < mCpuClusters.length; cluster++) {
+ final long token = proto.start(PowerProfileProto.CPU_CLUSTER);
+ proto.write(PowerProfileProto.CpuCluster.ID, cluster);
+ proto.write(PowerProfileProto.CpuCluster.CLUSTER_POWER,
+ sPowerItemMap.get(mCpuClusters[cluster].clusterPowerKey));
+ proto.write(PowerProfileProto.CpuCluster.CORES, mCpuClusters[cluster].numCpus);
+ for (Double speed : sPowerArrayMap.get(mCpuClusters[cluster].freqKey)) {
+ proto.write(PowerProfileProto.CpuCluster.SPEED, speed);
+ }
+ for (Double corePower : sPowerArrayMap.get(mCpuClusters[cluster].corePowerKey)) {
+ proto.write(PowerProfileProto.CpuCluster.CORE_POWER, corePower);
+ }
+ proto.end(token);
+ }
+
+ // wifi.scan
+ writePowerConstantToProto(proto, POWER_WIFI_SCAN, PowerProfileProto.WIFI_SCAN);
+
+ // wifi.on
+ writePowerConstantToProto(proto, POWER_WIFI_ON, PowerProfileProto.WIFI_ON);
+
+ // wifi.active
+ writePowerConstantToProto(proto, POWER_WIFI_ACTIVE, PowerProfileProto.WIFI_ACTIVE);
+
+ // wifi.controller.idle
+ writePowerConstantToProto(proto, POWER_WIFI_CONTROLLER_IDLE,
+ PowerProfileProto.WIFI_CONTROLLER_IDLE);
+
+ // wifi.controller.rx
+ writePowerConstantToProto(proto, POWER_WIFI_CONTROLLER_RX,
+ PowerProfileProto.WIFI_CONTROLLER_RX);
+
+ // wifi.controller.tx
+ writePowerConstantToProto(proto, POWER_WIFI_CONTROLLER_TX,
+ PowerProfileProto.WIFI_CONTROLLER_TX);
+
+ // wifi.controller.tx_levels
+ writePowerConstantArrayToProto(proto, POWER_WIFI_CONTROLLER_TX_LEVELS,
+ PowerProfileProto.WIFI_CONTROLLER_TX_LEVELS);
+
+ // wifi.controller.voltage
+ writePowerConstantToProto(proto, POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE,
+ PowerProfileProto.WIFI_CONTROLLER_OPERATING_VOLTAGE);
+
+ // bluetooth.controller.idle
+ writePowerConstantToProto(proto, POWER_BLUETOOTH_CONTROLLER_IDLE,
+ PowerProfileProto.BLUETOOTH_CONTROLLER_IDLE);
+
+ // bluetooth.controller.rx
+ writePowerConstantToProto(proto, POWER_BLUETOOTH_CONTROLLER_RX,
+ PowerProfileProto.BLUETOOTH_CONTROLLER_RX);
+
+ // bluetooth.controller.tx
+ writePowerConstantToProto(proto, POWER_BLUETOOTH_CONTROLLER_TX,
+ PowerProfileProto.BLUETOOTH_CONTROLLER_TX);
+
+ // bluetooth.controller.voltage
+ writePowerConstantToProto(proto, POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE,
+ PowerProfileProto.BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE);
+
+ // modem.controller.sleep
+ writePowerConstantToProto(proto, POWER_MODEM_CONTROLLER_SLEEP,
+ PowerProfileProto.MODEM_CONTROLLER_SLEEP);
+
+ // modem.controller.idle
+ writePowerConstantToProto(proto, POWER_MODEM_CONTROLLER_IDLE,
+ PowerProfileProto.MODEM_CONTROLLER_IDLE);
+
+ // modem.controller.rx
+ writePowerConstantToProto(proto, POWER_MODEM_CONTROLLER_RX,
+ PowerProfileProto.MODEM_CONTROLLER_RX);
+
+ // modem.controller.tx
+ writePowerConstantArrayToProto(proto, POWER_MODEM_CONTROLLER_TX,
+ PowerProfileProto.MODEM_CONTROLLER_TX);
+
+ // modem.controller.voltage
+ writePowerConstantToProto(proto, POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE,
+ PowerProfileProto.MODEM_CONTROLLER_OPERATING_VOLTAGE);
+
+ // gps.on
+ writePowerConstantToProto(proto, POWER_GPS_ON, PowerProfileProto.GPS_ON);
+
+ // gps.signalqualitybased
+ writePowerConstantArrayToProto(proto, POWER_GPS_SIGNAL_QUALITY_BASED,
+ PowerProfileProto.GPS_SIGNAL_QUALITY_BASED);
+
+ // gps.voltage
+ writePowerConstantToProto(proto, POWER_GPS_OPERATING_VOLTAGE,
+ PowerProfileProto.GPS_OPERATING_VOLTAGE);
+
+ // bluetooth.on
+ writePowerConstantToProto(proto, POWER_BLUETOOTH_ON, PowerProfileProto.BLUETOOTH_ON);
+
+ // bluetooth.active
+ writePowerConstantToProto(proto, POWER_BLUETOOTH_ACTIVE,
+ PowerProfileProto.BLUETOOTH_ACTIVE);
+
+ // bluetooth.at
+ writePowerConstantToProto(proto, POWER_BLUETOOTH_AT_CMD,
+ PowerProfileProto.BLUETOOTH_AT_CMD);
+
+ // ambient.on
+ writePowerConstantToProto(proto, POWER_AMBIENT_DISPLAY, PowerProfileProto.AMBIENT_DISPLAY);
+
+ // screen.on
+ writePowerConstantToProto(proto, POWER_SCREEN_ON, PowerProfileProto.SCREEN_ON);
+
+ // radio.on
+ writePowerConstantToProto(proto, POWER_RADIO_ON, PowerProfileProto.RADIO_ON);
+
+ // radio.scanning
+ writePowerConstantToProto(proto, POWER_RADIO_SCANNING, PowerProfileProto.RADIO_SCANNING);
+
+ // radio.active
+ writePowerConstantToProto(proto, POWER_RADIO_ACTIVE, PowerProfileProto.RADIO_ACTIVE);
+
+ // screen.full
+ writePowerConstantToProto(proto, POWER_SCREEN_FULL, PowerProfileProto.SCREEN_FULL);
+
+ // audio
+ writePowerConstantToProto(proto, POWER_AUDIO, PowerProfileProto.AUDIO);
+
+ // video
+ writePowerConstantToProto(proto, POWER_VIDEO, PowerProfileProto.VIDEO);
+
+ // camera.flashlight
+ writePowerConstantToProto(proto, POWER_FLASHLIGHT, PowerProfileProto.FLASHLIGHT);
+
+ // memory.bandwidths
+ writePowerConstantToProto(proto, POWER_MEMORY, PowerProfileProto.MEMORY);
+
+ // camera.avg
+ writePowerConstantToProto(proto, POWER_CAMERA, PowerProfileProto.CAMERA);
+
+ // wifi.batchedscan
+ writePowerConstantToProto(proto, POWER_WIFI_BATCHED_SCAN,
+ PowerProfileProto.WIFI_BATCHED_SCAN);
+
+ // battery.capacity
+ writePowerConstantToProto(proto, POWER_BATTERY_CAPACITY,
+ PowerProfileProto.BATTERY_CAPACITY);
+ }
+
+ /**
+ * Dump the PowerProfile values.
+ */
+ public void dump(PrintWriter pw) {
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ sPowerItemMap.forEach((key, value) -> {
+ ipw.print(key, value);
+ ipw.println();
+ });
+ sPowerArrayMap.forEach((key, value) -> {
+ ipw.print(key, Arrays.toString(value));
+ ipw.println();
+ });
+ ipw.println("Modem values:");
+ ipw.increaseIndent();
+ sModemPowerProfile.dump(ipw);
+ ipw.decreaseIndent();
+ }
+
+ // Writes items in sPowerItemMap to proto if exists.
+ private void writePowerConstantToProto(ProtoOutputStream proto, String key, long fieldId) {
+ if (sPowerItemMap.containsKey(key)) {
+ proto.write(fieldId, sPowerItemMap.get(key));
+ }
+ }
+
+ // Writes items in sPowerArrayMap to proto if exists.
+ private void writePowerConstantArrayToProto(ProtoOutputStream proto, String key, long fieldId) {
+ if (sPowerArrayMap.containsKey(key)) {
+ for (Double d : sPowerArrayMap.get(key)) {
+ proto.write(fieldId, d);
+ }
+ }
+ }
+
+ // Creates the key for an ordinaled power constant from the group and ordinal.
+ private static String getOrdinalPowerType(@PowerGroup String group, int ordinal) {
+ return group + ordinal;
+ }
+}
diff --git a/android-35/com/android/internal/os/PowerStats.java b/android-35/com/android/internal/os/PowerStats.java
new file mode 100644
index 0000000..24971f5
--- /dev/null
+++ b/android-35/com/android/internal/os/PowerStats.java
@@ -0,0 +1,718 @@
+/*
+ * Copyright (C) 2023 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.internal.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for
+ * details.
+ */
[email protected]
+public final class PowerStats {
+ private static final String TAG = "PowerStats";
+
+ private static final BatteryStatsHistory.VarintParceler VARINT_PARCELER =
+ new BatteryStatsHistory.VarintParceler();
+ private static final byte PARCEL_FORMAT_VERSION = 2;
+
+ private static final int PARCEL_FORMAT_VERSION_MASK = 0x000000FF;
+ private static final int PARCEL_FORMAT_VERSION_SHIFT =
+ Integer.numberOfTrailingZeros(PARCEL_FORMAT_VERSION_MASK);
+ private static final int STATS_ARRAY_LENGTH_MASK = 0x0000FF00;
+ private static final int STATS_ARRAY_LENGTH_SHIFT =
+ Integer.numberOfTrailingZeros(STATS_ARRAY_LENGTH_MASK);
+ public static final int MAX_STATS_ARRAY_LENGTH =
+ (1 << Integer.bitCount(STATS_ARRAY_LENGTH_MASK)) - 1;
+ private static final int STATE_STATS_ARRAY_LENGTH_MASK = 0x00FF0000;
+ private static final int STATE_STATS_ARRAY_LENGTH_SHIFT =
+ Integer.numberOfTrailingZeros(STATE_STATS_ARRAY_LENGTH_MASK);
+ public static final int MAX_STATE_STATS_ARRAY_LENGTH =
+ (1 << Integer.bitCount(STATE_STATS_ARRAY_LENGTH_MASK)) - 1;
+ private static final int UID_STATS_ARRAY_LENGTH_MASK = 0xFF000000;
+ private static final int UID_STATS_ARRAY_LENGTH_SHIFT =
+ Integer.numberOfTrailingZeros(UID_STATS_ARRAY_LENGTH_MASK);
+ public static final int MAX_UID_STATS_ARRAY_LENGTH =
+ (1 << Integer.bitCount(UID_STATS_ARRAY_LENGTH_MASK)) - 1;
+
+ /**
+ * Descriptor of the stats collected for a given power component (e.g. CPU, WiFi etc).
+ * This descriptor is used for storing PowerStats and can also be used by power models
+ * to adjust the algorithm in accordance with the stats available on the device.
+ */
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
+ public static class Descriptor {
+ public static final String EXTRA_DEVICE_STATS_FORMAT = "format-device";
+ public static final String EXTRA_STATE_STATS_FORMAT = "format-state";
+ public static final String EXTRA_UID_STATS_FORMAT = "format-uid";
+
+ public static final String XML_TAG_DESCRIPTOR = "descriptor";
+ private static final String XML_ATTR_ID = "id";
+ private static final String XML_ATTR_NAME = "name";
+ private static final String XML_ATTR_STATS_ARRAY_LENGTH = "stats-array-length";
+ private static final String XML_TAG_STATE = "state";
+ private static final String XML_ATTR_STATE_KEY = "key";
+ private static final String XML_ATTR_STATE_LABEL = "label";
+ private static final String XML_ATTR_STATE_STATS_ARRAY_LENGTH = "state-stats-array-length";
+ private static final String XML_ATTR_UID_STATS_ARRAY_LENGTH = "uid-stats-array-length";
+ private static final String XML_TAG_EXTRAS = "extras";
+
+ /**
+ * {@link BatteryConsumer.PowerComponent} (e.g. CPU, WIFI etc) that this snapshot relates
+ * to; or a custom power component ID (if the value
+ * is >= {@link BatteryConsumer#FIRST_CUSTOM_POWER_COMPONENT_ID}).
+ */
+ public final int powerComponentId;
+ public final String name;
+
+ /**
+ * Stats for the power component, such as the total usage time.
+ */
+ public final int statsArrayLength;
+
+ /**
+ * Map of device state codes to their corresponding human-readable labels.
+ */
+ public final SparseArray<String> stateLabels;
+
+ /**
+ * Stats for a specific state of the power component, e.g. "mobile radio in the 5G mode"
+ */
+ public final int stateStatsArrayLength;
+
+ /**
+ * Stats for the usage of this power component by a specific UID (app)
+ */
+ public final int uidStatsArrayLength;
+
+ /**
+ * Extra parameters specific to the power component, e.g. the availability of power
+ * monitors.
+ */
+ public final PersistableBundle extras;
+
+ private PowerStatsFormatter mDeviceStatsFormatter;
+ private PowerStatsFormatter mStateStatsFormatter;
+ private PowerStatsFormatter mUidStatsFormatter;
+
+ public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId,
+ int statsArrayLength, @Nullable SparseArray<String> stateLabels,
+ int stateStatsArrayLength, int uidStatsArrayLength,
+ @NonNull PersistableBundle extras) {
+ this(powerComponentId, BatteryConsumer.powerComponentIdToString(powerComponentId),
+ statsArrayLength, stateLabels, stateStatsArrayLength, uidStatsArrayLength,
+ extras);
+ }
+
+ public Descriptor(int customPowerComponentId, String name, int statsArrayLength,
+ @Nullable SparseArray<String> stateLabels, int stateStatsArrayLength,
+ int uidStatsArrayLength, @NonNull PersistableBundle extras) {
+ if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) {
+ throw new IllegalArgumentException(
+ "statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH);
+ }
+ if (stateStatsArrayLength > MAX_STATE_STATS_ARRAY_LENGTH) {
+ throw new IllegalArgumentException(
+ "stateStatsArrayLength is too high. Max = " + MAX_STATE_STATS_ARRAY_LENGTH);
+ }
+ if (uidStatsArrayLength > MAX_UID_STATS_ARRAY_LENGTH) {
+ throw new IllegalArgumentException(
+ "uidStatsArrayLength is too high. Max = " + MAX_UID_STATS_ARRAY_LENGTH);
+ }
+ this.powerComponentId = customPowerComponentId;
+ this.name = name;
+ this.statsArrayLength = statsArrayLength;
+ this.stateLabels = stateLabels != null ? stateLabels : new SparseArray<>();
+ this.stateStatsArrayLength = stateStatsArrayLength;
+ this.uidStatsArrayLength = uidStatsArrayLength;
+ this.extras = extras;
+ }
+
+ /**
+ * Returns a custom formatter for this type of power stats.
+ */
+ public PowerStatsFormatter getDeviceStatsFormatter() {
+ if (mDeviceStatsFormatter == null) {
+ mDeviceStatsFormatter = new PowerStatsFormatter(
+ extras.getString(EXTRA_DEVICE_STATS_FORMAT));
+ }
+ return mDeviceStatsFormatter;
+ }
+
+ /**
+ * Returns a custom formatter for this type of power stats, specifically per-state stats.
+ */
+ public PowerStatsFormatter getStateStatsFormatter() {
+ if (mStateStatsFormatter == null) {
+ mStateStatsFormatter = new PowerStatsFormatter(
+ extras.getString(EXTRA_STATE_STATS_FORMAT));
+ }
+ return mStateStatsFormatter;
+ }
+
+ /**
+ * Returns a custom formatter for this type of power stats, specifically per-UID stats.
+ */
+ public PowerStatsFormatter getUidStatsFormatter() {
+ if (mUidStatsFormatter == null) {
+ mUidStatsFormatter = new PowerStatsFormatter(
+ extras.getString(EXTRA_UID_STATS_FORMAT));
+ }
+ return mUidStatsFormatter;
+ }
+
+ /**
+ * Returns the label associated with the give state key, e.g. "5G-high" for the
+ * state of Mobile Radio representing the 5G mode and high signal power.
+ */
+ public String getStateLabel(int key) {
+ String label = stateLabels.get(key);
+ if (label != null) {
+ return label;
+ }
+ return name + "-" + Integer.toHexString(key);
+ }
+
+ /**
+ * Writes the Descriptor into the parcel.
+ */
+ public void writeSummaryToParcel(Parcel parcel) {
+ int firstWord = ((PARCEL_FORMAT_VERSION << PARCEL_FORMAT_VERSION_SHIFT)
+ & PARCEL_FORMAT_VERSION_MASK)
+ | ((statsArrayLength << STATS_ARRAY_LENGTH_SHIFT)
+ & STATS_ARRAY_LENGTH_MASK)
+ | ((stateStatsArrayLength << STATE_STATS_ARRAY_LENGTH_SHIFT)
+ & STATE_STATS_ARRAY_LENGTH_MASK)
+ | ((uidStatsArrayLength << UID_STATS_ARRAY_LENGTH_SHIFT)
+ & UID_STATS_ARRAY_LENGTH_MASK);
+ parcel.writeInt(firstWord);
+ parcel.writeInt(powerComponentId);
+ parcel.writeString(name);
+ parcel.writeInt(stateLabels.size());
+ for (int i = 0, size = stateLabels.size(); i < size; i++) {
+ parcel.writeInt(stateLabels.keyAt(i));
+ parcel.writeString(stateLabels.valueAt(i));
+ }
+ extras.writeToParcel(parcel, 0);
+ }
+
+ /**
+ * Reads a Descriptor from the parcel. If the parcel has an incompatible format,
+ * returns null.
+ */
+ @Nullable
+ public static Descriptor readSummaryFromParcel(Parcel parcel) {
+ int firstWord = parcel.readInt();
+ int version = (firstWord & PARCEL_FORMAT_VERSION_MASK) >>> PARCEL_FORMAT_VERSION_SHIFT;
+ if (version != PARCEL_FORMAT_VERSION) {
+ Slog.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
+ + "changed from " + version + " to " + PARCEL_FORMAT_VERSION);
+ return null;
+ }
+ int statsArrayLength =
+ (firstWord & STATS_ARRAY_LENGTH_MASK) >>> STATS_ARRAY_LENGTH_SHIFT;
+ int stateStatsArrayLength =
+ (firstWord & STATE_STATS_ARRAY_LENGTH_MASK) >>> STATE_STATS_ARRAY_LENGTH_SHIFT;
+ int uidStatsArrayLength =
+ (firstWord & UID_STATS_ARRAY_LENGTH_MASK) >>> UID_STATS_ARRAY_LENGTH_SHIFT;
+ int powerComponentId = parcel.readInt();
+ String name = parcel.readString();
+ int stateLabelCount = parcel.readInt();
+ SparseArray<String> stateLabels = new SparseArray<>(stateLabelCount);
+ for (int i = stateLabelCount; i > 0; i--) {
+ int key = parcel.readInt();
+ String label = parcel.readString();
+ stateLabels.put(key, label);
+ }
+ PersistableBundle extras = parcel.readPersistableBundle();
+ return new Descriptor(powerComponentId, name, statsArrayLength, stateLabels,
+ stateStatsArrayLength, uidStatsArrayLength, extras);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Descriptor)) return false;
+ Descriptor that = (Descriptor) o;
+ return powerComponentId == that.powerComponentId
+ && statsArrayLength == that.statsArrayLength
+ && stateLabels.contentEquals(that.stateLabels)
+ && stateStatsArrayLength == that.stateStatsArrayLength
+ && uidStatsArrayLength == that.uidStatsArrayLength
+ && Objects.equals(name, that.name)
+ && extras.size() == that.extras.size() // Unparcel the Parcel if not yet
+ && Bundle.kindofEquals(extras,
+ that.extras); // Since the Parcel is now unparceled, do a deep comparison
+ }
+
+ /**
+ * Stores contents in an XML doc.
+ */
+ public void writeXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, XML_TAG_DESCRIPTOR);
+ serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
+ serializer.attribute(null, XML_ATTR_NAME, name);
+ serializer.attributeInt(null, XML_ATTR_STATS_ARRAY_LENGTH, statsArrayLength);
+ serializer.attributeInt(null, XML_ATTR_STATE_STATS_ARRAY_LENGTH, stateStatsArrayLength);
+ serializer.attributeInt(null, XML_ATTR_UID_STATS_ARRAY_LENGTH, uidStatsArrayLength);
+ for (int i = stateLabels.size() - 1; i >= 0; i--) {
+ serializer.startTag(null, XML_TAG_STATE);
+ serializer.attributeInt(null, XML_ATTR_STATE_KEY, stateLabels.keyAt(i));
+ serializer.attribute(null, XML_ATTR_STATE_LABEL, stateLabels.valueAt(i));
+ serializer.endTag(null, XML_TAG_STATE);
+ }
+ try {
+ serializer.startTag(null, XML_TAG_EXTRAS);
+ extras.saveToXml(serializer);
+ serializer.endTag(null, XML_TAG_EXTRAS);
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ serializer.endTag(null, XML_TAG_DESCRIPTOR);
+ }
+
+ /**
+ * Creates a Descriptor by parsing an XML doc. The parser is expected to be positioned
+ * on or before the opening "descriptor" tag.
+ */
+ public static Descriptor createFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int powerComponentId = -1;
+ String name = null;
+ int statsArrayLength = 0;
+ SparseArray<String> stateLabels = new SparseArray<>();
+ int stateStatsArrayLength = 0;
+ int uidStatsArrayLength = 0;
+ PersistableBundle extras = null;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(XML_TAG_DESCRIPTOR))) {
+ if (eventType == XmlPullParser.START_TAG) {
+ switch (parser.getName()) {
+ case XML_TAG_DESCRIPTOR:
+ powerComponentId = parser.getAttributeInt(null, XML_ATTR_ID);
+ name = parser.getAttributeValue(null, XML_ATTR_NAME);
+ statsArrayLength = parser.getAttributeInt(null,
+ XML_ATTR_STATS_ARRAY_LENGTH);
+ stateStatsArrayLength = parser.getAttributeInt(null,
+ XML_ATTR_STATE_STATS_ARRAY_LENGTH);
+ uidStatsArrayLength = parser.getAttributeInt(null,
+ XML_ATTR_UID_STATS_ARRAY_LENGTH);
+ break;
+ case XML_TAG_STATE:
+ int value = parser.getAttributeInt(null, XML_ATTR_STATE_KEY);
+ String label = parser.getAttributeValue(null, XML_ATTR_STATE_LABEL);
+ stateLabels.put(value, label);
+ break;
+ case XML_TAG_EXTRAS:
+ extras = PersistableBundle.restoreFromXml(parser);
+ break;
+ }
+ }
+ eventType = parser.next();
+ }
+ if (powerComponentId == -1) {
+ return null;
+ } else if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
+ return new Descriptor(powerComponentId, name, statsArrayLength,
+ stateLabels, stateStatsArrayLength, uidStatsArrayLength, extras);
+ } else if (powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT) {
+ return new Descriptor(powerComponentId, statsArrayLength, stateLabels,
+ stateStatsArrayLength, uidStatsArrayLength, extras);
+ } else {
+ Slog.e(TAG, "Unrecognized power component: " + powerComponentId);
+ return null;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(powerComponentId);
+ }
+
+ @Override
+ public String toString() {
+ if (extras != null) {
+ extras.size(); // Unparcel
+ }
+ return "PowerStats.Descriptor{"
+ + "powerComponentId=" + powerComponentId
+ + ", name='" + name + '\''
+ + ", statsArrayLength=" + statsArrayLength
+ + ", stateStatsArrayLength=" + stateStatsArrayLength
+ + ", stateLabels=" + stateLabels
+ + ", uidStatsArrayLength=" + uidStatsArrayLength
+ + ", extras=" + extras
+ + '}';
+ }
+ }
+
+ /**
+ * A registry for all supported power component types (e.g. CPU, WiFi).
+ */
+ public static class DescriptorRegistry {
+ private final SparseArray<Descriptor> mDescriptors = new SparseArray<>();
+
+ /**
+ * Adds the specified descriptor to the registry. If the registry already
+ * contained a descriptor for the same power component, then the new one replaces
+ * the old one.
+ */
+ public void register(Descriptor descriptor) {
+ mDescriptors.put(descriptor.powerComponentId, descriptor);
+ }
+
+ /**
+ * @param powerComponentId either a BatteryConsumer.PowerComponent or a custom power
+ * component ID
+ */
+ public Descriptor get(int powerComponentId) {
+ return mDescriptors.get(powerComponentId);
+ }
+ }
+
+ public final Descriptor descriptor;
+
+ /**
+ * Duration, in milliseconds, covered by this snapshot.
+ */
+ public long durationMs;
+
+ /**
+ * Device-wide stats.
+ */
+ public long[] stats;
+
+ /**
+ * Device-wide mode stats, used when the power component can operate in different modes,
+ * e.g. RATs such as LTE and 5G.
+ */
+ public final SparseArray<long[]> stateStats = new SparseArray<>();
+
+ /**
+ * Per-UID CPU stats.
+ */
+ public final SparseArray<long[]> uidStats = new SparseArray<>();
+
+ public PowerStats(Descriptor descriptor) {
+ this.descriptor = descriptor;
+ stats = new long[descriptor.statsArrayLength];
+ }
+
+ /**
+ * Writes the object into the parcel.
+ */
+ public void writeToParcel(Parcel parcel) {
+ int lengthPos = parcel.dataPosition();
+ parcel.writeInt(0); // Placeholder for length
+
+ int startPos = parcel.dataPosition();
+ parcel.writeInt(descriptor.powerComponentId);
+ parcel.writeLong(durationMs);
+ VARINT_PARCELER.writeLongArray(parcel, stats);
+
+ if (descriptor.stateStatsArrayLength != 0) {
+ parcel.writeInt(stateStats.size());
+ for (int i = 0; i < stateStats.size(); i++) {
+ parcel.writeInt(stateStats.keyAt(i));
+ VARINT_PARCELER.writeLongArray(parcel, stateStats.valueAt(i));
+ }
+ }
+
+ parcel.writeInt(uidStats.size());
+ for (int i = 0; i < uidStats.size(); i++) {
+ parcel.writeInt(uidStats.keyAt(i));
+ VARINT_PARCELER.writeLongArray(parcel, uidStats.valueAt(i));
+ }
+
+ int endPos = parcel.dataPosition();
+ parcel.setDataPosition(lengthPos);
+ parcel.writeInt(endPos - startPos);
+ parcel.setDataPosition(endPos);
+ }
+
+ /**
+ * Reads a PowerStats object from the supplied Parcel. If the parcel has an incompatible
+ * format, returns null.
+ */
+ @Nullable
+ public static PowerStats readFromParcel(Parcel parcel, DescriptorRegistry registry) {
+ int length = parcel.readInt();
+ int startPos = parcel.dataPosition();
+ int endPos = startPos + length;
+
+ try {
+ int powerComponentId = parcel.readInt();
+
+ Descriptor descriptor = registry.get(powerComponentId);
+ if (descriptor == null) {
+ Slog.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
+ return null;
+ }
+ PowerStats stats = new PowerStats(descriptor);
+ stats.durationMs = parcel.readLong();
+ stats.stats = new long[descriptor.statsArrayLength];
+ VARINT_PARCELER.readLongArray(parcel, stats.stats);
+
+ if (descriptor.stateStatsArrayLength != 0) {
+ int count = parcel.readInt();
+ for (int i = 0; i < count; i++) {
+ int state = parcel.readInt();
+ long[] stateStats = new long[descriptor.stateStatsArrayLength];
+ VARINT_PARCELER.readLongArray(parcel, stateStats);
+ stats.stateStats.put(state, stateStats);
+ }
+ }
+
+ int uidCount = parcel.readInt();
+ for (int i = 0; i < uidCount; i++) {
+ int uid = parcel.readInt();
+ long[] uidStats = new long[descriptor.uidStatsArrayLength];
+ VARINT_PARCELER.readLongArray(parcel, uidStats);
+ stats.uidStats.put(uid, uidStats);
+ }
+ if (parcel.dataPosition() != endPos) {
+ Slog.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
+ + ", actual length: " + (parcel.dataPosition() - startPos));
+ return null;
+ }
+ return stats;
+ } finally {
+ // Unconditionally skip to the end of the written data, even if the actual parcel
+ // format is incompatible
+ if (endPos > parcel.dataPosition()) {
+ if (endPos >= parcel.dataSize()) {
+ throw new IndexOutOfBoundsException(
+ "PowerStats end position: " + endPos + " is outside the parcel bounds: "
+ + parcel.dataSize());
+ }
+ parcel.setDataPosition(endPos);
+ }
+ }
+ }
+
+ /**
+ * Formats the stats as a string suitable to be included in the Battery History dump.
+ */
+ public String formatForBatteryHistory(String uidPrefix) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("duration=").append(durationMs).append(" ").append(descriptor.name);
+ if (stats.length > 0) {
+ sb.append("=").append(descriptor.getDeviceStatsFormatter().format(stats));
+ }
+ if (descriptor.stateStatsArrayLength != 0) {
+ PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
+ for (int i = 0; i < stateStats.size(); i++) {
+ sb.append(" (");
+ sb.append(descriptor.getStateLabel(stateStats.keyAt(i)));
+ sb.append(") ");
+ sb.append(formatter.format(stateStats.valueAt(i)));
+ }
+ }
+ PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
+ for (int i = 0; i < uidStats.size(); i++) {
+ sb.append(uidPrefix)
+ .append(UserHandle.formatUid(uidStats.keyAt(i)))
+ .append(": ").append(uidStatsFormatter.format(uidStats.valueAt(i)));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Prints the contents of the stats snapshot.
+ */
+ public void dump(IndentingPrintWriter pw) {
+ pw.println(descriptor.name + " (" + descriptor.powerComponentId + ')');
+ pw.increaseIndent();
+ pw.print("duration", durationMs).println();
+
+ if (descriptor.statsArrayLength != 0) {
+ pw.println(descriptor.getDeviceStatsFormatter().format(stats));
+ }
+ if (descriptor.stateStatsArrayLength != 0) {
+ PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
+ for (int i = 0; i < stateStats.size(); i++) {
+ pw.print(" (");
+ pw.print(descriptor.getStateLabel(stateStats.keyAt(i)));
+ pw.print(") ");
+ pw.print(formatter.format(stateStats.valueAt(i)));
+ pw.println();
+ }
+ }
+ PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
+ for (int i = 0; i < uidStats.size(); i++) {
+ pw.print("UID ");
+ pw.print(UserHandle.formatUid(uidStats.keyAt(i)));
+ pw.print(": ");
+ pw.print(uidStatsFormatter.format(uidStats.valueAt(i)));
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+
+ @Override
+ public String toString() {
+ return "PowerStats: " + formatForBatteryHistory(" UID ");
+ }
+
+ public static class PowerStatsFormatter {
+ private static class Section {
+ public String label;
+ public int position;
+ public int length;
+ public boolean optional;
+ public boolean typePower;
+ }
+
+ private static final double NANO_TO_MILLI_MULTIPLIER = 1.0 / 1000000.0;
+ private static final Pattern SECTION_PATTERN =
+ Pattern.compile("([^:]+):(\\d+)(\\[(?<L>\\d+)])?(?<F>\\S*)\\s*");
+ private final List<Section> mSections;
+
+ public PowerStatsFormatter(String format) {
+ mSections = parseFormat(format);
+ }
+
+ /**
+ * Produces a formatted string representing the supplied array, with labels
+ * and other adornments specific to the power stats layout.
+ */
+ public String format(long[] stats) {
+ return format(mSections, stats);
+ }
+
+ private List<Section> parseFormat(String format) {
+ if (format == null || format.isBlank()) {
+ return null;
+ }
+
+ ArrayList<Section> sections = new ArrayList<>();
+ Matcher matcher = SECTION_PATTERN.matcher(format);
+ for (int position = 0; position < format.length(); position = matcher.end()) {
+ if (!matcher.find() || matcher.start() != position) {
+ Slog.wtf(TAG, "Bad power stats format '" + format + "'");
+ return null;
+ }
+ Section section = new Section();
+ section.label = matcher.group(1);
+ section.position = Integer.parseUnsignedInt(matcher.group(2));
+ String length = matcher.group("L");
+ if (length != null) {
+ section.length = Integer.parseUnsignedInt(length);
+ } else {
+ section.length = 1;
+ }
+ String flags = matcher.group("F");
+ if (flags != null) {
+ for (int i = 0; i < flags.length(); i++) {
+ char flag = flags.charAt(i);
+ switch (flag) {
+ case '?':
+ section.optional = true;
+ break;
+ case 'p':
+ section.typePower = true;
+ break;
+ default:
+ Slog.e(TAG,
+ "Unsupported format option '" + flag + "' in " + format);
+ break;
+ }
+ }
+ }
+ sections.add(section);
+ }
+
+ return sections;
+ }
+
+ private String format(List<Section> sections, long[] stats) {
+ if (sections == null) {
+ return Arrays.toString(stats);
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0, count = sections.size(); i < count; i++) {
+ Section section = sections.get(i);
+ if (section.length == 0) {
+ continue;
+ }
+
+ if (section.optional) {
+ boolean nonZero = false;
+ for (int offset = 0; offset < section.length; offset++) {
+ if (stats[section.position + offset] != 0) {
+ nonZero = true;
+ break;
+ }
+ }
+ if (!nonZero) {
+ continue;
+ }
+ }
+
+ if (!sb.isEmpty()) {
+ sb.append(' ');
+ }
+ sb.append(section.label).append(": ");
+ if (section.length != 1) {
+ sb.append('[');
+ }
+ for (int offset = 0; offset < section.length; offset++) {
+ if (offset != 0) {
+ sb.append(", ");
+ }
+ if (section.typePower) {
+ sb.append(BatteryStats.formatCharge(
+ stats[section.position + offset] * NANO_TO_MILLI_MULTIPLIER));
+ } else {
+ sb.append(stats[section.position + offset]);
+ }
+ }
+ if (section.length != 1) {
+ sb.append(']');
+ }
+ }
+ return sb.toString();
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/ProcLocksReader.java b/android-35/com/android/internal/os/ProcLocksReader.java
new file mode 100644
index 0000000..6b85e08
--- /dev/null
+++ b/android-35/com/android/internal/os/ProcLocksReader.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2021 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.internal.os;
+
+import android.util.IntArray;
+
+import com.android.internal.util.ProcFileReader;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * Reads and parses {@code locks} files in the {@code proc} filesystem.
+ * A typical example of /proc/locks
+ *
+ * 1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335
+ * 2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF
+ * 2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF
+ * 2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF
+ * 3: POSIX ADVISORY READ 3888 fd:09:13992 128 128
+ * 4: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335
+ */
[email protected]
+public class ProcLocksReader {
+ private final String mPath;
+ private ProcFileReader mReader = null;
+ private IntArray mPids = new IntArray();
+
+ public ProcLocksReader() {
+ mPath = "/proc/locks";
+ }
+
+ public ProcLocksReader(String path) {
+ mPath = path;
+ }
+
+ /**
+ * This interface is for AMS to run callback function on every processes one by one
+ * that hold file locks blocking other processes.
+ */
+ public interface ProcLocksReaderCallback {
+ /**
+ * Call the callback function of handleBlockingFileLocks().
+ * @param pids Each process that hold file locks blocking other processes.
+ * pids[0] is the process blocking others
+ * pids[1..n-1] are the processes being blocked
+ * NOTE: pids are cleared immediately after onBlockingFileLock() returns. If the caller
+ * needs to cache it, please make a copy, e.g. by calling pids.toArray().
+ */
+ void onBlockingFileLock(IntArray pids);
+ }
+
+ /**
+ * Checks if a process corresponding to a specific pid owns any file locks.
+ * @param callback Callback function, accepting pid as the input parameter.
+ * @throws IOException if /proc/locks can't be accessed or correctly parsed.
+ */
+ public void handleBlockingFileLocks(ProcLocksReaderCallback callback) throws IOException {
+ long last = -1;
+ long id; // ordinal position of the lock in the list
+ int pid = -1; // the PID of the process being blocked
+
+ if (mReader == null) {
+ mReader = new ProcFileReader(new FileInputStream(mPath));
+ } else {
+ mReader.rewind();
+ }
+
+ mPids.clear();
+ while (mReader.hasMoreData()) {
+ id = mReader.nextLong(true); // lock id
+ if (id == last) {
+ // blocked lock found
+ mReader.nextIgnored(); // ->
+ mReader.nextIgnored(); // lock type: POSIX?
+ mReader.nextIgnored(); // lock type: MANDATORY?
+ mReader.nextIgnored(); // lock type: RW?
+
+ pid = mReader.nextInt(); // pid
+ if (pid > 0) {
+ mPids.add(pid);
+ }
+
+ mReader.finishLine();
+ } else {
+ // process blocking lock and move on to a new lock
+ if (mPids.size() > 1) {
+ callback.onBlockingFileLock(mPids);
+ mPids.clear();
+ }
+
+ // new lock found
+ mReader.nextIgnored(); // lock type: POSIX?
+ mReader.nextIgnored(); // lock type: MANDATORY?
+ mReader.nextIgnored(); // lock type: RW?
+
+ pid = mReader.nextInt(); // pid
+ if (pid > 0) {
+ if (mPids.size() == 0) {
+ mPids.add(pid);
+ } else {
+ mPids.set(0, pid);
+ }
+ }
+ mReader.finishLine();
+ last = id;
+ }
+ }
+ // The last unprocessed blocking lock immediately before EOF
+ if (mPids.size() > 1) {
+ callback.onBlockingFileLock(mPids);
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/ProcStatsUtil.java b/android-35/com/android/internal/os/ProcStatsUtil.java
new file mode 100644
index 0000000..1d8cf83
--- /dev/null
+++ b/android-35/com/android/internal/os/ProcStatsUtil.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import android.annotation.Nullable;
+import android.os.StrictMode;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * Utility functions for reading {@code proc} files
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
[email protected]
+public final class ProcStatsUtil {
+
+ private static final boolean DEBUG = false;
+
+ private static final String TAG = "ProcStatsUtil";
+
+ /**
+ * How much to read into a buffer when reading a proc file
+ */
+ private static final int READ_SIZE = 1024;
+
+ /**
+ * Class only contains static utility functions, and should not be instantiated
+ */
+ private ProcStatsUtil() {
+ }
+
+ /**
+ * Read a {@code proc} file where the contents are separated by null bytes. Replaces the null
+ * bytes with spaces, and removes any trailing null bytes
+ *
+ * @param path path of the file to read
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ @Nullable
+ public static String readNullSeparatedFile(String path) {
+ String contents = readSingleLineProcFile(path);
+ if (contents == null) {
+ return null;
+ }
+
+ // Content is either double-null terminated, or terminates at end of line. Remove anything
+ // after the double-null
+ final int endIndex = contents.indexOf("\0\0");
+ if (endIndex != -1) {
+ contents = contents.substring(0, endIndex);
+ }
+
+ // Change the null-separated contents into space-seperated
+ return contents.replace("\0", " ");
+ }
+
+ /**
+ * Read a {@code proc} file that contains a single line (e.g. {@code /proc/$PID/cmdline}, {@code
+ * /proc/$PID/comm})
+ *
+ * @param path path of the file to read
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ @Nullable
+ public static String readSingleLineProcFile(String path) {
+ return readTerminatedProcFile(path, (byte) '\n');
+ }
+
+ /**
+ * Read a {@code proc} file that terminates with a specific byte
+ *
+ * @param path path of the file to read
+ * @param terminator byte that terminates the file. We stop reading once this character is
+ * seen, or at the end of the file
+ */
+ @Nullable
+ public static String readTerminatedProcFile(String path, byte terminator) {
+ // Permit disk reads here, as /proc isn't really "on disk" and should be fast.
+ // TODO: make BlockGuard ignore /proc/ and /sys/ files perhaps?
+ final int savedPolicy = StrictMode.allowThreadDiskReadsMask();
+ try {
+ return readTerminatedProcFileInternal(path, terminator);
+ } finally {
+ StrictMode.setThreadPolicyMask(savedPolicy);
+ }
+ }
+
+ private static String readTerminatedProcFileInternal(String path, byte terminator) {
+ try (FileInputStream is = new FileInputStream(path)) {
+ ByteArrayOutputStream byteStream = null;
+ final byte[] buffer = new byte[READ_SIZE];
+ while (true) {
+ // Read file into buffer
+ final int len = is.read(buffer);
+ if (len <= 0) {
+ // If we've read nothing, we're done
+ break;
+ }
+
+ // Find the terminating character
+ int terminatingIndex = -1;
+ for (int i = 0; i < len; i++) {
+ if (buffer[i] == terminator) {
+ terminatingIndex = i;
+ break;
+ }
+ }
+ final boolean foundTerminator = terminatingIndex != -1;
+
+ // If we have found it and the byte stream isn't initialized, we don't need to
+ // initialize it and can return the string here
+ if (foundTerminator && byteStream == null) {
+ return new String(buffer, 0, terminatingIndex);
+ }
+
+ // Initialize the byte stream
+ if (byteStream == null) {
+ byteStream = new ByteArrayOutputStream(READ_SIZE);
+ }
+
+ // Write the whole buffer if terminator not found, or up to the terminator if found
+ byteStream.write(buffer, 0, foundTerminator ? terminatingIndex : len);
+
+ // If we've found the terminator, we can finish
+ if (foundTerminator) {
+ break;
+ }
+ }
+
+ // If the byte stream is null at the end, this means that we have read an empty file
+ if (byteStream == null) {
+ return "";
+ }
+ return byteStream.toString();
+ } catch (IOException e) {
+ if (DEBUG) {
+ Slog.d(TAG, "Failed to open proc file", e);
+ }
+ return null;
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/ProcTimeInStateReader.java b/android-35/com/android/internal/os/ProcTimeInStateReader.java
new file mode 100644
index 0000000..d54a9c7
--- /dev/null
+++ b/android-35/com/android/internal/os/ProcTimeInStateReader.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import android.annotation.Nullable;
+import android.os.Process;
+import android.util.IntArray;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * Reads and parses {@code time_in_state} files in the {@code proc} filesystem.
+ *
+ * Every line in a {@code time_in_state} file contains two numbers, separated by a single space
+ * character. The first number is the frequency of the CPU used in kilohertz. The second number is
+ * the time spent in this frequency. In the {@code time_in_state} file, this is given in 10s of
+ * milliseconds, but this class returns in milliseconds. This can be per user, process, or thread
+ * depending on which {@code time_in_state} file is used.
+ *
+ * For example, a {@code time_in_state} file would look like this:
+ * <pre>
+ * 300000 3
+ * 364800 0
+ * ...
+ * 1824000 0
+ * 1900800 1
+ * </pre>
+ *
+ * This file would indicate that the CPU has spent 30 milliseconds at frequency 300,000KHz (300Mhz)
+ * and 10 milliseconds at frequency 1,900,800KHz (1.9GHz).
+ *
+ * <p>This class will also read {@code time_in_state} files with headers, such as:
+ * <pre>
+ * cpu0
+ * 300000 3
+ * 364800 0
+ * ...
+ * cpu4
+ * 300000 1
+ * 364800 4
+ * </pre>
+ */
+public class ProcTimeInStateReader {
+ private static final String TAG = "ProcTimeInStateReader";
+
+ /**
+ * The format of a single line of the {@code time_in_state} file that exports the frequency
+ * values
+ */
+ private static final int[] TIME_IN_STATE_LINE_FREQUENCY_FORMAT = new int[] {
+ Process.PROC_OUT_LONG | Process.PROC_SPACE_TERM,
+ Process.PROC_NEWLINE_TERM
+ };
+
+ /**
+ * The format of a single line of the {@code time_in_state} file that exports the time values
+ */
+ private static final int[] TIME_IN_STATE_LINE_TIME_FORMAT = new int[] {
+ Process.PROC_SPACE_TERM,
+ Process.PROC_OUT_LONG | Process.PROC_NEWLINE_TERM
+ };
+
+ /**
+ * The format of a header line of the {@code time_in_state} file
+ */
+ private static final int[] TIME_IN_STATE_HEADER_LINE_FORMAT = new int[] {
+ Process.PROC_NEWLINE_TERM
+ };
+
+ /**
+ * The format of the {@code time_in_state} file to extract times, defined using {@link
+ * Process}'s {@code PROC_OUT_LONG} and related variables
+ */
+ private int[] mTimeInStateTimeFormat;
+
+ /**
+ * The frequencies reported in each {@code time_in_state} file
+ *
+ * Defined on first successful read of {@code time_in_state} file.
+ */
+ private long[] mFrequenciesKhz;
+
+ /**
+ * @param initialTimeInStateFile the file to base the format of the frequency files on, and to
+ * read frequencies from. Expected to be in the same format as all other {@code time_in_state}
+ * files, and contain the same frequencies.
+ * @throws IOException if reading the initial {@code time_in_state} file failed
+ */
+ public ProcTimeInStateReader(Path initialTimeInStateFile) throws IOException {
+ initializeTimeInStateFormat(initialTimeInStateFile);
+ }
+
+ /**
+ * Read the CPU usages from a file
+ *
+ * @param timeInStatePath path where the CPU usages are read from
+ * @return list of CPU usage times from the file. These correspond to the CPU frequencies given
+ * by {@link ProcTimeInStateReader#getFrequenciesKhz}
+ */
+ @Nullable
+ public long[] getUsageTimesMillis(final Path timeInStatePath) {
+ // Read in the time_in_state file
+ final long[] readLongs = new long[mFrequenciesKhz.length];
+ final boolean readSuccess = Process.readProcFile(
+ timeInStatePath.toString(),
+ mTimeInStateTimeFormat,
+ null, readLongs, null);
+ if (!readSuccess) {
+ return null;
+ }
+ // Usage time is given in 10ms, so convert to ms
+ for (int i = 0; i < readLongs.length; i++) {
+ readLongs[i] *= 10;
+ }
+ return readLongs;
+ }
+
+ /**
+ * Get the frequencies found in each {@code time_in_state} file
+ *
+ * @return list of CPU frequencies. These correspond to the CPU times given by {@link
+ * ProcTimeInStateReader#getUsageTimesMillis(Path)}()}.
+ */
+ @Nullable
+ public long[] getFrequenciesKhz() {
+ return mFrequenciesKhz;
+ }
+
+ /**
+ * Set the {@link #mTimeInStateTimeFormat} and {@link #mFrequenciesKhz} variables based on the
+ * an input file. If the file is empty, these variables aren't set
+ *
+ * This needs to be run once on the first invocation of {@link #getUsageTimesMillis(Path)}. This
+ * is because we need to know how many frequencies are available in order to parse time
+ * {@code time_in_state} file using {@link Process#readProcFile}, which only accepts
+ * fixed-length formats. Also, as the frequencies do not change between {@code time_in_state}
+ * files, we read and store them here.
+ *
+ * @param timeInStatePath the input file to base the format off of
+ */
+ private void initializeTimeInStateFormat(final Path timeInStatePath) throws IOException {
+ // Read the bytes of the `time_in_state` file
+ byte[] timeInStateBytes = Files.readAllBytes(timeInStatePath);
+
+ // Iterate over the lines of the time_in_state file, for each one adding a line to the
+ // formats. These formats are used to extract either the frequencies or the times from a
+ // time_in_state file
+ // Also check if each line is a header, and handle this in the created format arrays
+ IntArray timeInStateFrequencyFormat = new IntArray();
+ IntArray timeInStateTimeFormat = new IntArray();
+ int numFrequencies = 0;
+ for (int i = 0; i < timeInStateBytes.length; i++) {
+ // If the first character of the line is not a digit, we treat it as a header
+ if (!Character.isDigit(timeInStateBytes[i])) {
+ timeInStateFrequencyFormat.addAll(TIME_IN_STATE_HEADER_LINE_FORMAT);
+ timeInStateTimeFormat.addAll(TIME_IN_STATE_HEADER_LINE_FORMAT);
+ } else {
+ timeInStateFrequencyFormat.addAll(TIME_IN_STATE_LINE_FREQUENCY_FORMAT);
+ timeInStateTimeFormat.addAll(TIME_IN_STATE_LINE_TIME_FORMAT);
+ numFrequencies++;
+ }
+ // Go to the next line
+ while (i < timeInStateBytes.length && timeInStateBytes[i] != '\n') {
+ i++;
+ }
+ }
+
+ if (numFrequencies == 0) {
+ throw new IOException("Empty time_in_state file");
+ }
+
+ // Read the frequencies from the `time_in_state` file and store them, as they will be the
+ // same for every `time_in_state` file
+ final long[] readLongs = new long[numFrequencies];
+ final boolean readSuccess = Process.parseProcLine(
+ timeInStateBytes, 0, timeInStateBytes.length,
+ timeInStateFrequencyFormat.toArray(), null, readLongs, null);
+ if (!readSuccess) {
+ throw new IOException("Failed to parse time_in_state file");
+ }
+
+ mTimeInStateTimeFormat = timeInStateTimeFormat.toArray();
+ mFrequenciesKhz = readLongs;
+ }
+}
diff --git a/android-35/com/android/internal/os/ProcessCpuTracker.java b/android-35/com/android/internal/os/ProcessCpuTracker.java
new file mode 100644
index 0000000..01c91ba
--- /dev/null
+++ b/android-35/com/android/internal/os/ProcessCpuTracker.java
@@ -0,0 +1,1006 @@
+/*
+ * Copyright (C) 2007 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.internal.os;
+
+import static android.os.Process.PROC_COMBINE;
+import static android.os.Process.PROC_OUT_FLOAT;
+import static android.os.Process.PROC_OUT_LONG;
+import static android.os.Process.PROC_OUT_STRING;
+import static android.os.Process.PROC_PARENS;
+import static android.os.Process.PROC_SPACE_TERM;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.BatteryStats;
+import android.os.Build;
+import android.os.CpuUsageProto;
+import android.os.Process;
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+
+public class ProcessCpuTracker {
+ private static final String TAG = "ProcessCpuTracker";
+ private static final boolean DEBUG = false;
+ private static final boolean localLOGV = DEBUG || false;
+
+ private static final int[] PROCESS_STATS_FORMAT = new int[] {
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM|PROC_PARENS,
+ 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, // 10: minor faults
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM|PROC_OUT_LONG, // 12: major faults
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM|PROC_OUT_LONG, // 14: utime
+ PROC_SPACE_TERM|PROC_OUT_LONG, // 15: stime
+ };
+
+ static final int PROCESS_STAT_MINOR_FAULTS = 0;
+ static final int PROCESS_STAT_MAJOR_FAULTS = 1;
+ static final int PROCESS_STAT_UTIME = 2;
+ static final int PROCESS_STAT_STIME = 3;
+
+ /** Stores user time and system time in jiffies. */
+ private final long[] mProcessStatsData = new long[4];
+
+ private static final int[] PROCESS_FULL_STATS_FORMAT = new int[] {
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM|PROC_PARENS|PROC_OUT_STRING, // 2: name
+ 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, // 10: minor faults
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM|PROC_OUT_LONG, // 12: major faults
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM|PROC_OUT_LONG, // 14: utime
+ PROC_SPACE_TERM|PROC_OUT_LONG, // 15: stime
+ 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, // 23: vsize
+ };
+
+ static final int PROCESS_FULL_STAT_MINOR_FAULTS = 1;
+ static final int PROCESS_FULL_STAT_MAJOR_FAULTS = 2;
+ static final int PROCESS_FULL_STAT_UTIME = 3;
+ static final int PROCESS_FULL_STAT_STIME = 4;
+ static final int PROCESS_FULL_STAT_VSIZE = 5;
+
+ private final String[] mProcessFullStatsStringData = new String[6];
+ private final long[] mProcessFullStatsData = new long[6];
+
+ private static final int[] PROCESS_SCHEDSTATS_FORMAT = new int[] {
+ PROC_SPACE_TERM|PROC_OUT_LONG,
+ PROC_SPACE_TERM|PROC_OUT_LONG,
+ };
+
+ static final int PROCESS_SCHEDSTAT_CPU_TIME = 0;
+ static final int PROCESS_SCHEDSTAT_CPU_DELAY_TIME = 1;
+
+ private static final int[] SYSTEM_CPU_FORMAT = new int[] {
+ PROC_SPACE_TERM|PROC_COMBINE,
+ PROC_SPACE_TERM|PROC_OUT_LONG, // 1: user time
+ PROC_SPACE_TERM|PROC_OUT_LONG, // 2: nice time
+ PROC_SPACE_TERM|PROC_OUT_LONG, // 3: sys time
+ PROC_SPACE_TERM|PROC_OUT_LONG, // 4: idle time
+ PROC_SPACE_TERM|PROC_OUT_LONG, // 5: iowait time
+ PROC_SPACE_TERM|PROC_OUT_LONG, // 6: irq time
+ PROC_SPACE_TERM|PROC_OUT_LONG // 7: softirq time
+ };
+
+ private final long[] mSystemCpuData = new long[7];
+
+ private static final int[] LOAD_AVERAGE_FORMAT = new int[] {
+ PROC_SPACE_TERM|PROC_OUT_FLOAT, // 0: 1 min
+ PROC_SPACE_TERM|PROC_OUT_FLOAT, // 1: 5 mins
+ PROC_SPACE_TERM|PROC_OUT_FLOAT // 2: 15 mins
+ };
+
+ private final float[] mLoadAverageData = new float[3];
+
+ private final boolean mIncludeThreads;
+
+ // How long a CPU jiffy is in milliseconds.
+ private final long mJiffyMillis;
+
+ private float mLoad1 = 0;
+ private float mLoad5 = 0;
+ private float mLoad15 = 0;
+
+ // All times are in milliseconds. They are converted from jiffies to milliseconds
+ // when extracted from the kernel.
+ private long mCurrentSampleTime;
+ private long mLastSampleTime;
+
+ private long mCurrentSampleRealTime;
+ private long mLastSampleRealTime;
+
+ private long mCurrentSampleWallTime;
+ private long mLastSampleWallTime;
+
+ private long mBaseUserTime;
+ private long mBaseSystemTime;
+ private long mBaseIoWaitTime;
+ private long mBaseIrqTime;
+ private long mBaseSoftIrqTime;
+ private long mBaseIdleTime;
+ private int mRelUserTime;
+ private int mRelSystemTime;
+ private int mRelIoWaitTime;
+ private int mRelIrqTime;
+ private int mRelSoftIrqTime;
+ private int mRelIdleTime;
+ private boolean mRelStatsAreGood;
+
+ private int[] mCurPids;
+ private int[] mCurThreadPids;
+
+ private final ArrayList<Stats> mProcStats = new ArrayList<Stats>();
+ private final ArrayList<Stats> mWorkingProcs = new ArrayList<Stats>();
+ private boolean mWorkingProcsSorted;
+
+ private boolean mFirst = true;
+
+ public interface FilterStats {
+ /** Which stats to pick when filtering */
+ boolean needed(Stats stats);
+ }
+
+ public static class Stats {
+ public final int pid;
+ public final int uid;
+ final String statFile;
+ final String cmdlineFile;
+ final String threadsDir;
+ final ArrayList<Stats> threadStats;
+ final ArrayList<Stats> workingThreads;
+
+ public BatteryStats.Uid.Proc batteryStats;
+
+ public boolean interesting;
+
+ public String baseName;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public String name;
+ public int nameWidth;
+
+ // vsize capture when process first detected; can be used to
+ // filter out kernel processes.
+ public long vsize;
+
+ /**
+ * Time in milliseconds.
+ */
+ public long base_uptime;
+
+ /**
+ * Time in milliseconds.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public long rel_uptime;
+
+ /**
+ * Time in milliseconds.
+ */
+ public long base_utime;
+
+ /**
+ * Time in milliseconds.
+ */
+ public long base_stime;
+
+ /**
+ * Time in milliseconds.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int rel_utime;
+
+ /**
+ * Time in milliseconds.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int rel_stime;
+
+ public long base_minfaults;
+ public long base_majfaults;
+ public int rel_minfaults;
+ public int rel_majfaults;
+
+ public boolean active;
+ public boolean working;
+ public boolean added;
+ public boolean removed;
+
+ Stats(int _pid, int parentPid, boolean includeThreads) {
+ pid = _pid;
+ if (parentPid < 0) {
+ final File procDir = new File("/proc", Integer.toString(pid));
+ uid = getUid(procDir.toString());
+ statFile = new File(procDir, "stat").toString();
+ cmdlineFile = new File(procDir, "cmdline").toString();
+ threadsDir = (new File(procDir, "task")).toString();
+ if (includeThreads) {
+ threadStats = new ArrayList<Stats>();
+ workingThreads = new ArrayList<Stats>();
+ } else {
+ threadStats = null;
+ workingThreads = null;
+ }
+ } else {
+ final File procDir = new File("/proc", Integer.toString(
+ parentPid));
+ final File taskDir = new File(
+ new File(procDir, "task"), Integer.toString(pid));
+ uid = getUid(taskDir.toString());
+ statFile = new File(taskDir, "stat").toString();
+ cmdlineFile = null;
+ threadsDir = null;
+ threadStats = null;
+ workingThreads = null;
+ }
+ }
+
+ private static int getUid(String path) {
+ try {
+ return Os.stat(path).st_uid;
+ } catch (ErrnoException e) {
+ Slog.w(TAG, "Failed to stat(" + path + "): " + e);
+ return -1;
+ }
+ }
+ }
+
+ private final static Comparator<Stats> sLoadComparator = new Comparator<Stats>() {
+ public final int
+ compare(Stats sta, Stats stb) {
+ int ta = sta.rel_utime + sta.rel_stime;
+ int tb = stb.rel_utime + stb.rel_stime;
+ if (ta != tb) {
+ return ta > tb ? -1 : 1;
+ }
+ if (sta.added != stb.added) {
+ return sta.added ? -1 : 1;
+ }
+ if (sta.removed != stb.removed) {
+ return sta.added ? -1 : 1;
+ }
+ return 0;
+ }
+ };
+
+
+ @UnsupportedAppUsage
+ public ProcessCpuTracker(boolean includeThreads) {
+ mIncludeThreads = includeThreads;
+ long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
+ mJiffyMillis = 1000/jiffyHz;
+ }
+
+ public void onLoadChanged(float load1, float load5, float load15) {
+ }
+
+ public int onMeasureProcessName(String name) {
+ return 0;
+ }
+
+ public void init() {
+ if (DEBUG) Slog.v(TAG, "Init: " + this);
+ mFirst = true;
+ update();
+ }
+
+ @UnsupportedAppUsage
+ public void update() {
+ synchronized (this) {
+ updateLocked();
+ }
+ }
+
+ private void updateLocked() {
+ if (DEBUG) Slog.v(TAG, "Update: " + this);
+
+ final long nowUptime = SystemClock.uptimeMillis();
+ final long nowRealtime = SystemClock.elapsedRealtime();
+ final long nowWallTime = System.currentTimeMillis();
+
+ final long[] sysCpu = mSystemCpuData;
+ if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT,
+ null, sysCpu, null)) {
+ // Total user time is user + nice time.
+ final long usertime = (sysCpu[0]+sysCpu[1]) * mJiffyMillis;
+ // Total system time is simply system time.
+ final long systemtime = sysCpu[2] * mJiffyMillis;
+ // Total idle time is simply idle time.
+ final long idletime = sysCpu[3] * mJiffyMillis;
+ // Total irq time is iowait + irq + softirq time.
+ final long iowaittime = sysCpu[4] * mJiffyMillis;
+ final long irqtime = sysCpu[5] * mJiffyMillis;
+ final long softirqtime = sysCpu[6] * mJiffyMillis;
+
+ // This code is trying to avoid issues with idle time going backwards,
+ // but currently it gets into situations where it triggers most of the time. :(
+ if (true || (usertime >= mBaseUserTime && systemtime >= mBaseSystemTime
+ && iowaittime >= mBaseIoWaitTime && irqtime >= mBaseIrqTime
+ && softirqtime >= mBaseSoftIrqTime && idletime >= mBaseIdleTime)) {
+ mRelUserTime = (int)(usertime - mBaseUserTime);
+ mRelSystemTime = (int)(systemtime - mBaseSystemTime);
+ mRelIoWaitTime = (int)(iowaittime - mBaseIoWaitTime);
+ mRelIrqTime = (int)(irqtime - mBaseIrqTime);
+ mRelSoftIrqTime = (int)(softirqtime - mBaseSoftIrqTime);
+ mRelIdleTime = (int)(idletime - mBaseIdleTime);
+ mRelStatsAreGood = true;
+
+ if (DEBUG) {
+ Slog.i("Load", "Total U:" + (sysCpu[0]*mJiffyMillis)
+ + " N:" + (sysCpu[1]*mJiffyMillis)
+ + " S:" + (sysCpu[2]*mJiffyMillis) + " I:" + (sysCpu[3]*mJiffyMillis)
+ + " W:" + (sysCpu[4]*mJiffyMillis) + " Q:" + (sysCpu[5]*mJiffyMillis)
+ + " O:" + (sysCpu[6]*mJiffyMillis));
+ Slog.i("Load", "Rel U:" + mRelUserTime + " S:" + mRelSystemTime
+ + " I:" + mRelIdleTime + " Q:" + mRelIrqTime);
+ }
+
+ mBaseUserTime = usertime;
+ mBaseSystemTime = systemtime;
+ mBaseIoWaitTime = iowaittime;
+ mBaseIrqTime = irqtime;
+ mBaseSoftIrqTime = softirqtime;
+ mBaseIdleTime = idletime;
+
+ } else {
+ mRelUserTime = 0;
+ mRelSystemTime = 0;
+ mRelIoWaitTime = 0;
+ mRelIrqTime = 0;
+ mRelSoftIrqTime = 0;
+ mRelIdleTime = 0;
+ mRelStatsAreGood = false;
+ Slog.w(TAG, "/proc/stats has gone backwards; skipping CPU update");
+ return;
+ }
+ }
+
+ mLastSampleTime = mCurrentSampleTime;
+ mCurrentSampleTime = nowUptime;
+ mLastSampleRealTime = mCurrentSampleRealTime;
+ mCurrentSampleRealTime = nowRealtime;
+ mLastSampleWallTime = mCurrentSampleWallTime;
+ mCurrentSampleWallTime = nowWallTime;
+
+ final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
+ try {
+ mCurPids = collectStats("/proc", -1, mFirst, mCurPids, mProcStats);
+ } finally {
+ StrictMode.setThreadPolicy(savedPolicy);
+ }
+
+ final float[] loadAverages = mLoadAverageData;
+ if (Process.readProcFile("/proc/loadavg", LOAD_AVERAGE_FORMAT,
+ null, null, loadAverages)) {
+ float load1 = loadAverages[0];
+ float load5 = loadAverages[1];
+ float load15 = loadAverages[2];
+ if (load1 != mLoad1 || load5 != mLoad5 || load15 != mLoad15) {
+ mLoad1 = load1;
+ mLoad5 = load5;
+ mLoad15 = load15;
+ onLoadChanged(load1, load5, load15);
+ }
+ }
+
+ if (DEBUG) Slog.i(TAG, "*** TIME TO COLLECT STATS: "
+ + (SystemClock.uptimeMillis()-mCurrentSampleTime));
+
+ mWorkingProcsSorted = false;
+ mFirst = false;
+ }
+
+ private int[] collectStats(String statsFile, int parentPid, boolean first,
+ int[] curPids, ArrayList<Stats> allProcs) {
+
+ int[] pids = Process.getPids(statsFile, curPids);
+ int NP = (pids == null) ? 0 : pids.length;
+ int NS = allProcs.size();
+ int curStatsIndex = 0;
+ for (int i=0; i<NP; i++) {
+ int pid = pids[i];
+ if (pid < 0) {
+ NP = pid;
+ break;
+ }
+ Stats st = curStatsIndex < NS ? allProcs.get(curStatsIndex) : null;
+
+ if (st != null && st.pid == pid) {
+ // Update an existing process...
+ st.added = false;
+ st.working = false;
+ curStatsIndex++;
+ if (DEBUG) Slog.v(TAG, "Existing "
+ + (parentPid < 0 ? "process" : "thread")
+ + " pid " + pid + ": " + st);
+
+ if (st.interesting) {
+ final long uptime = SystemClock.uptimeMillis();
+
+ final long[] procStats = mProcessStatsData;
+ if (!Process.readProcFile(st.statFile.toString(),
+ PROCESS_STATS_FORMAT, null, procStats, null)) {
+ continue;
+ }
+
+ final long minfaults = procStats[PROCESS_STAT_MINOR_FAULTS];
+ final long majfaults = procStats[PROCESS_STAT_MAJOR_FAULTS];
+ final long utime = procStats[PROCESS_STAT_UTIME] * mJiffyMillis;
+ final long stime = procStats[PROCESS_STAT_STIME] * mJiffyMillis;
+
+ if (utime == st.base_utime && stime == st.base_stime) {
+ st.rel_utime = 0;
+ st.rel_stime = 0;
+ st.rel_minfaults = 0;
+ st.rel_majfaults = 0;
+ if (st.active) {
+ st.active = false;
+ }
+ continue;
+ }
+
+ if (!st.active) {
+ st.active = true;
+ }
+
+ if (parentPid < 0) {
+ getName(st, st.cmdlineFile);
+ if (st.threadStats != null) {
+ mCurThreadPids = collectStats(st.threadsDir, pid, false,
+ mCurThreadPids, st.threadStats);
+ }
+ }
+
+ if (DEBUG) Slog.v("Load", "Stats changed " + st.name + " pid=" + st.pid
+ + " utime=" + utime + "-" + st.base_utime
+ + " stime=" + stime + "-" + st.base_stime
+ + " minfaults=" + minfaults + "-" + st.base_minfaults
+ + " majfaults=" + majfaults + "-" + st.base_majfaults);
+
+ st.rel_uptime = uptime - st.base_uptime;
+ st.base_uptime = uptime;
+ st.rel_utime = (int)(utime - st.base_utime);
+ st.rel_stime = (int)(stime - st.base_stime);
+ st.base_utime = utime;
+ st.base_stime = stime;
+ st.rel_minfaults = (int)(minfaults - st.base_minfaults);
+ st.rel_majfaults = (int)(majfaults - st.base_majfaults);
+ st.base_minfaults = minfaults;
+ st.base_majfaults = majfaults;
+ st.working = true;
+ }
+
+ continue;
+ }
+
+ if (st == null || st.pid > pid) {
+ // We have a new process!
+ st = new Stats(pid, parentPid, mIncludeThreads);
+ allProcs.add(curStatsIndex, st);
+ curStatsIndex++;
+ NS++;
+ if (DEBUG) Slog.v(TAG, "New "
+ + (parentPid < 0 ? "process" : "thread")
+ + " pid " + pid + ": " + st);
+
+ final String[] procStatsString = mProcessFullStatsStringData;
+ final long[] procStats = mProcessFullStatsData;
+ st.base_uptime = SystemClock.uptimeMillis();
+ String path = st.statFile.toString();
+ //Slog.d(TAG, "Reading proc file: " + path);
+ if (Process.readProcFile(path, PROCESS_FULL_STATS_FORMAT, procStatsString,
+ procStats, null)) {
+ // This is a possible way to filter out processes that
+ // are actually kernel threads... do we want to? Some
+ // of them do use CPU, but there can be a *lot* that are
+ // not doing anything.
+ st.vsize = procStats[PROCESS_FULL_STAT_VSIZE];
+ if (true || procStats[PROCESS_FULL_STAT_VSIZE] != 0) {
+ st.interesting = true;
+ st.baseName = procStatsString[0];
+ st.base_minfaults = procStats[PROCESS_FULL_STAT_MINOR_FAULTS];
+ st.base_majfaults = procStats[PROCESS_FULL_STAT_MAJOR_FAULTS];
+ st.base_utime = procStats[PROCESS_FULL_STAT_UTIME] * mJiffyMillis;
+ st.base_stime = procStats[PROCESS_FULL_STAT_STIME] * mJiffyMillis;
+ } else {
+ Slog.i(TAG, "Skipping kernel process pid " + pid
+ + " name " + procStatsString[0]);
+ st.baseName = procStatsString[0];
+ }
+ } else {
+ Slog.w(TAG, "Skipping unknown process pid " + pid);
+ st.baseName = "<unknown>";
+ st.base_utime = st.base_stime = 0;
+ st.base_minfaults = st.base_majfaults = 0;
+ }
+
+ if (parentPid < 0) {
+ getName(st, st.cmdlineFile);
+ if (st.threadStats != null) {
+ mCurThreadPids = collectStats(st.threadsDir, pid, true,
+ mCurThreadPids, st.threadStats);
+ }
+ } else if (st.interesting) {
+ st.name = st.baseName;
+ st.nameWidth = onMeasureProcessName(st.name);
+ }
+
+ if (DEBUG) Slog.v("Load", "Stats added " + st.name + " pid=" + st.pid
+ + " utime=" + st.base_utime + " stime=" + st.base_stime
+ + " minfaults=" + st.base_minfaults + " majfaults=" + st.base_majfaults);
+
+ st.rel_utime = 0;
+ st.rel_stime = 0;
+ st.rel_minfaults = 0;
+ st.rel_majfaults = 0;
+ st.added = true;
+ if (!first && st.interesting) {
+ st.working = true;
+ }
+ continue;
+ }
+
+ // This process has gone away!
+ st.rel_utime = 0;
+ st.rel_stime = 0;
+ st.rel_minfaults = 0;
+ st.rel_majfaults = 0;
+ st.removed = true;
+ st.working = true;
+ allProcs.remove(curStatsIndex);
+ NS--;
+ if (DEBUG) Slog.v(TAG, "Removed "
+ + (parentPid < 0 ? "process" : "thread")
+ + " pid " + pid + ": " + st);
+ // Decrement the loop counter so that we process the current pid
+ // again the next time through the loop.
+ i--;
+ continue;
+ }
+
+ while (curStatsIndex < NS) {
+ // This process has gone away!
+ final Stats st = allProcs.get(curStatsIndex);
+ st.rel_utime = 0;
+ st.rel_stime = 0;
+ st.rel_minfaults = 0;
+ st.rel_majfaults = 0;
+ st.removed = true;
+ st.working = true;
+ allProcs.remove(curStatsIndex);
+ NS--;
+ if (localLOGV) Slog.v(TAG, "Removed pid " + st.pid + ": " + st);
+ }
+
+ return pids;
+ }
+
+ /**
+ * Returns the total time (in milliseconds) the given PID has spent
+ * executing in both user and system code. Safe to call without lock held.
+ */
+ public long getCpuTimeForPid(int pid) {
+ final String statFile = "/proc/" + pid + "/stat";
+ final long[] statsData = new long[4];
+ if (Process.readProcFile(statFile, PROCESS_STATS_FORMAT,
+ null, statsData, null)) {
+ long time = statsData[PROCESS_STAT_UTIME]
+ + statsData[PROCESS_STAT_STIME];
+ return time * mJiffyMillis;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the total time (in milliseconds) the given PID has spent waiting
+ * in the runqueue. Safe to call without lock held.
+ */
+ public long getCpuDelayTimeForPid(int pid) {
+ final String statFile = "/proc/" + pid + "/schedstat";
+ final long[] statsData = new long[4];
+ if (Process.readProcFile(statFile, PROCESS_SCHEDSTATS_FORMAT,
+ null, statsData, null)) {
+ return statsData[PROCESS_SCHEDSTAT_CPU_DELAY_TIME] / 1_000_000;
+ }
+ return 0;
+ }
+
+ /**
+ * @return time in milliseconds.
+ */
+ final public int getLastUserTime() {
+ return mRelUserTime;
+ }
+
+ /**
+ * @return time in milliseconds.
+ */
+ final public int getLastSystemTime() {
+ return mRelSystemTime;
+ }
+
+ /**
+ * @return time in milliseconds.
+ */
+ final public int getLastIoWaitTime() {
+ return mRelIoWaitTime;
+ }
+
+ /**
+ * @return time in milliseconds.
+ */
+ final public int getLastIrqTime() {
+ return mRelIrqTime;
+ }
+
+ /**
+ * @return time in milliseconds.
+ */
+ final public int getLastSoftIrqTime() {
+ return mRelSoftIrqTime;
+ }
+
+ /**
+ * @return time in milliseconds.
+ */
+ final public int getLastIdleTime() {
+ return mRelIdleTime;
+ }
+
+ final public boolean hasGoodLastStats() {
+ return mRelStatsAreGood;
+ }
+
+ final public float getTotalCpuPercent() {
+ int denom = mRelUserTime+mRelSystemTime+mRelIrqTime+mRelIdleTime;
+ if (denom <= 0) {
+ return 0;
+ }
+ return ((float)(mRelUserTime+mRelSystemTime+mRelIrqTime)*100) / denom;
+ }
+
+ final void buildWorkingProcs() {
+ if (!mWorkingProcsSorted) {
+ mWorkingProcs.clear();
+ final int N = mProcStats.size();
+ for (int i=0; i<N; i++) {
+ Stats stats = mProcStats.get(i);
+ if (stats.working) {
+ mWorkingProcs.add(stats);
+ if (stats.threadStats != null && stats.threadStats.size() > 1) {
+ stats.workingThreads.clear();
+ final int M = stats.threadStats.size();
+ for (int j=0; j<M; j++) {
+ Stats tstats = stats.threadStats.get(j);
+ if (tstats.working) {
+ stats.workingThreads.add(tstats);
+ }
+ }
+ Collections.sort(stats.workingThreads, sLoadComparator);
+ }
+ }
+ }
+ Collections.sort(mWorkingProcs, sLoadComparator);
+ mWorkingProcsSorted = true;
+ }
+ }
+
+ final public int countStats() {
+ return mProcStats.size();
+ }
+
+ final public Stats getStats(int index) {
+ return mProcStats.get(index);
+ }
+
+ final public List<Stats> getStats(FilterStats filter) {
+ final ArrayList<Stats> statses = new ArrayList<>(mProcStats.size());
+ final int N = mProcStats.size();
+ for (int p = 0; p < N; p++) {
+ Stats stats = mProcStats.get(p);
+ if (filter.needed(stats)) {
+ statses.add(stats);
+ }
+ }
+ return statses;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ final public int countWorkingStats() {
+ buildWorkingProcs();
+ return mWorkingProcs.size();
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ final public Stats getWorkingStats(int index) {
+ return mWorkingProcs.get(index);
+ }
+
+ /** Dump cpuinfo in protobuf format. */
+ public final void dumpProto(FileDescriptor fd) {
+ final long now = SystemClock.uptimeMillis();
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ final long currentLoadToken = proto.start(CpuUsageProto.CURRENT_LOAD);
+ proto.write(CpuUsageProto.Load.LOAD1, mLoad1);
+ proto.write(CpuUsageProto.Load.LOAD5, mLoad5);
+ proto.write(CpuUsageProto.Load.LOAD15, mLoad15);
+ proto.end(currentLoadToken);
+
+ buildWorkingProcs();
+
+ proto.write(CpuUsageProto.NOW, now);
+ proto.write(CpuUsageProto.LAST_SAMPLE_TIME, mLastSampleTime);
+ proto.write(CpuUsageProto.CURRENT_SAMPLE_TIME, mCurrentSampleTime);
+ proto.write(CpuUsageProto.LAST_SAMPLE_REAL_TIME, mLastSampleRealTime);
+ proto.write(CpuUsageProto.CURRENT_SAMPLE_REAL_TIME, mCurrentSampleRealTime);
+ proto.write(CpuUsageProto.LAST_SAMPLE_WALL_TIME, mLastSampleWallTime);
+ proto.write(CpuUsageProto.CURRENT_SAMPLE_WALL_TIME, mCurrentSampleWallTime);
+
+ proto.write(CpuUsageProto.TOTAL_USER_TIME, mRelUserTime);
+ proto.write(CpuUsageProto.TOTAL_SYSTEM_TIME, mRelSystemTime);
+ proto.write(CpuUsageProto.TOTAL_IOWAIT_TIME, mRelIoWaitTime);
+ proto.write(CpuUsageProto.TOTAL_IRQ_TIME, mRelIrqTime);
+ proto.write(CpuUsageProto.TOTAL_SOFT_IRQ_TIME, mRelSoftIrqTime);
+ proto.write(CpuUsageProto.TOTAL_IDLE_TIME, mRelIdleTime);
+ final int totalTime = mRelUserTime + mRelSystemTime + mRelIoWaitTime
+ + mRelIrqTime + mRelSoftIrqTime + mRelIdleTime;
+ proto.write(CpuUsageProto.TOTAL_TIME, totalTime);
+
+ for (Stats st : mWorkingProcs) {
+ dumpProcessCpuProto(proto, st, null);
+ if (!st.removed && st.workingThreads != null) {
+ for (Stats tst : st.workingThreads) {
+ dumpProcessCpuProto(proto, tst, st);
+ }
+ }
+ }
+ proto.flush();
+ }
+
+ private static void dumpProcessCpuProto(ProtoOutputStream proto, Stats st, Stats proc) {
+ long statToken = proto.start(CpuUsageProto.PROCESSES);
+ proto.write(CpuUsageProto.Stat.UID, st.uid);
+ proto.write(CpuUsageProto.Stat.PID, st.pid);
+ proto.write(CpuUsageProto.Stat.NAME, st.name);
+ proto.write(CpuUsageProto.Stat.ADDED, st.added);
+ proto.write(CpuUsageProto.Stat.REMOVED, st.removed);
+ proto.write(CpuUsageProto.Stat.UPTIME, st.rel_uptime);
+ proto.write(CpuUsageProto.Stat.USER_TIME, st.rel_utime);
+ proto.write(CpuUsageProto.Stat.SYSTEM_TIME, st.rel_stime);
+ proto.write(CpuUsageProto.Stat.MINOR_FAULTS, st.rel_minfaults);
+ proto.write(CpuUsageProto.Stat.MAJOR_FAULTS, st.rel_majfaults);
+ if (proc != null) {
+ proto.write(CpuUsageProto.Stat.PARENT_PID, proc.pid);
+ }
+ proto.end(statToken);
+ }
+
+ final public String printCurrentLoad() {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new FastPrintWriter(sw, false, 128);
+ pw.print("Load: ");
+ pw.print(mLoad1);
+ pw.print(" / ");
+ pw.print(mLoad5);
+ pw.print(" / ");
+ pw.println(mLoad15);
+ pw.flush();
+ return sw.toString();
+ }
+
+ /**
+ * Returns current CPU state with all the processes as a String, sorted by load
+ * in descending order.
+ */
+ public final String printCurrentState(long now) {
+ return printCurrentState(now, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Returns current CPU state with the top {@code maxProcessesToDump} highest load
+ * processes as a String, sorted by load in descending order.
+ */
+ public final String printCurrentState(long now, int maxProcessesToDump) {
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+
+ buildWorkingProcs();
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new FastPrintWriter(sw, false, 1024);
+
+ pw.print("CPU usage from ");
+ if (now > mLastSampleTime) {
+ pw.print(now-mLastSampleTime);
+ pw.print("ms to ");
+ pw.print(now-mCurrentSampleTime);
+ pw.print("ms ago");
+ } else {
+ pw.print(mLastSampleTime-now);
+ pw.print("ms to ");
+ pw.print(mCurrentSampleTime-now);
+ pw.print("ms later");
+ }
+ pw.print(" (");
+ pw.print(sdf.format(new Date(mLastSampleWallTime)));
+ pw.print(" to ");
+ pw.print(sdf.format(new Date(mCurrentSampleWallTime)));
+ pw.print(")");
+
+ long sampleTime = mCurrentSampleTime - mLastSampleTime;
+ long sampleRealTime = mCurrentSampleRealTime - mLastSampleRealTime;
+ long percAwake = sampleRealTime > 0 ? ((sampleTime*100) / sampleRealTime) : 0;
+ if (percAwake != 100) {
+ pw.print(" with ");
+ pw.print(percAwake);
+ pw.print("% awake");
+ }
+ pw.println(":");
+
+ final int totalTime = mRelUserTime + mRelSystemTime + mRelIoWaitTime
+ + mRelIrqTime + mRelSoftIrqTime + mRelIdleTime;
+
+ if (DEBUG) Slog.i(TAG, "totalTime " + totalTime + " over sample time "
+ + (mCurrentSampleTime-mLastSampleTime));
+
+ int dumpedProcessCount = Math.min(maxProcessesToDump, mWorkingProcs.size());
+ for (int i = 0; i < dumpedProcessCount; i++) {
+ Stats st = mWorkingProcs.get(i);
+ printProcessCPU(pw, st.added ? " +" : (st.removed ? " -": " "),
+ st.pid, st.name, (int)st.rel_uptime,
+ st.rel_utime, st.rel_stime, 0, 0, 0, st.rel_minfaults, st.rel_majfaults);
+ if (!st.removed && st.workingThreads != null) {
+ int M = st.workingThreads.size();
+ for (int j=0; j<M; j++) {
+ Stats tst = st.workingThreads.get(j);
+ printProcessCPU(pw,
+ tst.added ? " +" : (tst.removed ? " -": " "),
+ tst.pid, tst.name, (int)st.rel_uptime,
+ tst.rel_utime, tst.rel_stime, 0, 0, 0, 0, 0);
+ }
+ }
+ }
+
+ printProcessCPU(pw, "", -1, "TOTAL", totalTime, mRelUserTime, mRelSystemTime,
+ mRelIoWaitTime, mRelIrqTime, mRelSoftIrqTime, 0, 0);
+
+ pw.flush();
+ return sw.toString();
+ }
+
+ private void printRatio(PrintWriter pw, long numerator, long denominator) {
+ long thousands = (numerator*1000)/denominator;
+ long hundreds = thousands/10;
+ pw.print(hundreds);
+ if (hundreds < 10) {
+ long remainder = thousands - (hundreds*10);
+ if (remainder != 0) {
+ pw.print('.');
+ pw.print(remainder);
+ }
+ }
+ }
+
+ private void printProcessCPU(PrintWriter pw, String prefix, int pid, String label,
+ int totalTime, int user, int system, int iowait, int irq, int softIrq,
+ int minFaults, int majFaults) {
+ pw.print(prefix);
+ if (totalTime == 0) totalTime = 1;
+ printRatio(pw, user+system+iowait+irq+softIrq, totalTime);
+ pw.print("% ");
+ if (pid >= 0) {
+ pw.print(pid);
+ pw.print("/");
+ }
+ pw.print(label);
+ pw.print(": ");
+ printRatio(pw, user, totalTime);
+ pw.print("% user + ");
+ printRatio(pw, system, totalTime);
+ pw.print("% kernel");
+ if (iowait > 0) {
+ pw.print(" + ");
+ printRatio(pw, iowait, totalTime);
+ pw.print("% iowait");
+ }
+ if (irq > 0) {
+ pw.print(" + ");
+ printRatio(pw, irq, totalTime);
+ pw.print("% irq");
+ }
+ if (softIrq > 0) {
+ pw.print(" + ");
+ printRatio(pw, softIrq, totalTime);
+ pw.print("% softirq");
+ }
+ if (minFaults > 0 || majFaults > 0) {
+ pw.print(" / faults:");
+ if (minFaults > 0) {
+ pw.print(" ");
+ pw.print(minFaults);
+ pw.print(" minor");
+ }
+ if (majFaults > 0) {
+ pw.print(" ");
+ pw.print(majFaults);
+ pw.print(" major");
+ }
+ }
+ pw.println();
+ }
+
+ private void getName(Stats st, String cmdlineFile) {
+ String newName = st.name;
+ if (st.name == null
+ || st.name.equals("app_process")
+ || st.name.equals("<pre-initialized>")
+ || st.name.equals("usap32")
+ || st.name.equals("usap64")) {
+ String cmdName = ProcStatsUtil.readTerminatedProcFile(cmdlineFile, (byte) '\0');
+ if (cmdName != null && cmdName.length() > 1) {
+ newName = cmdName;
+ int i = newName.lastIndexOf("/");
+ if (i > 0 && i < newName.length()-1) {
+ newName = newName.substring(i+1);
+ }
+ }
+ if (newName == null) {
+ newName = st.baseName;
+ }
+ }
+ if (st.name == null || !newName.equals(st.name)) {
+ st.name = newName;
+ st.nameWidth = onMeasureProcessName(st.name);
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/RailStats.java b/android-35/com/android/internal/os/RailStats.java
new file mode 100644
index 0000000..ff00831
--- /dev/null
+++ b/android-35/com/android/internal/os/RailStats.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2019 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.internal.os;
+
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import java.util.Map;
+
+/** Rail Stats Power Monitoring Class */
+public final class RailStats {
+ private static final String TAG = "RailStats";
+
+ private static final String WIFI_SUBSYSTEM = "wifi";
+ private static final String CELLULAR_SUBSYSTEM = "cellular";
+
+ private Map<Long, RailInfoData> mRailInfoData = new ArrayMap<>();
+
+ private long mCellularTotalEnergyUseduWs = 0;
+ private long mWifiTotalEnergyUseduWs = 0;
+ private boolean mRailStatsAvailability = true;
+
+ /** Updates the rail data map of all power monitor rails being monitored
+ * Function is called from native side
+ * @param index
+ * @param railName
+ * @param subSystemName
+ * @param timestampSinceBootMs
+ * @param energyUsedSinceBootuWs
+ */
+ public void updateRailData(long index, String railName, String subSystemName,
+ long timestampSinceBootMs, long energyUsedSinceBootuWs) {
+ if (!(subSystemName.equals(WIFI_SUBSYSTEM) || subSystemName.equals(CELLULAR_SUBSYSTEM))) {
+ return;
+ }
+ RailInfoData node = mRailInfoData.get(index);
+ if (node == null) {
+ mRailInfoData.put(index, new RailInfoData(index, railName, subSystemName,
+ timestampSinceBootMs, energyUsedSinceBootuWs));
+ if (subSystemName.equals(WIFI_SUBSYSTEM)) {
+ mWifiTotalEnergyUseduWs += energyUsedSinceBootuWs;
+ return;
+ }
+ if (subSystemName.equals(CELLULAR_SUBSYSTEM)) {
+ mCellularTotalEnergyUseduWs += energyUsedSinceBootuWs;
+ }
+ return;
+ }
+ long timeSinceLastLogMs = timestampSinceBootMs - node.timestampSinceBootMs;
+ long energyUsedSinceLastLoguWs = energyUsedSinceBootuWs - node.energyUsedSinceBootuWs;
+ if (timeSinceLastLogMs < 0 || energyUsedSinceLastLoguWs < 0) {
+ energyUsedSinceLastLoguWs = node.energyUsedSinceBootuWs;
+ }
+ node.timestampSinceBootMs = timestampSinceBootMs;
+ node.energyUsedSinceBootuWs = energyUsedSinceBootuWs;
+ if (subSystemName.equals(WIFI_SUBSYSTEM)) {
+ mWifiTotalEnergyUseduWs += energyUsedSinceLastLoguWs;
+ return;
+ }
+ if (subSystemName.equals(CELLULAR_SUBSYSTEM)) {
+ mCellularTotalEnergyUseduWs += energyUsedSinceLastLoguWs;
+ }
+ }
+
+ /** resets the cellular total energy used aspect.
+ */
+ public void resetCellularTotalEnergyUsed() {
+ mCellularTotalEnergyUseduWs = 0;
+ }
+
+ /** resets the wifi total energy used aspect.
+ */
+ public void resetWifiTotalEnergyUsed() {
+ mWifiTotalEnergyUseduWs = 0;
+ }
+
+ public long getCellularTotalEnergyUseduWs() {
+ return mCellularTotalEnergyUseduWs;
+ }
+
+ public long getWifiTotalEnergyUseduWs() {
+ return mWifiTotalEnergyUseduWs;
+ }
+
+ /** reset the total energy subsystems
+ *
+ */
+ public void reset() {
+ mCellularTotalEnergyUseduWs = 0;
+ mWifiTotalEnergyUseduWs = 0;
+ }
+
+ public RailStats getRailStats() {
+ return this;
+ }
+
+ public void setRailStatsAvailability(boolean railStatsAvailability) {
+ mRailStatsAvailability = railStatsAvailability;
+ }
+
+ public boolean isRailStatsAvailable() {
+ return mRailStatsAvailability;
+ }
+
+ /** Container class to contain rail data information */
+ public static class RailInfoData {
+ private static final String TAG = "RailInfoData";
+ public long index;
+ public String railName;
+ public String subSystemName;
+ public long timestampSinceBootMs;
+ public long energyUsedSinceBootuWs;
+
+ private RailInfoData(long index, String railName, String subSystemName,
+ long timestampSinceBootMs, long energyUsedSinceBoot) {
+ this.index = index;
+ this.railName = railName;
+ this.subSystemName = subSystemName;
+ this.timestampSinceBootMs = timestampSinceBootMs;
+ this.energyUsedSinceBootuWs = energyUsedSinceBoot;
+ }
+
+ /** print the rail data
+ *
+ */
+ public void printData() {
+ Slog.d(TAG, "Index = " + index);
+ Slog.d(TAG, "RailName = " + railName);
+ Slog.d(TAG, "SubSystemName = " + subSystemName);
+ Slog.d(TAG, "TimestampSinceBootMs = " + timestampSinceBootMs);
+ Slog.d(TAG, "EnergyUsedSinceBootuWs = " + energyUsedSinceBootuWs);
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/RoSystemProperties.java b/android-35/com/android/internal/os/RoSystemProperties.java
new file mode 100644
index 0000000..40d5c47
--- /dev/null
+++ b/android-35/com/android/internal/os/RoSystemProperties.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 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.internal.os;
+
+import android.os.SystemProperties;
+import android.sysprop.CryptoProperties;
+import android.sysprop.HdmiProperties;
+
+/**
+ * This is a cache of various ro.* properties so that they can be read just once
+ * at class init time.
+ */
+public class RoSystemProperties {
+ public static final boolean DEBUGGABLE =
+ SystemProperties.getInt("ro.debuggable", 0) == 1;
+ public static final int FACTORYTEST =
+ SystemProperties.getInt("ro.factorytest", 0);
+ public static final String CONTROL_PRIVAPP_PERMISSIONS =
+ SystemProperties.get("ro.control_privapp_permissions");
+ public static final boolean SUPPORT_ONE_HANDED_MODE =
+ SystemProperties.getBoolean("ro.support_one_handed_mode", /* def= */ false);
+
+ // ------ ro.hdmi.* -------- //
+ /**
+ * Property to indicate if a CEC audio device should forward volume keys when system audio
+ * mode is off.
+ */
+ public static final boolean CEC_AUDIO_DEVICE_FORWARD_VOLUME_KEYS_SYSTEM_AUDIO_MODE_OFF =
+ HdmiProperties.forward_volume_keys_when_system_audio_mode_off().orElse(false);
+
+ // ------ ro.config.* -------- //
+ public static final boolean CONFIG_AVOID_GFX_ACCEL =
+ SystemProperties.getBoolean("ro.config.avoid_gfx_accel", false);
+ public static final boolean CONFIG_LOW_RAM =
+ SystemProperties.getBoolean("ro.config.low_ram", false);
+ public static final boolean CONFIG_SMALL_BATTERY =
+ SystemProperties.getBoolean("ro.config.small_battery", false);
+
+ /**
+ * Indicates whether the device should run in headless system user mode,
+ * in which user 0 only runs the system, not a real user.
+ * <p>WARNING about changing this value during an non-wiping update (OTA):
+ * <li>If this value is modified via an update, the change will have no effect, since an
+ * already-existing system user cannot change its mode.
+ * <li>Changing this value during an OTA from a pre-R device is not permitted; attempting to
+ * do so will corrupt the system user.
+ */
+ public static final boolean MULTIUSER_HEADLESS_SYSTEM_USER =
+ SystemProperties.getBoolean("ro.fw.mu.headless_system_user", false);
+
+ // ------ ro.crypto.* -------- //
+ public static final CryptoProperties.state_values CRYPTO_STATE =
+ CryptoProperties.state().orElse(CryptoProperties.state_values.UNSUPPORTED);
+ public static final CryptoProperties.type_values CRYPTO_TYPE =
+ CryptoProperties.type().orElse(CryptoProperties.type_values.NONE);
+ // These are pseudo-properties
+ public static final boolean CRYPTO_ENCRYPTED =
+ CRYPTO_STATE == CryptoProperties.state_values.ENCRYPTED;
+ public static final boolean CRYPTO_FILE_ENCRYPTED =
+ CRYPTO_TYPE == CryptoProperties.type_values.FILE;
+
+ public static final boolean CONTROL_PRIVAPP_PERMISSIONS_LOG =
+ "log".equalsIgnoreCase(CONTROL_PRIVAPP_PERMISSIONS);
+ public static final boolean CONTROL_PRIVAPP_PERMISSIONS_ENFORCE =
+ "enforce".equalsIgnoreCase(CONTROL_PRIVAPP_PERMISSIONS);
+ public static final boolean CONTROL_PRIVAPP_PERMISSIONS_DISABLE =
+ !CONTROL_PRIVAPP_PERMISSIONS_LOG && !CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
+
+}
diff --git a/android-35/com/android/internal/os/RpmStats.java b/android-35/com/android/internal/os/RpmStats.java
new file mode 100644
index 0000000..befc76e
--- /dev/null
+++ b/android-35/com/android/internal/os/RpmStats.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 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.internal.os;
+
+import android.util.ArrayMap;
+
+import java.util.Map;
+
+/**
+ * Container for Resource Power Manager states and their data.
+ * Values can be populated by the BatteryStatsService.fillLowPowerStats jni function.
+ */
+public final class RpmStats {
+ public Map<String, PowerStatePlatformSleepState> mPlatformLowPowerStats = new ArrayMap<>();
+ public Map<String, PowerStateSubsystem> mSubsystemLowPowerStats = new ArrayMap<>();
+
+ /**
+ * Finds the PowerStatePlatformSleepState with the given name (creating it if it doesn't exist),
+ * updates its timeMs and count, and returns it.
+ */
+ @SuppressWarnings("unused")
+ public PowerStatePlatformSleepState getAndUpdatePlatformState(
+ String name, long timeMs, int count) {
+
+ PowerStatePlatformSleepState e = mPlatformLowPowerStats.get(name);
+ if (e == null) {
+ e = new PowerStatePlatformSleepState();
+ mPlatformLowPowerStats.put(name, e);
+ }
+ e.mTimeMs = timeMs;
+ e.mCount = count;
+ return e;
+ }
+
+ /**
+ * Returns the PowerStateSubsystem with the given name (creating it if it doesn't exist).
+ */
+ public PowerStateSubsystem getSubsystem(String name) {
+ PowerStateSubsystem e = mSubsystemLowPowerStats.get(name);
+ if (e == null) {
+ e = new PowerStateSubsystem();
+ mSubsystemLowPowerStats.put(name, e);
+ }
+ return e;
+ }
+
+ /** Represents a subsystem state or a platform voter. */
+ public static class PowerStateElement {
+ public long mTimeMs; // totalTimeInMsecVotedForSinceBoot
+ public int mCount; // totalNumberOfTimesVotedSinceBoot
+
+ private PowerStateElement(long timeMs, int count) {
+ this.mTimeMs = timeMs;
+ this.mCount = count;
+ }
+ }
+
+ /** Represents a PowerStatePlatformSleepState, per hardware/interfaces/power/1.0/types.hal */
+ public static class PowerStatePlatformSleepState {
+ public long mTimeMs; // residencyInMsecSinceBoot
+ public int mCount; // totalTransitions
+ public Map<String, PowerStateElement> mVoters = new ArrayMap<>(); // voters for this platform-level sleep state
+
+ /**
+ * Updates (creating if necessary) the voter with the given name, with the given timeMs and
+ * count.
+ */
+ @SuppressWarnings("unused")
+ public void putVoter(String name, long timeMs, int count) {
+ PowerStateElement e = mVoters.get(name);
+ if (e == null) {
+ mVoters.put(name, new PowerStateElement(timeMs, count));
+ } else {
+ e.mTimeMs = timeMs;
+ e.mCount = count;
+ }
+ }
+ }
+
+ /** Represents a PowerStateSubsystem, per hardware/interfaces/power/1.1/types.hal */
+ public static class PowerStateSubsystem {
+ public Map<String, PowerStateElement> mStates = new ArrayMap<>(); // sleep states supported by this susbsystem
+
+ /**
+ * Updates (creating if necessary) the subsystem state with the given name, with the given
+ * timeMs and count.
+ */
+ @SuppressWarnings("unused")
+ public void putState(String name, long timeMs, int count) {
+ PowerStateElement e = mStates.get(name);
+ if (e == null) {
+ mStates.put(name, new PowerStateElement(timeMs, count));
+ } else {
+ e.mTimeMs = timeMs;
+ e.mCount = count;
+ }
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/RuntimeInit.java b/android-35/com/android/internal/os/RuntimeInit.java
new file mode 100644
index 0000000..cdac097
--- /dev/null
+++ b/android-35/com/android/internal/os/RuntimeInit.java
@@ -0,0 +1,594 @@
+/*
+ * Copyright (C) 2006 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.internal.os;
+
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.app.ApplicationErrorReport;
+import android.app.IActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.type.DefaultMimeMapFactory;
+import android.net.TrafficStats;
+import android.os.Build;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.logging.AndroidConfig;
+
+import dalvik.system.RuntimeHooks;
+import dalvik.system.VMRuntime;
+
+import libcore.content.type.MimeMap;
+
+import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Objects;
+import java.util.logging.LogManager;
+
+/**
+ * Main entry point for runtime initialization. Not for
+ * public consumption.
+ * @hide
+ */
[email protected]
+public class RuntimeInit {
+ final static String TAG = "AndroidRuntime";
+ final static boolean DEBUG = false;
+
+ /** true if commonInit() has been called */
+ @UnsupportedAppUsage
+ private static boolean initialized;
+
+ @UnsupportedAppUsage
+ private static IBinder mApplicationObject;
+
+ private static volatile boolean mCrashing = false;
+ private static final String SYSPROP_CRASH_COUNT = "sys.system_server.crash_java";
+ private static int mCrashCount;
+
+ private static volatile ApplicationWtfHandler sDefaultApplicationWtfHandler;
+
+ /**
+ * Stored values of System.out and System.err before they've been replaced by
+ * redirectLogStreams(). Kept open here for other Ravenwood internals to use.
+ */
+ public static PrintStream sOut$ravenwood;
+ public static PrintStream sErr$ravenwood;
+
+ private static final native void nativeFinishInit();
+
+ private static final native void nativeSetExitWithoutCleanup(boolean exitWithoutCleanup);
+
+ private static int Clog_e(String tag, String msg, Throwable tr) {
+ return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr);
+ }
+
+ public static void logUncaught(String threadName, String processName, int pid, Throwable e) {
+ StringBuilder message = new StringBuilder();
+ // The "FATAL EXCEPTION" string is still used on Android even though
+ // apps can set a custom UncaughtExceptionHandler that renders uncaught
+ // exceptions non-fatal.
+ message.append("FATAL EXCEPTION: ").append(threadName).append("\n");
+ if (processName != null) {
+ message.append("Process: ").append(processName).append(", ");
+ }
+ message.append("PID: ").append(pid);
+ Clog_e(TAG, message.toString(), e);
+ }
+
+ /**
+ * Logs a message when a thread encounters an uncaught exception. By
+ * default, {@link KillApplicationHandler} will terminate this process later,
+ * but apps can override that behavior.
+ */
+ private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
+ public volatile boolean mTriggered = false;
+
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ mTriggered = true;
+
+ // Don't re-enter if KillApplicationHandler has already run
+ if (mCrashing) return;
+
+ // mApplicationObject is null for non-zygote java programs (e.g. "am")
+ // There are also apps running with the system UID. We don't want the
+ // first clause in either of these two cases, only for system_server.
+ if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
+ Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
+ mCrashCount = SystemProperties.getInt(SYSPROP_CRASH_COUNT, 0) + 1;
+ SystemProperties.set(SYSPROP_CRASH_COUNT, String.valueOf(mCrashCount));
+ } else {
+ logUncaught(t.getName(), ActivityThread.currentProcessName(), Process.myPid(), e);
+ }
+ }
+ }
+
+ /**
+ * Handle application death from an uncaught exception. The framework
+ * catches these for the main threads, so this should only matter for
+ * threads created by applications. Before this method runs, the given
+ * instance of {@link LoggingHandler} should already have logged details
+ * (and if not it is run first).
+ */
+ private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
+ private final LoggingHandler mLoggingHandler;
+
+ /**
+ * Create a new KillApplicationHandler that follows the given LoggingHandler.
+ * If {@link #uncaughtException(Thread, Throwable) uncaughtException} is called
+ * on the created instance without {@code loggingHandler} having been triggered,
+ * {@link LoggingHandler#uncaughtException(Thread, Throwable)
+ * loggingHandler.uncaughtException} will be called first.
+ *
+ * @param loggingHandler the {@link LoggingHandler} expected to have run before
+ * this instance's {@link #uncaughtException(Thread, Throwable) uncaughtException}
+ * is being called.
+ */
+ public KillApplicationHandler(LoggingHandler loggingHandler) {
+ this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
+ }
+
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ try {
+ ensureLogging(t, e);
+
+ // Don't re-enter -- avoid infinite loops if crash-reporting crashes.
+ if (mCrashing) return;
+ mCrashing = true;
+
+ // Try to end profiling. If a profiler is running at this point, and we kill the
+ // process (below), the in-memory buffer will be lost. So try to stop, which will
+ // flush the buffer. (This makes method trace profiling useful to debug crashes.)
+ if (ActivityThread.currentActivityThread() != null) {
+ ActivityThread.currentActivityThread().stopProfiling();
+ }
+
+ // Bring up crash dialog, wait for it to be dismissed
+ ActivityManager.getService().handleApplicationCrash(
+ mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
+ } catch (Throwable t2) {
+ if (t2 instanceof DeadObjectException) {
+ // System process is dead; ignore
+ } else {
+ try {
+ Clog_e(TAG, "Error reporting crash", t2);
+ } catch (Throwable t3) {
+ // Even Clog_e() fails! Oh well.
+ }
+ }
+ } finally {
+ // Try everything to make sure this process goes away.
+ Process.killProcess(Process.myPid());
+ System.exit(10);
+ }
+ }
+
+ /**
+ * Ensures that the logging handler has been triggered.
+ *
+ * See b/73380984. This reinstates the pre-O behavior of
+ *
+ * {@code thread.getUncaughtExceptionHandler().uncaughtException(thread, e);}
+ *
+ * logging the exception (in addition to killing the app). This behavior
+ * was never documented / guaranteed but helps in diagnostics of apps
+ * using the pattern.
+ *
+ * If this KillApplicationHandler is invoked the "regular" way (by
+ * {@link Thread#dispatchUncaughtException(Throwable)
+ * Thread.dispatchUncaughtException} in case of an uncaught exception)
+ * then the pre-handler (expected to be {@link #mLoggingHandler}) will already
+ * have run. Otherwise, we manually invoke it here.
+ */
+ private void ensureLogging(Thread t, Throwable e) {
+ if (!mLoggingHandler.mTriggered) {
+ try {
+ mLoggingHandler.uncaughtException(t, e);
+ } catch (Throwable loggingThrowable) {
+ // Ignored.
+ }
+ }
+ }
+ }
+
+ /**
+ * Common initialization that (unlike {@link #commonInit()} should happen prior to
+ * the Zygote fork.
+ */
+ public static void preForkInit() {
+ if (DEBUG) Slog.d(TAG, "Entered preForkInit.");
+ RuntimeInit.enableDdms();
+ // TODO(b/142019040#comment13): Decide whether to load the default instance eagerly, i.e.
+ // MimeMap.setDefault(DefaultMimeMapFactory.create());
+ /*
+ * Replace libcore's minimal default mapping between MIME types and file
+ * extensions with a mapping that's suitable for Android. Android's mapping
+ * contains many more entries that are derived from IANA registrations but
+ * with several customizations (extensions, overrides).
+ */
+ MimeMap.setDefaultSupplier(DefaultMimeMapFactory::create);
+ }
+
+ @UnsupportedAppUsage
+ protected static final void commonInit() {
+ if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
+
+ /*
+ * set handlers; these apply to all threads in the VM. Apps can replace
+ * the default handler, but not the pre handler.
+ */
+ LoggingHandler loggingHandler = new LoggingHandler();
+ RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
+ Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
+
+ /*
+ * Install a time zone supplier that uses the Android persistent time zone system property.
+ */
+ RuntimeHooks.setTimeZoneIdSupplier(() -> SystemProperties.get("persist.sys.timezone"));
+
+ /*
+ * Sets handler for java.util.logging to use Android log facilities.
+ * The odd "new instance-and-then-throw-away" is a mirror of how
+ * the "java.util.logging.config.class" system property works. We
+ * can't use the system property here since the logger has almost
+ * certainly already been initialized.
+ */
+ LogManager.getLogManager().reset();
+ new AndroidConfig();
+
+ /*
+ * Sets the default HTTP User-Agent used by HttpURLConnection.
+ */
+ String userAgent = getDefaultUserAgent();
+ System.setProperty("http.agent", userAgent);
+
+ /*
+ * Wire socket tagging to traffic stats.
+ */
+ TrafficStats.attachSocketTagger();
+
+ initialized = true;
+ }
+
+ /**
+ * Returns an HTTP user agent of the form
+ * "Dalvik/1.1.0 (Linux; U; Android Eclair Build/MAIN)".
+ */
+ private static String getDefaultUserAgent() {
+ StringBuilder result = new StringBuilder(64);
+ result.append("Dalvik/");
+ result.append(System.getProperty("java.vm.version")); // such as 1.1.0
+ result.append(" (Linux; U; Android ");
+
+ String version = Build.VERSION.RELEASE_OR_CODENAME; // "1.0" or "3.4b5"
+ result.append(version.length() > 0 ? version : "1.0");
+
+ // add the model for the release build
+ if ("REL".equals(Build.VERSION.CODENAME)) {
+ String model = Build.MODEL;
+ if (model.length() > 0) {
+ result.append("; ");
+ result.append(model);
+ }
+ }
+ String id = Build.ID; // "MAIN" or "M4-rc20"
+ if (id.length() > 0) {
+ result.append(" Build/");
+ result.append(id);
+ }
+ result.append(")");
+ return result.toString();
+ }
+
+ /**
+ * Invokes a static "main(argv[]) method on class "className".
+ * Converts various failing exceptions into RuntimeExceptions, with
+ * the assumption that they will then cause the VM instance to exit.
+ *
+ * @param className Fully-qualified class name
+ * @param argv Argument vector for main()
+ * @param classLoader the classLoader to load {@className} with
+ */
+ protected static Runnable findStaticMain(String className, String[] argv,
+ ClassLoader classLoader) {
+ Class<?> cl;
+
+ try {
+ cl = Class.forName(className, true, classLoader);
+ } catch (ClassNotFoundException ex) {
+ throw new RuntimeException(
+ "Missing class when invoking static main " + className,
+ ex);
+ }
+
+ Method m;
+ try {
+ m = cl.getMethod("main", new Class[] { String[].class });
+ } catch (NoSuchMethodException ex) {
+ throw new RuntimeException(
+ "Missing static main on " + className, ex);
+ } catch (SecurityException ex) {
+ throw new RuntimeException(
+ "Problem getting static main on " + className, ex);
+ }
+
+ int modifiers = m.getModifiers();
+ if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
+ throw new RuntimeException(
+ "Main method is not public and static on " + className);
+ }
+
+ /*
+ * This throw gets caught in ZygoteInit.main(), which responds
+ * by invoking the exception's run() method. This arrangement
+ * clears up all the stack frames that were required in setting
+ * up the process.
+ */
+ return new MethodAndArgsCaller(m, argv);
+ }
+
+ @UnsupportedAppUsage
+ public static final void main(String[] argv) {
+ preForkInit();
+ if (argv.length == 2 && argv[1].equals("application")) {
+ if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application");
+ redirectLogStreams();
+ } else {
+ if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting tool");
+ }
+
+ commonInit();
+
+ /*
+ * Now that we're running in interpreted code, call back into native code
+ * to run the system.
+ */
+ nativeFinishInit();
+
+ if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
+ }
+
+ protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges,
+ String[] argv, ClassLoader classLoader) {
+ // If the application calls System.exit(), terminate the process
+ // immediately without running any shutdown hooks. It is not possible to
+ // shutdown an Android application gracefully. Among other things, the
+ // Android runtime shutdown hooks close the Binder driver, which can cause
+ // leftover running threads to crash before the process actually exits.
+ nativeSetExitWithoutCleanup(true);
+
+ VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
+ VMRuntime.getRuntime().setDisabledCompatChanges(disabledCompatChanges);
+
+ final Arguments args = new Arguments(argv);
+
+ // The end of of the RuntimeInit event (see #zygoteInit).
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ // Remaining arguments are passed to the start class's static main
+ return findStaticMain(args.startClass, args.startArgs, classLoader);
+ }
+
+ /**
+ * Redirect System.out and System.err to the Android log.
+ */
+ @android.ravenwood.annotation.RavenwoodReplace
+ public static void redirectLogStreams() {
+ System.out.close();
+ System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
+ System.err.close();
+ System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
+ }
+
+ public static void redirectLogStreams$ravenwood() {
+ if (sOut$ravenwood == null) {
+ sOut$ravenwood = System.out;
+ System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
+ }
+ if (sErr$ravenwood == null) {
+ sErr$ravenwood = System.err;
+ System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
+ }
+ }
+
+ /**
+ * Report a serious error in the current process. May or may not cause
+ * the process to terminate (depends on system settings).
+ *
+ * @param tag to record with the error
+ * @param t exception describing the error site and conditions
+ */
+ @android.ravenwood.annotation.RavenwoodReplace
+ public static void wtf(String tag, Throwable t, boolean system) {
+ try {
+ boolean exit = false;
+ final IActivityManager am = ActivityManager.getService();
+ if (am != null) {
+ exit = am.handleApplicationWtf(
+ mApplicationObject, tag, system,
+ new ApplicationErrorReport.ParcelableCrashInfo(t),
+ Process.myPid());
+ } else {
+ // Unlikely but possible in early system boot
+ final ApplicationWtfHandler handler = sDefaultApplicationWtfHandler;
+ if (handler != null) {
+ exit = handler.handleApplicationWtf(
+ mApplicationObject, tag, system,
+ new ApplicationErrorReport.ParcelableCrashInfo(t),
+ Process.myPid());
+ } else {
+ // Simply log the error
+ Slog.e(TAG, "Original WTF:", t);
+ }
+ }
+ if (exit) {
+ // The Activity Manager has already written us off -- now exit.
+ Process.killProcess(Process.myPid());
+ System.exit(10);
+ }
+ } catch (Throwable t2) {
+ if (t2 instanceof DeadObjectException) {
+ // System process is dead; ignore
+ } else {
+ Slog.e(TAG, "Error reporting WTF", t2);
+ Slog.e(TAG, "Original WTF:", t);
+ }
+ }
+ }
+
+ public static void wtf$ravenwood(String tag, Throwable t, boolean system) {
+ // We've already emitted to logs, so there's nothing more to do here,
+ // as we don't have a DropBox pipeline configured
+ }
+
+ /**
+ * Set the default {@link ApplicationWtfHandler}, in case the ActivityManager is not ready yet.
+ */
+ public static void setDefaultApplicationWtfHandler(final ApplicationWtfHandler handler) {
+ sDefaultApplicationWtfHandler = handler;
+ }
+
+ /**
+ * The handler to deal with the serious application errors.
+ */
+ public interface ApplicationWtfHandler {
+ /**
+ * @param app object of the crashing app, null for the system server
+ * @param tag reported by the caller
+ * @param system whether this wtf is coming from the system
+ * @param crashInfo describing the context of the error
+ * @param immediateCallerPid the caller Pid
+ * @return true if the process should exit immediately (WTF is fatal)
+ */
+ boolean handleApplicationWtf(IBinder app, String tag, boolean system,
+ ApplicationErrorReport.ParcelableCrashInfo crashInfo, int immediateCallerPid);
+ }
+
+ /**
+ * Set the object identifying this application/process, for reporting VM
+ * errors.
+ */
+ public static final void setApplicationObject(IBinder app) {
+ mApplicationObject = app;
+ }
+
+ @UnsupportedAppUsage
+ public static final IBinder getApplicationObject() {
+ return mApplicationObject;
+ }
+
+ /**
+ * Enable DDMS.
+ */
+ private static void enableDdms() {
+ // Register handlers for DDM messages.
+ android.ddm.DdmRegister.registerHandlers();
+ }
+
+ /**
+ * Handles argument parsing for args related to the runtime.
+ *
+ * Current recognized args:
+ * <ul>
+ * <li> <code> [--] <start class name> <args>
+ * </ul>
+ */
+ static class Arguments {
+ /** first non-option argument */
+ String startClass;
+
+ /** all following arguments */
+ String[] startArgs;
+
+ /**
+ * Constructs instance and parses args
+ * @param args runtime command-line args
+ * @throws IllegalArgumentException
+ */
+ Arguments(String args[]) throws IllegalArgumentException {
+ parseArgs(args);
+ }
+
+ /**
+ * Parses the commandline arguments intended for the Runtime.
+ */
+ private void parseArgs(String args[])
+ throws IllegalArgumentException {
+ int curArg = 0;
+ for (; curArg < args.length; curArg++) {
+ String arg = args[curArg];
+
+ if (arg.equals("--")) {
+ curArg++;
+ break;
+ } else if (!arg.startsWith("--")) {
+ break;
+ }
+ }
+
+ if (curArg == args.length) {
+ throw new IllegalArgumentException("Missing classname argument to RuntimeInit!");
+ }
+
+ startClass = args[curArg++];
+ startArgs = new String[args.length - curArg];
+ System.arraycopy(args, curArg, startArgs, 0, startArgs.length);
+ }
+ }
+
+ /**
+ * Helper class which holds a method and arguments and can call them. This is used as part of
+ * a trampoline to get rid of the initial process setup stack frames.
+ */
+ static class MethodAndArgsCaller implements Runnable {
+ /** method to call */
+ private final Method mMethod;
+
+ /** argument array */
+ private final String[] mArgs;
+
+ public MethodAndArgsCaller(Method method, String[] args) {
+ mMethod = method;
+ mArgs = args;
+ }
+
+ public void run() {
+ try {
+ mMethod.invoke(null, new Object[] { mArgs });
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException(ex);
+ } catch (InvocationTargetException ex) {
+ Throwable cause = ex.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else if (cause instanceof Error) {
+ throw (Error) cause;
+ }
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/SafeZipPathValidatorCallback.java b/android-35/com/android/internal/os/SafeZipPathValidatorCallback.java
new file mode 100644
index 0000000..a6ee108
--- /dev/null
+++ b/android-35/com/android/internal/os/SafeZipPathValidatorCallback.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.internal.os;
+
+import android.annotation.NonNull;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.os.Build;
+
+import dalvik.system.ZipPathValidator;
+
+import java.io.File;
+import java.util.zip.ZipException;
+
+/**
+ * A child implementation of the {@link dalvik.system.ZipPathValidator.Callback} that removes the
+ * risk of zip path traversal vulnerabilities.
+ *
+ * @hide
+ */
+public class SafeZipPathValidatorCallback implements ZipPathValidator.Callback {
+ /**
+ * This change targets zip path traversal vulnerabilities by throwing
+ * {@link java.util.zip.ZipException} if zip path entries contain ".." or start with "/".
+ * <p>
+ * The exception will be thrown in {@link java.util.zip.ZipInputStream#getNextEntry} or
+ * {@link java.util.zip.ZipFile#ZipFile(String)}.
+ * <p>
+ * This validation is enabled for apps with targetSDK >= U.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL = 242716250L;
+
+ @Override
+ public void onZipEntryAccess(@NonNull String path) throws ZipException {
+ if (path.startsWith("/")) {
+ throw new ZipException("Invalid zip entry path: " + path);
+ }
+ if (path.contains("..")) {
+ // If the string does contain "..", break it down into its actual name elements to
+ // ensure it actually contains ".." as a name, not just a name like "foo..bar" or even
+ // "foo..", which should be fine.
+ File file = new File(path);
+ while (file != null) {
+ if (file.getName().equals("..")) {
+ throw new ZipException("Invalid zip entry path: " + path);
+ }
+ file = file.getParentFile();
+ }
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/SelectedProcessCpuThreadReader.java b/android-35/com/android/internal/os/SelectedProcessCpuThreadReader.java
new file mode 100644
index 0000000..2ffff73
--- /dev/null
+++ b/android-35/com/android/internal/os/SelectedProcessCpuThreadReader.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 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.internal.os;
+
+import android.annotation.Nullable;
+import android.os.Process;
+
+/**
+ * Reads CPU usage statistics about a selected process identified by its cmdline.
+ *
+ * Handles finding the pid for the process and delegates CPU usage reading from the eBPF map to
+ * KernelSingleProcessCpuThreadReader. Exactly one long-lived instance of the process is expected.
+ * Otherwise, no statistics are returned.
+ *
+ * See also SystemServerCpuThreadReader.
+ */
+public final class SelectedProcessCpuThreadReader {
+ private final String[] mCmdline;
+
+ private int mPid;
+ private KernelSingleProcessCpuThreadReader mKernelCpuThreadReader;
+
+ public SelectedProcessCpuThreadReader(String cmdline) {
+ mCmdline = new String[] { cmdline };
+ }
+
+ /** Returns CPU times, per thread group, since tracking started. */
+ @Nullable
+ public KernelSingleProcessCpuThreadReader.ProcessCpuUsage readAbsolute() {
+ int[] pids = Process.getPidsForCommands(mCmdline);
+ if (pids == null || pids.length != 1) {
+ return null;
+ }
+ int pid = pids[0];
+ if (mPid == pid) {
+ return mKernelCpuThreadReader.getProcessCpuUsage();
+ }
+ mPid = pid;
+ mKernelCpuThreadReader = KernelSingleProcessCpuThreadReader.create(mPid);
+ mKernelCpuThreadReader.startTrackingThreadCpuTimes();
+ return null;
+ }
+}
diff --git a/android-35/com/android/internal/os/SomeArgs.java b/android-35/com/android/internal/os/SomeArgs.java
new file mode 100644
index 0000000..4701af3
--- /dev/null
+++ b/android-35/com/android/internal/os/SomeArgs.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2012 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.internal.os;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+
+/**
+ * Helper class for passing more arguments though a message
+ * and avoiding allocation of a custom class for wrapping the
+ * arguments. This class maintains a pool of instances and
+ * it is responsibility of the client to recycle and instance
+ * once it is no longer used.
+ */
[email protected]
+public final class SomeArgs {
+
+ private static final int MAX_POOL_SIZE = 10;
+
+ private static SomeArgs sPool;
+ private static int sPoolSize;
+ private static Object sPoolLock = new Object();
+
+ private SomeArgs mNext;
+
+ private boolean mInPool;
+
+ static final int WAIT_NONE = 0;
+ static final int WAIT_WAITING = 1;
+ static final int WAIT_FINISHED = 2;
+ int mWaitState = WAIT_NONE;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public Object arg1;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public Object arg2;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public Object arg3;
+ public Object arg4;
+ public Object arg5;
+ public Object arg6;
+ public Object arg7;
+ public int argi1;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int argi2;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int argi3;
+ public int argi4;
+ public int argi5;
+ public int argi6;
+ public long argl1;
+ public long argl2;
+
+ private SomeArgs() {
+ /* do nothing - reduce visibility */
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static SomeArgs obtain() {
+ synchronized (sPoolLock) {
+ if (sPoolSize > 0) {
+ SomeArgs args = sPool;
+ sPool = sPool.mNext;
+ args.mNext = null;
+ args.mInPool = false;
+ sPoolSize--;
+ return args;
+ } else {
+ return new SomeArgs();
+ }
+ }
+ }
+
+ public void complete() {
+ synchronized (this) {
+ if (mWaitState != WAIT_WAITING) {
+ throw new IllegalStateException("Not waiting");
+ }
+ mWaitState = WAIT_FINISHED;
+ notifyAll();
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void recycle() {
+ if (mInPool) {
+ throw new IllegalStateException("Already recycled.");
+ }
+ if (mWaitState != WAIT_NONE) {
+ return;
+ }
+ synchronized (sPoolLock) {
+ clear();
+ if (sPoolSize < MAX_POOL_SIZE) {
+ mNext = sPool;
+ mInPool = true;
+ sPool = this;
+ sPoolSize++;
+ }
+ }
+ }
+
+ private void clear() {
+ arg1 = null;
+ arg2 = null;
+ arg3 = null;
+ arg4 = null;
+ arg5 = null;
+ arg6 = null;
+ arg7 = null;
+ argi1 = 0;
+ argi2 = 0;
+ argi3 = 0;
+ argi4 = 0;
+ argi5 = 0;
+ argi6 = 0;
+ argl1 = 0;
+ argl2 = 0;
+ }
+}
diff --git a/android-35/com/android/internal/os/StatsdHiddenApiUsageLogger.java b/android-35/com/android/internal/os/StatsdHiddenApiUsageLogger.java
new file mode 100644
index 0000000..89773b3
--- /dev/null
+++ b/android-35/com/android/internal/os/StatsdHiddenApiUsageLogger.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 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.internal.os;
+
+import android.metrics.LogMaker;
+import android.os.Process;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.FrameworkStatsLog;
+
+import dalvik.system.VMRuntime.HiddenApiUsageLogger;
+
+class StatsdHiddenApiUsageLogger implements HiddenApiUsageLogger {
+
+ private final MetricsLogger mMetricsLogger = new MetricsLogger();
+ private static final StatsdHiddenApiUsageLogger sInstance = new StatsdHiddenApiUsageLogger();
+ private int mHiddenApiAccessLogSampleRate = 0;
+ private int mHiddenApiAccessStatslogSampleRate = 0;
+
+ static void setHiddenApiAccessLogSampleRates(int sampleRate, int newSampleRate) {
+ sInstance.mHiddenApiAccessLogSampleRate = sampleRate;
+ sInstance.mHiddenApiAccessStatslogSampleRate = newSampleRate;
+ }
+
+ static StatsdHiddenApiUsageLogger getInstance() {
+ return StatsdHiddenApiUsageLogger.sInstance;
+ }
+
+ public void hiddenApiUsed(int sampledValue, String packageName, String signature,
+ int accessMethod, boolean accessDenied) {
+ if (sampledValue < mHiddenApiAccessLogSampleRate) {
+ logUsage(packageName, signature, accessMethod, accessDenied);
+ }
+
+ if (sampledValue < mHiddenApiAccessStatslogSampleRate) {
+ newLogUsage(signature, accessMethod, accessDenied);
+ }
+ }
+
+ private void logUsage(String packageName, String signature, int accessMethod,
+ boolean accessDenied) {
+ int accessMethodMetric = HiddenApiUsageLogger.ACCESS_METHOD_NONE;
+ switch(accessMethod) {
+ case HiddenApiUsageLogger.ACCESS_METHOD_NONE:
+ accessMethodMetric = MetricsEvent.ACCESS_METHOD_NONE;
+ break;
+ case HiddenApiUsageLogger.ACCESS_METHOD_REFLECTION:
+ accessMethodMetric = MetricsEvent.ACCESS_METHOD_REFLECTION;
+ break;
+ case HiddenApiUsageLogger.ACCESS_METHOD_JNI:
+ accessMethodMetric = MetricsEvent.ACCESS_METHOD_JNI;
+ break;
+ case HiddenApiUsageLogger.ACCESS_METHOD_LINKING:
+ accessMethodMetric = MetricsEvent.ACCESS_METHOD_LINKING;
+ break;
+ }
+
+ LogMaker logMaker = new LogMaker(MetricsEvent.ACTION_HIDDEN_API_ACCESSED)
+ .setPackageName(packageName)
+ .addTaggedData(MetricsEvent.FIELD_HIDDEN_API_SIGNATURE, signature)
+ .addTaggedData(MetricsEvent.FIELD_HIDDEN_API_ACCESS_METHOD,
+ accessMethodMetric);
+
+ if (accessDenied) {
+ logMaker.addTaggedData(MetricsEvent.FIELD_HIDDEN_API_ACCESS_DENIED, 1);
+ }
+
+ mMetricsLogger.write(logMaker);
+ }
+
+ private void newLogUsage(String signature, int accessMethod, boolean accessDenied) {
+ int accessMethodProto = FrameworkStatsLog.HIDDEN_API_USED__ACCESS_METHOD__NONE;
+ switch(accessMethod) {
+ case HiddenApiUsageLogger.ACCESS_METHOD_NONE:
+ accessMethodProto = FrameworkStatsLog.HIDDEN_API_USED__ACCESS_METHOD__NONE;
+ break;
+ case HiddenApiUsageLogger.ACCESS_METHOD_REFLECTION:
+ accessMethodProto = FrameworkStatsLog.HIDDEN_API_USED__ACCESS_METHOD__REFLECTION;
+ break;
+ case HiddenApiUsageLogger.ACCESS_METHOD_JNI:
+ accessMethodProto = FrameworkStatsLog.HIDDEN_API_USED__ACCESS_METHOD__JNI;
+ break;
+ case HiddenApiUsageLogger.ACCESS_METHOD_LINKING:
+ accessMethodProto = FrameworkStatsLog.HIDDEN_API_USED__ACCESS_METHOD__LINKING;
+ break;
+ }
+
+ int uid = Process.myUid();
+ FrameworkStatsLog.write(FrameworkStatsLog.HIDDEN_API_USED, uid, signature,
+ accessMethodProto, accessDenied);
+ }
+}
diff --git a/android-35/com/android/internal/os/StoragedUidIoStatsReader.java b/android-35/com/android/internal/os/StoragedUidIoStatsReader.java
new file mode 100644
index 0000000..fb6e133
--- /dev/null
+++ b/android-35/com/android/internal/os/StoragedUidIoStatsReader.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import android.os.StrictMode;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+
+/**
+ * Reads /proc/uid_io/stats which has the line format:
+ *
+ * uid: foreground_read_chars foreground_write_chars foreground_read_bytes foreground_write_bytes
+ * background_read_chars background_write_chars background_read_bytes background_write_bytes
+ * foreground_fsync background_fsync
+ *
+ * This provides the number of bytes/chars read/written in foreground/background for each uid.
+ * The file contains a monotonically increasing count of bytes/chars for a single boot.
+ */
[email protected]
+public class StoragedUidIoStatsReader {
+
+ private static final String TAG = StoragedUidIoStatsReader.class.getSimpleName();
+ private static String sUidIoFile = "/proc/uid_io/stats";
+
+ public StoragedUidIoStatsReader() {
+ }
+
+ @VisibleForTesting
+ public StoragedUidIoStatsReader(String file) {
+ sUidIoFile = file;
+ }
+
+ /**
+ * Notifies when new data is available.
+ */
+ public interface Callback {
+
+ /**
+ * Provides data to the client.
+ *
+ * Note: Bytes are I/O events from a storage device. Chars are data requested by syscalls,
+ * and can be satisfied by caching.
+ */
+ void onUidStorageStats(int uid, long fgCharsRead, long fgCharsWrite, long fgBytesRead,
+ long fgBytesWrite, long bgCharsRead, long bgCharsWrite, long bgBytesRead,
+ long bgBytesWrite, long fgFsync, long bgFsync);
+ }
+
+ /**
+ * Reads the proc file, calling into the callback with raw absolute value of I/O stats
+ * for each UID.
+ *
+ * @param callback The callback to invoke for each line of the proc file.
+ */
+ public void readAbsolute(Callback callback) {
+ final int oldMask = StrictMode.allowThreadDiskReadsMask();
+ try {
+ readAbsoluteInternal(callback);
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
+ }
+ }
+
+ private void readAbsoluteInternal(Callback callback) {
+ File file = new File(sUidIoFile);
+ try (BufferedReader reader = Files.newBufferedReader(file.toPath())) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ String[] fields = TextUtils.split(line, " ");
+ if (fields.length != 11) {
+ Slog.e(TAG, "Malformed entry in " + sUidIoFile + ": " + line);
+ continue;
+ }
+ try {
+ final String uidStr = fields[0];
+ final int uid = Integer.parseInt(fields[0], 10);
+ final long fgCharsRead = Long.parseLong(fields[1], 10);
+ final long fgCharsWrite = Long.parseLong(fields[2], 10);
+ final long fgBytesRead = Long.parseLong(fields[3], 10);
+ final long fgBytesWrite = Long.parseLong(fields[4], 10);
+ final long bgCharsRead = Long.parseLong(fields[5], 10);
+ final long bgCharsWrite = Long.parseLong(fields[6], 10);
+ final long bgBytesRead = Long.parseLong(fields[7], 10);
+ final long bgBytesWrite = Long.parseLong(fields[8], 10);
+ final long fgFsync = Long.parseLong(fields[9], 10);
+ final long bgFsync = Long.parseLong(fields[10], 10);
+ callback.onUidStorageStats(uid, fgCharsRead, fgCharsWrite, fgBytesRead,
+ fgBytesWrite, bgCharsRead, bgCharsWrite, bgBytesRead, bgBytesWrite,
+ fgFsync, bgFsync);
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Could not parse entry in " + sUidIoFile + ": " + e.getMessage());
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read " + sUidIoFile + ": " + e.getMessage());
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/SystemServerClassLoaderFactory.java b/android-35/com/android/internal/os/SystemServerClassLoaderFactory.java
new file mode 100644
index 0000000..90ad34d
--- /dev/null
+++ b/android-35/com/android/internal/os/SystemServerClassLoaderFactory.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 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.internal.os;
+
+import android.os.Build;
+import android.util.ArrayMap;
+
+import dalvik.system.PathClassLoader;
+
+/** @hide */
+public final class SystemServerClassLoaderFactory {
+ /**
+ * Map of paths to PathClassLoader for standalone system server jars.
+ */
+ private static final ArrayMap<String, PathClassLoader> sLoadedPaths = new ArrayMap<>();
+
+ /**
+ * Creates and caches a ClassLoader for the jar at the given path.
+ *
+ * This method should only be called by ZygoteInit to prefetch jars. For other users, use
+ * {@link getOrCreateClassLoader} instead.
+ *
+ * The parent class loader should always be the system server class loader. Changing it has
+ * implications that require discussion with the mainline team.
+ *
+ * @hide for internal use only
+ */
+ /* package */ static PathClassLoader createClassLoader(String path, ClassLoader parent) {
+ if (sLoadedPaths.containsKey(path)) {
+ throw new IllegalStateException("A ClassLoader for " + path + " already exists");
+ }
+ PathClassLoader pathClassLoader = (PathClassLoader) ClassLoaderFactory.createClassLoader(
+ path, /*librarySearchPath=*/null, /*libraryPermittedPath=*/null, parent,
+ Build.VERSION.SDK_INT, /*isNamespaceShared=*/true , /*classLoaderName=*/null);
+ sLoadedPaths.put(path, pathClassLoader);
+ return pathClassLoader;
+ }
+
+ /**
+ * Returns a cached ClassLoader to be used at runtime for the jar at the given path. Or, creates
+ * one if it is not prefetched and is allowed to be created at runtime.
+ *
+ * The parent class loader should always be the system server class loader. Changing it has
+ * implications that require discussion with the mainline team.
+ *
+ * @hide for internal use only
+ */
+ public static PathClassLoader getOrCreateClassLoader(
+ String path, ClassLoader parent, boolean isTestOnly) {
+ PathClassLoader pathClassLoader = sLoadedPaths.get(path);
+ if (pathClassLoader != null) {
+ return pathClassLoader;
+ }
+ if (!allowClassLoaderCreation(path, isTestOnly)) {
+ throw new RuntimeException("Creating a ClassLoader from " + path + " is not allowed. "
+ + "Please make sure that the jar is listed in "
+ + "`PRODUCT_APEX_STANDALONE_SYSTEM_SERVER_JARS` in the Makefile and added as a "
+ + "`standalone_contents` of a `systemserverclasspath_fragment` in "
+ + "`Android.bp`.");
+ }
+ return createClassLoader(path, parent);
+ }
+
+ /**
+ * Returns whether a class loader for the jar is allowed to be created at runtime.
+ */
+ private static boolean allowClassLoaderCreation(String path, boolean isTestOnly) {
+ // Currently, we only enforce prefetching for APEX jars.
+ if (!path.startsWith("/apex/")) {
+ return true;
+ }
+ // APEXes for testing only are okay to ignore.
+ if (isTestOnly) {
+ return true;
+ }
+ // If system server is being profiled, it's OK to create class loaders anytime.
+ if (ZygoteInit.shouldProfileSystemServer()) {
+ return true;
+ }
+ return false;
+ }
+
+
+}
diff --git a/android-35/com/android/internal/os/TimeoutRecord.java b/android-35/com/android/internal/os/TimeoutRecord.java
new file mode 100644
index 0000000..1f4abc1
--- /dev/null
+++ b/android-35/com/android/internal/os/TimeoutRecord.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2022 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.internal.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.SystemClock;
+
+import com.android.internal.os.anr.AnrLatencyTracker;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A timeout that has triggered on the system.
+ *
+ * @hide
+ */
+public class TimeoutRecord {
+ /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
+ @IntDef(value = {
+ TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW,
+ TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE,
+ TimeoutKind.BROADCAST_RECEIVER,
+ TimeoutKind.SERVICE_START,
+ TimeoutKind.SERVICE_EXEC,
+ TimeoutKind.CONTENT_PROVIDER,
+ TimeoutKind.APP_REGISTERED,
+ TimeoutKind.SHORT_FGS_TIMEOUT,
+ TimeoutKind.JOB_SERVICE,
+ TimeoutKind.FGS_TIMEOUT,
+ })
+
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TimeoutKind {
+ int INPUT_DISPATCH_NO_FOCUSED_WINDOW = 1;
+ int INPUT_DISPATCH_WINDOW_UNRESPONSIVE = 2;
+ int BROADCAST_RECEIVER = 3;
+ int SERVICE_START = 4;
+ int SERVICE_EXEC = 5;
+ int CONTENT_PROVIDER = 6;
+ int APP_REGISTERED = 7;
+ int SHORT_FGS_TIMEOUT = 8;
+ int JOB_SERVICE = 9;
+ int APP_START = 10;
+ int FGS_TIMEOUT = 11;
+ }
+
+ /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
+ @TimeoutKind
+ public final int mKind;
+
+ /** Reason for the timeout. */
+ public final String mReason;
+
+ /** System uptime in millis when the timeout was triggered. */
+ public final long mEndUptimeMillis;
+
+ /**
+ * Was the end timestamp taken right after the timeout triggered, before any potentially
+ * expensive operations such as taking locks?
+ */
+ public final boolean mEndTakenBeforeLocks;
+
+ /** Latency tracker associated with this instance. */
+ public final AnrLatencyTracker mLatencyTracker;
+
+ private TimeoutRecord(@TimeoutKind int kind, @NonNull String reason, long endUptimeMillis,
+ boolean endTakenBeforeLocks) {
+ this.mKind = kind;
+ this.mReason = reason;
+ this.mEndUptimeMillis = endUptimeMillis;
+ this.mEndTakenBeforeLocks = endTakenBeforeLocks;
+ this.mLatencyTracker = new AnrLatencyTracker(kind, endUptimeMillis);
+ }
+
+ private static TimeoutRecord endingNow(@TimeoutKind int kind, String reason) {
+ long endUptimeMillis = SystemClock.uptimeMillis();
+ return new TimeoutRecord(kind, reason, endUptimeMillis, /* endTakenBeforeLocks */ true);
+ }
+
+ private static TimeoutRecord endingApproximatelyNow(@TimeoutKind int kind, String reason) {
+ long endUptimeMillis = SystemClock.uptimeMillis();
+ return new TimeoutRecord(kind, reason, endUptimeMillis, /* endTakenBeforeLocks */ false);
+ }
+
+ /** Record for a broadcast receiver timeout. */
+ @NonNull
+ public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent,
+ @Nullable String packageName, @Nullable String className) {
+ final Intent logIntent;
+ if (packageName != null) {
+ if (className != null) {
+ logIntent = new Intent(intent);
+ logIntent.setComponent(new ComponentName(packageName, className));
+ } else {
+ logIntent = new Intent(intent);
+ logIntent.setPackage(packageName);
+ }
+ } else {
+ logIntent = intent;
+ }
+ return forBroadcastReceiver(logIntent);
+ }
+
+ /** Record for a broadcast receiver timeout. */
+ @NonNull
+ public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent) {
+ final StringBuilder reason = new StringBuilder("Broadcast of ");
+ intent.toString(reason);
+ return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason.toString());
+ }
+
+ /** Record for a broadcast receiver timeout. */
+ @NonNull
+ public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent,
+ long timeoutDurationMs) {
+ final StringBuilder reason = new StringBuilder("Broadcast of ");
+ intent.toString(reason);
+ reason.append(", waited ");
+ reason.append(timeoutDurationMs);
+ reason.append("ms");
+ return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason.toString());
+ }
+
+ /** Record for an input dispatch no focused window timeout */
+ @NonNull
+ public static TimeoutRecord forInputDispatchNoFocusedWindow(@NonNull String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW, reason);
+ }
+
+ /** Record for an input dispatch window unresponsive timeout. */
+ @NonNull
+ public static TimeoutRecord forInputDispatchWindowUnresponsive(@NonNull String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE, reason);
+ }
+
+ /** Record for a service exec timeout. */
+ @NonNull
+ public static TimeoutRecord forServiceExec(@NonNull String shortInstanceName,
+ long timeoutDurationMs) {
+ String reason =
+ "executing service " + shortInstanceName + ", waited "
+ + timeoutDurationMs + "ms";
+ return TimeoutRecord.endingNow(TimeoutKind.SERVICE_EXEC, reason);
+ }
+
+ /** Record for a service start timeout. */
+ @NonNull
+ public static TimeoutRecord forServiceStartWithEndTime(@NonNull String reason,
+ long endUptimeMillis) {
+ return new TimeoutRecord(TimeoutKind.SERVICE_START, reason,
+ endUptimeMillis, /* endTakenBeforeLocks */ true);
+ }
+
+ /** Record for a content provider timeout. */
+ @NonNull
+ public static TimeoutRecord forContentProvider(@NonNull String reason) {
+ return TimeoutRecord.endingApproximatelyNow(TimeoutKind.CONTENT_PROVIDER, reason);
+ }
+
+ /** Record for an app registered timeout. */
+ @NonNull
+ public static TimeoutRecord forApp(@NonNull String reason) {
+ return TimeoutRecord.endingApproximatelyNow(TimeoutKind.APP_REGISTERED, reason);
+ }
+
+ /** Record for a "short foreground service" timeout. */
+ @NonNull
+ public static TimeoutRecord forShortFgsTimeout(String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.SHORT_FGS_TIMEOUT, reason);
+ }
+
+ /** Record for a "foreground service" timeout. */
+ @NonNull
+ public static TimeoutRecord forFgsTimeout(String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.FGS_TIMEOUT, reason);
+ }
+
+ /** Record for a job related timeout. */
+ @NonNull
+ public static TimeoutRecord forJobService(String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.JOB_SERVICE, reason);
+ }
+
+ /** Record for app startup timeout. */
+ @NonNull
+ public static TimeoutRecord forAppStart(String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.APP_START, reason);
+ }
+}
diff --git a/android-35/com/android/internal/os/TransferPipe.java b/android-35/com/android/internal/os/TransferPipe.java
new file mode 100644
index 0000000..1c09bd6
--- /dev/null
+++ b/android-35/com/android/internal/os/TransferPipe.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2011 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.internal.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import libcore.io.IoUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Helper for transferring data through a pipe from a client app.
+ */
+public class TransferPipe implements Runnable, Closeable {
+ static final String TAG = "TransferPipe";
+ static final boolean DEBUG = false;
+
+ static final long DEFAULT_TIMEOUT = 5000; // 5 seconds
+
+ final Thread mThread;
+ final ParcelFileDescriptor[] mFds;
+
+ FileDescriptor mOutFd;
+ long mEndTime;
+ String mFailure;
+ boolean mComplete;
+
+ String mBufferPrefix;
+
+ interface Caller {
+ void go(IInterface iface, FileDescriptor fd, String prefix,
+ String[] args) throws RemoteException;
+ }
+
+ public TransferPipe() throws IOException {
+ this(null);
+ }
+
+ public TransferPipe(String bufferPrefix) throws IOException {
+ this(bufferPrefix, "TransferPipe");
+ }
+
+ protected TransferPipe(String bufferPrefix, String threadName) throws IOException {
+ mThread = new Thread(this, threadName);
+ mFds = ParcelFileDescriptor.createPipe();
+ mBufferPrefix = bufferPrefix;
+ }
+
+ ParcelFileDescriptor getReadFd() {
+ return mFds[0];
+ }
+
+ public ParcelFileDescriptor getWriteFd() {
+ return mFds[1];
+ }
+
+ public void setBufferPrefix(String prefix) {
+ mBufferPrefix = prefix;
+ }
+
+ public static void dumpAsync(IBinder binder, FileDescriptor out, String[] args)
+ throws IOException, RemoteException {
+ goDump(binder, out, args);
+ }
+
+ /**
+ * Read raw bytes from a service's dump function.
+ *
+ * <p>This can be used for dumping {@link android.util.proto.ProtoOutputStream protos}.
+ *
+ * @param binder The service providing the data
+ * @param args The arguments passed to the dump function of the service
+ */
+ public static byte[] dumpAsync(@NonNull IBinder binder, @Nullable String... args)
+ throws IOException, RemoteException {
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ try {
+ TransferPipe.dumpAsync(binder, pipe[1].getFileDescriptor(), args);
+
+ // Data is written completely when dumpAsync is done
+ pipe[1].close();
+ pipe[1] = null;
+
+ byte[] buffer = new byte[4096];
+ try (ByteArrayOutputStream combinedBuffer = new ByteArrayOutputStream()) {
+ try (FileInputStream is = new FileInputStream(pipe[0].getFileDescriptor())) {
+ while (true) {
+ int numRead = is.read(buffer);
+ if (numRead == -1) {
+ break;
+ }
+
+ combinedBuffer.write(buffer, 0, numRead);
+ }
+ }
+
+ return combinedBuffer.toByteArray();
+ }
+ } finally {
+ pipe[0].close();
+ IoUtils.closeQuietly(pipe[1]);
+ }
+ }
+
+ static void go(Caller caller, IInterface iface, FileDescriptor out,
+ String prefix, String[] args) throws IOException, RemoteException {
+ go(caller, iface, out, prefix, args, DEFAULT_TIMEOUT);
+ }
+
+ static void go(Caller caller, IInterface iface, FileDescriptor out,
+ String prefix, String[] args, long timeout) throws IOException, RemoteException {
+ if ((iface.asBinder()) instanceof Binder) {
+ // This is a local object... just call it directly.
+ try {
+ caller.go(iface, out, prefix, args);
+ } catch (RemoteException e) {
+ }
+ return;
+ }
+
+ try (TransferPipe tp = new TransferPipe()) {
+ caller.go(iface, tp.getWriteFd().getFileDescriptor(), prefix, args);
+ tp.go(out, timeout);
+ }
+ }
+
+ static void goDump(IBinder binder, FileDescriptor out,
+ String[] args) throws IOException, RemoteException {
+ goDump(binder, out, args, DEFAULT_TIMEOUT);
+ }
+
+ static void goDump(IBinder binder, FileDescriptor out,
+ String[] args, long timeout) throws IOException, RemoteException {
+ if (binder instanceof Binder) {
+ // This is a local object... just call it directly.
+ try {
+ binder.dump(out, args);
+ } catch (RemoteException e) {
+ }
+ return;
+ }
+
+ try (TransferPipe tp = new TransferPipe()) {
+ binder.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
+ tp.go(out, timeout);
+ }
+ }
+
+ public void go(FileDescriptor out) throws IOException {
+ go(out, DEFAULT_TIMEOUT);
+ }
+
+ public void go(FileDescriptor out, long timeout) throws IOException {
+ try {
+ synchronized (this) {
+ mOutFd = out;
+ mEndTime = SystemClock.uptimeMillis() + timeout;
+
+ if (DEBUG) Slog.i(TAG, "read=" + getReadFd() + " write=" + getWriteFd()
+ + " out=" + out);
+
+ // Close the write fd, so we know when the other side is done.
+ closeFd(1);
+
+ mThread.start();
+
+ while (mFailure == null && !mComplete) {
+ long waitTime = mEndTime - SystemClock.uptimeMillis();
+ if (waitTime <= 0) {
+ if (DEBUG) Slog.i(TAG, "TIMEOUT!");
+ mThread.interrupt();
+ throw new IOException("Timeout");
+ }
+
+ try {
+ wait(waitTime);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ if (DEBUG) Slog.i(TAG, "Finished: " + mFailure);
+ if (mFailure != null) {
+ throw new IOException(mFailure);
+ }
+ }
+ } finally {
+ kill();
+ }
+ }
+
+ void closeFd(int num) {
+ if (mFds[num] != null) {
+ if (DEBUG) Slog.i(TAG, "Closing: " + mFds[num]);
+ try {
+ mFds[num].close();
+ } catch (IOException e) {
+ }
+ mFds[num] = null;
+ }
+ }
+
+ @Override
+ public void close() {
+ kill();
+ }
+
+ public void kill() {
+ synchronized (this) {
+ closeFd(0);
+ closeFd(1);
+ }
+ }
+
+ protected OutputStream getNewOutputStream() {
+ return new FileOutputStream(mOutFd);
+ }
+
+ @Override
+ public void run() {
+ final byte[] buffer = new byte[1024];
+ final FileInputStream fis;
+ final OutputStream fos;
+
+ synchronized (this) {
+ ParcelFileDescriptor readFd = getReadFd();
+ if (readFd == null) {
+ Slog.w(TAG, "Pipe has been closed...");
+ return;
+ }
+ fis = new FileInputStream(readFd.getFileDescriptor());
+ fos = getNewOutputStream();
+ }
+
+ if (DEBUG) Slog.i(TAG, "Ready to read pipe...");
+ byte[] bufferPrefix = null;
+ boolean needPrefix = true;
+ if (mBufferPrefix != null) {
+ bufferPrefix = mBufferPrefix.getBytes();
+ }
+
+ int size;
+ try {
+ while ((size=fis.read(buffer)) > 0) {
+ if (DEBUG) Slog.i(TAG, "Got " + size + " bytes");
+ if (bufferPrefix == null) {
+ fos.write(buffer, 0, size);
+ } else {
+ int start = 0;
+ for (int i=0; i<size; i++) {
+ if (buffer[i] != '\n') {
+ if (i > start) {
+ fos.write(buffer, start, i-start);
+ }
+ start = i;
+ if (needPrefix) {
+ fos.write(bufferPrefix);
+ needPrefix = false;
+ }
+ do {
+ i++;
+ } while (i<size && buffer[i] != '\n');
+ if (i < size) {
+ needPrefix = true;
+ }
+ }
+ }
+ if (size > start) {
+ fos.write(buffer, start, size-start);
+ }
+ }
+ }
+ if (DEBUG) Slog.i(TAG, "End of pipe: size=" + size);
+ if (mThread.isInterrupted()) {
+ if (DEBUG) Slog.i(TAG, "Interrupted!");
+ }
+ } catch (IOException e) {
+ synchronized (this) {
+ mFailure = e.toString();
+ notifyAll();
+ return;
+ }
+ }
+
+ synchronized (this) {
+ mComplete = true;
+ notifyAll();
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/WebViewZygoteInit.java b/android-35/com/android/internal/os/WebViewZygoteInit.java
new file mode 100644
index 0000000..9ed7384
--- /dev/null
+++ b/android-35/com/android/internal/os/WebViewZygoteInit.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2016 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.internal.os;
+
+import android.app.ApplicationLoaders;
+import android.app.LoadedApk;
+import android.content.pm.ApplicationInfo;
+import android.net.LocalSocket;
+import android.text.TextUtils;
+import android.util.Log;
+import android.webkit.WebViewFactory;
+import android.webkit.WebViewFactoryProvider;
+import android.webkit.WebViewLibraryLoader;
+
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * Startup class for the WebView zygote process.
+ *
+ * See {@link ZygoteInit} for generic zygote startup documentation.
+ *
+ * @hide
+ */
+class WebViewZygoteInit {
+ public static final String TAG = "WebViewZygoteInit";
+
+ private static class WebViewZygoteServer extends ZygoteServer {
+ @Override
+ protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
+ throws IOException {
+ return new WebViewZygoteConnection(socket, abiList);
+ }
+ }
+
+ private static class WebViewZygoteConnection extends ZygoteConnection {
+ WebViewZygoteConnection(LocalSocket socket, String abiList) throws IOException {
+ super(socket, abiList);
+ }
+
+ @Override
+ protected void preload() {
+ // Nothing to preload by default.
+ }
+
+ @Override
+ protected boolean isPreloadComplete() {
+ // Webview zygotes don't preload any classes or resources or defaults, all of their
+ // preloading is package specific.
+ return true;
+ }
+
+ @Override
+ protected boolean canPreloadApp() {
+ return true;
+ }
+
+ @Override
+ protected void handlePreloadApp(ApplicationInfo appInfo) {
+ Log.i(TAG, "Beginning application preload for " + appInfo.packageName);
+ LoadedApk loadedApk = new LoadedApk(null, appInfo, null, null, false, true, false);
+ ClassLoader loader = loadedApk.getClassLoader();
+ doPreload(loader, WebViewFactory.getWebViewLibrary(appInfo));
+
+ Zygote.allowAppFilesAcrossFork(appInfo);
+
+ Log.i(TAG, "Application preload done");
+ }
+
+ @Override
+ protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName,
+ String cacheKey) {
+ Log.i(TAG, "Beginning package preload");
+ // Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
+ // our children will reuse the same classloader instead of creating their own.
+ // This enables us to preload Java and native code in the webview zygote process and
+ // have the preloaded versions actually be used post-fork.
+ ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader(
+ packagePath, libsPath, cacheKey);
+
+ // Add the APK to the Zygote's list of allowed files for children.
+ String[] packageList = TextUtils.split(packagePath, File.pathSeparator);
+ for (String packageEntry : packageList) {
+ Zygote.nativeAllowFileAcrossFork(packageEntry);
+ }
+
+ doPreload(loader, libFileName);
+
+ Log.i(TAG, "Package preload done");
+ }
+
+ private void doPreload(ClassLoader loader, String libFileName) {
+ // Load the native library using WebViewLibraryLoader to share the RELRO data with other
+ // processes.
+ WebViewLibraryLoader.loadNativeLibrary(loader, libFileName);
+
+ // Once we have the classloader, look up the WebViewFactoryProvider implementation and
+ // call preloadInZygote() on it to give it the opportunity to preload the native library
+ // and perform any other initialisation work that should be shared among the children.
+ boolean preloadSucceeded = false;
+ try {
+ Class<WebViewFactoryProvider> providerClass =
+ WebViewFactory.getWebViewProviderClass(loader);
+ Method preloadInZygote = providerClass.getMethod("preloadInZygote");
+ preloadInZygote.setAccessible(true);
+ if (preloadInZygote.getReturnType() != Boolean.TYPE) {
+ Log.e(TAG, "Unexpected return type: preloadInZygote must return boolean");
+ } else {
+ preloadSucceeded = (boolean) providerClass.getMethod("preloadInZygote")
+ .invoke(null);
+ if (!preloadSucceeded) {
+ Log.e(TAG, "preloadInZygote returned false");
+ }
+ }
+ } catch (ReflectiveOperationException e) {
+ Log.e(TAG, "Exception while preloading package", e);
+ }
+
+ try {
+ DataOutputStream socketOut = getSocketOutputStream();
+ socketOut.writeInt(preloadSucceeded ? 1 : 0);
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Error writing to command socket", ioe);
+ }
+ }
+ }
+
+ public static void main(String argv[]) {
+ Log.i(TAG, "Starting WebViewZygoteInit");
+ WebViewZygoteServer server = new WebViewZygoteServer();
+ ChildZygoteInit.runZygoteServer(server, argv);
+ }
+}
diff --git a/android-35/com/android/internal/os/WrapperInit.java b/android-35/com/android/internal/os/WrapperInit.java
new file mode 100644
index 0000000..6860759
--- /dev/null
+++ b/android-35/com/android/internal/os/WrapperInit.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2011 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.internal.os;
+
+import android.os.Process;
+import android.os.Trace;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructCapUserData;
+import android.system.StructCapUserHeader;
+import android.util.Slog;
+import android.util.TimingsTraceLog;
+
+import dalvik.system.VMRuntime;
+
+import libcore.io.IoUtils;
+
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Startup class for the wrapper process.
+ * @hide
+ */
+public class WrapperInit {
+ private final static String TAG = "AndroidRuntime";
+
+ /**
+ * Class not instantiable.
+ */
+ private WrapperInit() {
+ }
+
+ /**
+ * The main function called when starting a runtime application through a
+ * wrapper process instead of by forking Zygote.
+ *
+ * The first argument specifies the file descriptor for a pipe that should receive
+ * the pid of this process, or 0 if none.
+ *
+ * The second argument is the target SDK version for the app.
+ *
+ * The remaining arguments are passed to the runtime.
+ *
+ * @param args The command-line arguments.
+ */
+ public static void main(String[] args) {
+ // Parse our mandatory arguments.
+ int fdNum = Integer.parseInt(args[0], 10);
+ int targetSdkVersion = Integer.parseInt(args[1], 10);
+
+ // Tell the Zygote what our actual PID is (since it only knows about the
+ // wrapper that it directly forked).
+ if (fdNum != 0) {
+ FileDescriptor fd = new FileDescriptor();
+ try {
+ fd.setInt$(fdNum);
+ DataOutputStream os = new DataOutputStream(new FileOutputStream(fd));
+ os.writeInt(Process.myPid());
+ os.close();
+ } catch (IOException ex) {
+ Slog.d(TAG, "Could not write pid of wrapped process to Zygote pipe.", ex);
+ } finally {
+ IoUtils.closeQuietly(fd);
+ }
+ }
+
+ // Mimic system Zygote preloading.
+ ZygoteInit.preload(new TimingsTraceLog("WrapperInitTiming",
+ Trace.TRACE_TAG_DALVIK));
+
+ // Launch the application.
+ String[] runtimeArgs = new String[args.length - 2];
+ System.arraycopy(args, 2, runtimeArgs, 0, runtimeArgs.length);
+ Runnable r = wrapperInit(targetSdkVersion, runtimeArgs);
+
+ r.run();
+ }
+
+ /**
+ * Executes a runtime application with a wrapper command.
+ * This method never returns.
+ *
+ * @param invokeWith The wrapper command.
+ * @param niceName The nice name for the application, or null if none.
+ * @param targetSdkVersion The target SDK version for the app.
+ * @param pipeFd The pipe to which the application's pid should be written, or null if none.
+ * @param args Arguments for {@link RuntimeInit#main}.
+ */
+ public static void execApplication(String invokeWith, String niceName,
+ int targetSdkVersion, String instructionSet, FileDescriptor pipeFd,
+ String[] args) {
+ StringBuilder command = new StringBuilder(invokeWith);
+
+ final String appProcess;
+ if (VMRuntime.is64BitInstructionSet(instructionSet)) {
+ appProcess = "/system/bin/app_process64";
+ } else {
+ appProcess = "/system/bin/app_process32";
+ }
+ command.append(' ');
+ command.append(appProcess);
+
+ // Generate bare minimum of debug information to be able to backtrace through JITed code.
+ // We assume that if the invoke wrapper is used, backtraces are desirable:
+ // * The wrap.sh script can only be used by debuggable apps, which would enable this flag
+ // without the script anyway (the fork-zygote path). So this makes the two consistent.
+ // * The wrap.* property can only be used on userdebug builds and is likely to be used by
+ // developers (e.g. enable debug-malloc), in which case backtraces are also useful.
+ command.append(" -Xcompiler-option --generate-mini-debug-info");
+
+ command.append(" /system/bin --application");
+ if (niceName != null) {
+ command.append(" '--nice-name=").append(niceName).append("'");
+ }
+ command.append(" com.android.internal.os.WrapperInit ");
+ command.append(pipeFd != null ? pipeFd.getInt$() : 0);
+ command.append(' ');
+ command.append(targetSdkVersion);
+ Zygote.appendQuotedShellArgs(command, args);
+ preserveCapabilities();
+ Zygote.execShell(command.toString());
+ }
+
+ /**
+ * The main function called when an application is started through a
+ * wrapper process.
+ *
+ * When the wrapper starts, the runtime starts {@link RuntimeInit#main}
+ * which calls {@link main} which then calls this method.
+ * So we don't need to call commonInit() here.
+ *
+ * @param targetSdkVersion target SDK version
+ * @param argv arg strings
+ */
+ private static Runnable wrapperInit(int targetSdkVersion, String[] argv) {
+ if (RuntimeInit.DEBUG) {
+ Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from wrapper");
+ }
+
+ // Check whether the first argument is a "-cp" in argv, and assume the next argument is the
+ // classpath. If found, create a PathClassLoader and use it for applicationInit.
+ ClassLoader classLoader = null;
+ if (argv != null && argv.length > 2 && argv[0].equals("-cp")) {
+ classLoader = ZygoteInit.createPathClassLoader(argv[1], targetSdkVersion);
+
+ // Install this classloader as the context classloader, too.
+ Thread.currentThread().setContextClassLoader(classLoader);
+
+ // Remove the classpath from the arguments.
+ String removedArgs[] = new String[argv.length - 2];
+ System.arraycopy(argv, 2, removedArgs, 0, argv.length - 2);
+ argv = removedArgs;
+ }
+ // Perform the same initialization that would happen after the Zygote forks.
+ Zygote.nativePreApplicationInit();
+ return RuntimeInit.applicationInit(targetSdkVersion, /*disabledCompatChanges*/ null,
+ argv, classLoader);
+ }
+
+ /**
+ * Copy current capabilities to ambient capabilities. This is required for apps using
+ * capabilities, as execv will re-evaluate the capability set, and the set of sh is
+ * empty. Ambient capabilities have to be set to inherit them effectively.
+ *
+ * Note: This is BEST EFFORT ONLY. In case capabilities can't be raised, this function
+ * will silently return. In THIS CASE ONLY, as this is a development feature, it
+ * is better to return and try to run anyways, instead of blocking the wrapped app.
+ * This is acceptable here as failure will leave the wrapped app with strictly less
+ * capabilities, which may make it crash, but not exceed its allowances.
+ */
+ private static void preserveCapabilities() {
+ StructCapUserHeader header = new StructCapUserHeader(
+ OsConstants._LINUX_CAPABILITY_VERSION_3, 0);
+ StructCapUserData[] data;
+ try {
+ data = Os.capget(header);
+ } catch (ErrnoException e) {
+ Slog.e(RuntimeInit.TAG, "RuntimeInit: Failed capget", e);
+ return;
+ }
+
+ if (data[0].permitted != data[0].inheritable ||
+ data[1].permitted != data[1].inheritable) {
+ data[0] = new StructCapUserData(data[0].effective, data[0].permitted,
+ data[0].permitted);
+ data[1] = new StructCapUserData(data[1].effective, data[1].permitted,
+ data[1].permitted);
+ try {
+ Os.capset(header, data);
+ } catch (ErrnoException e) {
+ Slog.e(RuntimeInit.TAG, "RuntimeInit: Failed capset", e);
+ return;
+ }
+ }
+
+ for (int i = 0; i < 64; i++) {
+ int dataIndex = OsConstants.CAP_TO_INDEX(i);
+ int capMask = OsConstants.CAP_TO_MASK(i);
+ if ((data[dataIndex].inheritable & capMask) != 0) {
+ try {
+ Os.prctl(OsConstants.PR_CAP_AMBIENT, OsConstants.PR_CAP_AMBIENT_RAISE, i, 0,
+ 0);
+ } catch (ErrnoException ex) {
+ // Only log here. Try to run the wrapped application even without this
+ // ambient capability. It may crash after fork, but at least we'll try.
+ Slog.e(RuntimeInit.TAG, "RuntimeInit: Failed to raise ambient capability "
+ + i, ex);
+ }
+ }
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/Zygote.java b/android-35/com/android/internal/os/Zygote.java
new file mode 100644
index 0000000..cab84bb
--- /dev/null
+++ b/android-35/com/android/internal/os/Zygote.java
@@ -0,0 +1,1500 @@
+/*
+ * Copyright (C) 2014 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.internal.os;
+
+import static android.system.OsConstants.O_CLOEXEC;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ProcessInfo;
+import android.net.Credentials;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.os.Build;
+import android.os.FactoryTest;
+import android.os.IVold;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.provider.DeviceConfig;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.net.NetworkUtilsInternal;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+import dalvik.system.ZygoteHooks;
+
+import libcore.io.IoUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/** @hide */
+public final class Zygote {
+ /*
+ * Bit values for "runtimeFlags" argument. The definitions are duplicated
+ * in the native code.
+ */
+
+ /** enable debugging over JDWP */
+ public static final int DEBUG_ENABLE_JDWP = 1;
+ /** enable JNI checks */
+ public static final int DEBUG_ENABLE_CHECKJNI = 1 << 1;
+ /** enable Java programming language "assert" statements */
+ public static final int DEBUG_ENABLE_ASSERT = 1 << 2;
+ /** disable the AOT compiler and JIT */
+ public static final int DEBUG_ENABLE_SAFEMODE = 1 << 3;
+ /** Enable logging of third-party JNI activity. */
+ public static final int DEBUG_ENABLE_JNI_LOGGING = 1 << 4;
+ /** Force generation of native debugging information. */
+ public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 5;
+ /** Always use JIT-ed code. */
+ public static final int DEBUG_ALWAYS_JIT = 1 << 6;
+ /** Make the code native debuggable by turning off some optimizations. */
+ public static final int DEBUG_NATIVE_DEBUGGABLE = 1 << 7;
+ /** Make the code Java debuggable by turning off some optimizations. */
+ public static final int DEBUG_JAVA_DEBUGGABLE = 1 << 8;
+
+ /** Turn off the verifier. */
+ public static final int DISABLE_VERIFIER = 1 << 9;
+ /** Only use oat files located in /system. Otherwise use dex/jar/apk . */
+ public static final int ONLY_USE_SYSTEM_OAT_FILES = 1 << 10;
+ /** Force generation of native debugging information for backtraces. */
+ public static final int DEBUG_GENERATE_MINI_DEBUG_INFO = 1 << 11;
+ /**
+ * Hidden API access restrictions. This is a mask for bits representing the API enforcement
+ * policy, defined by {@code @ApplicationInfo.HiddenApiEnforcementPolicy}.
+ */
+ public static final int API_ENFORCEMENT_POLICY_MASK = (1 << 12) | (1 << 13);
+ /**
+ * Bit shift for use with {@link #API_ENFORCEMENT_POLICY_MASK}.
+ *
+ * (flags & API_ENFORCEMENT_POLICY_MASK) >> API_ENFORCEMENT_POLICY_SHIFT gives
+ * {@link ApplicationInfo.HiddenApiEnforcementPolicy} values.
+ */
+ public static final int API_ENFORCEMENT_POLICY_SHIFT =
+ Integer.numberOfTrailingZeros(API_ENFORCEMENT_POLICY_MASK);
+ /**
+ * Enable system server ART profiling.
+ */
+ public static final int PROFILE_SYSTEM_SERVER = 1 << 14;
+
+ /**
+ * Enable profiling from shell.
+ */
+ public static final int PROFILE_FROM_SHELL = 1 << 15;
+
+ /*
+ * Enable using the ART app image startup cache
+ */
+ public static final int USE_APP_IMAGE_STARTUP_CACHE = 1 << 16;
+
+ /**
+ * When set, application specified signal handlers are not chained (i.e, ignored)
+ * by the runtime.
+ *
+ * Used for debugging only. Usage: set debug.ignoreappsignalhandler to 1.
+ */
+ public static final int DEBUG_IGNORE_APP_SIGNAL_HANDLER = 1 << 17;
+
+ /**
+ * Disable runtime access to {@link android.annotation.TestApi} annotated members.
+ *
+ * <p>This only takes effect if Hidden API access restrictions are enabled as well.
+ */
+ public static final int DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18;
+
+ public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20);
+
+ public static final int MEMORY_TAG_LEVEL_NONE = 0;
+
+ /**
+ * Enable pointer tagging in this process.
+ * Tags are checked during memory deallocation, but not on access.
+ * TBI stands for Top-Byte-Ignore, an ARM CPU feature.
+ * {@link https://developer.arm.com/docs/den0024/latest/the-memory-management-unit/translation-table-configuration/virtual-address-tagging}
+ */
+ public static final int MEMORY_TAG_LEVEL_TBI = 1 << 19;
+
+ /**
+ * Enable asynchronous memory tag checks in this process.
+ */
+ public static final int MEMORY_TAG_LEVEL_ASYNC = 2 << 19;
+
+ /**
+ * Enable synchronous memory tag checks in this process.
+ */
+ public static final int MEMORY_TAG_LEVEL_SYNC = 3 << 19;
+
+ /**
+ * A two-bit field for GWP-ASan level of this process. See the possible values below.
+ */
+ public static final int GWP_ASAN_LEVEL_MASK = (1 << 21) | (1 << 22);
+
+ /**
+ * Disable GWP-ASan in this process.
+ * GWP-ASan is a low-overhead memory bug detector using guard pages on a small
+ * subset of heap allocations.
+ */
+ public static final int GWP_ASAN_LEVEL_NEVER = 0 << 21;
+
+ /**
+ * Enable GWP-ASan in this process with a small sampling rate.
+ * With approx. 1% chance GWP-ASan will be activated and apply its protection
+ * to a small subset of heap allocations.
+ * Otherwise (~99% chance) this process is unaffected.
+ */
+ public static final int GWP_ASAN_LEVEL_LOTTERY = 1 << 21;
+
+ /**
+ * Always enable GWP-ASan in this process.
+ * GWP-ASan is activated unconditionally (but still, only a small subset of
+ * allocations is protected).
+ */
+ public static final int GWP_ASAN_LEVEL_ALWAYS = 2 << 21;
+
+ /**
+ * GWP-ASan's `gwpAsanMode` manifest flag was unspecified. Currently, this
+ * means GWP_ASAN_LEVEL_LOTTERY for system apps, and GWP_ASAN_LEVEL_NONE for
+ * non-system apps.
+ */
+ public static final int GWP_ASAN_LEVEL_DEFAULT = 3 << 21;
+
+ /** Enable automatic zero-initialization of native heap memory allocations. */
+ public static final int NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23;
+
+ /**
+ * Enable profiling from system services. This loads profiling related plugins in ART.
+ */
+ public static final int PROFILEABLE = 1 << 24;
+
+ /**
+ * Enable ptrace. This is enabled on eng, if the app is debuggable, or if
+ * the persist.debug.ptrace.enabled property is set.
+ */
+ public static final int DEBUG_ENABLE_PTRACE = 1 << 25;
+
+ /** No external storage should be mounted. */
+ public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
+ /** Default external storage should be mounted. */
+ public static final int MOUNT_EXTERNAL_DEFAULT = IVold.REMOUNT_MODE_DEFAULT;
+ /**
+ * Mount mode for package installers which should give them access to
+ * all obb dirs in addition to their package sandboxes
+ */
+ public static final int MOUNT_EXTERNAL_INSTALLER = IVold.REMOUNT_MODE_INSTALLER;
+ /** The lower file system should be bind mounted directly on external storage */
+ public static final int MOUNT_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH;
+
+ /** Use the regular scoped storage filesystem, but Android/ should be writable.
+ * Used to support the applications hosting DownloadManager and the MTP server.
+ */
+ public static final int MOUNT_EXTERNAL_ANDROID_WRITABLE = IVold.REMOUNT_MODE_ANDROID_WRITABLE;
+
+ /** Number of bytes sent to the Zygote over USAP pipes or the pool event FD */
+ static final int USAP_MANAGEMENT_MESSAGE_BYTES = 8;
+
+ /** Make the new process have top application priority. */
+ public static final String START_AS_TOP_APP_ARG = "--is-top-app";
+
+ /** List of packages with the same uid, and its app data info: volume uuid and inode. */
+ public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map";
+
+ /** List of allowlisted packages and its app data info: volume uuid and inode. */
+ public static final String ALLOWLISTED_DATA_INFO_MAP = "--allowlisted-data-info-map";
+
+ /** Bind mount app storage dirs to lower fs not via fuse */
+ public static final String BIND_MOUNT_APP_STORAGE_DIRS = "--bind-mount-storage-dirs";
+
+ /** Bind mount app storage dirs to lower fs not via fuse */
+ public static final String BIND_MOUNT_APP_DATA_DIRS = "--bind-mount-data-dirs";
+
+ /** Bind the system properties to an alternate set, for appcompat reasons */
+ public static final String BIND_MOUNT_SYSPROP_OVERRIDES = "--bind-mount-sysprop-overrides";
+
+ /**
+ * An extraArg passed when a zygote process is forking a child-zygote, specifying a name
+ * in the abstract socket namespace. This socket name is what the new child zygote
+ * should listen for connections on.
+ */
+ public static final String CHILD_ZYGOTE_SOCKET_NAME_ARG = "--zygote-socket=";
+
+ /**
+ * An extraArg passed when a zygote process is forking a child-zygote, specifying the
+ * requested ABI for the child Zygote.
+ */
+ public static final String CHILD_ZYGOTE_ABI_LIST_ARG = "--abi-list=";
+
+ /**
+ * An extraArg passed when a zygote process is forking a child-zygote, specifying the
+ * start of the UID range the children of the Zygote may setuid()/setgid() to. This
+ * will be enforced with a seccomp filter.
+ */
+ public static final String CHILD_ZYGOTE_UID_RANGE_START = "--uid-range-start=";
+
+ /**
+ * An extraArg passed when a zygote process is forking a child-zygote, specifying the
+ * end of the UID range the children of the Zygote may setuid()/setgid() to. This
+ * will be enforced with a seccomp filter.
+ */
+ public static final String CHILD_ZYGOTE_UID_RANGE_END = "--uid-range-end=";
+
+ private static final String TAG = "Zygote";
+
+ /** Prefix prepended to socket names created by init */
+ private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
+
+ /**
+ * The duration to wait before re-checking Zygote related system properties.
+ *
+ * One minute in milliseconds.
+ */
+ public static final long PROPERTY_CHECK_INTERVAL = 60000;
+
+ /**
+ * @hide for internal use only
+ */
+ public static final int SOCKET_BUFFER_SIZE = 256;
+
+ /**
+ * @hide for internal use only
+ */
+ private static final int PRIORITY_MAX = -20;
+
+ /** a prototype instance for a future List.toArray() */
+ static final int[][] INT_ARRAY_2D = new int[0][0];
+
+ /**
+ * @hide for internal use only.
+ */
+ public static final String PRIMARY_SOCKET_NAME = "zygote";
+
+ /**
+ * @hide for internal use only.
+ */
+ public static final String SECONDARY_SOCKET_NAME = "zygote_secondary";
+
+ /**
+ * @hide for internal use only
+ */
+ public static final String USAP_POOL_PRIMARY_SOCKET_NAME = "usap_pool_primary";
+
+ /**
+ * @hide for internal use only
+ */
+ public static final String USAP_POOL_SECONDARY_SOCKET_NAME = "usap_pool_secondary";
+
+ private Zygote() {}
+
+ private static boolean containsInetGid(int[] gids) {
+ for (int i = 0; i < gids.length; i++) {
+ if (gids[i] == android.os.Process.INET_GID) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Forks a new VM instance. The current VM must have been started
+ * with the -Xzygote flag. <b>NOTE: new instance keeps all
+ * root capabilities. The new process is expected to call capset()</b>.
+ *
+ * @param uid the UNIX uid that the new process should setuid() to after
+ * fork()ing and and before spawning any threads.
+ * @param gid the UNIX gid that the new process should setgid() to after
+ * fork()ing and and before spawning any threads.
+ * @param gids null-ok; a list of UNIX gids that the new process should
+ * setgroups() to after fork and before spawning any threads.
+ * @param runtimeFlags bit flags that enable ART features.
+ * @param rlimits null-ok an array of rlimit tuples, with the second
+ * dimension having a length of 3 and representing
+ * (resource, rlim_cur, rlim_max). These are set via the posix
+ * setrlimit(2) call.
+ * @param seInfo null-ok a string specifying SELinux information for
+ * the new process.
+ * @param niceName null-ok a string specifying the process name.
+ * @param fdsToClose an array of ints, holding one or more POSIX
+ * file descriptor numbers that are to be closed by the child
+ * (and replaced by /dev/null) after forking. An integer value
+ * of -1 in any entry in the array means "ignore this one".
+ * @param fdsToIgnore null-ok an array of ints, either null or holding
+ * one or more POSIX file descriptor numbers that are to be ignored
+ * in the file descriptor table check.
+ * @param startChildZygote if true, the new child process will itself be a
+ * new zygote process.
+ * @param instructionSet null-ok the instruction set to use.
+ * @param appDataDir null-ok the data directory of the app.
+ * @param isTopApp true if the process is for top (high priority) application.
+ * @param pkgDataInfoList A list that stores related packages and its app data
+ * info: volume uuid and inode.
+ * @param allowlistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps.
+ * @param bindMountAppDataDirs True if the zygote needs to mount data dirs.
+ * @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs.
+ * @param bindMountSyspropOverrides True if the zygote needs to mount the override system
+ * properties
+ *
+ * @return 0 if this is the child, pid of the child
+ * if this is the parent, or -1 on error.
+ */
+ static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
+ int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
+ int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
+ boolean isTopApp, String[] pkgDataInfoList, String[] allowlistedDataInfoList,
+ boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs,
+ boolean bindMountSyspropOverrides) {
+ ZygoteHooks.preFork();
+
+ int pid = nativeForkAndSpecialize(
+ uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
+ fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
+ pkgDataInfoList, allowlistedDataInfoList, bindMountAppDataDirs,
+ bindMountAppStorageDirs, bindMountSyspropOverrides);
+ if (pid == 0) {
+ // Note that this event ends at the end of handleChildProc,
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
+
+ // If no GIDs were specified, don't make any permissions changes based on groups.
+ if (gids != null && gids.length > 0) {
+ NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids));
+ }
+ }
+
+ // Set the Java Language thread priority to the default value for new apps.
+ Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
+
+ ZygoteHooks.postForkCommon();
+ return pid;
+ }
+
+ private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
+ int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
+ int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
+ String appDataDir, boolean isTopApp, String[] pkgDataInfoList,
+ String[] allowlistedDataInfoList, boolean bindMountAppDataDirs,
+ boolean bindMountAppStorageDirs, boolean bindMountSyspropOverrides);
+
+ /**
+ * Specialize an unspecialized app process. The current VM must have been started
+ * with the -Xzygote flag.
+ *
+ * @param uid The UNIX uid that the new process should setuid() to before spawning any threads
+ * @param gid The UNIX gid that the new process should setgid() to before spawning any threads
+ * @param gids null-ok; A list of UNIX gids that the new process should
+ * setgroups() to before spawning any threads
+ * @param runtimeFlags Bit flags that enable ART features
+ * @param rlimits null-ok An array of rlimit tuples, with the second
+ * dimension having a length of 3 and representing
+ * (resource, rlim_cur, rlim_max). These are set via the posix
+ * setrlimit(2) call.
+ * @param seInfo null-ok A string specifying SELinux information for
+ * the new process.
+ * @param niceName null-ok A string specifying the process name.
+ * @param startChildZygote If true, the new child process will itself be a
+ * new zygote process.
+ * @param instructionSet null-ok The instruction set to use.
+ * @param appDataDir null-ok The data directory of the app.
+ * @param isTopApp True if the process is for top (high priority) application.
+ * @param pkgDataInfoList A list that stores related packages and its app data
+ * volume uuid and CE dir inode. For example, pkgDataInfoList = [app_a_pkg_name,
+ * app_a_data_volume_uuid, app_a_ce_inode, app_b_pkg_name, app_b_data_volume_uuid,
+ * app_b_ce_inode, ...];
+ * @param allowlistedDataInfoList Like pkgDataInfoList, but it's for allowlisted apps.
+ * @param bindMountAppDataDirs True if the zygote needs to mount data dirs.
+ * @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs.
+ * @param bindMountSyspropOverrides True if the zygote needs to mount the override system
+ * properties
+ */
+ private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags,
+ int[][] rlimits, int mountExternal, String seInfo, String niceName,
+ boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
+ String[] pkgDataInfoList, String[] allowlistedDataInfoList,
+ boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs,
+ boolean bindMountSyspropOverrides) {
+ nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
+ niceName, startChildZygote, instructionSet, appDataDir, isTopApp,
+ pkgDataInfoList, allowlistedDataInfoList,
+ bindMountAppDataDirs, bindMountAppStorageDirs, bindMountSyspropOverrides);
+
+ // Note that this event ends at the end of handleChildProc.
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
+
+ if (gids != null && gids.length > 0) {
+ NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids));
+ }
+
+ // Set the Java Language thread priority to the default value for new apps.
+ Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
+
+ /*
+ * This is called here (instead of after the fork but before the specialize) to maintain
+ * consistancy with the code paths for forkAndSpecialize.
+ *
+ * TODO (chriswailes): Look into moving this to immediately after the fork.
+ */
+ ZygoteHooks.postForkCommon();
+ }
+
+ private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids,
+ int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
+ boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
+ String[] pkgDataInfoList, String[] allowlistedDataInfoList,
+ boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs,
+ boolean bindMountSyspropOverrides);
+
+ /**
+ * Called to do any initialization before starting an application.
+ */
+ static native void nativePreApplicationInit();
+
+ /**
+ * Special method to start the system server process. In addition to the
+ * common actions performed in forkAndSpecialize, the pid of the child
+ * process is recorded such that the death of the child process will cause
+ * zygote to exit.
+ *
+ * @param uid the UNIX uid that the new process should setuid() to after
+ * fork()ing and and before spawning any threads.
+ * @param gid the UNIX gid that the new process should setgid() to after
+ * fork()ing and and before spawning any threads.
+ * @param gids null-ok; a list of UNIX gids that the new process should
+ * setgroups() to after fork and before spawning any threads.
+ * @param runtimeFlags bit flags that enable ART features.
+ * @param rlimits null-ok an array of rlimit tuples, with the second
+ * dimension having a length of 3 and representing
+ * (resource, rlim_cur, rlim_max). These are set via the posix
+ * setrlimit(2) call.
+ * @param permittedCapabilities argument for setcap()
+ * @param effectiveCapabilities argument for setcap()
+ *
+ * @return 0 if this is the child, pid of the child
+ * if this is the parent, or -1 on error.
+ */
+ static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
+ int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
+ ZygoteHooks.preFork();
+
+ int pid = nativeForkSystemServer(
+ uid, gid, gids, runtimeFlags, rlimits,
+ permittedCapabilities, effectiveCapabilities);
+
+ // Set the Java Language thread priority to the default value for new apps.
+ Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
+
+ ZygoteHooks.postForkCommon();
+ return pid;
+ }
+
+ private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
+ int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
+
+ /**
+ * Lets children of the zygote inherit open file descriptors to this path.
+ */
+ protected static native void nativeAllowFileAcrossFork(String path);
+
+ /**
+ * Lets children of the zygote inherit open file descriptors that belong to the
+ * ApplicationInfo that is passed in.
+ *
+ * @param appInfo ApplicationInfo of the application
+ */
+ static void allowAppFilesAcrossFork(ApplicationInfo appInfo) {
+ for (String path : appInfo.getAllApkPaths()) {
+ Zygote.nativeAllowFileAcrossFork(path);
+ }
+ }
+
+ /**
+ * Scans file descriptors in /proc/self/fd/, stores their metadata from readlink(2)/stat(2) when
+ * available. Saves this information in a global on native side, to be used by subsequent call
+ * to allowFilesOpenedByPreload(). Fatally fails if the FDs are of unsupported type and are not
+ * explicitly allowed. Ignores repeated invocations.
+ *
+ * Inspecting the FDs is more permissive than in forkAndSpecialize() because preload is invoked
+ * earlier and hence needs to allow a few open sockets. The checks in forkAndSpecialize()
+ * enforce that these sockets are closed when forking.
+ */
+ static void markOpenedFilesBeforePreload() {
+ nativeMarkOpenedFilesBeforePreload();
+ }
+
+ private static native void nativeMarkOpenedFilesBeforePreload();
+
+ /**
+ * By scanning /proc/self/fd/ determines file descriptor numbers in this process opened since
+ * the first call to markOpenedFilesBeforePreload(). These FDs are treated as 'owned' by the
+ * custom preload of the App Zygote - the app is responsible for not sharing data with its other
+ * processes using these FDs, including by lseek(2). File descriptor types and file names are
+ * not checked. Changes in FDs recorded by markOpenedFilesBeforePreload() are not expected and
+ * kill the current process.
+ */
+ static void allowFilesOpenedByPreload() {
+ nativeAllowFilesOpenedByPreload();
+ }
+
+ private static native void nativeAllowFilesOpenedByPreload();
+
+ /**
+ * Installs a seccomp filter that limits setresuid()/setresgid() to the passed-in range
+ * @param uidGidMin The smallest allowed uid/gid
+ * @param uidGidMax The largest allowed uid/gid
+ */
+ native protected static void nativeInstallSeccompUidGidFilter(int uidGidMin, int uidGidMax);
+
+ /**
+ * Initialize the native state of the Zygote. This inclues
+ * - Fetching socket FDs from the environment
+ * - Initializing security properties
+ * - Unmounting storage as appropriate
+ * - Loading necessary performance profile information
+ *
+ * @param isPrimary True if this is the zygote process, false if it is zygote_secondary
+ */
+ static void initNativeState(boolean isPrimary) {
+ nativeInitNativeState(isPrimary);
+ }
+
+ protected static native void nativeInitNativeState(boolean isPrimary);
+
+ /**
+ * Returns the raw string value of a system property.
+ *
+ * Note that Device Config is not available without an application so SystemProperties is used
+ * instead.
+ *
+ * TODO (chriswailes): Cache the system property location in native code and then write a JNI
+ * function to fetch it.
+ */
+ public static String getConfigurationProperty(String propertyName, String defaultValue) {
+ return SystemProperties.get(
+ String.join(".",
+ "persist.device_config",
+ DeviceConfig.NAMESPACE_RUNTIME_NATIVE,
+ propertyName),
+ defaultValue);
+ }
+
+ static void emptyUsapPool() {
+ nativeEmptyUsapPool();
+ }
+
+ private static native void nativeEmptyUsapPool();
+
+ /**
+ * Returns the value of a system property converted to a boolean using specific logic.
+ *
+ * Note that Device Config is not available without an application so SystemProperties is used
+ * instead.
+ *
+ * @see SystemProperties#getBoolean
+ *
+ * TODO (chriswailes): Cache the system property location in native code and then write a JNI
+ * function to fetch it.
+ * TODO (chriswailes): Move into ZygoteConfig.java once the necessary CL lands (go/ag/6580627)
+ */
+ public static boolean getConfigurationPropertyBoolean(
+ String propertyName, Boolean defaultValue) {
+ return SystemProperties.getBoolean(
+ String.join(".",
+ "persist.device_config",
+ DeviceConfig.NAMESPACE_RUNTIME_NATIVE,
+ propertyName),
+ defaultValue);
+ }
+
+ /**
+ * @return Number of unspecialized app processes currently in the pool
+ */
+ static int getUsapPoolCount() {
+ return nativeGetUsapPoolCount();
+ }
+
+ private static native int nativeGetUsapPoolCount();
+
+ /**
+ * @return The event FD used for communication between the signal handler and the ZygoteServer
+ * poll loop
+ */
+ static FileDescriptor getUsapPoolEventFD() {
+ FileDescriptor fd = new FileDescriptor();
+ fd.setInt$(nativeGetUsapPoolEventFD());
+
+ return fd;
+ }
+
+ private static native int nativeGetUsapPoolEventFD();
+
+ /**
+ * Fork a new unspecialized app process from the zygote. Adds the Usap to the native
+ * Usap table.
+ *
+ * @param usapPoolSocket The server socket the USAP will call accept on
+ * @param sessionSocketRawFDs Anonymous session sockets that are currently open.
+ * These are closed in the child.
+ * @param isPriorityFork Raise the initial process priority level because this is on the
+ * critical path for application startup.
+ * @return In the child process, this returns a Runnable that waits for specialization
+ * info to start an app process. In the sygote/parent process this returns null.
+ */
+ static @Nullable Runnable forkUsap(LocalServerSocket usapPoolSocket,
+ int[] sessionSocketRawFDs,
+ boolean isPriorityFork) {
+ FileDescriptor readFD;
+ FileDescriptor writeFD;
+
+ try {
+ FileDescriptor[] pipeFDs = Os.pipe2(O_CLOEXEC);
+ readFD = pipeFDs[0];
+ writeFD = pipeFDs[1];
+ } catch (ErrnoException errnoEx) {
+ throw new IllegalStateException("Unable to create USAP pipe.", errnoEx);
+ }
+
+ int pid = nativeForkApp(readFD.getInt$(), writeFD.getInt$(),
+ sessionSocketRawFDs, /*argsKnown=*/ false, isPriorityFork);
+ if (pid == 0) {
+ IoUtils.closeQuietly(readFD);
+ return childMain(null, usapPoolSocket, writeFD);
+ } else if (pid == -1) {
+ // Fork failed.
+ return null;
+ } else {
+ // readFD will be closed by the native code. See removeUsapTableEntry();
+ IoUtils.closeQuietly(writeFD);
+ nativeAddUsapTableEntry(pid, readFD.getInt$());
+ return null;
+ }
+ }
+
+ private static native int nativeForkApp(int readPipeFD,
+ int writePipeFD,
+ int[] sessionSocketRawFDs,
+ boolean argsKnown,
+ boolean isPriorityFork);
+
+ /**
+ * Add an entry for a new Usap to the table maintained in native code.
+ */
+ @CriticalNative
+ private static native void nativeAddUsapTableEntry(int pid, int readPipeFD);
+
+ /**
+ * Fork a new app process from the zygote. argBuffer contains a fork command that
+ * request neither a child zygote, nor a wrapped process. Continue to accept connections
+ * on the specified socket, use those to refill argBuffer, and continue to process
+ * sufficiently simple fork requests. We presume that the only open file descriptors
+ * requiring special treatment are the session socket embedded in argBuffer, and
+ * zygoteSocket.
+ * @param argBuffer containing initial command and the connected socket from which to
+ * read more
+ * @param zygoteSocket socket from which to obtain new connections when current argBuffer
+ * one is disconnected
+ * @param expectedUId Uid of peer for initial requests. Subsequent requests from a different
+ * peer will cause us to return rather than perform the requested fork.
+ * @param minUid Minimum Uid enforced for all but first fork request. The caller checks
+ * the Uid policy for the initial request.
+ * @param firstNiceName name of first created process. Used for error reporting only.
+ * @return A Runnable in each child process, null in the parent.
+ * If this returns in then argBuffer still contains a command needing to be executed.
+ */
+ static @Nullable Runnable forkSimpleApps(@NonNull ZygoteCommandBuffer argBuffer,
+ @NonNull FileDescriptor zygoteSocket,
+ int expectedUid,
+ int minUid,
+ @Nullable String firstNiceName) {
+ boolean in_child =
+ argBuffer.forkRepeatedly(zygoteSocket, expectedUid, minUid, firstNiceName);
+ if (in_child) {
+ return childMain(argBuffer, /*usapPoolSocket=*/null, /*writePipe=*/null);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Specialize the current process into one described by argBuffer or the command read from
+ * usapPoolSocket. Exactly one of those must be null. If we are given an argBuffer, we close
+ * it. Used both for a specializing a USAP process, and for process creation without USAPs.
+ * In both cases, we specialize the process after first returning to Java code.
+ *
+ * @param writePipe The write end of the reporting pipe used to communicate with the poll loop
+ * of the ZygoteServer.
+ * @return A runnable oject representing the new application.
+ */
+ private static Runnable childMain(@Nullable ZygoteCommandBuffer argBuffer,
+ @Nullable LocalServerSocket usapPoolSocket,
+ FileDescriptor writePipe) {
+ final int pid = Process.myPid();
+
+ DataOutputStream usapOutputStream = null;
+ ZygoteArguments args = null;
+
+ LocalSocket sessionSocket = null;
+ if (argBuffer == null) {
+ // Read arguments from usapPoolSocket instead.
+
+ Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32");
+
+ // Change the priority to max before calling accept so we can respond to new
+ // specialization requests as quickly as possible. This will be reverted to the
+ // default priority in the native specialization code.
+ boostUsapPriority();
+
+ while (true) {
+ ZygoteCommandBuffer tmpArgBuffer = null;
+ try {
+ sessionSocket = usapPoolSocket.accept();
+ // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool.
+ // This is safe from a race condition because the pool is only flushed after
+ // the SystemServer changes its internal state to stop using the USAP pool.
+ blockSigTerm();
+
+ usapOutputStream =
+ new DataOutputStream(sessionSocket.getOutputStream());
+ Credentials peerCredentials = sessionSocket.getPeerCredentials();
+ tmpArgBuffer = new ZygoteCommandBuffer(sessionSocket);
+ args = ZygoteArguments.getInstance(tmpArgBuffer);
+ applyUidSecurityPolicy(args, peerCredentials);
+ // TODO (chriswailes): Should this only be run for debug builds?
+ validateUsapCommand(args);
+ break;
+ } catch (Exception ex) {
+ Log.e("USAP", ex.getMessage());
+ }
+ // Re-enable SIGTERM so the USAP can be flushed from the pool if necessary.
+ unblockSigTerm();
+ IoUtils.closeQuietly(sessionSocket);
+ IoUtils.closeQuietly(tmpArgBuffer);
+ }
+ } else {
+ // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool.
+ blockSigTerm();
+ try {
+ args = ZygoteArguments.getInstance(argBuffer);
+ } catch (Exception ex) {
+ Log.e("AppStartup", ex.getMessage());
+ throw new AssertionError("Failed to parse application start command", ex);
+ }
+ // peerCredentials were checked in parent.
+ }
+ if (args == null) {
+ throw new AssertionError("Empty command line");
+ }
+ try {
+ // SIGTERM is blocked here. This prevents a USAP that is specializing from being
+ // killed during a pool flush.
+
+ applyDebuggerSystemProperty(args);
+
+ int[][] rlimits = null;
+
+ if (args.mRLimits != null) {
+ rlimits = args.mRLimits.toArray(INT_ARRAY_2D);
+ }
+
+ if (argBuffer == null) {
+ // This must happen before the SELinux policy for this process is
+ // changed when specializing.
+ try {
+ // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a
+ // Process.ProcessStartResult object.
+ usapOutputStream.writeInt(pid);
+ } catch (IOException ioEx) {
+ Log.e("USAP", "Failed to write response to session socket: "
+ + ioEx.getMessage());
+ throw new RuntimeException(ioEx);
+ } finally {
+ try {
+ // Since the raw FD is created by init and then loaded from an environment
+ // variable (as opposed to being created by the LocalSocketImpl itself),
+ // the LocalSocket/LocalSocketImpl does not own the Os-level socket. See
+ // the spec for LocalSocket.createConnectedLocalSocket(FileDescriptor fd).
+ // Thus closing the LocalSocket does not suffice. See b/130309968 for more
+ // discussion.
+ FileDescriptor fd = usapPoolSocket.getFileDescriptor();
+ usapPoolSocket.close();
+ Os.close(fd);
+ } catch (ErrnoException | IOException ex) {
+ Log.e("USAP", "Failed to close USAP pool socket");
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+
+ if (writePipe != null) {
+ try {
+ ByteArrayOutputStream buffer =
+ new ByteArrayOutputStream(Zygote.USAP_MANAGEMENT_MESSAGE_BYTES);
+ DataOutputStream outputStream = new DataOutputStream(buffer);
+
+ // This is written as a long so that the USAP reporting pipe and USAP pool
+ // event FD handlers in ZygoteServer.runSelectLoop can be unified. These two
+ // cases should both send/receive 8 bytes.
+ // TODO: Needs tweaking to handle the non-Usap invoke-with case, which expects
+ // a different format.
+ outputStream.writeLong(pid);
+ outputStream.flush();
+ Os.write(writePipe, buffer.toByteArray(), 0, buffer.size());
+ } catch (Exception ex) {
+ Log.e("USAP",
+ String.format("Failed to write PID (%d) to pipe (%d): %s",
+ pid, writePipe.getInt$(), ex.getMessage()));
+ throw new RuntimeException(ex);
+ } finally {
+ IoUtils.closeQuietly(writePipe);
+ }
+ }
+
+ specializeAppProcess(args.mUid, args.mGid, args.mGids,
+ args.mRuntimeFlags, rlimits, args.mMountExternal,
+ args.mSeInfo, args.mNiceName, args.mStartChildZygote,
+ args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
+ args.mPkgDataInfoList, args.mAllowlistedDataInfoList,
+ args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs,
+ args.mBindMountSyspropOverrides);
+
+ // While `specializeAppProcess` sets the thread name on the process's main thread, this
+ // is distinct from the app process name which appears in stack traces, as the latter is
+ // sourced from the argument buffer of the Process class. Set the app process name here.
+ Zygote.setAppProcessName(args, TAG);
+
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ return ZygoteInit.zygoteInit(args.mTargetSdkVersion,
+ args.mDisabledCompatChanges,
+ args.mRemainingArgs,
+ null /* classLoader */);
+ } finally {
+ // Unblock SIGTERM to restore the process to default behavior.
+ unblockSigTerm();
+ }
+ }
+
+ private static void blockSigTerm() {
+ nativeBlockSigTerm();
+ }
+
+ private static native void nativeBlockSigTerm();
+
+ private static void unblockSigTerm() {
+ nativeUnblockSigTerm();
+ }
+
+ private static native void nativeUnblockSigTerm();
+
+ private static void boostUsapPriority() {
+ nativeBoostUsapPriority();
+ }
+
+ private static native void nativeBoostUsapPriority();
+
+ static void setAppProcessName(ZygoteArguments args, String loggingTag) {
+ if (args.mNiceName != null) {
+ Process.setArgV0(args.mNiceName);
+ } else if (args.mPackageName != null) {
+ Process.setArgV0(args.mPackageName);
+ } else {
+ Log.w(loggingTag, "Unable to set package name.");
+ }
+ }
+
+ private static final String USAP_ERROR_PREFIX = "Invalid command to USAP: ";
+
+ /**
+ * Checks a set of zygote arguments to see if they can be handled by a USAP. Throws an
+ * exception if an invalid arugment is encountered.
+ * @param args The arguments to test
+ */
+ private static void validateUsapCommand(ZygoteArguments args) {
+ if (args.mAbiListQuery) {
+ throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--query-abi-list");
+ } else if (args.mPidQuery) {
+ throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--get-pid");
+ } else if (args.mPreloadDefault) {
+ throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-default");
+ } else if (args.mPreloadPackage != null) {
+ throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-package");
+ } else if (args.mPreloadApp != null) {
+ throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--preload-app");
+ } else if (args.mStartChildZygote) {
+ throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--start-child-zygote");
+ } else if (args.mApiDenylistExemptions != null) {
+ throw new IllegalArgumentException(
+ USAP_ERROR_PREFIX + "--set-api-denylist-exemptions");
+ } else if (args.mHiddenApiAccessLogSampleRate != -1) {
+ throw new IllegalArgumentException(
+ USAP_ERROR_PREFIX + "--hidden-api-log-sampling-rate=");
+ } else if (args.mHiddenApiAccessStatslogSampleRate != -1) {
+ throw new IllegalArgumentException(
+ USAP_ERROR_PREFIX + "--hidden-api-statslog-sampling-rate=");
+ } else if (args.mInvokeWith != null) {
+ throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--invoke-with");
+ } else if (args.mPermittedCapabilities != 0 || args.mEffectiveCapabilities != 0) {
+ throw new ZygoteSecurityException("Client may not specify capabilities: "
+ + "permitted=0x" + Long.toHexString(args.mPermittedCapabilities)
+ + ", effective=0x" + Long.toHexString(args.mEffectiveCapabilities));
+ }
+ }
+
+ /**
+ * @return Raw file descriptors for the read-end of USAP reporting pipes.
+ */
+ static int[] getUsapPipeFDs() {
+ return nativeGetUsapPipeFDs();
+ }
+
+ private static native int[] nativeGetUsapPipeFDs();
+
+ /**
+ * Remove the USAP table entry for the provided process ID.
+ *
+ * @param usapPID Process ID of the entry to remove
+ * @return True if the entry was removed; false if it doesn't exist
+ */
+ static boolean removeUsapTableEntry(int usapPID) {
+ return nativeRemoveUsapTableEntry(usapPID);
+ }
+
+ @CriticalNative
+ private static native boolean nativeRemoveUsapTableEntry(int usapPID);
+
+ /**
+ * Return the minimum child uid that the given peer is allowed to create.
+ * uid 1000 (Process.SYSTEM_UID) may specify any uid ≥ 1000 in normal
+ * operation. It may also specify any gid and setgroups() list it chooses.
+ * In factory test mode, it may specify any UID.
+ */
+ static int minChildUid(Credentials peer) {
+ if (peer.getUid() == Process.SYSTEM_UID
+ && FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF) {
+ /* In normal operation, SYSTEM_UID can only specify a restricted
+ * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
+ */
+ return Process.SYSTEM_UID;
+ } else {
+ return 0;
+ }
+ }
+
+ /*
+ * Adjust uid and gid arguments, ensuring that the security policy is satisfied.
+ * @param args non-null; zygote spawner arguments
+ * @param peer non-null; peer credentials
+ * @throws ZygoteSecurityException Indicates a security issue when applying the UID based
+ * security policies
+ */
+ static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer)
+ throws ZygoteSecurityException {
+
+ if (args.mUidSpecified && (args.mUid < minChildUid(peer))) {
+ throw new ZygoteSecurityException(
+ "System UID may not launch process with UID < "
+ + Process.SYSTEM_UID);
+ }
+
+ // If not otherwise specified, uid and gid are inherited from peer
+ if (!args.mUidSpecified) {
+ args.mUid = peer.getUid();
+ args.mUidSpecified = true;
+ }
+ if (!args.mGidSpecified) {
+ args.mGid = peer.getGid();
+ args.mGidSpecified = true;
+ }
+ }
+
+ /**
+ * This will enable jdwp by default for all apps. It is OK to cache this property
+ * because we expect to reboot the system whenever this property changes
+ */
+ private static final boolean ENABLE_JDWP = SystemProperties.get(
+ "persist.debug.dalvik.vm.jdwp.enabled").equals("1");
+
+ /**
+ * This will enable ptrace by default for all apps. It is OK to cache this property
+ * because we expect to reboot the system whenever this property changes
+ */
+ private static final boolean ENABLE_PTRACE = SystemProperties.get(
+ "persist.debug.ptrace.enabled").equals("1");
+
+ /**
+ * Applies debugger system properties to the zygote arguments.
+ *
+ * For eng builds all apps are debuggable with JDWP and ptrace.
+ *
+ * On userdebug builds if persist.debug.dalvik.vm.jdwp.enabled
+ * is 1 all apps are debuggable with JDWP and ptrace. Otherwise, the
+ * debugger state is specified via the "--enable-jdwp" flag in the
+ * spawn request.
+ *
+ * On userdebug builds if persist.debug.ptrace.enabled is 1 all
+ * apps are debuggable with ptrace.
+ *
+ * @param args non-null; zygote spawner args
+ */
+ static void applyDebuggerSystemProperty(ZygoteArguments args) {
+ if (Build.IS_ENG || (Build.IS_USERDEBUG && ENABLE_JDWP)) {
+ args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
+ // Also enable ptrace when JDWP is enabled for consistency with
+ // before persist.debug.ptrace.enabled existed.
+ args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_PTRACE;
+ }
+ if (Build.IS_ENG || (Build.IS_USERDEBUG && ENABLE_PTRACE)) {
+ args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_PTRACE;
+ }
+ }
+
+ /**
+ * Applies zygote security policy.
+ * Based on the credentials of the process issuing a zygote command:
+ * <ol>
+ * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a
+ * wrapper command.
+ * <li> Any other uid may not specify any invoke-with argument.
+ * </ul>
+ *
+ * @param args non-null; zygote spawner arguments
+ * @param peer non-null; peer credentials
+ * @throws ZygoteSecurityException Thrown when `--invoke-with` is specified for a non-debuggable
+ * application.
+ */
+ static void applyInvokeWithSecurityPolicy(ZygoteArguments args, Credentials peer)
+ throws ZygoteSecurityException {
+ int peerUid = peer.getUid();
+
+ if (args.mInvokeWith != null && peerUid != 0
+ && (args.mRuntimeFlags
+ & (Zygote.DEBUG_ENABLE_JDWP | Zygote.DEBUG_ENABLE_PTRACE)) == 0) {
+ throw new ZygoteSecurityException("Peer is permitted to specify an "
+ + "explicit invoke-with wrapper command only for debuggable "
+ + "applications.");
+ }
+ }
+
+ /**
+ * Gets the wrap property if set.
+ *
+ * @param appName the application name to check
+ * @return value of wrap property or null if property not set or
+ * null if app_name is null or null if app_name is empty
+ */
+ public static String getWrapProperty(String appName) {
+ if (appName == null || appName.isEmpty()) {
+ return null;
+ }
+
+ String propertyValue = SystemProperties.get("wrap." + appName);
+ if (propertyValue != null && !propertyValue.isEmpty()) {
+ return propertyValue;
+ }
+ return null;
+ }
+
+ /**
+ * Applies invoke-with system properties to the zygote arguments.
+ *
+ * @param args non-null; zygote args
+ */
+ static void applyInvokeWithSystemProperty(ZygoteArguments args) {
+ if (args.mInvokeWith == null) {
+ args.mInvokeWith = getWrapProperty(args.mNiceName);
+ }
+ }
+
+ /**
+ * Creates a managed LocalServerSocket object using a file descriptor
+ * created by an init.rc script. The init scripts that specify the
+ * sockets name can be found in system/core/rootdir. The socket is bound
+ * to the file system in the /dev/sockets/ directory, and the file
+ * descriptor is shared via the ANDROID_SOCKET_<socketName> environment
+ * variable.
+ */
+ static LocalServerSocket createManagedSocketFromInitSocket(String socketName) {
+ int fileDesc;
+ final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
+
+ try {
+ String env = System.getenv(fullSocketName);
+ fileDesc = Integer.parseInt(env);
+ } catch (RuntimeException ex) {
+ throw new RuntimeException("Socket unset or invalid: " + fullSocketName, ex);
+ }
+
+ try {
+ FileDescriptor fd = new FileDescriptor();
+ fd.setInt$(fileDesc);
+ return new LocalServerSocket(fd);
+ } catch (IOException ex) {
+ throw new RuntimeException(
+ "Error building socket from file descriptor: " + fileDesc, ex);
+ }
+ }
+
+ // This function is called from native code in com_android_internal_os_Zygote.cpp
+ @SuppressWarnings("unused")
+ private static void callPostForkSystemServerHooks(int runtimeFlags) {
+ // SystemServer specific post fork hooks run before child post fork hooks.
+ ZygoteHooks.postForkSystemServer(runtimeFlags);
+ }
+
+ // This function is called from native code in com_android_internal_os_Zygote.cpp
+ @SuppressWarnings("unused")
+ private static void callPostForkChildHooks(int runtimeFlags, boolean isSystemServer,
+ boolean isZygote, String instructionSet) {
+ ZygoteHooks.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet);
+ }
+
+ /**
+ * Executes "/system/bin/sh -c <command>" using the exec() system call.
+ * This method throws a runtime exception if exec() failed, otherwise, this
+ * method never returns.
+ *
+ * @param command The shell command to execute.
+ */
+ static void execShell(String command) {
+ String[] args = { "/system/bin/sh", "-c", command };
+ try {
+ Os.execv(args[0], args);
+ } catch (ErrnoException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Appends quotes shell arguments to the specified string builder.
+ * The arguments are quoted using single-quotes, escaped if necessary,
+ * prefixed with a space, and appended to the command.
+ *
+ * @param command A string builder for the shell command being constructed.
+ * @param args An array of argument strings to be quoted and appended to the command.
+ * @see #execShell(String)
+ */
+ static void appendQuotedShellArgs(StringBuilder command, String[] args) {
+ for (String arg : args) {
+ command.append(" '").append(arg.replace("'", "'\\''")).append("'");
+ }
+ }
+
+ /**
+ * Parse the given unsolicited zygote message as type SIGCHLD,
+ * extract the payload information into the given output buffer.
+ *
+ * @param in The unsolicited zygote message to be parsed
+ * @param length The number of bytes in the message
+ * @param out The output buffer where the payload information will be placed
+ * @return Number of elements being place into output buffer, or -1 if
+ * either the message is malformed or not the type as expected here.
+ *
+ * @hide
+ */
+ @FastNative
+ public static native int nativeParseSigChld(byte[] in, int length, int[] out);
+
+ /**
+ * Returns whether the hardware supports memory tagging (ARM MTE).
+ */
+ public static native boolean nativeSupportsMemoryTagging();
+
+ /**
+ * Returns whether the kernel supports tagged pointers. Present in the
+ * Android Common Kernel from 4.14 and up. By default, you should prefer
+ * fully-feature Memory Tagging, rather than the static Tagged Pointers.
+ */
+ public static native boolean nativeSupportsTaggedPointers();
+
+ /**
+ * Returns the current native tagging level, as one of the
+ * MEMORY_TAG_LEVEL_* constants. Returns zero if no tagging is present, or
+ * we failed to determine the level.
+ */
+ public static native int nativeCurrentTaggingLevel();
+
+ /**
+ * Native heap allocations will now have a non-zero tag in the most significant byte.
+ *
+ * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
+ * Pointers</a>
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
+
+ /**
+ * Native heap allocations in AppZygote process and its descendants will now have a non-zero tag
+ * in the most significant byte.
+ *
+ * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
+ * Pointers</a>
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
+ private static final long NATIVE_HEAP_POINTER_TAGGING_SECONDARY_ZYGOTE = 207557677;
+
+ /**
+ * Enable asynchronous (ASYNC) memory tag checking in this process. This flag will only have an
+ * effect on hardware supporting the ARM Memory Tagging Extension (MTE).
+ */
+ @ChangeId @Disabled
+ private static final long NATIVE_MEMTAG_ASYNC = 135772972; // This is a bug id.
+
+ /**
+ * Enable synchronous (SYNC) memory tag checking in this process. This flag will only have an
+ * effect on hardware supporting the ARM Memory Tagging Extension (MTE). If both
+ * NATIVE_MEMTAG_ASYNC and this option is selected, this option takes preference and MTE is
+ * enabled in SYNC mode.
+ */
+ @ChangeId @Disabled
+ private static final long NATIVE_MEMTAG_SYNC = 177438394; // This is a bug id.
+
+ /** Enable automatic zero-initialization of native heap memory allocations. */
+ @ChangeId @Disabled
+ private static final long NATIVE_HEAP_ZERO_INIT = 178038272; // This is a bug id.
+
+ /**
+ * Enable sampled memory bug detection in the app.
+ *
+ * @see <a href="https://source.android.com/devices/tech/debug/gwp-asan">GWP-ASan</a>.
+ */
+ @ChangeId @Disabled private static final long GWP_ASAN = 135634846; // This is a bug id.
+
+ private static int memtagModeToZygoteMemtagLevel(int memtagMode) {
+ switch (memtagMode) {
+ case ApplicationInfo.MEMTAG_ASYNC:
+ return MEMORY_TAG_LEVEL_ASYNC;
+ case ApplicationInfo.MEMTAG_SYNC:
+ return MEMORY_TAG_LEVEL_SYNC;
+ default:
+ return MEMORY_TAG_LEVEL_NONE;
+ }
+ }
+
+ private static boolean isCompatChangeEnabled(
+ long change,
+ @NonNull ApplicationInfo info,
+ @Nullable IPlatformCompat platformCompat,
+ int enabledAfter) {
+ try {
+ if (platformCompat != null) return platformCompat.isChangeEnabled(change, info);
+ } catch (RemoteException ignore) {
+ }
+ return enabledAfter > 0 && info.targetSdkVersion > enabledAfter;
+ }
+
+ // Returns the requested memory tagging level.
+ private static int getRequestedMemtagLevel(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable IPlatformCompat platformCompat) {
+ String appOverride = SystemProperties.get("persist.arm64.memtag.app." + info.packageName);
+ if ("sync".equals(appOverride)) {
+ return MEMORY_TAG_LEVEL_SYNC;
+ } else if ("async".equals(appOverride)) {
+ return MEMORY_TAG_LEVEL_ASYNC;
+ } else if ("off".equals(appOverride)) {
+ return MEMORY_TAG_LEVEL_NONE;
+ }
+
+ // Look at the process attribute first.
+ if (processInfo != null && processInfo.memtagMode != ApplicationInfo.MEMTAG_DEFAULT) {
+ return memtagModeToZygoteMemtagLevel(processInfo.memtagMode);
+ }
+
+ // Then at the application attribute.
+ if (info.getMemtagMode() != ApplicationInfo.MEMTAG_DEFAULT) {
+ return memtagModeToZygoteMemtagLevel(info.getMemtagMode());
+ }
+
+ if (isCompatChangeEnabled(NATIVE_MEMTAG_SYNC, info, platformCompat, 0)) {
+ return MEMORY_TAG_LEVEL_SYNC;
+ }
+
+ if (isCompatChangeEnabled(NATIVE_MEMTAG_ASYNC, info, platformCompat, 0)) {
+ return MEMORY_TAG_LEVEL_ASYNC;
+ }
+
+ // Check to ensure the app hasn't explicitly opted-out of TBI via. the manifest attribute.
+ if (!info.allowsNativeHeapPointerTagging()) {
+ return MEMORY_TAG_LEVEL_NONE;
+ }
+
+ String defaultLevel = SystemProperties.get("persist.arm64.memtag.app_default");
+ if ("sync".equals(defaultLevel)) {
+ return MEMORY_TAG_LEVEL_SYNC;
+ } else if ("async".equals(defaultLevel)) {
+ return MEMORY_TAG_LEVEL_ASYNC;
+ }
+
+ // Check to see that the compat feature for TBI is enabled.
+ if (isCompatChangeEnabled(
+ NATIVE_HEAP_POINTER_TAGGING, info, platformCompat, Build.VERSION_CODES.Q)) {
+ return MEMORY_TAG_LEVEL_TBI;
+ }
+
+ return MEMORY_TAG_LEVEL_NONE;
+ }
+
+ private static int decideTaggingLevel(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable IPlatformCompat platformCompat) {
+ // Get the desired tagging level (app manifest + compat features).
+ int level = getRequestedMemtagLevel(info, processInfo, platformCompat);
+
+ // Take into account the hardware capabilities.
+ if (nativeSupportsMemoryTagging()) {
+ // MTE devices can not do TBI, because the Zygote process already has live MTE
+ // allocations. Downgrade TBI to NONE.
+ if (level == MEMORY_TAG_LEVEL_TBI) {
+ level = MEMORY_TAG_LEVEL_NONE;
+ }
+ } else if (nativeSupportsTaggedPointers()) {
+ // TBI-but-not-MTE devices downgrade MTE modes to TBI.
+ // The idea is that if an app opts into full hardware tagging (MTE), it must be ok with
+ // the "fake" pointer tagging (TBI).
+ if (level == MEMORY_TAG_LEVEL_ASYNC || level == MEMORY_TAG_LEVEL_SYNC) {
+ level = MEMORY_TAG_LEVEL_TBI;
+ }
+ } else {
+ // Otherwise disable all tagging.
+ level = MEMORY_TAG_LEVEL_NONE;
+ }
+
+ // If we requested "sync" mode for the whole platform, upgrade mode for apps that enable
+ // MTE.
+ // This makes debugging a lot easier.
+ if (level == MEMORY_TAG_LEVEL_ASYNC
+ && (Build.IS_USERDEBUG || Build.IS_ENG)
+ && "sync".equals(SystemProperties.get("persist.arm64.memtag.default"))) {
+ level = MEMORY_TAG_LEVEL_SYNC;
+ }
+
+ return level;
+ }
+
+ private static int decideGwpAsanLevel(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable IPlatformCompat platformCompat) {
+ // Look at the process attribute first.
+ if (processInfo != null && processInfo.gwpAsanMode != ApplicationInfo.GWP_ASAN_DEFAULT) {
+ return processInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_ALWAYS
+ ? GWP_ASAN_LEVEL_ALWAYS
+ : GWP_ASAN_LEVEL_NEVER;
+ }
+ // Then at the application attribute.
+ if (info.getGwpAsanMode() != ApplicationInfo.GWP_ASAN_DEFAULT) {
+ return info.getGwpAsanMode() == ApplicationInfo.GWP_ASAN_ALWAYS
+ ? GWP_ASAN_LEVEL_ALWAYS
+ : GWP_ASAN_LEVEL_NEVER;
+ }
+ if (isCompatChangeEnabled(GWP_ASAN, info, platformCompat, 0)) {
+ return GWP_ASAN_LEVEL_ALWAYS;
+ }
+ if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return GWP_ASAN_LEVEL_LOTTERY;
+ }
+ return GWP_ASAN_LEVEL_DEFAULT;
+ }
+
+ private static boolean enableNativeHeapZeroInit(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable IPlatformCompat platformCompat) {
+ // Look at the process attribute first.
+ if (processInfo != null
+ && processInfo.nativeHeapZeroInitialized != ApplicationInfo.ZEROINIT_DEFAULT) {
+ return processInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_ENABLED;
+ }
+ // Then at the application attribute.
+ if (info.getNativeHeapZeroInitialized() != ApplicationInfo.ZEROINIT_DEFAULT) {
+ return info.getNativeHeapZeroInitialized() == ApplicationInfo.ZEROINIT_ENABLED;
+ }
+ // Compat feature last.
+ if (isCompatChangeEnabled(NATIVE_HEAP_ZERO_INIT, info, platformCompat, 0)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns Zygote runtimeFlags for memory safety features (MTE, GWP-ASan, nativeHeadZeroInit)
+ * for a given app.
+ */
+ public static int getMemorySafetyRuntimeFlags(
+ @NonNull ApplicationInfo info,
+ @Nullable ProcessInfo processInfo,
+ @Nullable String instructionSet,
+ @Nullable IPlatformCompat platformCompat) {
+ int runtimeFlags = decideGwpAsanLevel(info, processInfo, platformCompat);
+ // If instructionSet is non-null, this indicates that the system_server is spawning a
+ // process with an ISA that may be different from its own. System (kernel and hardware)
+ // compatibility for these features is checked in the decideTaggingLevel in the
+ // system_server process (not the child process). As both MTE and TBI are only supported
+ // in aarch64, we can simply ensure that the new process is also aarch64. This prevents
+ // the mismatch where a 64-bit system server spawns a 32-bit child that thinks it should
+ // enable some tagging variant. Theoretically, a 32-bit system server could exist that
+ // spawns 64-bit processes, in which case the new process won't get any tagging. This is
+ // fine as we haven't seen this configuration in practice, and we can reasonable assume
+ // that if tagging is desired, the system server will be 64-bit.
+ if (instructionSet == null || instructionSet.equals("arm64")) {
+ runtimeFlags |= decideTaggingLevel(info, processInfo, platformCompat);
+ }
+ if (enableNativeHeapZeroInit(info, processInfo, platformCompat)) {
+ runtimeFlags |= NATIVE_HEAP_ZERO_INIT_ENABLED;
+ }
+ return runtimeFlags;
+ }
+
+ /**
+ * Returns Zygote runtimeFlags for memory safety features (MTE, GWP-ASan, nativeHeadZeroInit)
+ * for a secondary zygote (AppZygote or WebViewZygote).
+ */
+ public static int getMemorySafetyRuntimeFlagsForSecondaryZygote(
+ @NonNull ApplicationInfo info, @Nullable ProcessInfo processInfo) {
+ final IPlatformCompat platformCompat =
+ IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ int runtimeFlags =
+ getMemorySafetyRuntimeFlags(
+ info, processInfo, null /*instructionSet*/, platformCompat);
+
+ // TBI ("fake" pointer tagging) in AppZygote is controlled by a separate compat feature.
+ if ((runtimeFlags & MEMORY_TAG_LEVEL_MASK) == MEMORY_TAG_LEVEL_TBI
+ && isCompatChangeEnabled(
+ NATIVE_HEAP_POINTER_TAGGING_SECONDARY_ZYGOTE,
+ info,
+ platformCompat,
+ Build.VERSION_CODES.S)) {
+ // Reset memory tag level to NONE.
+ runtimeFlags &= ~MEMORY_TAG_LEVEL_MASK;
+ runtimeFlags |= MEMORY_TAG_LEVEL_NONE;
+ }
+ return runtimeFlags;
+ }
+}
diff --git a/android-35/com/android/internal/os/ZygoteArguments.java b/android-35/com/android/internal/os/ZygoteArguments.java
new file mode 100644
index 0000000..86b9a59
--- /dev/null
+++ b/android-35/com/android/internal/os/ZygoteArguments.java
@@ -0,0 +1,556 @@
+/*
+ * Copyright (C) 2007 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.internal.os;
+
+import java.io.EOFException;
+import java.util.ArrayList;
+
+/**
+ * Handles argument parsing for args related to the zygote spawner.
+ *
+ * Current recognized args:
+ * <ul>
+ * <li> --setuid=<i>uid of child process, defaults to 0</i>
+ * <li> --setgid=<i>gid of child process, defaults to 0</i>
+ * <li> --setgroups=<i>comma-separated list of supplimentary gid's</i>
+ * <li> --capabilities=<i>a pair of comma-separated integer strings
+ * indicating Linux capabilities(2) set for child. The first string
+ * represents the <code>permitted</code> set, and the second the
+ * <code>effective</code> set. Precede each with 0 or
+ * 0x for octal or hexidecimal value. If unspecified, both default to 0.
+ * This parameter is only applied if the uid of the new process will
+ * be non-0. </i>
+ * <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call.
+ * <code>r</code> is the resource, <code>c</code> and <code>m</code>
+ * are the settings for current and max value.</i>
+ * <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate.
+ * <li> --nice-name=<i>nice name to appear in ps</i>
+ * <li> --package-name=<i>package name this process belongs to</i>
+ * <li> --runtime-args indicates that the remaining arg list should
+ * be handed off to com.android.internal.os.RuntimeInit, rather than
+ * processed directly.
+ * Android runtime startup (eg, Binder initialization) is also eschewed.
+ * <li> [--] <args for RuntimeInit >
+ * </ul>
+ */
+class ZygoteArguments {
+
+ /**
+ * from --setuid
+ */
+ int mUid = 0;
+ boolean mUidSpecified;
+
+ /**
+ * from --setgid
+ */
+ int mGid = 0;
+ boolean mGidSpecified;
+
+ /**
+ * from --setgroups
+ */
+ int[] mGids;
+
+ /**
+ * From --runtime-flags.
+ */
+ int mRuntimeFlags;
+
+ /**
+ * From --mount-external
+ */
+ int mMountExternal = Zygote.MOUNT_EXTERNAL_NONE;
+
+ /**
+ * from --target-sdk-version.
+ */
+ private boolean mTargetSdkVersionSpecified;
+ int mTargetSdkVersion;
+
+ /**
+ * from --nice-name
+ */
+ String mNiceName;
+
+ /**
+ * from --capabilities
+ */
+ private boolean mCapabilitiesSpecified;
+ long mPermittedCapabilities;
+ long mEffectiveCapabilities;
+
+ /**
+ * from --seinfo
+ */
+ private boolean mSeInfoSpecified;
+ String mSeInfo;
+
+ /**
+ *
+ */
+ boolean mUsapPoolEnabled;
+ boolean mUsapPoolStatusSpecified = false;
+
+ /**
+ * from all --rlimit=r,c,m
+ */
+ ArrayList<int[]> mRLimits;
+
+ /**
+ * from --invoke-with
+ */
+ String mInvokeWith;
+
+ /** from --package-name */
+ String mPackageName;
+
+ /**
+ * Any args after and including the first non-option arg (or after a '--')
+ */
+ String[] mRemainingArgs;
+
+ /**
+ * Whether the current arguments constitute an ABI list query.
+ */
+ boolean mAbiListQuery;
+
+ /**
+ * The instruction set to use, or null when not important.
+ */
+ String mInstructionSet;
+
+ /**
+ * The app data directory. May be null, e.g., for the system server. Note that this might not be
+ * reliable in the case of process-sharing apps.
+ */
+ String mAppDataDir;
+
+ /**
+ * The APK path of the package to preload, when using --preload-package.
+ */
+ String mPreloadPackage;
+
+ /**
+ * A Base64 string representing a serialize ApplicationInfo Parcel,
+ when using --preload-app.
+ */
+ String mPreloadApp;
+
+ /**
+ * The native library path of the package to preload, when using --preload-package.
+ */
+ String mPreloadPackageLibs;
+
+ /**
+ * The filename of the native library to preload, when using --preload-package.
+ */
+ String mPreloadPackageLibFileName;
+
+ /**
+ * The cache key under which to enter the preloaded package into the classloader cache, when
+ * using --preload-package.
+ */
+ String mPreloadPackageCacheKey;
+
+ /**
+ * Whether this is a request to start preloading the default resources and classes. This
+ * argument only makes sense when the zygote is in lazy preload mode (i.e, when it's started
+ * with --enable-lazy-preload).
+ */
+ boolean mPreloadDefault;
+
+ /**
+ * Whether this is a request to start a zygote process as a child of this zygote. Set with
+ * --start-child-zygote. The remaining arguments must include the CHILD_ZYGOTE_SOCKET_NAME_ARG
+ * flag to indicate the abstract socket name that should be used for communication.
+ */
+ boolean mStartChildZygote;
+
+ /**
+ * Whether the current arguments constitute a request for the zygote's PID.
+ */
+ boolean mPidQuery;
+
+ /**
+ * Whether the current arguments constitute a notification that boot completed.
+ */
+ boolean mBootCompleted;
+
+ /**
+ * Exemptions from API deny-listing. These are sent to the pre-forked zygote at boot time, or
+ * when they change, via --set-api-denylist-exemptions.
+ */
+ String[] mApiDenylistExemptions;
+
+ /**
+ * Sampling rate for logging hidden API accesses to the event log. This is sent to the
+ * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate.
+ */
+ int mHiddenApiAccessLogSampleRate = -1;
+
+ /**
+ * Sampling rate for logging hidden API accesses to statslog. This is sent to the
+ * pre-forked zygote at boot time, or when it changes, via --hidden-api-statslog-sampling-rate.
+ */
+ int mHiddenApiAccessStatslogSampleRate = -1;
+
+ /**
+ * @see Zygote#START_AS_TOP_APP_ARG
+ */
+ boolean mIsTopApp;
+
+ /**
+ * A set of disabled app compatibility changes for the running app. From
+ * --disabled-compat-changes.
+ */
+ long[] mDisabledCompatChanges = null;
+
+ /**
+ * A list that stores all related packages and its data info: volume uuid and inode.
+ * Null if it does need to do app data isolation.
+ */
+ String[] mPkgDataInfoList;
+
+ /**
+ * A list that stores all allowlisted app data info: volume uuid and inode.
+ * Null if it does need to do app data isolation.
+ */
+ String[] mAllowlistedDataInfoList;
+
+ /**
+ * @see Zygote#BIND_MOUNT_APP_STORAGE_DIRS
+ */
+ boolean mBindMountAppStorageDirs;
+
+ /**
+ * @see Zygote#BIND_MOUNT_APP_DATA_DIRS
+ */
+ boolean mBindMountAppDataDirs;
+
+ /**
+ * @see Zygote#BIND_MOUNT_SYSPROP_OVERRIDES
+ */
+ boolean mBindMountSyspropOverrides;
+
+ /**
+ * Constructs instance and parses args
+ *
+ * @param args zygote command-line args as ZygoteCommandBuffer, positioned after argument count.
+ */
+ private ZygoteArguments(ZygoteCommandBuffer args, int argCount)
+ throws IllegalArgumentException, EOFException {
+ parseArgs(args, argCount);
+ }
+
+ /**
+ * Return a new ZygoteArguments reflecting the contents of the given ZygoteCommandBuffer. Return
+ * null if the ZygoteCommandBuffer was positioned at EOF. Assumes the buffer is initially
+ * positioned at the beginning of the command.
+ */
+ public static ZygoteArguments getInstance(ZygoteCommandBuffer args)
+ throws IllegalArgumentException, EOFException {
+ int argCount = args.getCount();
+ return argCount == 0 ? null : new ZygoteArguments(args, argCount);
+ }
+
+ /**
+ * Parses the commandline arguments intended for the Zygote spawner (such as "--setuid=" and
+ * "--setgid=") and creates an array containing the remaining args. Return false if we were
+ * at EOF.
+ *
+ * Per security review bug #1112214, duplicate args are disallowed in critical cases to make
+ * injection harder.
+ */
+ private void parseArgs(ZygoteCommandBuffer args, int argCount)
+ throws IllegalArgumentException, EOFException {
+ /*
+ * See android.os.ZygoteProcess.zygoteSendArgsAndGetResult()
+ * Presently the wire format to the zygote process is:
+ * a) a count of arguments (argc, in essence)
+ * b) a number of newline-separated argument strings equal to count
+ *
+ * After the zygote process reads these it will write the pid of
+ * the child or -1 on failure.
+ */
+
+ String unprocessedArg = null;
+ int curArg = 0; // Index of arg
+ boolean seenRuntimeArgs = false;
+ boolean expectRuntimeArgs = true;
+
+ for ( /* curArg */ ; curArg < argCount; ++curArg) {
+ String arg = args.nextArg();
+
+ if (arg.equals("--")) {
+ curArg++;
+ break;
+ } else if (arg.startsWith("--setuid=")) {
+ if (mUidSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mUidSpecified = true;
+ mUid = Integer.parseInt(getAssignmentValue(arg));
+ } else if (arg.startsWith("--setgid=")) {
+ if (mGidSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mGidSpecified = true;
+ mGid = Integer.parseInt(getAssignmentValue(arg));
+ } else if (arg.startsWith("--target-sdk-version=")) {
+ if (mTargetSdkVersionSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate target-sdk-version specified");
+ }
+ mTargetSdkVersionSpecified = true;
+ mTargetSdkVersion = Integer.parseInt(getAssignmentValue(arg));
+ } else if (arg.equals("--runtime-args")) {
+ seenRuntimeArgs = true;
+ } else if (arg.startsWith("--runtime-flags=")) {
+ mRuntimeFlags = Integer.parseInt(getAssignmentValue(arg));
+ } else if (arg.startsWith("--seinfo=")) {
+ if (mSeInfoSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mSeInfoSpecified = true;
+ mSeInfo = getAssignmentValue(arg);
+ } else if (arg.startsWith("--capabilities=")) {
+ if (mCapabilitiesSpecified) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mCapabilitiesSpecified = true;
+ String capString = getAssignmentValue(arg);
+
+ String[] capStrings = capString.split(",", 2);
+
+ if (capStrings.length == 1) {
+ mEffectiveCapabilities = Long.decode(capStrings[0]);
+ mPermittedCapabilities = mEffectiveCapabilities;
+ } else {
+ mPermittedCapabilities = Long.decode(capStrings[0]);
+ mEffectiveCapabilities = Long.decode(capStrings[1]);
+ }
+ } else if (arg.startsWith("--rlimit=")) {
+ // Duplicate --rlimit arguments are specifically allowed.
+ String[] limitStrings = getAssignmentList(arg);
+
+ if (limitStrings.length != 3) {
+ throw new IllegalArgumentException(
+ "--rlimit= should have 3 comma-delimited ints");
+ }
+ int[] rlimitTuple = new int[limitStrings.length];
+
+ for (int i = 0; i < limitStrings.length; i++) {
+ rlimitTuple[i] = Integer.parseInt(limitStrings[i]);
+ }
+
+ if (mRLimits == null) {
+ mRLimits = new ArrayList<>();
+ }
+
+ mRLimits.add(rlimitTuple);
+ } else if (arg.startsWith("--setgroups=")) {
+ if (mGids != null) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+
+ String[] params = getAssignmentList(arg);
+
+ mGids = new int[params.length];
+
+ for (int i = params.length - 1; i >= 0; i--) {
+ mGids[i] = Integer.parseInt(params[i]);
+ }
+ } else if (arg.equals("--invoke-with")) {
+ if (mInvokeWith != null) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ try {
+ ++curArg;
+ mInvokeWith = args.nextArg();
+ } catch (IndexOutOfBoundsException ex) {
+ throw new IllegalArgumentException(
+ "--invoke-with requires argument");
+ }
+ } else if (arg.startsWith("--nice-name=")) {
+ if (mNiceName != null) {
+ throw new IllegalArgumentException(
+ "Duplicate arg specified");
+ }
+ mNiceName = getAssignmentValue(arg);
+ } else if (arg.equals("--mount-external-default")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
+ } else if (arg.equals("--mount-external-installer")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
+ } else if (arg.equals("--mount-external-pass-through")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_PASS_THROUGH;
+ } else if (arg.equals("--mount-external-android-writable")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+ } else if (arg.equals("--query-abi-list")) {
+ mAbiListQuery = true;
+ } else if (arg.equals("--get-pid")) {
+ mPidQuery = true;
+ } else if (arg.equals("--boot-completed")) {
+ mBootCompleted = true;
+ } else if (arg.startsWith("--instruction-set=")) {
+ mInstructionSet = getAssignmentValue(arg);
+ } else if (arg.startsWith("--app-data-dir=")) {
+ mAppDataDir = getAssignmentValue(arg);
+ } else if (arg.equals("--preload-app")) {
+ ++curArg;
+ mPreloadApp = args.nextArg();
+ } else if (arg.equals("--preload-package")) {
+ curArg += 4;
+ mPreloadPackage = args.nextArg();
+ mPreloadPackageLibs = args.nextArg();
+ mPreloadPackageLibFileName = args.nextArg();
+ mPreloadPackageCacheKey = args.nextArg();
+ } else if (arg.equals("--preload-default")) {
+ mPreloadDefault = true;
+ expectRuntimeArgs = false;
+ } else if (arg.equals("--start-child-zygote")) {
+ mStartChildZygote = true;
+ } else if (arg.equals("--set-api-denylist-exemptions")) {
+ // consume all remaining args; this is a stand-alone command, never included
+ // with the regular fork command.
+ mApiDenylistExemptions = new String[argCount - curArg - 1];
+ ++curArg;
+ for (int i = 0; curArg < argCount; ++curArg, ++i) {
+ mApiDenylistExemptions[i] = args.nextArg();
+ }
+ expectRuntimeArgs = false;
+ } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) {
+ String rateStr = getAssignmentValue(arg);
+ try {
+ mHiddenApiAccessLogSampleRate = Integer.parseInt(rateStr);
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException(
+ "Invalid log sampling rate: " + rateStr, nfe);
+ }
+ expectRuntimeArgs = false;
+ } else if (arg.startsWith("--hidden-api-statslog-sampling-rate=")) {
+ String rateStr = getAssignmentValue(arg);
+ try {
+ mHiddenApiAccessStatslogSampleRate = Integer.parseInt(rateStr);
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException(
+ "Invalid statslog sampling rate: " + rateStr, nfe);
+ }
+ expectRuntimeArgs = false;
+ } else if (arg.startsWith("--package-name=")) {
+ if (mPackageName != null) {
+ throw new IllegalArgumentException("Duplicate arg specified");
+ }
+ mPackageName = getAssignmentValue(arg);
+ } else if (arg.startsWith("--usap-pool-enabled=")) {
+ mUsapPoolStatusSpecified = true;
+ mUsapPoolEnabled = Boolean.parseBoolean(getAssignmentValue(arg));
+ expectRuntimeArgs = false;
+ } else if (arg.startsWith(Zygote.START_AS_TOP_APP_ARG)) {
+ mIsTopApp = true;
+ } else if (arg.startsWith("--disabled-compat-changes=")) {
+ if (mDisabledCompatChanges != null) {
+ throw new IllegalArgumentException("Duplicate arg specified");
+ }
+ final String[] params = getAssignmentList(arg);
+ final int length = params.length;
+ mDisabledCompatChanges = new long[length];
+ for (int i = 0; i < length; i++) {
+ mDisabledCompatChanges[i] = Long.parseLong(params[i]);
+ }
+ } else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) {
+ mPkgDataInfoList = getAssignmentList(arg);
+ } else if (arg.startsWith(Zygote.ALLOWLISTED_DATA_INFO_MAP)) {
+ mAllowlistedDataInfoList = getAssignmentList(arg);
+ } else if (arg.equals(Zygote.BIND_MOUNT_APP_STORAGE_DIRS)) {
+ mBindMountAppStorageDirs = true;
+ } else if (arg.equals(Zygote.BIND_MOUNT_APP_DATA_DIRS)) {
+ mBindMountAppDataDirs = true;
+ } else if (arg.equals(Zygote.BIND_MOUNT_SYSPROP_OVERRIDES)) {
+ mBindMountSyspropOverrides = true;
+ } else {
+ unprocessedArg = arg;
+ break;
+ }
+ }
+ // curArg is the index of the first unprocessed argument. That argument is either referenced
+ // by unprocessedArg or not read yet.
+
+ if (mBootCompleted) {
+ if (argCount > curArg) {
+ throw new IllegalArgumentException("Unexpected arguments after --boot-completed");
+ }
+ } else if (mAbiListQuery || mPidQuery) {
+ if (argCount > curArg) {
+ throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
+ }
+ } else if (mPreloadPackage != null) {
+ if (argCount > curArg) {
+ throw new IllegalArgumentException(
+ "Unexpected arguments after --preload-package.");
+ }
+ } else if (mPreloadApp != null) {
+ if (argCount > curArg) {
+ throw new IllegalArgumentException(
+ "Unexpected arguments after --preload-app.");
+ }
+ } else if (expectRuntimeArgs) {
+ if (!seenRuntimeArgs) {
+ throw new IllegalArgumentException("Unexpected argument : "
+ + (unprocessedArg == null ? args.nextArg() : unprocessedArg));
+ }
+
+ mRemainingArgs = new String[argCount - curArg];
+ int i = 0;
+ if (unprocessedArg != null) {
+ mRemainingArgs[0] = unprocessedArg;
+ ++i;
+ }
+ for (; i < argCount - curArg; ++i) {
+ mRemainingArgs[i] = args.nextArg();
+ }
+ }
+
+ if (mStartChildZygote) {
+ boolean seenChildSocketArg = false;
+ for (String arg : mRemainingArgs) {
+ if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
+ seenChildSocketArg = true;
+ break;
+ }
+ }
+ if (!seenChildSocketArg) {
+ throw new IllegalArgumentException("--start-child-zygote specified "
+ + "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG);
+ }
+ }
+ }
+
+ private static String getAssignmentValue(String arg) {
+ return arg.substring(arg.indexOf('=') + 1);
+ }
+
+ private static String[] getAssignmentList(String arg) {
+ return getAssignmentValue(arg).split(",");
+ }
+}
diff --git a/android-35/com/android/internal/os/ZygoteCommandBuffer.java b/android-35/com/android/internal/os/ZygoteCommandBuffer.java
new file mode 100644
index 0000000..83a68ca
--- /dev/null
+++ b/android-35/com/android/internal/os/ZygoteCommandBuffer.java
@@ -0,0 +1,187 @@
+/*
+ * 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.internal.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.LocalSocket;
+
+import java.io.FileDescriptor;
+import java.lang.ref.Reference; // For reachabilityFence.
+
+/**
+ * A native-accessible buffer for Zygote commands. Designed to support repeated forking
+ * of applications without intervening memory allocation, thus keeping zygote memory
+ * as stable as possible.
+ * A ZygoteCommandBuffer may have an associated socket from which it can be refilled.
+ * Otherwise the contents are explicitly set by getInstance().
+ *
+ * NOT THREAD-SAFE. No methods may be called concurrently from multiple threads.
+ *
+ * Only one ZygoteCommandBuffer can exist at a time.
+ * Must be explicitly closed before being dropped.
+ * @hide
+ */
+class ZygoteCommandBuffer implements AutoCloseable {
+ private long mNativeBuffer; // Not final so that we can clear it in close().
+
+ /**
+ * The command socket.
+ *
+ * mSocket is retained in the child process in "peer wait" mode, so
+ * that it closes when the child process terminates. In other cases,
+ * it is closed in the peer.
+ */
+ private final LocalSocket mSocket;
+ private final int mNativeSocket;
+
+ /**
+ * Constructs instance from file descriptor from which the command will be read.
+ * Only a single instance may be live in a given process. The native code checks.
+ *
+ * @param fd file descriptor to read from. The setCommand() method may be used if and only if
+ * fd is null.
+ */
+ ZygoteCommandBuffer(@Nullable LocalSocket socket) {
+ mSocket = socket;
+ if (socket == null) {
+ mNativeSocket = -1;
+ } else {
+ mNativeSocket = mSocket.getFileDescriptor().getInt$();
+ }
+ mNativeBuffer = getNativeBuffer(mNativeSocket);
+ }
+
+ /**
+ * Constructs an instance with explicitly supplied arguments and an invalid
+ * file descriptor. Can only be used for a single command.
+ */
+ ZygoteCommandBuffer(@NonNull String[] args) {
+ this((LocalSocket) null);
+ setCommand(args);
+ }
+
+
+ private static native long getNativeBuffer(int fd);
+
+ /**
+ * Deallocate native resources associated with the one and only command buffer, and prevent
+ * reuse. Subsequent calls to getInstance() will yield a new buffer.
+ * We do not close the associated socket, if any.
+ */
+ @Override
+ public void close() {
+ freeNativeBuffer(mNativeBuffer);
+ mNativeBuffer = 0;
+ }
+
+ private static native void freeNativeBuffer(long /* NativeCommandBuffer* */ nbuffer);
+
+ /**
+ * Read at least the first line of the next command into the buffer, return the argument count
+ * from that line. Assumes we are initially positioned at the beginning of the first line of
+ * the command. Leave the buffer positioned at the beginning of the second command line, i.e.
+ * the first argument. If the buffer has no associated file descriptor, we just reposition to
+ * the beginning of the buffer, and reread existing contents. Returns zero if we started out
+ * at EOF.
+ */
+ int getCount() {
+ try {
+ return nativeGetCount(mNativeBuffer);
+ } finally {
+ // Make sure the mNativeSocket doesn't get closed due to early finalization.
+ Reference.reachabilityFence(mSocket);
+ }
+ }
+
+ private static native int nativeGetCount(long /* NativeCommandBuffer* */ nbuffer);
+
+
+ /*
+ * Set the buffer to contain the supplied sequence of arguments.
+ */
+ private void setCommand(String[] command) {
+ int nArgs = command.length;
+ insert(mNativeBuffer, Integer.toString(nArgs));
+ for (String s: command) {
+ insert(mNativeBuffer, s);
+ }
+ // Native code checks there is no socket; hence no reachabilityFence.
+ }
+
+ private static native void insert(long /* NativeCommandBuffer* */ nbuffer, String s);
+
+ /**
+ * Retrieve the next argument/line from the buffer, filling the buffer as necessary.
+ */
+ String nextArg() {
+ try {
+ return nativeNextArg(mNativeBuffer);
+ } finally {
+ Reference.reachabilityFence(mSocket);
+ }
+ }
+
+ private static native String nativeNextArg(long /* NativeCommandBuffer* */ nbuffer);
+
+ void readFullyAndReset() {
+ try {
+ nativeReadFullyAndReset(mNativeBuffer);
+ } finally {
+ Reference.reachabilityFence(mSocket);
+ }
+ }
+
+ private static native void nativeReadFullyAndReset(long /* NativeCommandBuffer* */ nbuffer);
+
+ /**
+ * Fork a child as specified by the current command in the buffer, and repeat this process
+ * after refilling the buffer, so long as the buffer clearly contains another fork command.
+ *
+ * @param zygoteSocket socket from which to obtain new connections when current one is
+ * disconnected
+ * @param expectedUid Peer UID for current connection. We refuse to deal with requests from
+ * a different UID.
+ * @param minUid the smallest uid that may be request for the child process.
+ * @param firstNiceName The name for the initial process to be forked. Used only for error
+ * reporting.
+ *
+ * @return true in the child, false in the parent. In the parent case, the buffer is positioned
+ * at the beginning of a command that still needs to be processed.
+ */
+ boolean forkRepeatedly(FileDescriptor zygoteSocket, int expectedUid, int minUid,
+ String firstNiceName) {
+ try {
+ return nativeForkRepeatedly(mNativeBuffer, zygoteSocket.getInt$(),
+ expectedUid, minUid, firstNiceName);
+ } finally {
+ Reference.reachabilityFence(mSocket);
+ Reference.reachabilityFence(zygoteSocket);
+ }
+ }
+
+ /*
+ * Repeatedly fork children as above. It commonly does not return in the parent, but it may.
+ * @return true in the child, false in the parent if we encounter a command we couldn't handle.
+ */
+ private static native boolean nativeForkRepeatedly(long /* NativeCommandBuffer* */ nbuffer,
+ int zygoteSocketRawFd,
+ int expectedUid,
+ int minUid,
+ String firstNiceName);
+
+}
diff --git a/android-35/com/android/internal/os/ZygoteConfig.java b/android-35/com/android/internal/os/ZygoteConfig.java
new file mode 100644
index 0000000..e5dc874
--- /dev/null
+++ b/android-35/com/android/internal/os/ZygoteConfig.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 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.internal.os;
+
+import android.os.SystemProperties;
+import android.provider.DeviceConfig;
+
+/**
+ * Flag names for configuring the zygote.
+ *
+ * @hide
+ */
+public class ZygoteConfig {
+
+ /** If {@code true}, enables the unspecialized app process (USAP) pool feature */
+ public static final String USAP_POOL_ENABLED = "usap_pool_enabled";
+
+ /**
+ * The default value for enabling the unspecialized app process (USAP) pool. This value will
+ * not be used if the devices has a DeviceConfig profile pushed to it that contains a value for
+ * this key or if the System Property dalvik.vm.usap_pool_enabled is set.
+ */
+ public static final boolean USAP_POOL_ENABLED_DEFAULT = false;
+
+
+
+ /** The threshold used to determine if the pool should be refilled */
+ public static final String USAP_POOL_REFILL_THRESHOLD = "usap_refill_threshold";
+
+ public static final int USAP_POOL_REFILL_THRESHOLD_DEFAULT = 1;
+
+
+
+ /** The maximum number of processes to keep in the USAP pool */
+ public static final String USAP_POOL_SIZE_MAX = "usap_pool_size_max";
+
+ public static final int USAP_POOL_SIZE_MAX_DEFAULT = 3;
+
+ /**
+ * The maximim value that will be accepted from the USAP_POOL_SIZE_MAX device property.
+ * is a mirror of USAP_POOL_MAX_LIMIT found in com_android_internal_os_Zygote.cpp.
+ */
+ public static final int USAP_POOL_SIZE_MAX_LIMIT = 100;
+
+
+
+ /** The minimum number of processes to keep in the USAP pool */
+ public static final String USAP_POOL_SIZE_MIN = "usap_pool_size_min";
+
+ public static final int USAP_POOL_SIZE_MIN_DEFAULT = 1;
+
+ /**
+ * The minimum value that will be accepted from the USAP_POOL_SIZE_MIN device property.
+ */
+ public static final int USAP_POOL_SIZE_MIN_LIMIT = 1;
+
+
+
+ /** The number of milliseconds to delay before refilling the USAP pool */
+ public static final String USAP_POOL_REFILL_DELAY_MS = "usap_pool_refill_delay_ms";
+
+ public static final int USAP_POOL_REFILL_DELAY_MS_DEFAULT = 3000;
+
+ public static final String PROPERTY_PREFIX_DEVICE_CONFIG = "persist.device_config";
+ public static final String PROPERTY_PREFIX_SYSTEM = "dalvik.vm.";
+
+ private static String getDeviceConfig(String name) {
+ return SystemProperties.get(
+ String.join(
+ ".",
+ PROPERTY_PREFIX_DEVICE_CONFIG,
+ DeviceConfig.NAMESPACE_RUNTIME_NATIVE,
+ name));
+ }
+
+ /**
+ * Get a property value from SystemProperties and convert it to an integer value.
+ */
+ public static int getInt(String name, int defaultValue) {
+ final String propString = getDeviceConfig(name);
+
+ if (!propString.isEmpty()) {
+ return Integer.parseInt(propString);
+ } else {
+ return SystemProperties.getInt(PROPERTY_PREFIX_SYSTEM + name, defaultValue);
+ }
+ }
+
+ /**
+ * Get a property value from SystemProperties and convert it to a Boolean value.
+ */
+ public static boolean getBool(String name, boolean defaultValue) {
+ final String propString = getDeviceConfig(name);
+
+ if (!propString.isEmpty()) {
+ return Boolean.parseBoolean(propString);
+ } else {
+ return SystemProperties.getBoolean(PROPERTY_PREFIX_SYSTEM + name, defaultValue);
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/ZygoteConnection.java b/android-35/com/android/internal/os/ZygoteConnection.java
new file mode 100644
index 0000000..d4dcec9
--- /dev/null
+++ b/android-35/com/android/internal/os/ZygoteConnection.java
@@ -0,0 +1,664 @@
+/*
+ * Copyright (C) 2007 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.internal.os;
+
+import static android.system.OsConstants.F_SETFD;
+import static android.system.OsConstants.O_CLOEXEC;
+import static android.system.OsConstants.POLLIN;
+
+import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEOUT_MILLIS;
+import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ApplicationInfo;
+import android.net.Credentials;
+import android.net.LocalSocket;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.Trace;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructPollfd;
+import android.util.Log;
+
+import dalvik.system.VMRuntime;
+import dalvik.system.ZygoteHooks;
+
+import libcore.io.IoUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A connection that can make spawn requests.
+ */
+class ZygoteConnection {
+ private static final String TAG = "Zygote";
+
+ /**
+ * The command socket.
+ *
+ * mSocket is retained in the child process in "peer wait" mode, so
+ * that it closes when the child process terminates. In other cases,
+ * it is closed in the peer.
+ */
+ @UnsupportedAppUsage
+ private final LocalSocket mSocket;
+ @UnsupportedAppUsage
+ private final DataOutputStream mSocketOutStream;
+ @UnsupportedAppUsage
+ private final Credentials peer;
+ private final String abiList;
+ private boolean isEof;
+
+ /**
+ * Constructs instance from connected socket.
+ *
+ * @param socket non-null; connected socket
+ * @param abiList non-null; a list of ABIs this zygote supports.
+ * @throws IOException If obtaining the peer credentials fails
+ */
+ ZygoteConnection(LocalSocket socket, String abiList) throws IOException {
+ mSocket = socket;
+ this.abiList = abiList;
+
+ mSocketOutStream = new DataOutputStream(socket.getOutputStream());
+
+ mSocket.setSoTimeout(CONNECTION_TIMEOUT_MILLIS);
+
+ try {
+ peer = mSocket.getPeerCredentials();
+ } catch (IOException ex) {
+ Log.e(TAG, "Cannot read peer credentials", ex);
+ throw ex;
+ }
+
+ if (peer.getUid() != Process.SYSTEM_UID) {
+ throw new ZygoteSecurityException("Only system UID is allowed to connect to Zygote.");
+ }
+ isEof = false;
+ }
+
+ /**
+ * Returns the file descriptor of the associated socket.
+ *
+ * @return null-ok; file descriptor
+ */
+ FileDescriptor getFileDescriptor() {
+ return mSocket.getFileDescriptor();
+ }
+
+ /**
+ * Reads a command from the command socket. If a child is successfully forked, a
+ * {@code Runnable} that calls the childs main method (or equivalent) is returned in the child
+ * process. {@code null} is always returned in the parent process (the zygote).
+ * If multipleOK is set, we may keep processing additional fork commands before returning.
+ *
+ * If the client closes the socket, an {@code EOF} condition is set, which callers can test
+ * for by calling {@code ZygoteConnection.isClosedByPeer}.
+ */
+ Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) {
+ ZygoteArguments parsedArgs;
+
+ try (ZygoteCommandBuffer argBuffer = new ZygoteCommandBuffer(mSocket)) {
+ while (true) {
+ try {
+ parsedArgs = ZygoteArguments.getInstance(argBuffer);
+ // Keep argBuffer around, since we need it to fork.
+ } catch (IOException ex) {
+ throw new IllegalStateException("IOException on command socket", ex);
+ }
+ if (parsedArgs == null) {
+ isEof = true;
+ return null;
+ }
+
+ int pid;
+ FileDescriptor childPipeFd = null;
+ FileDescriptor serverPipeFd = null;
+
+ if (parsedArgs.mBootCompleted) {
+ handleBootCompleted();
+ return null;
+ }
+
+ if (parsedArgs.mAbiListQuery) {
+ handleAbiListQuery();
+ return null;
+ }
+
+ if (parsedArgs.mPidQuery) {
+ handlePidQuery();
+ return null;
+ }
+
+ if (parsedArgs.mUsapPoolStatusSpecified
+ || parsedArgs.mApiDenylistExemptions != null
+ || parsedArgs.mHiddenApiAccessLogSampleRate != -1
+ || parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) {
+ // Handle these once we've released argBuffer, to avoid opening a second one.
+ break;
+ }
+
+ if (parsedArgs.mPreloadDefault) {
+ handlePreload();
+ return null;
+ }
+
+ if (parsedArgs.mPreloadPackage != null) {
+ handlePreloadPackage(parsedArgs.mPreloadPackage,
+ parsedArgs.mPreloadPackageLibs,
+ parsedArgs.mPreloadPackageLibFileName,
+ parsedArgs.mPreloadPackageCacheKey);
+ return null;
+ }
+
+ if (canPreloadApp() && parsedArgs.mPreloadApp != null) {
+ byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp);
+ Parcel appInfoParcel = Parcel.obtain();
+ appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length);
+ appInfoParcel.setDataPosition(0);
+ ApplicationInfo appInfo =
+ ApplicationInfo.CREATOR.createFromParcel(appInfoParcel);
+ appInfoParcel.recycle();
+ if (appInfo != null) {
+ handlePreloadApp(appInfo);
+ } else {
+ throw new IllegalArgumentException("Failed to deserialize --preload-app");
+ }
+ return null;
+ }
+
+ if (parsedArgs.mPermittedCapabilities != 0
+ || parsedArgs.mEffectiveCapabilities != 0) {
+ throw new ZygoteSecurityException("Client may not specify capabilities: "
+ + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities)
+ + ", effective=0x"
+ + Long.toHexString(parsedArgs.mEffectiveCapabilities));
+ }
+
+ Zygote.applyUidSecurityPolicy(parsedArgs, peer);
+ Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer);
+
+ Zygote.applyDebuggerSystemProperty(parsedArgs);
+ Zygote.applyInvokeWithSystemProperty(parsedArgs);
+
+ int[][] rlimits = null;
+
+ if (parsedArgs.mRLimits != null) {
+ rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D);
+ }
+
+ int[] fdsToIgnore = null;
+
+ if (parsedArgs.mInvokeWith != null) {
+ try {
+ FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
+ childPipeFd = pipeFds[1];
+ serverPipeFd = pipeFds[0];
+ Os.fcntlInt(childPipeFd, F_SETFD, 0);
+ fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()};
+ } catch (ErrnoException errnoEx) {
+ throw new IllegalStateException("Unable to set up pipe for invoke-with",
+ errnoEx);
+ }
+ }
+
+ /*
+ * In order to avoid leaking descriptors to the Zygote child,
+ * the native code must close the two Zygote socket descriptors
+ * in the child process before it switches from Zygote-root to
+ * the UID and privileges of the application being launched.
+ *
+ * In order to avoid "bad file descriptor" errors when the
+ * two LocalSocket objects are closed, the Posix file
+ * descriptors are released via a dup2() call which closes
+ * the socket and substitutes an open descriptor to /dev/null.
+ */
+
+ int [] fdsToClose = { -1, -1 };
+
+ FileDescriptor fd = mSocket.getFileDescriptor();
+
+ if (fd != null) {
+ fdsToClose[0] = fd.getInt$();
+ }
+
+ FileDescriptor zygoteFd = zygoteServer.getZygoteSocketFileDescriptor();
+
+ if (zygoteFd != null) {
+ fdsToClose[1] = zygoteFd.getInt$();
+ }
+
+ if (parsedArgs.mInvokeWith != null || parsedArgs.mStartChildZygote
+ || !multipleOK || peer.getUid() != Process.SYSTEM_UID) {
+ // Continue using old code for now. TODO: Handle these cases in the other path.
+ pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid,
+ parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits,
+ parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName,
+ fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
+ parsedArgs.mInstructionSet, parsedArgs.mAppDataDir,
+ parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList,
+ parsedArgs.mAllowlistedDataInfoList, parsedArgs.mBindMountAppDataDirs,
+ parsedArgs.mBindMountAppStorageDirs,
+ parsedArgs.mBindMountSyspropOverrides);
+
+ try {
+ if (pid == 0) {
+ // in child
+ zygoteServer.setForkChild();
+
+ zygoteServer.closeServerSocket();
+ IoUtils.closeQuietly(serverPipeFd);
+ serverPipeFd = null;
+
+ return handleChildProc(parsedArgs, childPipeFd,
+ parsedArgs.mStartChildZygote);
+ } else {
+ // In the parent. A pid < 0 indicates a failure and will be handled in
+ // handleParentProc.
+ IoUtils.closeQuietly(childPipeFd);
+ childPipeFd = null;
+ handleParentProc(pid, serverPipeFd);
+ return null;
+ }
+ } finally {
+ IoUtils.closeQuietly(childPipeFd);
+ IoUtils.closeQuietly(serverPipeFd);
+ }
+ } else {
+ ZygoteHooks.preFork();
+ Runnable result = Zygote.forkSimpleApps(argBuffer,
+ zygoteServer.getZygoteSocketFileDescriptor(),
+ peer.getUid(), Zygote.minChildUid(peer), parsedArgs.mNiceName);
+ if (result == null) {
+ // parent; we finished some number of forks. Result is Boolean.
+ // We already did the equivalent of handleParentProc().
+ ZygoteHooks.postForkCommon();
+ // argBuffer contains a command not understood by forksimpleApps.
+ continue;
+ } else {
+ // child; result is a Runnable.
+ zygoteServer.setForkChild();
+ return result;
+ }
+ }
+ }
+ }
+ // Handle anything that may need a ZygoteCommandBuffer after we've released ours.
+ if (parsedArgs.mUsapPoolStatusSpecified) {
+ return handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled);
+ }
+ if (parsedArgs.mApiDenylistExemptions != null) {
+ return handleApiDenylistExemptions(zygoteServer,
+ parsedArgs.mApiDenylistExemptions);
+ }
+ if (parsedArgs.mHiddenApiAccessLogSampleRate != -1
+ || parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) {
+ return handleHiddenApiAccessLogSampleRate(zygoteServer,
+ parsedArgs.mHiddenApiAccessLogSampleRate,
+ parsedArgs.mHiddenApiAccessStatslogSampleRate);
+ }
+ throw new AssertionError("Shouldn't get here");
+ }
+
+ private void handleAbiListQuery() {
+ try {
+ final byte[] abiListBytes = abiList.getBytes(StandardCharsets.US_ASCII);
+ mSocketOutStream.writeInt(abiListBytes.length);
+ mSocketOutStream.write(abiListBytes);
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Error writing to command socket", ioe);
+ }
+ }
+
+ private void handlePidQuery() {
+ try {
+ String pidString = String.valueOf(Process.myPid());
+ final byte[] pidStringBytes = pidString.getBytes(StandardCharsets.US_ASCII);
+ mSocketOutStream.writeInt(pidStringBytes.length);
+ mSocketOutStream.write(pidStringBytes);
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Error writing to command socket", ioe);
+ }
+ }
+
+ private void handleBootCompleted() {
+ try {
+ mSocketOutStream.writeInt(0);
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Error writing to command socket", ioe);
+ }
+
+ VMRuntime.bootCompleted();
+ }
+
+ /**
+ * Preloads resources if the zygote is in lazily preload mode. Writes the result of the
+ * preload operation; {@code 0} when a preload was initiated due to this request and {@code 1}
+ * if no preload was initiated. The latter implies that the zygote is not configured to load
+ * resources lazy or that the zygote has already handled a previous request to handlePreload.
+ */
+ private void handlePreload() {
+ try {
+ if (isPreloadComplete()) {
+ mSocketOutStream.writeInt(1);
+ } else {
+ preload();
+ mSocketOutStream.writeInt(0);
+ }
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Error writing to command socket", ioe);
+ }
+ }
+
+ private Runnable stateChangeWithUsapPoolReset(ZygoteServer zygoteServer,
+ Runnable stateChangeCode) {
+ try {
+ if (zygoteServer.isUsapPoolEnabled()) {
+ Log.i(TAG, "Emptying USAP Pool due to state change.");
+ Zygote.emptyUsapPool();
+ }
+
+ stateChangeCode.run();
+
+ if (zygoteServer.isUsapPoolEnabled()) {
+ Runnable fpResult =
+ zygoteServer.fillUsapPool(
+ new int[]{mSocket.getFileDescriptor().getInt$()}, false);
+
+ if (fpResult != null) {
+ zygoteServer.setForkChild();
+ return fpResult;
+ } else {
+ Log.i(TAG, "Finished refilling USAP Pool after state change.");
+ }
+ }
+
+ mSocketOutStream.writeInt(0);
+
+ return null;
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Error writing to command socket", ioe);
+ }
+ }
+
+ /**
+ * Makes the necessary changes to implement a new API deny list exemption policy, and then
+ * responds to the system server, letting it know that the task has been completed.
+ *
+ * This necessitates a change to the internal state of the Zygote. As such, if the USAP
+ * pool is enabled all existing USAPs have an incorrect API deny list exemption list. To
+ * properly handle this request the pool must be emptied and refilled. This process can return
+ * a Runnable object that must be returned to ZygoteServer.runSelectLoop to be invoked.
+ *
+ * @param zygoteServer The server object that received the request
+ * @param exemptions The new exemption list.
+ * @return A Runnable object representing a new app in any USAPs spawned from here; the
+ * zygote process will always receive a null value from this function.
+ */
+ private Runnable handleApiDenylistExemptions(ZygoteServer zygoteServer, String[] exemptions) {
+ return stateChangeWithUsapPoolReset(zygoteServer,
+ () -> ZygoteInit.setApiDenylistExemptions(exemptions));
+ }
+
+ private Runnable handleUsapPoolStatusChange(ZygoteServer zygoteServer, boolean newStatus) {
+ try {
+ Runnable fpResult = zygoteServer.setUsapPoolStatus(newStatus, mSocket);
+
+ if (fpResult == null) {
+ mSocketOutStream.writeInt(0);
+ } else {
+ zygoteServer.setForkChild();
+ }
+
+ return fpResult;
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Error writing to command socket", ioe);
+ }
+ }
+
+ /**
+ * Changes the API access log sample rate for the Zygote and processes spawned from it.
+ *
+ * This necessitates a change to the internal state of the Zygote. As such, if the USAP
+ * pool is enabled all existing USAPs have an incorrect API access log sample rate. To
+ * properly handle this request the pool must be emptied and refilled. This process can return
+ * a Runnable object that must be returned to ZygoteServer.runSelectLoop to be invoked.
+ *
+ * @param zygoteServer The server object that received the request
+ * @param samplingRate The new sample rate for regular logging
+ * @param statsdSamplingRate The new sample rate for statslog logging
+ * @return A Runnable object representing a new app in any blastulas spawned from here; the
+ * zygote process will always receive a null value from this function.
+ */
+ private Runnable handleHiddenApiAccessLogSampleRate(ZygoteServer zygoteServer,
+ int samplingRate, int statsdSamplingRate) {
+ return stateChangeWithUsapPoolReset(zygoteServer, () -> {
+ int maxSamplingRate = Math.max(samplingRate, statsdSamplingRate);
+ ZygoteInit.setHiddenApiAccessLogSampleRate(maxSamplingRate);
+ StatsdHiddenApiUsageLogger.setHiddenApiAccessLogSampleRates(
+ samplingRate, statsdSamplingRate);
+ ZygoteInit.setHiddenApiUsageLogger(StatsdHiddenApiUsageLogger.getInstance());
+ });
+ }
+
+ protected void preload() {
+ ZygoteInit.lazyPreload();
+ }
+
+ protected boolean isPreloadComplete() {
+ return ZygoteInit.isPreloadComplete();
+ }
+
+ protected DataOutputStream getSocketOutputStream() {
+ return mSocketOutStream;
+ }
+
+ protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName,
+ String cacheKey) {
+ throw new RuntimeException("Zygote does not support package preloading");
+ }
+
+ protected boolean canPreloadApp() {
+ return false;
+ }
+
+ protected void handlePreloadApp(ApplicationInfo aInfo) {
+ throw new RuntimeException("Zygote does not support app preloading");
+ }
+
+ /**
+ * Closes socket associated with this connection.
+ */
+ @UnsupportedAppUsage
+ void closeSocket() {
+ try {
+ mSocket.close();
+ } catch (IOException ex) {
+ Log.e(TAG, "Exception while closing command "
+ + "socket in parent", ex);
+ }
+ }
+
+ boolean isClosedByPeer() {
+ return isEof;
+ }
+
+ /**
+ * Handles post-fork setup of child proc, closing sockets as appropriate,
+ * reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller
+ * if successful or returning if failed.
+ *
+ * @param parsedArgs non-null; zygote args
+ * @param pipeFd null-ok; pipe for communication back to Zygote.
+ * @param isZygote whether this new child process is itself a new Zygote.
+ */
+ private Runnable handleChildProc(ZygoteArguments parsedArgs,
+ FileDescriptor pipeFd, boolean isZygote) {
+ /*
+ * By the time we get here, the native code has closed the two actual Zygote
+ * socket connections, and substituted /dev/null in their place. The LocalSocket
+ * objects still need to be closed properly.
+ */
+
+ closeSocket();
+
+ Zygote.setAppProcessName(parsedArgs, TAG);
+
+ // End of the postFork event.
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (parsedArgs.mInvokeWith != null) {
+ WrapperInit.execApplication(parsedArgs.mInvokeWith,
+ parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion,
+ VMRuntime.getCurrentInstructionSet(),
+ pipeFd, parsedArgs.mRemainingArgs);
+
+ // Should not get here.
+ throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
+ } else {
+ if (!isZygote) {
+ return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
+ parsedArgs.mDisabledCompatChanges,
+ parsedArgs.mRemainingArgs, null /* classLoader */);
+ } else {
+ return ZygoteInit.childZygoteInit(
+ parsedArgs.mRemainingArgs /* classLoader */);
+ }
+ }
+ }
+
+ /**
+ * Handles post-fork cleanup of parent proc
+ *
+ * @param pid != 0; pid of child if > 0 or indication of failed fork
+ * if < 0;
+ * @param pipeFd null-ok; pipe for communication with child.
+ */
+ private void handleParentProc(int pid, FileDescriptor pipeFd) {
+ if (pid > 0) {
+ setChildPgid(pid);
+ }
+
+ boolean usingWrapper = false;
+ if (pipeFd != null && pid > 0) {
+ int innerPid = -1;
+ try {
+ // Do a busy loop here. We can't guarantee that a failure (and thus an exception
+ // bail) happens in a timely manner.
+ final int BYTES_REQUIRED = 4; // Bytes in an int.
+
+ StructPollfd[] fds = new StructPollfd[] {
+ new StructPollfd()
+ };
+
+ byte[] data = new byte[BYTES_REQUIRED];
+
+ int remainingSleepTime = WRAPPED_PID_TIMEOUT_MILLIS;
+ int dataIndex = 0;
+ long startTime = System.nanoTime();
+
+ while (dataIndex < data.length && remainingSleepTime > 0) {
+ fds[0].fd = pipeFd;
+ fds[0].events = (short) POLLIN;
+ fds[0].revents = 0;
+ fds[0].userData = null;
+
+ int res = android.system.Os.poll(fds, remainingSleepTime);
+ long endTime = System.nanoTime();
+ int elapsedTimeMs =
+ (int) TimeUnit.MILLISECONDS.convert(
+ endTime - startTime,
+ TimeUnit.NANOSECONDS);
+ remainingSleepTime = WRAPPED_PID_TIMEOUT_MILLIS - elapsedTimeMs;
+
+ if (res > 0) {
+ if ((fds[0].revents & POLLIN) != 0) {
+ // Only read one byte, so as not to block. Really needed?
+ int readBytes = android.system.Os.read(pipeFd, data, dataIndex, 1);
+ if (readBytes < 0) {
+ throw new RuntimeException("Some error");
+ }
+ dataIndex += readBytes;
+ } else {
+ // Error case. revents should contain one of the error bits.
+ break;
+ }
+ } else if (res == 0) {
+ Log.w(TAG, "Timed out waiting for child.");
+ }
+ }
+
+ if (dataIndex == data.length) {
+ DataInputStream is = new DataInputStream(new ByteArrayInputStream(data));
+ innerPid = is.readInt();
+ }
+
+ if (innerPid == -1) {
+ Log.w(TAG, "Error reading pid from wrapped process, child may have died");
+ }
+ } catch (Exception ex) {
+ Log.w(TAG, "Error reading pid from wrapped process, child may have died", ex);
+ }
+
+ // Ensure that the pid reported by the wrapped process is either the
+ // child process that we forked, or a descendant of it.
+ if (innerPid > 0) {
+ int parentPid = innerPid;
+ while (parentPid > 0 && parentPid != pid) {
+ parentPid = Process.getParentPid(parentPid);
+ }
+ if (parentPid > 0) {
+ Log.i(TAG, "Wrapped process has pid " + innerPid);
+ pid = innerPid;
+ usingWrapper = true;
+ } else {
+ Log.w(TAG, "Wrapped process reported a pid that is not a child of "
+ + "the process that we forked: childPid=" + pid
+ + " innerPid=" + innerPid);
+ }
+ }
+ }
+
+ try {
+ mSocketOutStream.writeInt(pid);
+ mSocketOutStream.writeBoolean(usingWrapper);
+ } catch (IOException ex) {
+ throw new IllegalStateException("Error writing to command socket", ex);
+ }
+ }
+
+ private void setChildPgid(int pid) {
+ // Try to move the new child into the peer's process group.
+ try {
+ Os.setpgid(pid, Os.getpgid(peer.getPid()));
+ } catch (ErrnoException ex) {
+ // This exception is expected in the case where
+ // the peer is not in our session
+ // TODO get rid of this log message in the case where
+ // getsid(0) != getsid(peer.getPid())
+ Log.i(TAG, "Zygote: setpgid failed. This is "
+ + "normal if peer is not in our session");
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/ZygoteConnectionConstants.java b/android-35/com/android/internal/os/ZygoteConnectionConstants.java
new file mode 100644
index 0000000..fe9b992
--- /dev/null
+++ b/android-35/com/android/internal/os/ZygoteConnectionConstants.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2007 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.internal.os;
+
+/**
+ * Sharable zygote constants.
+ *
+ * @hide
+ */
+public class ZygoteConnectionConstants {
+ /**
+ * {@link android.net.LocalSocket#setSoTimeout} value for connections.
+ * Effectively, the amount of time a requestor has between the start of
+ * the request and the completed request. The select-loop mode Zygote
+ * doesn't have the logic to return to the select loop in the middle of
+ * a request, so we need to time out here to avoid being denial-of-serviced.
+ */
+ public static final int CONNECTION_TIMEOUT_MILLIS = 1000;
+
+ /**
+ * Wait time for a wrapped app to report back its pid.
+ *
+ * We'll wait up to thirty seconds. This should give enough time for the fork
+ * to go through, but not to trigger the watchdog in the system server (by default
+ * sixty seconds).
+ *
+ * WARNING: This may trigger the watchdog in debug mode. However, to support
+ * wrapping on lower-end devices we do not have much choice.
+ */
+ public static final int WRAPPED_PID_TIMEOUT_MILLIS = 20000;
+}
diff --git a/android-35/com/android/internal/os/ZygoteInit.java b/android-35/com/android/internal/os/ZygoteInit.java
new file mode 100644
index 0000000..b9cc457
--- /dev/null
+++ b/android-35/com/android/internal/os/ZygoteInit.java
@@ -0,0 +1,956 @@
+/*
+ * Copyright (C) 2007 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.internal.os;
+
+import static android.system.OsConstants.S_IRWXG;
+import static android.system.OsConstants.S_IRWXO;
+
+import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SECONDARY_ZYGOTE_INIT_START;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__ZYGOTE_INIT_START;
+
+import android.app.ApplicationLoaders;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.SharedLibraryInfo;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Environment;
+import android.os.IInstalld;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.ZygoteProcess;
+import android.provider.DeviceConfig;
+import android.security.keystore2.AndroidKeyStoreProvider;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructCapUserData;
+import android.system.StructCapUserHeader;
+import android.text.Hyphenator;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+import android.util.TimingsTraceLog;
+import android.view.WindowManager;
+import android.webkit.WebViewFactory;
+import android.widget.TextView;
+
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.Preconditions;
+
+import dalvik.system.VMRuntime;
+import dalvik.system.ZygoteHooks;
+
+import libcore.io.IoUtils;
+
+import java.io.BufferedReader;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.security.Provider;
+import java.security.Security;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Startup class for the zygote process.
+ *
+ * Pre-initializes some classes, and then waits for commands on a UNIX domain socket. Based on these
+ * commands, forks off child processes that inherit the initial state of the VM.
+ *
+ * Please see {@link ZygoteArguments} for documentation on the client protocol.
+ *
+ * @hide
+ */
+public class ZygoteInit {
+
+ private static final String TAG = "Zygote";
+
+ private static final boolean LOGGING_DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final String PROPERTY_DISABLE_GRAPHICS_DRIVER_PRELOADING =
+ "ro.zygote.disable_gl_preload";
+
+ private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020;
+ private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030;
+
+ private static final String ABI_LIST_ARG = "--abi-list=";
+
+ // TODO (chriswailes): Re-name this --zygote-socket-name= and then add a
+ // --usap-socket-name parameter.
+ private static final String SOCKET_NAME_ARG = "--socket-name=";
+
+ /**
+ * The path of a file that contains classes to preload.
+ */
+ private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";
+
+ private static final int UNPRIVILEGED_UID = 9999;
+ private static final int UNPRIVILEGED_GID = 9999;
+
+ private static final int ROOT_UID = 0;
+ private static final int ROOT_GID = 0;
+
+ private static boolean sPreloadComplete;
+
+ /**
+ * Cached classloader to use for the system server. Will only be populated in the system
+ * server process.
+ */
+ private static ClassLoader sCachedSystemServerClassLoader = null;
+
+ static void preload(TimingsTraceLog bootTimingsTraceLog) {
+ Log.d(TAG, "begin preload");
+ bootTimingsTraceLog.traceBegin("BeginPreload");
+ beginPreload();
+ bootTimingsTraceLog.traceEnd(); // BeginPreload
+ bootTimingsTraceLog.traceBegin("PreloadClasses");
+ preloadClasses();
+ bootTimingsTraceLog.traceEnd(); // PreloadClasses
+ bootTimingsTraceLog.traceBegin("CacheNonBootClasspathClassLoaders");
+ cacheNonBootClasspathClassLoaders();
+ bootTimingsTraceLog.traceEnd(); // CacheNonBootClasspathClassLoaders
+ bootTimingsTraceLog.traceBegin("PreloadResources");
+ Resources.preloadResources();
+ bootTimingsTraceLog.traceEnd(); // PreloadResources
+ Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadAppProcessHALs");
+ nativePreloadAppProcessHALs();
+ Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+ Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadGraphicsDriver");
+ maybePreloadGraphicsDriver();
+ Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+ preloadSharedLibraries();
+ preloadTextResources();
+ // Ask the WebViewFactory to do any initialization that must run in the zygote process,
+ // for memory sharing purposes.
+ WebViewFactory.prepareWebViewInZygote();
+ endPreload();
+ warmUpJcaProviders();
+ Log.d(TAG, "end preload");
+
+ sPreloadComplete = true;
+ }
+
+ static void lazyPreload() {
+ Preconditions.checkState(!sPreloadComplete);
+ Log.i(TAG, "Lazily preloading resources.");
+
+ preload(new TimingsTraceLog("ZygoteInitTiming_lazy", Trace.TRACE_TAG_DALVIK));
+ }
+
+ private static void beginPreload() {
+ Log.i(TAG, "Calling ZygoteHooks.beginPreload()");
+
+ ZygoteHooks.onBeginPreload();
+ }
+
+ private static void endPreload() {
+ ZygoteHooks.onEndPreload();
+
+ Log.i(TAG, "Called ZygoteHooks.endPreload()");
+ }
+
+ private static void preloadSharedLibraries() {
+ Log.i(TAG, "Preloading shared libraries...");
+ System.loadLibrary("android");
+ System.loadLibrary("jnigraphics");
+
+ // TODO(b/206676167): This library is only used for renderscript today. When renderscript is
+ // removed, this load can be removed as well.
+ if (!SystemProperties.getBoolean("config.disable_renderscript", false)) {
+ System.loadLibrary("compiler_rt");
+ }
+ }
+
+ native private static void nativePreloadAppProcessHALs();
+
+ /**
+ * This call loads the graphics driver by making an OpenGL or Vulkan call. If the driver is
+ * not currently in memory it will load and initialize it. The OpenGL call itself is relatively
+ * cheap and pure. This means that it is a low overhead on the initial call, and is safe and
+ * cheap to call later. Calls after the initial invocation will effectively be no-ops for the
+ * system.
+ */
+ static native void nativePreloadGraphicsDriver();
+
+ private static void maybePreloadGraphicsDriver() {
+ if (!SystemProperties.getBoolean(PROPERTY_DISABLE_GRAPHICS_DRIVER_PRELOADING, false)) {
+ nativePreloadGraphicsDriver();
+ }
+ }
+
+ private static void preloadTextResources() {
+ Hyphenator.init();
+ TextView.preloadFontCache();
+ }
+
+ /**
+ * Register AndroidKeyStoreProvider and warm up the providers that are already registered.
+ *
+ * By doing it here we avoid that each app does it when requesting a service from the provider
+ * for the first time.
+ */
+ private static void warmUpJcaProviders() {
+ long startTime = SystemClock.uptimeMillis();
+ Trace.traceBegin(
+ Trace.TRACE_TAG_DALVIK, "Starting installation of AndroidKeyStoreProvider");
+
+ AndroidKeyStoreProvider.install();
+ Log.i(TAG, "Installed AndroidKeyStoreProvider in "
+ + (SystemClock.uptimeMillis() - startTime) + "ms.");
+ Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+
+ startTime = SystemClock.uptimeMillis();
+ Trace.traceBegin(
+ Trace.TRACE_TAG_DALVIK, "Starting warm up of JCA providers");
+ for (Provider p : Security.getProviders()) {
+ p.warmUpServiceProvision();
+ }
+ Log.i(TAG, "Warmed up JCA providers in "
+ + (SystemClock.uptimeMillis() - startTime) + "ms.");
+ Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+ }
+
+ private static boolean isExperimentEnabled(String experiment) {
+ boolean defaultValue = SystemProperties.getBoolean(
+ "dalvik.vm." + experiment,
+ /*def=*/false);
+ // Can't use device_config since we are the zygote, and it's not initialized at this point.
+ return SystemProperties.getBoolean(
+ "persist.device_config." + DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT
+ + "." + experiment,
+ defaultValue);
+ }
+
+ /* package-private */ static boolean shouldProfileSystemServer() {
+ return isExperimentEnabled("profilesystemserver");
+ }
+
+ /**
+ * Performs Zygote process initialization. Loads and initializes commonly used classes.
+ *
+ * Most classes only cause a few hundred bytes to be allocated, but a few will allocate a dozen
+ * Kbytes (in one case, 500+K).
+ */
+ private static void preloadClasses() {
+ final VMRuntime runtime = VMRuntime.getRuntime();
+
+ InputStream is;
+ try {
+ is = new FileInputStream(PRELOADED_CLASSES);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
+ return;
+ }
+
+ Log.i(TAG, "Preloading classes...");
+ long startTime = SystemClock.uptimeMillis();
+
+ // Drop root perms while running static initializers.
+ final int reuid = Os.getuid();
+ final int regid = Os.getgid();
+
+ // We need to drop root perms only if we're already root. In the case of "wrapped"
+ // processes (see WrapperInit), this function is called from an unprivileged uid
+ // and gid.
+ boolean droppedPriviliges = false;
+ if (reuid == ROOT_UID && regid == ROOT_GID) {
+ try {
+ Os.setregid(ROOT_GID, UNPRIVILEGED_GID);
+ Os.setreuid(ROOT_UID, UNPRIVILEGED_UID);
+ } catch (ErrnoException ex) {
+ throw new RuntimeException("Failed to drop root", ex);
+ }
+
+ droppedPriviliges = true;
+ }
+
+ try {
+ BufferedReader br =
+ new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
+
+ int count = 0;
+ int missingLambdaCount = 0;
+ String line;
+ while ((line = br.readLine()) != null) {
+ // Skip comments and blank lines.
+ line = line.trim();
+ if (line.startsWith("#") || line.equals("")) {
+ continue;
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
+ try {
+ // Load and explicitly initialize the given class. Use
+ // Class.forName(String, boolean, ClassLoader) to avoid repeated stack lookups
+ // (to derive the caller's class-loader). Use true to force initialization, and
+ // null for the boot classpath class-loader (could as well cache the
+ // class-loader of this class in a variable).
+ Class.forName(line, true, null);
+ count++;
+ } catch (ClassNotFoundException e) {
+ if (line.contains("$$Lambda$")) {
+ if (LOGGING_DEBUG) {
+ missingLambdaCount++;
+ }
+ } else {
+ Log.w(TAG, "Class not found for preloading: " + line);
+ }
+ } catch (UnsatisfiedLinkError e) {
+ Log.w(TAG, "Problem preloading " + line + ": " + e);
+ } catch (Throwable t) {
+ Log.e(TAG, "Error preloading " + line + ".", t);
+ if (t instanceof Error) {
+ throw (Error) t;
+ } else if (t instanceof RuntimeException) {
+ throw (RuntimeException) t;
+ } else {
+ throw new RuntimeException(t);
+ }
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+ }
+
+ Log.i(TAG, "...preloaded " + count + " classes in "
+ + (SystemClock.uptimeMillis() - startTime) + "ms.");
+ if (LOGGING_DEBUG && missingLambdaCount != 0) {
+ Log.i(TAG, "Unresolved lambda preloads: " + missingLambdaCount);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
+ } finally {
+ IoUtils.closeQuietly(is);
+
+ // Fill in dex caches with classes, fields, and methods brought in by preloading.
+ Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadDexCaches");
+ runtime.preloadDexCaches();
+ Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+
+ // If we are profiling the boot image, reset the Jit counters after preloading the
+ // classes. We want to preload for performance, and we can use method counters to
+ // infer what clases are used after calling resetJitCounters, for profile purposes.
+ if (isExperimentEnabled("profilebootclasspath")) {
+ Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ResetJitCounters");
+ VMRuntime.resetJitCounters();
+ Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+ }
+
+ // Bring back root. We'll need it later if we're in the zygote.
+ if (droppedPriviliges) {
+ try {
+ Os.setreuid(ROOT_UID, ROOT_UID);
+ Os.setregid(ROOT_GID, ROOT_GID);
+ } catch (ErrnoException ex) {
+ throw new RuntimeException("Failed to restore root", ex);
+ }
+ }
+ }
+ }
+
+ /**
+ * Load in things which are used by many apps but which cannot be put in the boot
+ * classpath.
+ */
+ private static void cacheNonBootClasspathClassLoaders() {
+ // Ordered dependencies first
+ final List<SharedLibraryInfo> libs = new ArrayList<>();
+ // These libraries used to be part of the bootclasspath, but had to be removed.
+ // Old system applications still get them for backwards compatibility reasons,
+ // so they are cached here in order to preserve performance characteristics.
+ libs.add(new SharedLibraryInfo(
+ "/system/framework/android.hidl.base-V1.0-java.jar", null /*packageName*/,
+ null /*codePaths*/, null /*name*/, 0 /*version*/, SharedLibraryInfo.TYPE_BUILTIN,
+ null /*declaringPackage*/, null /*dependentPackages*/, null /*dependencies*/,
+ false /*isNative*/));
+ libs.add(new SharedLibraryInfo(
+ "/system/framework/android.hidl.manager-V1.0-java.jar", null /*packageName*/,
+ null /*codePaths*/, null /*name*/, 0 /*version*/, SharedLibraryInfo.TYPE_BUILTIN,
+ null /*declaringPackage*/, null /*dependentPackages*/, null /*dependencies*/,
+ false /*isNative*/));
+
+ libs.add(new SharedLibraryInfo(
+ "/system/framework/android.test.base.jar", null /*packageName*/,
+ null /*codePaths*/, null /*name*/, 0 /*version*/, SharedLibraryInfo.TYPE_BUILTIN,
+ null /*declaringPackage*/, null /*dependentPackages*/, null /*dependencies*/,
+ false /*isNative*/));
+
+ if (Flags.enableApacheHttpLegacyPreload()) {
+ libs.add(new SharedLibraryInfo(
+ "/system/framework/org.apache.http.legacy.jar", null /*packageName*/,
+ null /*codePaths*/, null /*name*/, 0 /*version*/,
+ SharedLibraryInfo.TYPE_BUILTIN, null /*declaringPackage*/,
+ null /*dependentPackages*/, null /*dependencies*/, false /*isNative*/));
+ }
+
+ // WindowManager Extensions is an optional shared library that is required for WindowManager
+ // Jetpack to fully function. Since it is a widely used library, preload it to improve apps
+ // startup performance.
+ if (WindowManager.HAS_WINDOW_EXTENSIONS_ON_DEVICE) {
+ final String systemExtFrameworkPath =
+ new File(Environment.getSystemExtDirectory(), "framework").getPath();
+ libs.add(new SharedLibraryInfo(
+ systemExtFrameworkPath + "/androidx.window.extensions.jar",
+ "androidx.window.extensions", null /*codePaths*/,
+ "androidx.window.extensions", SharedLibraryInfo.VERSION_UNDEFINED,
+ SharedLibraryInfo.TYPE_BUILTIN, null /*declaringPackage*/,
+ null /*dependentPackages*/, null /*dependencies*/, false /*isNative*/));
+ libs.add(new SharedLibraryInfo(
+ systemExtFrameworkPath + "/androidx.window.sidecar.jar",
+ "androidx.window.sidecar", null /*codePaths*/,
+ "androidx.window.sidecar", SharedLibraryInfo.VERSION_UNDEFINED,
+ SharedLibraryInfo.TYPE_BUILTIN, null /*declaringPackage*/,
+ null /*dependentPackages*/, null /*dependencies*/, false /*isNative*/));
+ }
+
+ ApplicationLoaders.getDefault().createAndCacheNonBootclasspathSystemClassLoaders(libs);
+ }
+
+ /**
+ * Runs several special GCs to try to clean up a few generations of softly- and final-reachable
+ * objects, along with any other garbage. This is only useful just before a fork().
+ */
+ private static void gcAndFinalize() {
+ ZygoteHooks.gcAndFinalize();
+ }
+
+ /**
+ * Finish remaining work for the newly forked system server process.
+ */
+ private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) {
+ // set umask to 0077 so new files and directories will default to owner-only permissions.
+ Os.umask(S_IRWXG | S_IRWXO);
+
+ if (parsedArgs.mNiceName != null) {
+ Process.setArgV0(parsedArgs.mNiceName);
+ }
+
+ final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
+ if (systemServerClasspath != null) {
+ // Capturing profiles is only supported for debug or eng builds since selinux normally
+ // prevents it.
+ if (shouldProfileSystemServer() && (Build.IS_USERDEBUG || Build.IS_ENG)) {
+ try {
+ Log.d(TAG, "Preparing system server profile");
+ final String standaloneSystemServerJars =
+ Os.getenv("STANDALONE_SYSTEMSERVER_JARS");
+ final String systemServerPaths = standaloneSystemServerJars != null
+ ? String.join(":", systemServerClasspath, standaloneSystemServerJars)
+ : systemServerClasspath;
+ prepareSystemServerProfile(systemServerPaths);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Failed to set up system server profile", e);
+ }
+ }
+ }
+
+ if (parsedArgs.mInvokeWith != null) {
+ String[] args = parsedArgs.mRemainingArgs;
+ // If we have a non-null system server class path, we'll have to duplicate the
+ // existing arguments and append the classpath to it. ART will handle the classpath
+ // correctly when we exec a new process.
+ if (systemServerClasspath != null) {
+ String[] amendedArgs = new String[args.length + 2];
+ amendedArgs[0] = "-cp";
+ amendedArgs[1] = systemServerClasspath;
+ System.arraycopy(args, 0, amendedArgs, 2, args.length);
+ args = amendedArgs;
+ }
+
+ WrapperInit.execApplication(parsedArgs.mInvokeWith,
+ parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion,
+ VMRuntime.getCurrentInstructionSet(), null, args);
+
+ throw new IllegalStateException("Unexpected return from WrapperInit.execApplication");
+ } else {
+ ClassLoader cl = getOrCreateSystemServerClassLoader();
+ if (cl != null) {
+ Thread.currentThread().setContextClassLoader(cl);
+ }
+
+ /*
+ * Pass the remaining arguments to SystemServer.
+ */
+ return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
+ parsedArgs.mDisabledCompatChanges,
+ parsedArgs.mRemainingArgs, cl);
+ }
+
+ /* should never reach here */
+ }
+
+ /**
+ * Create the classloader for the system server and store it in
+ * {@link sCachedSystemServerClassLoader}. This function is called through JNI in the forked
+ * system server process in the zygote SELinux domain.
+ */
+ private static ClassLoader getOrCreateSystemServerClassLoader() {
+ if (sCachedSystemServerClassLoader == null) {
+ final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
+ if (systemServerClasspath != null) {
+ sCachedSystemServerClassLoader = createPathClassLoader(systemServerClasspath,
+ VMRuntime.SDK_VERSION_CUR_DEVELOPMENT);
+ }
+ }
+ return sCachedSystemServerClassLoader;
+ }
+
+ /**
+ * Creates class loaders for standalone system server jars. This function is called through JNI
+ * in the forked system server process in the zygote SELinux domain.
+ */
+ private static void prefetchStandaloneSystemServerJars() {
+ if (shouldProfileSystemServer()) {
+ // We don't prefetch AOT artifacts if we are profiling system server, as we are going to
+ // JIT it.
+ // This method only gets called from native and should already be skipped if we profile
+ // system server. Still, be robust and check it again.
+ return;
+ }
+ String envStr = Os.getenv("STANDALONE_SYSTEMSERVER_JARS");
+ if (TextUtils.isEmpty(envStr)) {
+ return;
+ }
+ for (String jar : envStr.split(":")) {
+ try {
+ SystemServerClassLoaderFactory.createClassLoader(
+ jar, getOrCreateSystemServerClassLoader());
+ } catch (Error e) {
+ // We don't want the process to crash for this error because prefetching is just an
+ // optimization.
+ Log.e(TAG,
+ String.format("Failed to prefetch standalone system server jar \"%s\": %s",
+ jar, e.toString()));
+ }
+ }
+ }
+
+ /**
+ * Note that preparing the profiles for system server does not require special selinux
+ * permissions. From the installer perspective the system server is a regular package which can
+ * capture profile information.
+ */
+ private static void prepareSystemServerProfile(String systemServerPaths)
+ throws RemoteException {
+ if (systemServerPaths.isEmpty()) {
+ return;
+ }
+ String[] codePaths = systemServerPaths.split(":");
+
+ final IInstalld installd = IInstalld.Stub
+ .asInterface(ServiceManager.getService("installd"));
+
+ String systemServerPackageName = "android";
+ String systemServerProfileName = "primary.prof";
+ installd.prepareAppProfile(
+ systemServerPackageName,
+ UserHandle.USER_SYSTEM,
+ UserHandle.getAppId(Process.SYSTEM_UID),
+ systemServerProfileName,
+ codePaths[0],
+ /*dexMetadata*/ null);
+
+ File curProfileDir = Environment.getDataProfilesDePackageDirectory(
+ UserHandle.USER_SYSTEM, systemServerPackageName);
+ String curProfilePath = new File(curProfileDir, systemServerProfileName).getAbsolutePath();
+ File refProfileDir = Environment.getDataProfilesDePackageDirectory(
+ UserHandle.USER_SYSTEM, systemServerPackageName);
+ String refProfilePath = new File(refProfileDir, systemServerProfileName).getAbsolutePath();
+ VMRuntime.registerAppInfo(
+ systemServerPackageName,
+ curProfilePath,
+ refProfilePath,
+ codePaths,
+ VMRuntime.CODE_PATH_TYPE_PRIMARY_APK);
+ }
+
+ /**
+ * Sets the list of classes/methods for the hidden API
+ */
+ public static void setApiDenylistExemptions(String[] exemptions) {
+ VMRuntime.getRuntime().setHiddenApiExemptions(exemptions);
+ }
+
+ public static void setHiddenApiAccessLogSampleRate(int percent) {
+ VMRuntime.getRuntime().setHiddenApiAccessLogSamplingRate(percent);
+ }
+
+ /**
+ * Sets the implementation to be used for logging hidden API accesses
+ * @param logger the implementation of the VMRuntime.HiddenApiUsageLogger interface
+ */
+ public static void setHiddenApiUsageLogger(VMRuntime.HiddenApiUsageLogger logger) {
+ VMRuntime.getRuntime().setHiddenApiUsageLogger(logger);
+ }
+
+ /**
+ * Creates a PathClassLoader for the given class path that is associated with a shared
+ * namespace, i.e., this classloader can access platform-private native libraries.
+ *
+ * The classloader will add java.library.path to the native library path for the classloader
+ * namespace. Since it includes platform locations like /system/lib, this is only appropriate
+ * for platform code that don't need linker namespace isolation (as opposed to APEXes and apps).
+ */
+ static ClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
+ String libraryPath = System.getProperty("java.library.path");
+
+ // We use the boot class loader, that's what the runtime expects at AOT.
+ ClassLoader parent = ClassLoader.getSystemClassLoader().getParent();
+
+ return ClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath,
+ parent, targetSdkVersion, true /* isNamespaceShared */, null /* classLoaderName */);
+ }
+
+ /**
+ * Prepare the arguments and forks for the system server process.
+ *
+ * @return A {@code Runnable} that provides an entrypoint into system_server code in the child
+ * process; {@code null} in the parent.
+ */
+ private static Runnable forkSystemServer(String abiList, String socketName,
+ ZygoteServer zygoteServer) {
+ long capabilities = posixCapabilitiesAsBits(
+ OsConstants.CAP_IPC_LOCK,
+ OsConstants.CAP_KILL,
+ OsConstants.CAP_NET_ADMIN,
+ OsConstants.CAP_NET_BIND_SERVICE,
+ OsConstants.CAP_NET_BROADCAST,
+ OsConstants.CAP_NET_RAW,
+ OsConstants.CAP_SYS_MODULE,
+ OsConstants.CAP_SYS_NICE,
+ OsConstants.CAP_SYS_PTRACE,
+ OsConstants.CAP_SYS_TIME,
+ OsConstants.CAP_SYS_TTY_CONFIG,
+ OsConstants.CAP_WAKE_ALARM,
+ OsConstants.CAP_BLOCK_SUSPEND
+ );
+ /* Containers run without some capabilities, so drop any caps that are not available. */
+ StructCapUserHeader header = new StructCapUserHeader(
+ OsConstants._LINUX_CAPABILITY_VERSION_3, 0);
+ StructCapUserData[] data;
+ try {
+ data = Os.capget(header);
+ } catch (ErrnoException ex) {
+ throw new RuntimeException("Failed to capget()", ex);
+ }
+ capabilities &= Integer.toUnsignedLong(data[0].effective) |
+ (Integer.toUnsignedLong(data[1].effective) << 32);
+
+ /* Hardcoded command line to start the system server */
+ String[] args = {
+ "--setuid=1000",
+ "--setgid=1000",
+ "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
+ + "1024,1032,1065,3001,3002,3003,3005,3006,3007,3009,3010,3011,3012",
+ "--capabilities=" + capabilities + "," + capabilities,
+ "--nice-name=system_server",
+ "--runtime-args",
+ "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
+ "com.android.server.SystemServer",
+ };
+ ZygoteArguments parsedArgs;
+
+ int pid;
+
+ try {
+ ZygoteCommandBuffer commandBuffer = new ZygoteCommandBuffer(args);
+ try {
+ parsedArgs = ZygoteArguments.getInstance(commandBuffer);
+ } catch (EOFException e) {
+ throw new AssertionError("Unexpected argument error for forking system server", e);
+ }
+ commandBuffer.close();
+ Zygote.applyDebuggerSystemProperty(parsedArgs);
+ Zygote.applyInvokeWithSystemProperty(parsedArgs);
+
+ if (Zygote.nativeSupportsMemoryTagging()) {
+ String mode = SystemProperties.get("persist.arm64.memtag.system_server", "");
+ if (mode.isEmpty()) {
+ /* The system server has ASYNC MTE by default, in order to allow
+ * system services to specify their own MTE level later, as you
+ * can't re-enable MTE once it's disabled. */
+ mode = SystemProperties.get("persist.arm64.memtag.default", "async");
+ }
+ if (mode.equals("async")) {
+ parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_ASYNC;
+ } else if (mode.equals("sync")) {
+ parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_SYNC;
+ } else if (!mode.equals("off")) {
+ /* When we have an invalid memory tag level, keep the current level. */
+ parsedArgs.mRuntimeFlags |= Zygote.nativeCurrentTaggingLevel();
+ Slog.e(TAG, "Unknown memory tag level for the system server: \"" + mode + "\"");
+ }
+ } else if (Zygote.nativeSupportsTaggedPointers()) {
+ /* Enable pointer tagging in the system server. Hardware support for this is present
+ * in all ARMv8 CPUs. */
+ parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+ }
+
+ /* Enable gwp-asan on the system server with a small probability. This is the same
+ * policy as applied to native processes and system apps. */
+ parsedArgs.mRuntimeFlags |= Zygote.GWP_ASAN_LEVEL_LOTTERY;
+
+ if (shouldProfileSystemServer()) {
+ parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
+ }
+
+ /* Request to fork the system server process */
+ pid = Zygote.forkSystemServer(
+ parsedArgs.mUid, parsedArgs.mGid,
+ parsedArgs.mGids,
+ parsedArgs.mRuntimeFlags,
+ null,
+ parsedArgs.mPermittedCapabilities,
+ parsedArgs.mEffectiveCapabilities);
+ } catch (IllegalArgumentException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ /* For child process */
+ if (pid == 0) {
+ if (hasSecondZygote(abiList)) {
+ waitForSecondaryZygote(socketName);
+ }
+
+ zygoteServer.closeServerSocket();
+ return handleSystemServerProcess(parsedArgs);
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the bit array representation of the provided list of POSIX capabilities.
+ */
+ private static long posixCapabilitiesAsBits(int... capabilities) {
+ long result = 0;
+ for (int capability : capabilities) {
+ if ((capability < 0) || (capability > OsConstants.CAP_LAST_CAP)) {
+ throw new IllegalArgumentException(String.valueOf(capability));
+ }
+ result |= (1L << capability);
+ }
+ return result;
+ }
+
+ /**
+ * This is the entry point for a Zygote process. It creates the Zygote server, loads resources,
+ * and handles other tasks related to preparing the process for forking into applications.
+ *
+ * This process is started with a nice value of -20 (highest priority). All paths that flow
+ * into new processes are required to either set the priority to the default value or terminate
+ * before executing any non-system code. The native side of this occurs in SpecializeCommon,
+ * while the Java Language priority is changed in ZygoteInit.handleSystemServerProcess,
+ * ZygoteConnection.handleChildProc, and Zygote.childMain.
+ *
+ * @param argv Command line arguments used to specify the Zygote's configuration.
+ */
+ @UnsupportedAppUsage
+ public static void main(String[] argv) {
+ ZygoteServer zygoteServer = null;
+
+ // Mark zygote start. This ensures that thread creation will throw
+ // an error.
+ ZygoteHooks.startZygoteNoThreadCreation();
+
+ // Zygote goes into its own process group.
+ try {
+ Os.setpgid(0, 0);
+ } catch (ErrnoException ex) {
+ throw new RuntimeException("Failed to setpgid(0,0)", ex);
+ }
+
+ Runnable caller;
+ try {
+ // Store now for StatsLogging later.
+ final long startTime = SystemClock.elapsedRealtime();
+ final boolean isRuntimeRestarted = "1".equals(
+ SystemProperties.get("sys.boot_completed"));
+
+ String bootTimeTag = Process.is64Bit() ? "Zygote64Timing" : "Zygote32Timing";
+ TimingsTraceLog bootTimingsTraceLog = new TimingsTraceLog(bootTimeTag,
+ Trace.TRACE_TAG_DALVIK);
+ bootTimingsTraceLog.traceBegin("ZygoteInit");
+ RuntimeInit.preForkInit();
+
+ boolean startSystemServer = false;
+ String zygoteSocketName = "zygote";
+ String abiList = null;
+ boolean enableLazyPreload = false;
+ for (int i = 1; i < argv.length; i++) {
+ if ("start-system-server".equals(argv[i])) {
+ startSystemServer = true;
+ } else if ("--enable-lazy-preload".equals(argv[i])) {
+ enableLazyPreload = true;
+ } else if (argv[i].startsWith(ABI_LIST_ARG)) {
+ abiList = argv[i].substring(ABI_LIST_ARG.length());
+ } else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
+ zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length());
+ } else {
+ throw new RuntimeException("Unknown command line argument: " + argv[i]);
+ }
+ }
+
+ final boolean isPrimaryZygote = zygoteSocketName.equals(Zygote.PRIMARY_SOCKET_NAME);
+ if (!isRuntimeRestarted) {
+ if (isPrimaryZygote) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__ZYGOTE_INIT_START,
+ startTime);
+ } else if (zygoteSocketName.equals(Zygote.SECONDARY_SOCKET_NAME)) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SECONDARY_ZYGOTE_INIT_START,
+ startTime);
+ }
+ }
+
+ if (abiList == null) {
+ throw new RuntimeException("No ABI list supplied.");
+ }
+
+ // In some configurations, we avoid preloading resources and classes eagerly.
+ // In such cases, we will preload things prior to our first fork.
+ if (!enableLazyPreload) {
+ bootTimingsTraceLog.traceBegin("ZygotePreload");
+ EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
+ SystemClock.uptimeMillis());
+ preload(bootTimingsTraceLog);
+ EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
+ SystemClock.uptimeMillis());
+ bootTimingsTraceLog.traceEnd(); // ZygotePreload
+ }
+
+ // Do an initial gc to clean up after startup
+ bootTimingsTraceLog.traceBegin("PostZygoteInitGC");
+ gcAndFinalize();
+ bootTimingsTraceLog.traceEnd(); // PostZygoteInitGC
+
+ bootTimingsTraceLog.traceEnd(); // ZygoteInit
+
+ Zygote.initNativeState(isPrimaryZygote);
+
+ ZygoteHooks.stopZygoteNoThreadCreation();
+
+ zygoteServer = new ZygoteServer(isPrimaryZygote);
+
+ if (startSystemServer) {
+ Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
+
+ // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
+ // child (system_server) process.
+ if (r != null) {
+ r.run();
+ return;
+ }
+ }
+
+ Log.i(TAG, "Accepting command socket connections");
+
+ // The select loop returns early in the child process after a fork and
+ // loops forever in the zygote.
+ caller = zygoteServer.runSelectLoop(abiList);
+ } catch (Throwable ex) {
+ Log.e(TAG, "System zygote died with fatal exception", ex);
+ throw ex;
+ } finally {
+ if (zygoteServer != null) {
+ zygoteServer.closeServerSocket();
+ }
+ }
+
+ // We're in the child process and have exited the select loop. Proceed to execute the
+ // command.
+ if (caller != null) {
+ caller.run();
+ }
+ }
+
+ /**
+ * Return {@code true} if this device configuration has another zygote.
+ *
+ * We determine this by comparing the device ABI list with this zygotes list. If this zygote
+ * supports all ABIs this device supports, there won't be another zygote.
+ */
+ private static boolean hasSecondZygote(String abiList) {
+ return !SystemProperties.get("ro.product.cpu.abilist").equals(abiList);
+ }
+
+ private static void waitForSecondaryZygote(String socketName) {
+ String otherZygoteName = Zygote.PRIMARY_SOCKET_NAME.equals(socketName)
+ ? Zygote.SECONDARY_SOCKET_NAME : Zygote.PRIMARY_SOCKET_NAME;
+ ZygoteProcess.waitForConnectionToZygote(otherZygoteName);
+ }
+
+ static boolean isPreloadComplete() {
+ return sPreloadComplete;
+ }
+
+ /**
+ * Class not instantiable.
+ */
+ private ZygoteInit() {
+ }
+
+ /**
+ * The main function called when started through the zygote process. This could be unified with
+ * main(), if the native code in nativeFinishInit() were rationalized with Zygote startup.<p>
+ *
+ * Current recognized args:
+ * <ul>
+ * <li> <code> [--] <start class name> <args>
+ * </ul>
+ *
+ * @param targetSdkVersion target SDK version
+ * @param disabledCompatChanges set of disabled compat changes for the process (all others
+ * are enabled)
+ * @param argv arg strings
+ */
+ public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,
+ String[] argv, ClassLoader classLoader) {
+ if (RuntimeInit.DEBUG) {
+ Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
+ RuntimeInit.redirectLogStreams();
+
+ RuntimeInit.commonInit();
+ ZygoteInit.nativeZygoteInit();
+ return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,
+ classLoader);
+ }
+
+ /**
+ * The main function called when starting a child zygote process. This is used as an alternative
+ * to zygoteInit(), which skips calling into initialization routines that start the Binder
+ * threadpool.
+ */
+ static Runnable childZygoteInit(String[] argv) {
+ RuntimeInit.Arguments args = new RuntimeInit.Arguments(argv);
+ return RuntimeInit.findStaticMain(args.startClass, args.startArgs, /* classLoader= */null);
+ }
+
+ private static native void nativeZygoteInit();
+}
diff --git a/android-35/com/android/internal/os/ZygoteSecurityException.java b/android-35/com/android/internal/os/ZygoteSecurityException.java
new file mode 100644
index 0000000..8111483
--- /dev/null
+++ b/android-35/com/android/internal/os/ZygoteSecurityException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2007 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.internal.os;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+/**
+ * Exception thrown when a security policy is violated.
+ */
+class ZygoteSecurityException extends RuntimeException {
+ @UnsupportedAppUsage
+ ZygoteSecurityException(String message) {
+ super(message);
+ }
+}
diff --git a/android-35/com/android/internal/os/ZygoteServer.java b/android-35/com/android/internal/os/ZygoteServer.java
new file mode 100644
index 0000000..f8598f2
--- /dev/null
+++ b/android-35/com/android/internal/os/ZygoteServer.java
@@ -0,0 +1,659 @@
+/*
+ * Copyright (C) 2007 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.internal.os;
+
+import static android.system.OsConstants.POLLIN;
+
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructPollfd;
+import android.util.Log;
+import android.util.Slog;
+
+import dalvik.system.ZygoteHooks;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Server socket class for zygote processes.
+ *
+ * Provides functions to wait for commands on a UNIX domain socket, and fork
+ * off child processes that inherit the initial state of the VM.%
+ *
+ * Please see {@link ZygoteArguments} for documentation on the
+ * client protocol.
+ */
+class ZygoteServer {
+ // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate
+ public static final String TAG = "ZygoteServer";
+
+ /** The "not a timestamp" value for the refill delay timestamp mechanism. */
+ private static final int INVALID_TIMESTAMP = -1;
+
+ /**
+ * Indicates if this Zygote server can support a unspecialized app process pool. Currently this
+ * should only be true for the primary and secondary Zygotes, and not the App Zygotes or the
+ * WebView Zygote.
+ *
+ * TODO (chriswailes): Make this an explicit argument to the constructor
+ */
+
+ private final boolean mUsapPoolSupported;
+
+ /**
+ * If the unspecialized app process pool should be created and used to start applications.
+ *
+ * Setting this value to false will disable the creation, maintenance, and use of the USAP
+ * pool. When the USAP pool is disabled the application lifecycle will be identical to
+ * previous versions of Android.
+ */
+ private boolean mUsapPoolEnabled = false;
+
+ /**
+ * Listening socket that accepts new server connections.
+ */
+ private LocalServerSocket mZygoteSocket;
+
+ /**
+ * The name of the unspecialized app process pool socket to use if the USAP pool is enabled.
+ */
+ private final LocalServerSocket mUsapPoolSocket;
+
+ /**
+ * File descriptor used for communication between the signal handler and the ZygoteServer poll
+ * loop.
+ * */
+ private final FileDescriptor mUsapPoolEventFD;
+
+ /**
+ * Whether or not mZygoteSocket's underlying FD should be closed directly.
+ * If mZygoteSocket is created with an existing FD, closing the socket does
+ * not close the FD and it must be closed explicitly. If the socket is created
+ * with a name instead, then closing the socket will close the underlying FD
+ * and it should not be double-closed.
+ */
+ private boolean mCloseSocketFd;
+
+ /**
+ * Set by the child process, immediately after a call to {@code Zygote.forkAndSpecialize}.
+ */
+ private boolean mIsForkChild;
+
+ /**
+ * The runtime-adjustable maximum USAP pool size.
+ */
+ private int mUsapPoolSizeMax = 0;
+
+ /**
+ * The runtime-adjustable minimum USAP pool size.
+ */
+ private int mUsapPoolSizeMin = 0;
+
+ /**
+ * The runtime-adjustable value used to determine when to re-fill the USAP pool. The pool will
+ * be re-filled when (mUsapPoolMax - gUsapPoolCount) >= sUsapPoolRefillThreshold.
+ */
+ private int mUsapPoolRefillThreshold = 0;
+
+ /**
+ * Number of milliseconds to delay before refilling the pool if it hasn't reached its
+ * minimum value.
+ */
+ private int mUsapPoolRefillDelayMs = -1;
+
+ /**
+ * If and when we should refill the USAP pool.
+ */
+ private UsapPoolRefillAction mUsapPoolRefillAction;
+ private long mUsapPoolRefillTriggerTimestamp;
+
+ private enum UsapPoolRefillAction {
+ DELAYED,
+ IMMEDIATE,
+ NONE
+ }
+
+ ZygoteServer() {
+ mUsapPoolEventFD = null;
+ mZygoteSocket = null;
+ mUsapPoolSocket = null;
+
+ mUsapPoolSupported = false;
+ }
+
+ /**
+ * Initialize the Zygote server with the Zygote server socket, USAP pool server socket, and USAP
+ * pool event FD.
+ *
+ * @param isPrimaryZygote If this is the primary Zygote or not.
+ */
+ ZygoteServer(boolean isPrimaryZygote) {
+ mUsapPoolEventFD = Zygote.getUsapPoolEventFD();
+
+ if (isPrimaryZygote) {
+ mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.PRIMARY_SOCKET_NAME);
+ mUsapPoolSocket =
+ Zygote.createManagedSocketFromInitSocket(
+ Zygote.USAP_POOL_PRIMARY_SOCKET_NAME);
+ } else {
+ mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.SECONDARY_SOCKET_NAME);
+ mUsapPoolSocket =
+ Zygote.createManagedSocketFromInitSocket(
+ Zygote.USAP_POOL_SECONDARY_SOCKET_NAME);
+ }
+
+ mUsapPoolSupported = true;
+ fetchUsapPoolPolicyProps();
+ }
+
+ void setForkChild() {
+ mIsForkChild = true;
+ }
+
+ public boolean isUsapPoolEnabled() {
+ return mUsapPoolEnabled;
+ }
+
+ /**
+ * Registers a server socket for zygote command connections. This opens the server socket
+ * at the specified name in the abstract socket namespace.
+ */
+ void registerServerSocketAtAbstractName(String socketName) {
+ if (mZygoteSocket == null) {
+ try {
+ mZygoteSocket = new LocalServerSocket(socketName);
+ mCloseSocketFd = false;
+ } catch (IOException ex) {
+ throw new RuntimeException(
+ "Error binding to abstract socket '" + socketName + "'", ex);
+ }
+ }
+ }
+
+ /**
+ * Waits for and accepts a single command connection. Throws
+ * RuntimeException on failure.
+ */
+ private ZygoteConnection acceptCommandPeer(String abiList) {
+ try {
+ return createNewConnection(mZygoteSocket.accept(), abiList);
+ } catch (IOException ex) {
+ throw new RuntimeException(
+ "IOException during accept()", ex);
+ }
+ }
+
+ protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
+ throws IOException {
+ return new ZygoteConnection(socket, abiList);
+ }
+
+ /**
+ * Close and clean up zygote sockets. Called on shutdown and on the
+ * child's exit path.
+ */
+ void closeServerSocket() {
+ try {
+ if (mZygoteSocket != null) {
+ FileDescriptor fd = mZygoteSocket.getFileDescriptor();
+ mZygoteSocket.close();
+ if (fd != null && mCloseSocketFd) {
+ Os.close(fd);
+ }
+ }
+ } catch (IOException ex) {
+ Log.e(TAG, "Zygote: error closing sockets", ex);
+ } catch (ErrnoException ex) {
+ Log.e(TAG, "Zygote: error closing descriptor", ex);
+ }
+
+ mZygoteSocket = null;
+ }
+
+ /**
+ * Return the server socket's underlying file descriptor, so that
+ * ZygoteConnection can pass it to the native code for proper
+ * closure after a child process is forked off.
+ */
+
+ FileDescriptor getZygoteSocketFileDescriptor() {
+ return mZygoteSocket.getFileDescriptor();
+ }
+
+ private void fetchUsapPoolPolicyProps() {
+ if (mUsapPoolSupported) {
+ mUsapPoolSizeMax = Integer.min(
+ ZygoteConfig.getInt(
+ ZygoteConfig.USAP_POOL_SIZE_MAX,
+ ZygoteConfig.USAP_POOL_SIZE_MAX_DEFAULT),
+ ZygoteConfig.USAP_POOL_SIZE_MAX_LIMIT);
+
+ mUsapPoolSizeMin = Integer.max(
+ ZygoteConfig.getInt(
+ ZygoteConfig.USAP_POOL_SIZE_MIN,
+ ZygoteConfig.USAP_POOL_SIZE_MIN_DEFAULT),
+ ZygoteConfig.USAP_POOL_SIZE_MIN_LIMIT);
+
+ mUsapPoolRefillThreshold = Integer.min(
+ ZygoteConfig.getInt(
+ ZygoteConfig.USAP_POOL_REFILL_THRESHOLD,
+ ZygoteConfig.USAP_POOL_REFILL_THRESHOLD_DEFAULT),
+ mUsapPoolSizeMax);
+
+ mUsapPoolRefillDelayMs = ZygoteConfig.getInt(
+ ZygoteConfig.USAP_POOL_REFILL_DELAY_MS,
+ ZygoteConfig.USAP_POOL_REFILL_DELAY_MS_DEFAULT);
+
+ // Validity check
+ if (mUsapPoolSizeMin >= mUsapPoolSizeMax) {
+ Log.w(TAG, "The max size of the USAP pool must be greater than the minimum size."
+ + " Restoring default values.");
+
+ mUsapPoolSizeMax = ZygoteConfig.USAP_POOL_SIZE_MAX_DEFAULT;
+ mUsapPoolSizeMin = ZygoteConfig.USAP_POOL_SIZE_MIN_DEFAULT;
+ mUsapPoolRefillThreshold = mUsapPoolSizeMax / 2;
+ }
+ }
+ }
+
+ private boolean mIsFirstPropertyCheck = true;
+ private long mLastPropCheckTimestamp = 0;
+
+ private void fetchUsapPoolPolicyPropsWithMinInterval() {
+ final long currentTimestamp = SystemClock.elapsedRealtime();
+
+ if (mIsFirstPropertyCheck
+ || (currentTimestamp - mLastPropCheckTimestamp >= Zygote.PROPERTY_CHECK_INTERVAL)) {
+ mIsFirstPropertyCheck = false;
+ mLastPropCheckTimestamp = currentTimestamp;
+ fetchUsapPoolPolicyProps();
+ }
+ }
+
+ private void fetchUsapPoolPolicyPropsIfUnfetched() {
+ if (mIsFirstPropertyCheck) {
+ mIsFirstPropertyCheck = false;
+ fetchUsapPoolPolicyProps();
+ }
+ }
+
+ /**
+ * Refill the USAP Pool to the appropriate level, determined by whether this is a priority
+ * refill event or not.
+ *
+ * @param sessionSocketRawFDs Anonymous session sockets that are currently open
+ * @return In the Zygote process this function will always return null; in unspecialized app
+ * processes this function will return a Runnable object representing the new
+ * application that is passed up from childMain (the usap's main wait loop).
+ */
+
+ Runnable fillUsapPool(int[] sessionSocketRawFDs, boolean isPriorityRefill) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillUsapPool");
+
+ // Ensure that the pool properties have been fetched.
+ fetchUsapPoolPolicyPropsIfUnfetched();
+
+ int usapPoolCount = Zygote.getUsapPoolCount();
+ int numUsapsToSpawn;
+
+ if (isPriorityRefill) {
+ // Refill to min
+ numUsapsToSpawn = mUsapPoolSizeMin - usapPoolCount;
+
+ Log.i("zygote",
+ "Priority USAP Pool refill. New USAPs: " + numUsapsToSpawn);
+ } else {
+ // Refill up to max
+ numUsapsToSpawn = mUsapPoolSizeMax - usapPoolCount;
+
+ Log.i("zygote",
+ "Delayed USAP Pool refill. New USAPs: " + numUsapsToSpawn);
+ }
+
+ // Disable some VM functionality and reset some system values
+ // before forking.
+ ZygoteHooks.preFork();
+
+ while (--numUsapsToSpawn >= 0) {
+ Runnable caller =
+ Zygote.forkUsap(mUsapPoolSocket, sessionSocketRawFDs, isPriorityRefill);
+
+ if (caller != null) {
+ return caller;
+ }
+ }
+
+ // Re-enable runtime services for the Zygote. Services for unspecialized app process
+ // are re-enabled in specializeAppProcess.
+ ZygoteHooks.postForkCommon();
+
+ resetUsapRefillState();
+
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ return null;
+ }
+
+ /**
+ * Empty or fill the USAP pool as dictated by the current and new USAP pool statuses.
+ */
+ Runnable setUsapPoolStatus(boolean newStatus, LocalSocket sessionSocket) {
+ if (!mUsapPoolSupported) {
+ Log.w(TAG,
+ "Attempting to enable a USAP pool for a Zygote that doesn't support it.");
+ return null;
+ } else if (mUsapPoolEnabled == newStatus) {
+ return null;
+ }
+
+ Log.i(TAG, "USAP Pool status change: " + (newStatus ? "ENABLED" : "DISABLED"));
+
+ mUsapPoolEnabled = newStatus;
+
+ if (newStatus) {
+ return fillUsapPool(new int[]{ sessionSocket.getFileDescriptor().getInt$() }, false);
+ } else {
+ Zygote.emptyUsapPool();
+ return null;
+ }
+ }
+
+ private void resetUsapRefillState() {
+ mUsapPoolRefillAction = UsapPoolRefillAction.NONE;
+ mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
+ }
+
+ /**
+ * Runs the zygote process's select loop. Accepts new connections as
+ * they happen, and reads commands from connections one spawn-request's
+ * worth at a time.
+ * @param abiList list of ABIs supported by this zygote.
+ */
+ Runnable runSelectLoop(String abiList) {
+ ArrayList<FileDescriptor> socketFDs = new ArrayList<>();
+ ArrayList<ZygoteConnection> peers = new ArrayList<>();
+
+ socketFDs.add(mZygoteSocket.getFileDescriptor());
+ peers.add(null);
+
+ mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
+
+ while (true) {
+ fetchUsapPoolPolicyPropsWithMinInterval();
+ mUsapPoolRefillAction = UsapPoolRefillAction.NONE;
+
+ int[] usapPipeFDs = null;
+ StructPollfd[] pollFDs;
+
+ // Allocate enough space for the poll structs, taking into account
+ // the state of the USAP pool for this Zygote (could be a
+ // regular Zygote, a WebView Zygote, or an AppZygote).
+ if (mUsapPoolEnabled) {
+ usapPipeFDs = Zygote.getUsapPipeFDs();
+ pollFDs = new StructPollfd[socketFDs.size() + 1 + usapPipeFDs.length];
+ } else {
+ pollFDs = new StructPollfd[socketFDs.size()];
+ }
+
+ /*
+ * For reasons of correctness the USAP pool pipe and event FDs
+ * must be processed before the session and server sockets. This
+ * is to ensure that the USAP pool accounting information is
+ * accurate when handling other requests like API deny list
+ * exemptions.
+ */
+
+ int pollIndex = 0;
+ for (FileDescriptor socketFD : socketFDs) {
+ pollFDs[pollIndex] = new StructPollfd();
+ pollFDs[pollIndex].fd = socketFD;
+ pollFDs[pollIndex].events = (short) POLLIN;
+ ++pollIndex;
+ }
+
+ final int usapPoolEventFDIndex = pollIndex;
+
+ if (mUsapPoolEnabled) {
+ pollFDs[pollIndex] = new StructPollfd();
+ pollFDs[pollIndex].fd = mUsapPoolEventFD;
+ pollFDs[pollIndex].events = (short) POLLIN;
+ ++pollIndex;
+
+ // The usapPipeFDs array will always be filled in if the USAP Pool is enabled.
+ assert usapPipeFDs != null;
+ for (int usapPipeFD : usapPipeFDs) {
+ FileDescriptor managedFd = new FileDescriptor();
+ managedFd.setInt$(usapPipeFD);
+
+ pollFDs[pollIndex] = new StructPollfd();
+ pollFDs[pollIndex].fd = managedFd;
+ pollFDs[pollIndex].events = (short) POLLIN;
+ ++pollIndex;
+ }
+ }
+
+ int pollTimeoutMs;
+
+ if (mUsapPoolRefillTriggerTimestamp == INVALID_TIMESTAMP) {
+ pollTimeoutMs = -1;
+ } else {
+ long elapsedTimeMs = System.currentTimeMillis() - mUsapPoolRefillTriggerTimestamp;
+
+ if (elapsedTimeMs >= mUsapPoolRefillDelayMs) {
+ // The refill delay has elapsed during the period between poll invocations.
+ // We will now check for any currently ready file descriptors before refilling
+ // the USAP pool.
+ pollTimeoutMs = 0;
+ mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
+ mUsapPoolRefillAction = UsapPoolRefillAction.DELAYED;
+
+ } else if (elapsedTimeMs <= 0) {
+ // This can occur if the clock used by currentTimeMillis is reset, which is
+ // possible because it is not guaranteed to be monotonic. Because we can't tell
+ // how far back the clock was set the best way to recover is to simply re-start
+ // the respawn delay countdown.
+ pollTimeoutMs = mUsapPoolRefillDelayMs;
+
+ } else {
+ pollTimeoutMs = (int) (mUsapPoolRefillDelayMs - elapsedTimeMs);
+ }
+ }
+
+ int pollReturnValue;
+ try {
+ pollReturnValue = Os.poll(pollFDs, pollTimeoutMs);
+ } catch (ErrnoException ex) {
+ throw new RuntimeException("poll failed", ex);
+ }
+
+ if (pollReturnValue == 0) {
+ // The poll returned zero results either when the timeout value has been exceeded
+ // or when a non-blocking poll is issued and no FDs are ready. In either case it
+ // is time to refill the pool. This will result in a duplicate assignment when
+ // the non-blocking poll returns zero results, but it avoids an additional
+ // conditional in the else branch.
+ mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
+ mUsapPoolRefillAction = UsapPoolRefillAction.DELAYED;
+
+ } else {
+ boolean usapPoolFDRead = false;
+
+ while (--pollIndex >= 0) {
+ if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
+ continue;
+ }
+
+ if (pollIndex == 0) {
+ // Zygote server socket
+ ZygoteConnection newPeer = acceptCommandPeer(abiList);
+ peers.add(newPeer);
+ socketFDs.add(newPeer.getFileDescriptor());
+ } else if (pollIndex < usapPoolEventFDIndex) {
+ // Session socket accepted from the Zygote server socket
+
+ try {
+ ZygoteConnection connection = peers.get(pollIndex);
+ boolean multipleForksOK = !isUsapPoolEnabled()
+ && ZygoteHooks.isIndefiniteThreadSuspensionSafe();
+ final Runnable command =
+ connection.processCommand(this, multipleForksOK);
+
+ // TODO (chriswailes): Is this extra check necessary?
+ if (mIsForkChild) {
+ // We're in the child. We should always have a command to run at
+ // this stage if processCommand hasn't called "exec".
+ if (command == null) {
+ throw new IllegalStateException("command == null");
+ }
+
+ return command;
+ } else {
+ // We're in the server - we should never have any commands to run.
+ if (command != null) {
+ throw new IllegalStateException("command != null");
+ }
+
+ // We don't know whether the remote side of the socket was closed or
+ // not until we attempt to read from it from processCommand. This
+ // shows up as a regular POLLIN event in our regular processing
+ // loop.
+ if (connection.isClosedByPeer()) {
+ connection.closeSocket();
+ peers.remove(pollIndex);
+ socketFDs.remove(pollIndex);
+ }
+ }
+ } catch (Exception e) {
+ if (!mIsForkChild) {
+ // We're in the server so any exception here is one that has taken
+ // place pre-fork while processing commands or reading / writing
+ // from the control socket. Make a loud noise about any such
+ // exceptions so that we know exactly what failed and why.
+
+ Slog.e(TAG, "Exception executing zygote command: ", e);
+
+ // Make sure the socket is closed so that the other end knows
+ // immediately that something has gone wrong and doesn't time out
+ // waiting for a response.
+ ZygoteConnection conn = peers.remove(pollIndex);
+ conn.closeSocket();
+
+ socketFDs.remove(pollIndex);
+ } else {
+ // We're in the child so any exception caught here has happened post
+ // fork and before we execute ActivityThread.main (or any other
+ // main() method). Log the details of the exception and bring down
+ // the process.
+ Log.e(TAG, "Caught post-fork exception in child process.", e);
+ throw e;
+ }
+ } finally {
+ // Reset the child flag, in the event that the child process is a child-
+ // zygote. The flag will not be consulted this loop pass after the
+ // Runnable is returned.
+ mIsForkChild = false;
+ }
+
+ } else {
+ // Either the USAP pool event FD or a USAP reporting pipe.
+
+ // If this is the event FD the payload will be the number of USAPs removed.
+ // If this is a reporting pipe FD the payload will be the PID of the USAP
+ // that was just specialized. The `continue` statements below ensure that
+ // the messagePayload will always be valid if we complete the try block
+ // without an exception.
+ long messagePayload;
+
+ try {
+ byte[] buffer = new byte[Zygote.USAP_MANAGEMENT_MESSAGE_BYTES];
+ int readBytes =
+ Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length);
+
+ if (readBytes == Zygote.USAP_MANAGEMENT_MESSAGE_BYTES) {
+ DataInputStream inputStream =
+ new DataInputStream(new ByteArrayInputStream(buffer));
+
+ messagePayload = inputStream.readLong();
+ } else {
+ Log.e(TAG, "Incomplete read from USAP management FD of size "
+ + readBytes);
+ continue;
+ }
+ } catch (Exception ex) {
+ if (pollIndex == usapPoolEventFDIndex) {
+ Log.e(TAG, "Failed to read from USAP pool event FD: "
+ + ex.getMessage());
+ } else {
+ Log.e(TAG, "Failed to read from USAP reporting pipe: "
+ + ex.getMessage());
+ }
+
+ continue;
+ }
+
+ if (pollIndex > usapPoolEventFDIndex) {
+ Zygote.removeUsapTableEntry((int) messagePayload);
+ }
+
+ usapPoolFDRead = true;
+ }
+ }
+
+ if (usapPoolFDRead) {
+ int usapPoolCount = Zygote.getUsapPoolCount();
+
+ if (usapPoolCount < mUsapPoolSizeMin) {
+ // Immediate refill
+ mUsapPoolRefillAction = UsapPoolRefillAction.IMMEDIATE;
+ } else if (mUsapPoolSizeMax - usapPoolCount >= mUsapPoolRefillThreshold) {
+ // Delayed refill
+ mUsapPoolRefillTriggerTimestamp = System.currentTimeMillis();
+ }
+ }
+ }
+
+ if (mUsapPoolRefillAction != UsapPoolRefillAction.NONE) {
+ int[] sessionSocketRawFDs =
+ socketFDs.subList(1, socketFDs.size())
+ .stream()
+ .mapToInt(FileDescriptor::getInt$)
+ .toArray();
+
+ final boolean isPriorityRefill =
+ mUsapPoolRefillAction == UsapPoolRefillAction.IMMEDIATE;
+
+ final Runnable command =
+ fillUsapPool(sessionSocketRawFDs, isPriorityRefill);
+
+ if (command != null) {
+ return command;
+ } else if (isPriorityRefill) {
+ // Schedule a delayed refill to finish refilling the pool.
+ mUsapPoolRefillTriggerTimestamp = System.currentTimeMillis();
+ }
+ }
+ }
+ }
+}
diff --git a/android-35/com/android/internal/os/anr/AnrLatencyTracker.java b/android-35/com/android/internal/os/anr/AnrLatencyTracker.java
new file mode 100644
index 0000000..e11067d
--- /dev/null
+++ b/android-35/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -0,0 +1,607 @@
+/*
+ * Copyright (C) 2022 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.internal.os.anr;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import static com.android.internal.os.TimeoutRecord.TimeoutKind;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__BROADCAST_OF_INTENT;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__CONTENT_PROVIDER_NOT_RESPONDING;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__FGS_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT_NO_FOCUSED_WINDOW;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__START_FOREGROUND_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
+
+import android.annotation.IntDef;
+import android.os.SystemClock;
+import android.os.Trace;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Store different latencies from the ANR flow and trace functions, it records latency breakdown
+ * for key methods, lock acquisition and other potentially expensive operations in the ANR
+ * reporting flow and exports the data as comma separated text on calling
+ * dumpAsCommaSeparatedArrayWithHeader and as an atom to statsd on being closed.
+ */
+public class AnrLatencyTracker implements AutoCloseable {
+
+ /** Status of the early dumped pid. */
+ @IntDef(value = {
+ EarlyDumpStatus.UNKNOWN,
+ EarlyDumpStatus.SUCCEEDED,
+ EarlyDumpStatus.FAILED_TO_CREATE_FILE,
+ EarlyDumpStatus.TIMED_OUT
+ })
+
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface EarlyDumpStatus {
+ int UNKNOWN = 1;
+ int SUCCEEDED = 2;
+ int FAILED_TO_CREATE_FILE = 3;
+ int TIMED_OUT = 4;
+ }
+
+ private static final AtomicInteger sNextAnrRecordPlacedOnQueueCookieGenerator =
+ new AtomicInteger();
+
+ private long mAnrTriggerUptime;
+ private long mEndUptime;
+
+ private long mAppNotRespondingStartUptime;
+ private long mAnrRecordPlacedOnQueueUptime;
+ private long mAnrProcessingStartedUptime;
+ private long mDumpStackTracesStartUptime;
+
+ private long mUpdateCpuStatsNowLastCallUptime;
+ private long mUpdateCpuStatsNowTotalLatency = 0;
+ private long mCurrentPsiStateLastCallUptime;
+ private long mCurrentPsiStateTotalLatency = 0;
+ private long mProcessCpuTrackerMethodsLastCallUptime;
+ private long mProcessCpuTrackerMethodsTotalLatency = 0;
+ private long mCriticalEventLoglastCallUptime;
+ private long mCriticalEventLogTotalLatency = 0;
+
+ private long mGlobalLockLastTryAcquireStart;
+ private long mGlobalLockTotalContention = 0;
+ private long mPidLockLastTryAcquireStart;
+ private long mPidLockTotalContention = 0;
+ private long mAMSLockLastTryAcquireStart;
+ private long mAMSLockTotalContention = 0;
+ private long mProcLockLastTryAcquireStart;
+ private long mProcLockTotalContention = 0;
+ private long mAnrRecordLastTryAcquireStart;
+ private long mAnrRecordLockTotalContention = 0;
+
+ private int mAnrQueueSize;
+ private int mAnrType;
+ private final AtomicInteger mDumpedProcessesCount = new AtomicInteger(0);
+
+ private volatile @EarlyDumpStatus int mEarlyDumpStatus =
+ EarlyDumpStatus.UNKNOWN;
+ private volatile long mTempFileDumpingStartUptime;
+ private volatile long mTempFileDumpingDuration = 0;
+ private long mCopyingFirstPidStartUptime;
+ private long mCopyingFirstPidDuration = 0;
+ private long mEarlyDumpRequestSubmissionUptime = 0;
+ private long mEarlyDumpExecutorPidCount = 0;
+
+ private long mFirstPidsDumpingStartUptime;
+ private long mFirstPidsDumpingDuration = 0;
+ private long mNativePidsDumpingStartUptime;
+ private long mNativePidsDumpingDuration = 0;
+ private long mExtraPidsDumpingStartUptime;
+ private long mExtraPidsDumpingDuration = 0;
+
+ private boolean mIsPushed = false;
+ private boolean mIsSkipped = false;
+ private boolean mCopyingFirstPidSucceeded = false;
+
+ private long mPreDumpIfLockTooSlowStartUptime;
+ private long mPreDumpIfLockTooSlowDuration = 0;
+ private long mNotifyAppUnresponsiveStartUptime;
+ private long mNotifyAppUnresponsiveDuration = 0;
+ private long mNotifyWindowUnresponsiveStartUptime;
+ private long mNotifyWindowUnresponsiveDuration = 0;
+
+ private final int mAnrRecordPlacedOnQueueCookie =
+ sNextAnrRecordPlacedOnQueueCookieGenerator.incrementAndGet();
+
+ public AnrLatencyTracker(@TimeoutKind int timeoutKind, long anrTriggerUptime) {
+ mAnrTriggerUptime = anrTriggerUptime;
+ mAnrType = timeoutKindToAnrType(timeoutKind);
+
+ }
+
+ /** Records the start of AnrHelper#appNotResponding. */
+ public void appNotRespondingStarted() {
+ mAppNotRespondingStartUptime = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
+ "AnrHelper#appNotResponding()");
+ }
+
+ /** Records the end of AnrHelper#appNotResponding. */
+ public void appNotRespondingEnded() {
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /**
+ * Records the number of processes we are currently early-dumping, this number includes the
+ * current ANR's main process.
+ */
+ public void earlyDumpRequestSubmittedWithSize(int currentProcessedPidCount) {
+ mEarlyDumpRequestSubmissionUptime = getUptimeMillis();
+ mEarlyDumpExecutorPidCount = currentProcessedPidCount;
+ }
+
+ /** Records the placing of the AnrHelper.AnrRecord instance on the processing queue. */
+ public void anrRecordPlacingOnQueueWithSize(int queueSize) {
+ mAnrRecordPlacedOnQueueUptime = getUptimeMillis();
+ Trace.asyncTraceBegin(TRACE_TAG_ACTIVITY_MANAGER,
+ "anrRecordPlacedOnQueue", mAnrRecordPlacedOnQueueCookie);
+ mAnrQueueSize = queueSize;
+ // Since we are recording the anr record queue size after pushing the current
+ // record, we need to increment the current queue size by 1
+ Trace.traceCounter(TRACE_TAG_ACTIVITY_MANAGER, "anrRecordsQueueSize", queueSize + 1);
+ }
+
+ /** Records the start of ProcessErrorStateRecord#appNotResponding. */
+ public void anrProcessingStarted() {
+ mAnrProcessingStartedUptime = getUptimeMillis();
+ Trace.asyncTraceEnd(TRACE_TAG_ACTIVITY_MANAGER,
+ "anrRecordPlacedOnQueue", mAnrRecordPlacedOnQueueCookie);
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
+ "anrProcessing");
+ }
+
+ /** Records the end of ProcessErrorStateRecord#appNotResponding, the tracker is closed here. */
+ public void anrProcessingEnded() {
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ close();
+ }
+
+ /** Records the start of StackTracesDumpHelper#dumpStackTraces. */
+ public void dumpStackTracesStarted() {
+ mDumpStackTracesStartUptime = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
+ "dumpStackTraces()");
+ }
+
+ /** Records the end of StackTracesDumpHelper#dumpStackTraces. */
+ public void dumpStackTracesEnded() {
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of ActivityManagerService#updateCpuStatsNow. */
+ public void updateCpuStatsNowCalled() {
+ mUpdateCpuStatsNowLastCallUptime = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "updateCpuStatsNow()");
+ }
+
+ /** Records the return of ActivityManagerService#updateCpuStatsNow. */
+ public void updateCpuStatsNowReturned() {
+ mUpdateCpuStatsNowTotalLatency +=
+ getUptimeMillis() - mUpdateCpuStatsNowLastCallUptime;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of ResourcePressureUtil#currentPsiState. */
+ public void currentPsiStateCalled() {
+ mCurrentPsiStateLastCallUptime = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "currentPsiState()");
+ }
+
+ /** Records the return of ResourcePressureUtil#currentPsiState. */
+ public void currentPsiStateReturned() {
+ mCurrentPsiStateTotalLatency += getUptimeMillis() - mCurrentPsiStateLastCallUptime;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of ProcessCpuTracker methods. */
+ public void processCpuTrackerMethodsCalled() {
+ mProcessCpuTrackerMethodsLastCallUptime = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "processCpuTracker");
+ }
+
+ /** Records the return of ProcessCpuTracker methods. */
+ public void processCpuTrackerMethodsReturned() {
+ mProcessCpuTrackerMethodsTotalLatency +=
+ getUptimeMillis() - mProcessCpuTrackerMethodsLastCallUptime;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of ANR headers dumping to file (subject and criticalEventSection). */
+ public void criticalEventLogStarted() {
+ mCriticalEventLoglastCallUptime = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "criticalEventLog");
+ }
+
+ /** Records the end of ANR headers dumping to file (subject and criticalEventSection). */
+ public void criticalEventLogEnded() {
+ mCriticalEventLogTotalLatency +=
+ getUptimeMillis() - mCriticalEventLoglastCallUptime;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of native pid collection. */
+ public void nativePidCollectionStarted() {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "nativePidCollection");
+ }
+
+ /** Records the end of native pid collection. */
+ public void nativePidCollectionEnded() {
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of pid dumping to file. */
+ public void dumpingPidStarted(int pid) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dumpingPid#" + pid);
+ }
+
+ /** Records the end of pid dumping to file. */
+ public void dumpingPidEnded() {
+ mDumpedProcessesCount.incrementAndGet();
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of first pids dumping to file. */
+ public void dumpingFirstPidsStarted() {
+ mFirstPidsDumpingStartUptime = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dumpingFirstPids");
+ }
+
+ /** Records the end of first pids dumping to file. */
+ public void dumpingFirstPidsEnded() {
+ mFirstPidsDumpingDuration = getUptimeMillis() - mFirstPidsDumpingStartUptime;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ /** Records the start of the copying of the pre-dumped first pid. */
+ public void copyingFirstPidStarted() {
+ mCopyingFirstPidStartUptime = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "copyingFirstPid");
+ }
+
+ /** Records the end of the copying of the pre-dumped first pid. */
+ public void copyingFirstPidEnded(boolean copySucceeded) {
+ mCopyingFirstPidDuration = getUptimeMillis() - mCopyingFirstPidStartUptime;
+ mCopyingFirstPidSucceeded = copySucceeded;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of pre-dumping. */
+ public void dumpStackTracesTempFileStarted() {
+ mTempFileDumpingStartUptime = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dumpStackTracesTempFile");
+ }
+
+ /** Records the end of pre-dumping. */
+ public void dumpStackTracesTempFileEnded() {
+ mTempFileDumpingDuration = getUptimeMillis() - mTempFileDumpingStartUptime;
+ if (mEarlyDumpStatus == EarlyDumpStatus.UNKNOWN) {
+ mEarlyDumpStatus = EarlyDumpStatus.SUCCEEDED;
+ }
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records file creation failure events in dumpStackTracesTempFile. */
+ public void dumpStackTracesTempFileCreationFailed() {
+ mEarlyDumpStatus = EarlyDumpStatus.FAILED_TO_CREATE_FILE;
+ Trace.instant(TRACE_TAG_ACTIVITY_MANAGER, "dumpStackTracesTempFileCreationFailed");
+ }
+
+ /** Records timeout events in dumpStackTracesTempFile. */
+ public void dumpStackTracesTempFileTimedOut() {
+ mEarlyDumpStatus = EarlyDumpStatus.TIMED_OUT;
+ Trace.instant(TRACE_TAG_ACTIVITY_MANAGER, "dumpStackTracesTempFileTimedOut");
+ }
+
+ /** Records the start of native pids dumping to file. */
+ public void dumpingNativePidsStarted() {
+ mNativePidsDumpingStartUptime = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dumpingNativePids");
+ }
+
+ /** Records the end of native pids dumping to file . */
+ public void dumpingNativePidsEnded() {
+ mNativePidsDumpingDuration = getUptimeMillis() - mNativePidsDumpingStartUptime;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of extra pids dumping to file. */
+ public void dumpingExtraPidsStarted() {
+ mExtraPidsDumpingStartUptime = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dumpingExtraPids");
+ }
+
+ /** Records the end of extra pids dumping to file. */
+ public void dumpingExtraPidsEnded() {
+ mExtraPidsDumpingDuration = getUptimeMillis() - mExtraPidsDumpingStartUptime;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of contention on ActivityManagerService.mGlobalLock. */
+ public void waitingOnGlobalLockStarted() {
+ mGlobalLockLastTryAcquireStart = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "globalLock");
+ }
+
+ /** Records the end of contention on ActivityManagerService.mGlobalLock. */
+ public void waitingOnGlobalLockEnded() {
+ mGlobalLockTotalContention += getUptimeMillis() - mGlobalLockLastTryAcquireStart;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of contention on ActivityManagerService.mPidsSelfLocked. */
+ public void waitingOnPidLockStarted() {
+ mPidLockLastTryAcquireStart = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "pidLockContention");
+ }
+
+ /** Records the end of contention on ActivityManagerService.mPidsSelfLocked. */
+ public void waitingOnPidLockEnded() {
+ mPidLockTotalContention += getUptimeMillis() - mPidLockLastTryAcquireStart;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of contention on ActivityManagerService. */
+ public void waitingOnAMSLockStarted() {
+ mAMSLockLastTryAcquireStart = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "AMSLockContention");
+ }
+
+ /** Records the end of contention on ActivityManagerService. */
+ public void waitingOnAMSLockEnded() {
+ mAMSLockTotalContention += getUptimeMillis() - mAMSLockLastTryAcquireStart;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of contention on ActivityManagerService.mProcLock. */
+ public void waitingOnProcLockStarted() {
+ mProcLockLastTryAcquireStart = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "procLockContention");
+ }
+
+ /** Records the start of contention on ActivityManagerService.mProcLock. */
+ public void waitingOnProcLockEnded() {
+ mProcLockTotalContention += getUptimeMillis() - mProcLockLastTryAcquireStart;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of contention on AnrHelper.mAnrRecords. */
+ public void waitingOnAnrRecordLockStarted() {
+ mAnrRecordLastTryAcquireStart = getUptimeMillis();
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "anrRecordLockContention");
+ }
+
+ /** Records the end of contention on AnrHelper.mAnrRecords. */
+ public void waitingOnAnrRecordLockEnded() {
+ mAnrRecordLockTotalContention +=
+ getUptimeMillis() - mAnrRecordLastTryAcquireStart;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Counts the number of records in the records queue when the ANR record is popped. */
+ public void anrRecordsQueueSizeWhenPopped(int queueSize) {
+ Trace.traceCounter(TRACE_TAG_ACTIVITY_MANAGER, "anrRecordsQueueSize", queueSize);
+ }
+
+ /** Records the start of AnrController#preDumpIfLockTooSlow. */
+ public void preDumpIfLockTooSlowStarted() {
+ mPreDumpIfLockTooSlowStartUptime = getUptimeMillis();
+ }
+
+ /** Records the end of AnrController#preDumpIfLockTooSlow. */
+ public void preDumpIfLockTooSlowEnded() {
+ mPreDumpIfLockTooSlowDuration +=
+ getUptimeMillis() - mPreDumpIfLockTooSlowStartUptime;
+ }
+
+ /** Records a skipped ANR in ProcessErrorStateRecord#appNotResponding. */
+ public void anrSkippedProcessErrorStateRecordAppNotResponding() {
+ anrSkipped("appNotResponding");
+ }
+
+ /** Records a skipped ANR in StackTracesDumpHelper#dumpStackTraces. */
+ public void anrSkippedDumpStackTraces() {
+ anrSkipped("dumpStackTraces");
+ }
+
+ /** Records the start of AnrController#notifyAppUnresponsive. */
+ public void notifyAppUnresponsiveStarted() {
+ mNotifyAppUnresponsiveStartUptime = getUptimeMillis();
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "notifyAppUnresponsive()");
+ }
+
+ /** Records the end of AnrController#notifyAppUnresponsive. */
+ public void notifyAppUnresponsiveEnded() {
+ mNotifyAppUnresponsiveDuration = getUptimeMillis() - mNotifyAppUnresponsiveStartUptime;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of AnrController#notifyWindowUnresponsive. */
+ public void notifyWindowUnresponsiveStarted() {
+ mNotifyWindowUnresponsiveStartUptime = getUptimeMillis();
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "notifyWindowUnresponsive()");
+ }
+
+ /** Records the end of AnrController#notifyWindowUnresponsive. */
+ public void notifyWindowUnresponsiveEnded() {
+ mNotifyWindowUnresponsiveDuration = getUptimeMillis()
+ - mNotifyWindowUnresponsiveStartUptime;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /**
+ * Returns latency data as a comma separated value string for inclusion in ANR report.
+ */
+ public String dumpAsCommaSeparatedArrayWithHeader() {
+ return "DurationsV5: " + mAnrTriggerUptime
+ /* triggering_to_app_not_responding_duration = */
+ + "," + (mAppNotRespondingStartUptime - mAnrTriggerUptime)
+ /* app_not_responding_duration = */
+ + "," + (mAnrRecordPlacedOnQueueUptime - mAppNotRespondingStartUptime)
+ /* anr_record_placed_on_queue_duration = */
+ + "," + (mAnrProcessingStartedUptime - mAnrRecordPlacedOnQueueUptime)
+ /* anr_processing_duration = */
+ + "," + (mDumpStackTracesStartUptime - mAnrProcessingStartedUptime)
+
+ /* update_cpu_stats_now_total_duration = */
+ + "," + mUpdateCpuStatsNowTotalLatency
+ /* current_psi_state_total_duration = */
+ + "," + mCurrentPsiStateTotalLatency
+ /* process_cpu_tracker_methods_total_duration = */
+ + "," + mProcessCpuTrackerMethodsTotalLatency
+ /* critical_event_log_duration = */
+ + "," + mCriticalEventLogTotalLatency
+
+ /* global_lock_total_contention = */
+ + "," + mGlobalLockTotalContention
+ /* pid_lock_total_contention = */
+ + "," + mPidLockTotalContention
+ /* ams_lock_total_contention = */
+ + "," + mAMSLockTotalContention
+ /* proc_lock_total_contention = */
+ + "," + mProcLockTotalContention
+ /* anr_record_lock_total_contention = */
+ + "," + mAnrRecordLockTotalContention
+
+ /* anr_queue_size_when_pushed = */
+ + "," + mAnrQueueSize
+ /* dump_stack_traces_io_time = */
+ // We use copyingFirstPidUptime if we're dumping the durations list before the
+ // first pids ie after copying the early dump stacks.
+ + "," + ((mFirstPidsDumpingStartUptime > 0 ? mFirstPidsDumpingStartUptime
+ : mCopyingFirstPidStartUptime) - mDumpStackTracesStartUptime)
+ /* temp_file_dump_duration = */
+ + "," + mTempFileDumpingDuration
+ /* temp_dump_request_on_queue_duration = */
+ + "," + (mTempFileDumpingStartUptime - mEarlyDumpRequestSubmissionUptime)
+ /* temp_dump_pid_count_when_pushed = */
+ + "," + mEarlyDumpExecutorPidCount
+ /* first_pid_copying_time = */
+ + "," + mCopyingFirstPidDuration
+ /* early_dump_status = */
+ + "," + mEarlyDumpStatus
+ /* copying_first_pid_succeeded = */
+ + "," + (mCopyingFirstPidSucceeded ? 1 : 0)
+ /* preDumpIfLockTooSlow_duration = */
+ + "," + mPreDumpIfLockTooSlowDuration
+ /* notifyAppUnresponsive_duration = */
+ + "," + mNotifyAppUnresponsiveDuration
+ /* notifyWindowUnresponsive_duration = */
+ + "," + mNotifyWindowUnresponsiveDuration
+ + "\n\n";
+
+ }
+
+ /**
+ * Closes the ANR latency instance by writing the atom to statsd, this method is idempotent.
+ */
+ @Override
+ public void close() {
+ if (!mIsSkipped && !mIsPushed) {
+ mEndUptime = getUptimeMillis();
+ pushAtom();
+ mIsPushed = true;
+ }
+ }
+
+ private static int timeoutKindToAnrType(@TimeoutKind int timeoutKind) {
+ switch (timeoutKind) {
+ case TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW:
+ return ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT_NO_FOCUSED_WINDOW;
+ case TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE:
+ return ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT;
+ case TimeoutKind.BROADCAST_RECEIVER:
+ return ANRLATENCY_REPORTED__ANR_TYPE__BROADCAST_OF_INTENT;
+ case TimeoutKind.SERVICE_START:
+ return ANRLATENCY_REPORTED__ANR_TYPE__START_FOREGROUND_SERVICE;
+ case TimeoutKind.SERVICE_EXEC:
+ return ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE;
+ case TimeoutKind.CONTENT_PROVIDER:
+ return ANRLATENCY_REPORTED__ANR_TYPE__CONTENT_PROVIDER_NOT_RESPONDING;
+ case TimeoutKind.SHORT_FGS_TIMEOUT:
+ return ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
+ case TimeoutKind.JOB_SERVICE:
+ return ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE;
+ case TimeoutKind.FGS_TIMEOUT:
+ return ANRLATENCY_REPORTED__ANR_TYPE__FGS_TIMEOUT;
+ default:
+ return ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
+ }
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public long getUptimeMillis() {
+ return SystemClock.uptimeMillis();
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public void pushAtom() {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.ANR_LATENCY_REPORTED,
+
+ /* total_duration = */ mEndUptime - mAnrTriggerUptime,
+ /* triggering_to_stack_dump_duration = */
+ mFirstPidsDumpingStartUptime - mAnrTriggerUptime,
+ /* triggering_to_app_not_responding_duration = */
+ mAppNotRespondingStartUptime - mAnrTriggerUptime,
+ /* app_not_responding_duration = */
+ mAnrRecordPlacedOnQueueUptime - mAppNotRespondingStartUptime,
+ /* anr_record_placed_on_queue_duration = */
+ mAnrProcessingStartedUptime - mAnrRecordPlacedOnQueueUptime,
+ /* anr_processing_duration = */
+ mDumpStackTracesStartUptime - mAnrProcessingStartedUptime,
+ /* dump_stack_traces_duration = */ mFirstPidsDumpingDuration
+ + mNativePidsDumpingDuration
+ + mExtraPidsDumpingDuration,
+
+ /* update_cpu_stats_now_total_duration = */ mUpdateCpuStatsNowTotalLatency,
+ /* current_psi_state_total_duration = */ mCurrentPsiStateTotalLatency,
+ /* process_cpu_tracker_methods_total_duration = */
+ mProcessCpuTrackerMethodsTotalLatency,
+ /* critical_event_log_duration = */ mCriticalEventLogTotalLatency,
+
+ /* global_lock_total_contention = */ mGlobalLockTotalContention,
+ /* pid_lock_total_contention = */ mPidLockTotalContention,
+ /* ams_lock_total_contention = */ mAMSLockTotalContention,
+ /* proc_lock_total_contention = */ mProcLockTotalContention,
+ /* anr_record_lock_total_contention = */ mAnrRecordLockTotalContention,
+
+ /* anr_queue_size_when_pushed = */ mAnrQueueSize,
+ /* anr_type = */ mAnrType,
+ /* dumped_processes_count = */ mDumpedProcessesCount.get());
+ }
+
+ private void anrSkipped(String method) {
+ Trace.instant(TRACE_TAG_ACTIVITY_MANAGER, "AnrSkipped@" + method);
+ mIsSkipped = true;
+ }
+}
diff --git a/android-35/com/android/internal/os/logging/MetricsLoggerWrapper.java b/android-35/com/android/internal/os/logging/MetricsLoggerWrapper.java
new file mode 100644
index 0000000..b42ea7d
--- /dev/null
+++ b/android-35/com/android/internal/os/logging/MetricsLoggerWrapper.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 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.internal.os.logging;
+
+import android.view.WindowManager.LayoutParams;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Used to wrap different logging calls in one, so that client side code base is clean and more
+ * readable.
+ */
+public class MetricsLoggerWrapper {
+
+ public static void logAppOverlayEnter(int uid, String packageName, boolean changed, int type, boolean usingAlertWindow) {
+ if (changed) {
+ if (type != LayoutParams.TYPE_APPLICATION_OVERLAY) {
+ FrameworkStatsLog.write(FrameworkStatsLog.OVERLAY_STATE_CHANGED, uid, packageName,
+ true, FrameworkStatsLog.OVERLAY_STATE_CHANGED__STATE__ENTERED);
+ } else if (!usingAlertWindow){
+ FrameworkStatsLog.write(FrameworkStatsLog.OVERLAY_STATE_CHANGED, uid, packageName,
+ false, FrameworkStatsLog.OVERLAY_STATE_CHANGED__STATE__ENTERED);
+ }
+ }
+ }
+
+ public static void logAppOverlayExit(int uid, String packageName, boolean changed, int type, boolean usingAlertWindow) {
+ if (changed) {
+ if (type != LayoutParams.TYPE_APPLICATION_OVERLAY) {
+ FrameworkStatsLog.write(FrameworkStatsLog.OVERLAY_STATE_CHANGED, uid, packageName,
+ true, FrameworkStatsLog.OVERLAY_STATE_CHANGED__STATE__EXITED);
+ } else if (!usingAlertWindow){
+ FrameworkStatsLog.write(FrameworkStatsLog.OVERLAY_STATE_CHANGED, uid, packageName,
+ false, FrameworkStatsLog.OVERLAY_STATE_CHANGED__STATE__EXITED);
+ }
+ }
+ }
+}