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 &gt;= {@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> [--] &lt;start class name&gt;  &lt;args&gt;
+     * </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 &ge; 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 &lt;command&gt;" 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> [--] &lt;args for RuntimeInit &gt;
+ * </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 &gt; 0 or indication of failed fork
+     * if &lt; 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> [--] &lt;start class name&gt;  &lt;args&gt;
+     * </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);
+            }
+        }
+    }
+}