Import Android SDK Platform PI [4335822]

/google/data/ro/projects/android/fetch_artifact \
    --bid 4335822 \
    --target sdk_phone_armv7-win_sdk \
    sdk-repo-linux-sources-4335822.zip

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: Ic8f04be005a71c2b9abeaac754d8da8d6f9a2c32
diff --git a/com/android/internal/os/AndroidPrintStream.java b/com/android/internal/os/AndroidPrintStream.java
new file mode 100644
index 0000000..7f4807a
--- /dev/null
+++ b/com/android/internal/os/AndroidPrintStream.java
@@ -0,0 +1,49 @@
+/*
+ * 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.util.Log;
+
+/**
+ * Print stream which log lines using Android's logging system.
+ *
+ * {@hide}
+ */
+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
+     */
+    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/com/android/internal/os/AppFuseMount.java b/com/android/internal/os/AppFuseMount.java
new file mode 100644
index 0000000..04d7211
--- /dev/null
+++ b/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));
+        }
+
+        @Override
+        public AppFuseMount[] newArray(int size) {
+            return new AppFuseMount[size];
+        }
+    };
+}
diff --git a/com/android/internal/os/AtomicFile.java b/com/android/internal/os/AtomicFile.java
new file mode 100644
index 0000000..5a83f33
--- /dev/null
+++ b/com/android/internal/os/AtomicFile.java
@@ -0,0 +1,177 @@
+/*
+ * 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.os.FileUtils;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Helper class for performing atomic operations on a file, by creating a
+ * backup file until a write has successfully completed.
+ * <p>
+ * Atomic file guarantees file integrity by ensuring that a file has
+ * been completely written and sync'd to disk before removing its backup.
+ * As long as the backup file exists, the original file is considered
+ * to be invalid (left over from a previous attempt to write the file).
+ * </p><p>
+ * Atomic file does not confer any file locking semantics.
+ * Do not use this class when the file 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 file.
+ * </p>
+ */
+public final class AtomicFile {
+    private final File mBaseName;
+    private final File mBackupName;
+    
+    public AtomicFile(File baseName) {
+        mBaseName = baseName;
+        mBackupName = new File(baseName.getPath() + ".bak");
+    }
+    
+    public File getBaseFile() {
+        return mBaseName;
+    }
+    
+    public FileOutputStream startWrite() throws IOException {
+        // Rename the current file so it may be used as a backup during the next read
+        if (mBaseName.exists()) {
+            if (!mBackupName.exists()) {
+                if (!mBaseName.renameTo(mBackupName)) {
+                    Log.w("AtomicFile", "Couldn't rename file " + mBaseName
+                            + " to backup file " + mBackupName);
+                }
+            } else {
+                mBaseName.delete();
+            }
+        }
+        FileOutputStream str = null;
+        try {
+            str = new FileOutputStream(mBaseName);
+        } catch (FileNotFoundException e) {
+            File parent = mBaseName.getParentFile();
+            if (!parent.mkdir()) {
+                throw new IOException("Couldn't create directory " + mBaseName);
+            }
+            FileUtils.setPermissions(
+                parent.getPath(),
+                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                -1, -1);
+            try {
+                str = new FileOutputStream(mBaseName);
+            } catch (FileNotFoundException e2) {
+                throw new IOException("Couldn't create " + mBaseName);
+            }
+        }
+        return str;
+    }
+    
+    public void finishWrite(FileOutputStream str) {
+        if (str != null) {
+            FileUtils.sync(str);
+            try {
+                str.close();
+                mBackupName.delete();
+            } catch (IOException e) {
+                Log.w("AtomicFile", "finishWrite: Got exception:", e);
+            }
+        }
+    }
+    
+    public void failWrite(FileOutputStream str) {
+        if (str != null) {
+            FileUtils.sync(str);
+            try {
+                str.close();
+                mBaseName.delete();
+                mBackupName.renameTo(mBaseName);
+            } catch (IOException e) {
+                Log.w("AtomicFile", "failWrite: Got exception:", e);
+            }
+        }
+    }
+    
+    public FileOutputStream openAppend() throws IOException {
+        try {
+            return new FileOutputStream(mBaseName, true);
+        } catch (FileNotFoundException e) {
+            throw new IOException("Couldn't append " + mBaseName);
+        }
+    }
+    
+    public void truncate() throws IOException {
+        try {
+            FileOutputStream fos = new FileOutputStream(mBaseName);
+            FileUtils.sync(fos);
+            fos.close();
+        } catch (FileNotFoundException e) {
+            throw new IOException("Couldn't append " + mBaseName);
+        } catch (IOException e) {
+        }
+    }
+
+    public boolean exists() {
+        return mBaseName.exists() || mBackupName.exists();
+    }
+
+    public void delete() {
+        mBaseName.delete();
+        mBackupName.delete();
+    }
+
+    public FileInputStream openRead() throws FileNotFoundException {
+        if (mBackupName.exists()) {
+            mBaseName.delete();
+            mBackupName.renameTo(mBaseName);
+        }
+        return new FileInputStream(mBaseName);
+    }
+    
+    public byte[] readFully() throws IOException {
+        FileInputStream stream = openRead();
+        try {
+            int pos = 0;
+            int avail = stream.available();
+            byte[] data = new byte[avail];
+            while (true) {
+                int amt = stream.read(data, pos, data.length-pos);
+                //Log.i("foo", "Read " + amt + " bytes at " + pos
+                //        + " of avail " + data.length);
+                if (amt <= 0) {
+                    //Log.i("foo", "**** FINISHED READING: pos=" + pos
+                    //        + " len=" + data.length);
+                    return data;
+                }
+                pos += amt;
+                avail = stream.available();
+                if (avail > data.length-pos) {
+                    byte[] newData = new byte[pos+avail];
+                    System.arraycopy(data, 0, newData, 0, pos);
+                    data = newData;
+                }
+            }
+        } finally {
+            stream.close();
+        }
+    }
+}
diff --git a/com/android/internal/os/BackgroundThread.java b/com/android/internal/os/BackgroundThread.java
new file mode 100644
index 0000000..cffba01
--- /dev/null
+++ b/com/android/internal/os/BackgroundThread.java
@@ -0,0 +1,56 @@
+/*
+ * 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.HandlerThread;
+import android.os.Trace;
+
+/**
+ * Shared singleton background thread for each process.
+ */
+public final class BackgroundThread extends HandlerThread {
+    private static BackgroundThread sInstance;
+    private static Handler sHandler;
+
+    private BackgroundThread() {
+        super("android.bg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+    }
+
+    private static void ensureThreadLocked() {
+        if (sInstance == null) {
+            sInstance = new BackgroundThread();
+            sInstance.start();
+            sInstance.getLooper().setTraceTag(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+            sHandler = new Handler(sInstance.getLooper());
+        }
+    }
+
+    public static BackgroundThread get() {
+        synchronized (BackgroundThread.class) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+
+    public static Handler getHandler() {
+        synchronized (BackgroundThread.class) {
+            ensureThreadLocked();
+            return sHandler;
+        }
+    }
+}
diff --git a/com/android/internal/os/BaseCommand.java b/com/android/internal/os/BaseCommand.java
new file mode 100644
index 0000000..3baccee
--- /dev/null
+++ b/com/android/internal/os/BaseCommand.java
@@ -0,0 +1,122 @@
+/*
+**
+** 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.os.ShellCommand;
+
+import java.io.PrintStream;
+
+public abstract class BaseCommand {
+
+    final protected ShellCommand mArgs = new ShellCommand() {
+        @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;
+
+    /**
+     * 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, null, 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();
+    }
+
+    /**
+     * 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/com/android/internal/os/BatterySipper.java b/com/android/internal/os/BatterySipper.java
new file mode 100644
index 0000000..5457c1d
--- /dev/null
+++ b/com/android/internal/os/BatterySipper.java
@@ -0,0 +1,227 @@
+/*
+ * 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.os.BatteryStats.Uid;
+
+import java.util.List;
+
+/**
+ * Contains power usage of an application, system service, or hardware type.
+ */
+public class BatterySipper implements Comparable<BatterySipper> {
+    public int userId;
+    public Uid uidObj;
+    public DrainType drainType;
+
+    /**
+     * Smeared power from screen usage.
+     * We split the screen usage power and smear them among apps, based on activity time.
+     */
+    public double screenPowerMah;
+
+    /**
+     * Smeared power using proportional method.
+     *
+     * we smear power usage from hidden sippers to all apps proportionally.(except for screen usage)
+     *
+     * @see BatteryStatsHelper#shouldHideSipper(BatterySipper)
+     * @see BatteryStatsHelper#removeHiddenBatterySippers(List)
+     */
+    public double proportionalSmearMah;
+
+    /**
+     * Total power that adding the smeared power.
+     *
+     * @see #sumPower()
+     */
+    public double totalSmearedPowerMah;
+
+    /**
+     * Total power before smearing
+     */
+    public double totalPowerMah;
+
+    /**
+     * Whether we should hide this sipper
+     *
+     * @see BatteryStatsHelper#shouldHideSipper(BatterySipper)
+     */
+    public boolean shouldHide;
+
+    /**
+     * Generic usage time in milliseconds.
+     */
+    public long usageTimeMs;
+
+    /**
+     * Generic power usage in mAh.
+     */
+    public double usagePowerMah;
+
+    // Subsystem usage times.
+    public long cpuTimeMs;
+    public long gpsTimeMs;
+    public long wifiRunningTimeMs;
+    public long cpuFgTimeMs;
+    public long wakeLockTimeMs;
+    public long cameraTimeMs;
+    public long flashlightTimeMs;
+    public long bluetoothRunningTimeMs;
+
+    public long mobileRxPackets;
+    public long mobileTxPackets;
+    public long mobileActive;
+    public int mobileActiveCount;
+    public double mobilemspp;         // milliseconds per packet
+    public long wifiRxPackets;
+    public long wifiTxPackets;
+    public long mobileRxBytes;
+    public long mobileTxBytes;
+    public long wifiRxBytes;
+    public long wifiTxBytes;
+    public long btRxBytes;
+    public long btTxBytes;
+    public double percent;
+    public double noCoveragePercent;
+    public String[] mPackages;
+    public String packageWithHighestDrain;
+
+    // Measured in mAh (milli-ampere per hour).
+    // These are included when summed.
+    public double wifiPowerMah;
+    public double cpuPowerMah;
+    public double wakeLockPowerMah;
+    public double mobileRadioPowerMah;
+    public double gpsPowerMah;
+    public double sensorPowerMah;
+    public double cameraPowerMah;
+    public double flashlightPowerMah;
+    public double bluetoothPowerMah;
+
+    public enum DrainType {
+        IDLE,
+        CELL,
+        PHONE,
+        WIFI,
+        BLUETOOTH,
+        FLASHLIGHT,
+        SCREEN,
+        APP,
+        USER,
+        UNACCOUNTED,
+        OVERCOUNTED,
+        CAMERA,
+        MEMORY
+    }
+
+    public BatterySipper(DrainType drainType, Uid uid, double value) {
+        this.totalPowerMah = value;
+        this.drainType = drainType;
+        uidObj = uid;
+    }
+
+    public void computeMobilemspp() {
+        long packets = mobileRxPackets + mobileTxPackets;
+        mobilemspp = packets > 0 ? (mobileActive / (double) packets) : 0;
+    }
+
+    @Override
+    public int compareTo(BatterySipper other) {
+        // Over-counted always goes to the bottom.
+        if (drainType != other.drainType) {
+            if (drainType == DrainType.OVERCOUNTED) {
+                // This is "larger"
+                return 1;
+            } else if (other.drainType == DrainType.OVERCOUNTED) {
+                return -1;
+            }
+        }
+        // Return the flipped value because we want the items in descending order
+        return Double.compare(other.totalPowerMah, totalPowerMah);
+    }
+
+    /**
+     * Gets a list of packages associated with the current user
+     */
+    public String[] getPackages() {
+        return mPackages;
+    }
+
+    public int getUid() {
+        // Bail out if the current sipper is not an App sipper.
+        if (uidObj == null) {
+            return 0;
+        }
+        return uidObj.getUid();
+    }
+
+    /**
+     * Add stats from other to this BatterySipper.
+     */
+    public void add(BatterySipper other) {
+        totalPowerMah += other.totalPowerMah;
+        usageTimeMs += other.usageTimeMs;
+        usagePowerMah += other.usagePowerMah;
+        cpuTimeMs += other.cpuTimeMs;
+        gpsTimeMs += other.gpsTimeMs;
+        wifiRunningTimeMs += other.wifiRunningTimeMs;
+        cpuFgTimeMs += other.cpuFgTimeMs;
+        wakeLockTimeMs += other.wakeLockTimeMs;
+        cameraTimeMs += other.cameraTimeMs;
+        flashlightTimeMs += other.flashlightTimeMs;
+        bluetoothRunningTimeMs += other.bluetoothRunningTimeMs;
+        mobileRxPackets += other.mobileRxPackets;
+        mobileTxPackets += other.mobileTxPackets;
+        mobileActive += other.mobileActive;
+        mobileActiveCount += other.mobileActiveCount;
+        wifiRxPackets += other.wifiRxPackets;
+        wifiTxPackets += other.wifiTxPackets;
+        mobileRxBytes += other.mobileRxBytes;
+        mobileTxBytes += other.mobileTxBytes;
+        wifiRxBytes += other.wifiRxBytes;
+        wifiTxBytes += other.wifiTxBytes;
+        btRxBytes += other.btRxBytes;
+        btTxBytes += other.btTxBytes;
+        wifiPowerMah += other.wifiPowerMah;
+        gpsPowerMah += other.gpsPowerMah;
+        cpuPowerMah += other.cpuPowerMah;
+        sensorPowerMah += other.sensorPowerMah;
+        mobileRadioPowerMah += other.mobileRadioPowerMah;
+        wakeLockPowerMah += other.wakeLockPowerMah;
+        cameraPowerMah += other.cameraPowerMah;
+        flashlightPowerMah += other.flashlightPowerMah;
+        bluetoothPowerMah += other.bluetoothPowerMah;
+        screenPowerMah += other.screenPowerMah;
+        proportionalSmearMah += other.proportionalSmearMah;
+        totalSmearedPowerMah += other.totalSmearedPowerMah;
+    }
+
+    /**
+     * Sum all the powers and store the value into `value`.
+     * Also sum the {@code smearedTotalPowerMah} by adding smeared powerMah.
+     *
+     * @return the sum of all the power in this BatterySipper.
+     */
+    public double sumPower() {
+        totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah +
+                sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah +
+                flashlightPowerMah + bluetoothPowerMah;
+        totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah;
+
+        return totalPowerMah;
+    }
+}
diff --git a/com/android/internal/os/BatteryStatsHelper.java b/com/android/internal/os/BatteryStatsHelper.java
new file mode 100644
index 0000000..f085e29
--- /dev/null
+++ b/com/android/internal/os/BatteryStatsHelper.java
@@ -0,0 +1,1024 @@
+/*
+ * 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.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.hardware.SensorManager;
+import android.net.ConnectivityManager;
+import android.os.BatteryStats;
+import android.os.BatteryStats.Uid;
+import android.os.Bundle;
+import android.os.MemoryFile;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseLongArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.BatterySipper.DrainType;
+import com.android.internal.util.ArrayUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A helper class for retrieving the power usage information for all applications and services.
+ *
+ * The caller must initialize this class as soon as activity object is ready to use (for example, in
+ * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy().
+ */
+public class BatteryStatsHelper {
+    static final boolean DEBUG = false;
+
+    private static final String TAG = BatteryStatsHelper.class.getSimpleName();
+
+    private static BatteryStats sStatsXfer;
+    private static Intent sBatteryBroadcastXfer;
+    private static ArrayMap<File, BatteryStats> sFileXfer = new ArrayMap<>();
+
+    final private Context mContext;
+    final private boolean mCollectBatteryBroadcast;
+    final private boolean mWifiOnly;
+
+    private IBatteryStats mBatteryInfo;
+    private BatteryStats mStats;
+    private Intent mBatteryBroadcast;
+    private PowerProfile mPowerProfile;
+
+    private String[] mSystemPackageArray;
+    private String[] mServicepackageArray;
+    private PackageManager mPackageManager;
+
+    /**
+     * List of apps using power.
+     */
+    private final List<BatterySipper> mUsageList = new ArrayList<>();
+
+    /**
+     * List of apps using wifi power.
+     */
+    private final List<BatterySipper> mWifiSippers = new ArrayList<>();
+
+    /**
+     * List of apps using bluetooth power.
+     */
+    private final List<BatterySipper> mBluetoothSippers = new ArrayList<>();
+
+    private final SparseArray<List<BatterySipper>> mUserSippers = new SparseArray<>();
+
+    private final List<BatterySipper> mMobilemsppList = new ArrayList<>();
+
+    private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
+
+    long mRawRealtimeUs;
+    long mRawUptimeUs;
+    long mBatteryRealtimeUs;
+    long mBatteryUptimeUs;
+    long mTypeBatteryRealtimeUs;
+    long mTypeBatteryUptimeUs;
+    long mBatteryTimeRemainingUs;
+    long mChargeTimeRemainingUs;
+
+    private long mStatsPeriod = 0;
+
+    // The largest entry by power.
+    private double mMaxPower = 1;
+
+    // The largest real entry by power (not undercounted or overcounted).
+    private double mMaxRealPower = 1;
+
+    // Total computed power.
+    private double mComputedPower;
+    private double mTotalPower;
+    private double mMinDrainedPower;
+    private double mMaxDrainedPower;
+
+    PowerCalculator mCpuPowerCalculator;
+    PowerCalculator mWakelockPowerCalculator;
+    MobileRadioPowerCalculator mMobileRadioPowerCalculator;
+    PowerCalculator mWifiPowerCalculator;
+    PowerCalculator mBluetoothPowerCalculator;
+    PowerCalculator mSensorPowerCalculator;
+    PowerCalculator mCameraPowerCalculator;
+    PowerCalculator mFlashlightPowerCalculator;
+    PowerCalculator mMemoryPowerCalculator;
+
+    boolean mHasWifiPowerReporting = false;
+    boolean mHasBluetoothPowerReporting = false;
+
+    public static boolean checkWifiOnly(Context context) {
+        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+        return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+    }
+
+    public static boolean checkHasWifiPowerReporting(BatteryStats stats, PowerProfile profile) {
+        return stats.hasWifiActivityReporting() &&
+                profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE) != 0 &&
+                profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX) != 0 &&
+                profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX) != 0;
+    }
+
+    public static boolean checkHasBluetoothPowerReporting(BatteryStats stats,
+            PowerProfile profile) {
+        return stats.hasBluetoothActivityReporting() &&
+                profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE) != 0 &&
+                profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX) != 0 &&
+                profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX) != 0;
+    }
+
+    public BatteryStatsHelper(Context context) {
+        this(context, true);
+    }
+
+    public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast) {
+        this(context, collectBatteryBroadcast, checkWifiOnly(context));
+    }
+
+    public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast, boolean wifiOnly) {
+        mContext = context;
+        mCollectBatteryBroadcast = collectBatteryBroadcast;
+        mWifiOnly = wifiOnly;
+        mPackageManager = context.getPackageManager();
+
+        final Resources resources = context.getResources();
+        mSystemPackageArray = resources.getStringArray(
+                com.android.internal.R.array.config_batteryPackageTypeSystem);
+        mServicepackageArray = resources.getStringArray(
+                com.android.internal.R.array.config_batteryPackageTypeService);
+    }
+
+    public void storeStatsHistoryInFile(String fname) {
+        synchronized (sFileXfer) {
+            File path = makeFilePath(mContext, fname);
+            sFileXfer.put(path, this.getStats());
+            FileOutputStream fout = null;
+            try {
+                fout = new FileOutputStream(path);
+                Parcel hist = Parcel.obtain();
+                getStats().writeToParcelWithoutUids(hist, 0);
+                byte[] histData = hist.marshall();
+                fout.write(histData);
+            } catch (IOException e) {
+                Log.w(TAG, "Unable to write history to file", e);
+            } finally {
+                if (fout != null) {
+                    try {
+                        fout.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+    }
+
+    public static BatteryStats statsFromFile(Context context, String fname) {
+        synchronized (sFileXfer) {
+            File path = makeFilePath(context, fname);
+            BatteryStats stats = sFileXfer.get(path);
+            if (stats != null) {
+                return stats;
+            }
+            FileInputStream fin = null;
+            try {
+                fin = new FileInputStream(path);
+                byte[] data = readFully(fin);
+                Parcel parcel = Parcel.obtain();
+                parcel.unmarshall(data, 0, data.length);
+                parcel.setDataPosition(0);
+                return com.android.internal.os.BatteryStatsImpl.CREATOR.createFromParcel(parcel);
+            } catch (IOException e) {
+                Log.w(TAG, "Unable to read history to file", e);
+            } finally {
+                if (fin != null) {
+                    try {
+                        fin.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+        return getStats(IBatteryStats.Stub.asInterface(
+                ServiceManager.getService(BatteryStats.SERVICE_NAME)));
+    }
+
+    public static void dropFile(Context context, String fname) {
+        makeFilePath(context, fname).delete();
+    }
+
+    private static File makeFilePath(Context context, String fname) {
+        return new File(context.getFilesDir(), fname);
+    }
+
+    /** Clears the current stats and forces recreating for future use. */
+    public void clearStats() {
+        mStats = null;
+    }
+
+    public BatteryStats getStats() {
+        if (mStats == null) {
+            load();
+        }
+        return mStats;
+    }
+
+    public Intent getBatteryBroadcast() {
+        if (mBatteryBroadcast == null && mCollectBatteryBroadcast) {
+            load();
+        }
+        return mBatteryBroadcast;
+    }
+
+    public PowerProfile getPowerProfile() {
+        return mPowerProfile;
+    }
+
+    public void create(BatteryStats stats) {
+        mPowerProfile = new PowerProfile(mContext);
+        mStats = stats;
+    }
+
+    public void create(Bundle icicle) {
+        if (icicle != null) {
+            mStats = sStatsXfer;
+            mBatteryBroadcast = sBatteryBroadcastXfer;
+        }
+        mBatteryInfo = IBatteryStats.Stub.asInterface(
+                ServiceManager.getService(BatteryStats.SERVICE_NAME));
+        mPowerProfile = new PowerProfile(mContext);
+    }
+
+    public void storeState() {
+        sStatsXfer = mStats;
+        sBatteryBroadcastXfer = mBatteryBroadcast;
+    }
+
+    public static String makemAh(double power) {
+        if (power == 0) return "0";
+
+        final String format;
+        if (power < .00001) {
+            format = "%.8f";
+        } else if (power < .0001) {
+            format = "%.7f";
+        } else if (power < .001) {
+            format = "%.6f";
+        } else if (power < .01) {
+            format = "%.5f";
+        } else if (power < .1) {
+            format = "%.4f";
+        } else if (power < 1) {
+            format = "%.3f";
+        } else if (power < 10) {
+            format = "%.2f";
+        } else if (power < 100) {
+            format = "%.1f";
+        } else {
+            format = "%.0f";
+        }
+
+        // Use English locale because this is never used in UI (only in checkin and dump).
+        return String.format(Locale.ENGLISH, format, power);
+    }
+
+    /**
+     * Refreshes the power usage list.
+     */
+    public void refreshStats(int statsType, int asUser) {
+        SparseArray<UserHandle> users = new SparseArray<>(1);
+        users.put(asUser, new UserHandle(asUser));
+        refreshStats(statsType, users);
+    }
+
+    /**
+     * Refreshes the power usage list.
+     */
+    public void refreshStats(int statsType, List<UserHandle> asUsers) {
+        final int n = asUsers.size();
+        SparseArray<UserHandle> users = new SparseArray<>(n);
+        for (int i = 0; i < n; ++i) {
+            UserHandle userHandle = asUsers.get(i);
+            users.put(userHandle.getIdentifier(), userHandle);
+        }
+        refreshStats(statsType, users);
+    }
+
+    /**
+     * Refreshes the power usage list.
+     */
+    public void refreshStats(int statsType, SparseArray<UserHandle> asUsers) {
+        refreshStats(statsType, asUsers, SystemClock.elapsedRealtime() * 1000,
+                SystemClock.uptimeMillis() * 1000);
+    }
+
+    public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
+            long rawUptimeUs) {
+        // Initialize mStats if necessary.
+        getStats();
+
+        mMaxPower = 0;
+        mMaxRealPower = 0;
+        mComputedPower = 0;
+        mTotalPower = 0;
+
+        mUsageList.clear();
+        mWifiSippers.clear();
+        mBluetoothSippers.clear();
+        mUserSippers.clear();
+        mMobilemsppList.clear();
+
+        if (mStats == null) {
+            return;
+        }
+
+        if (mCpuPowerCalculator == null) {
+            mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
+        }
+        mCpuPowerCalculator.reset();
+
+        if (mMemoryPowerCalculator == null) {
+            mMemoryPowerCalculator = new MemoryPowerCalculator(mPowerProfile);
+        }
+        mMemoryPowerCalculator.reset();
+
+        if (mWakelockPowerCalculator == null) {
+            mWakelockPowerCalculator = new WakelockPowerCalculator(mPowerProfile);
+        }
+        mWakelockPowerCalculator.reset();
+
+        if (mMobileRadioPowerCalculator == null) {
+            mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile, mStats);
+        }
+        mMobileRadioPowerCalculator.reset(mStats);
+
+        // checkHasWifiPowerReporting can change if we get energy data at a later point, so
+        // always check this field.
+        final boolean hasWifiPowerReporting = checkHasWifiPowerReporting(mStats, mPowerProfile);
+        if (mWifiPowerCalculator == null || hasWifiPowerReporting != mHasWifiPowerReporting) {
+            mWifiPowerCalculator = hasWifiPowerReporting ?
+                    new WifiPowerCalculator(mPowerProfile) :
+                    new WifiPowerEstimator(mPowerProfile);
+            mHasWifiPowerReporting = hasWifiPowerReporting;
+        }
+        mWifiPowerCalculator.reset();
+
+        final boolean hasBluetoothPowerReporting = checkHasBluetoothPowerReporting(mStats,
+                mPowerProfile);
+        if (mBluetoothPowerCalculator == null ||
+                hasBluetoothPowerReporting != mHasBluetoothPowerReporting) {
+            mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile);
+            mHasBluetoothPowerReporting = hasBluetoothPowerReporting;
+        }
+        mBluetoothPowerCalculator.reset();
+
+        if (mSensorPowerCalculator == null) {
+            mSensorPowerCalculator = new SensorPowerCalculator(mPowerProfile,
+                    (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE));
+        }
+        mSensorPowerCalculator.reset();
+
+        if (mCameraPowerCalculator == null) {
+            mCameraPowerCalculator = new CameraPowerCalculator(mPowerProfile);
+        }
+        mCameraPowerCalculator.reset();
+
+        if (mFlashlightPowerCalculator == null) {
+            mFlashlightPowerCalculator = new FlashlightPowerCalculator(mPowerProfile);
+        }
+        mFlashlightPowerCalculator.reset();
+
+        mStatsType = statsType;
+        mRawUptimeUs = rawUptimeUs;
+        mRawRealtimeUs = rawRealtimeUs;
+        mBatteryUptimeUs = mStats.getBatteryUptime(rawUptimeUs);
+        mBatteryRealtimeUs = mStats.getBatteryRealtime(rawRealtimeUs);
+        mTypeBatteryUptimeUs = mStats.computeBatteryUptime(rawUptimeUs, mStatsType);
+        mTypeBatteryRealtimeUs = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType);
+        mBatteryTimeRemainingUs = mStats.computeBatteryTimeRemaining(rawRealtimeUs);
+        mChargeTimeRemainingUs = mStats.computeChargeTimeRemaining(rawRealtimeUs);
+
+        if (DEBUG) {
+            Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs / 1000) + " uptime="
+                    + (rawUptimeUs / 1000));
+            Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtimeUs / 1000) + " uptime="
+                    + (mBatteryUptimeUs / 1000));
+            Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtimeUs / 1000) + " uptime="
+                    + (mTypeBatteryUptimeUs / 1000));
+        }
+        mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge()
+                * mPowerProfile.getBatteryCapacity()) / 100;
+        mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge()
+                * mPowerProfile.getBatteryCapacity()) / 100;
+
+        processAppUsage(asUsers);
+
+        // Before aggregating apps in to users, collect all apps to sort by their ms per packet.
+        for (int i = 0; i < mUsageList.size(); i++) {
+            BatterySipper bs = mUsageList.get(i);
+            bs.computeMobilemspp();
+            if (bs.mobilemspp != 0) {
+                mMobilemsppList.add(bs);
+            }
+        }
+
+        for (int i = 0; i < mUserSippers.size(); i++) {
+            List<BatterySipper> user = mUserSippers.valueAt(i);
+            for (int j = 0; j < user.size(); j++) {
+                BatterySipper bs = user.get(j);
+                bs.computeMobilemspp();
+                if (bs.mobilemspp != 0) {
+                    mMobilemsppList.add(bs);
+                }
+            }
+        }
+        Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() {
+            @Override
+            public int compare(BatterySipper lhs, BatterySipper rhs) {
+                return Double.compare(rhs.mobilemspp, lhs.mobilemspp);
+            }
+        });
+
+        processMiscUsage();
+
+        Collections.sort(mUsageList);
+
+        // At this point, we've sorted the list so we are guaranteed the max values are at the top.
+        // We have only added real powers so far.
+        if (!mUsageList.isEmpty()) {
+            mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah;
+            final int usageListCount = mUsageList.size();
+            for (int i = 0; i < usageListCount; i++) {
+                mComputedPower += mUsageList.get(i).totalPowerMah;
+            }
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "Accuracy: total computed=" + makemAh(mComputedPower) + ", min discharge="
+                    + makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower));
+        }
+
+        mTotalPower = mComputedPower;
+        if (mStats.getLowDischargeAmountSinceCharge() > 1) {
+            if (mMinDrainedPower > mComputedPower) {
+                double amount = mMinDrainedPower - mComputedPower;
+                mTotalPower = mMinDrainedPower;
+                BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount);
+
+                // Insert the BatterySipper in its sorted position.
+                int index = Collections.binarySearch(mUsageList, bs);
+                if (index < 0) {
+                    index = -(index + 1);
+                }
+                mUsageList.add(index, bs);
+                mMaxPower = Math.max(mMaxPower, amount);
+            } else if (mMaxDrainedPower < mComputedPower) {
+                double amount = mComputedPower - mMaxDrainedPower;
+
+                // Insert the BatterySipper in its sorted position.
+                BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount);
+                int index = Collections.binarySearch(mUsageList, bs);
+                if (index < 0) {
+                    index = -(index + 1);
+                }
+                mUsageList.add(index, bs);
+                mMaxPower = Math.max(mMaxPower, amount);
+            }
+        }
+
+        // Smear it!
+        final double hiddenPowerMah = removeHiddenBatterySippers(mUsageList);
+        final double totalRemainingPower = getTotalPower() - hiddenPowerMah;
+        if (Math.abs(totalRemainingPower) > 1e-3) {
+            for (int i = 0, size = mUsageList.size(); i < size; i++) {
+                final BatterySipper sipper = mUsageList.get(i);
+                if (!sipper.shouldHide) {
+                    sipper.proportionalSmearMah = hiddenPowerMah
+                            * ((sipper.totalPowerMah + sipper.screenPowerMah)
+                            / totalRemainingPower);
+                    sipper.sumPower();
+                }
+            }
+        }
+    }
+
+    private void processAppUsage(SparseArray<UserHandle> asUsers) {
+        final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
+        mStatsPeriod = mTypeBatteryRealtimeUs;
+
+        BatterySipper osSipper = null;
+        final SparseArray<? extends Uid> uidStats = mStats.getUidStats();
+        final int NU = uidStats.size();
+        for (int iu = 0; iu < NU; iu++) {
+            final Uid u = uidStats.valueAt(iu);
+            final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);
+
+            mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
+            mWakelockPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
+            mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
+                    mStatsType);
+            mWifiPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
+            mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
+                    mStatsType);
+            mSensorPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
+            mCameraPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
+            mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs,
+                    mStatsType);
+
+            final double totalPower = app.sumPower();
+            if (DEBUG && totalPower != 0) {
+                Log.d(TAG, String.format("UID %d: total power=%s", u.getUid(),
+                        makemAh(totalPower)));
+            }
+
+            // Add the app to the list if it is consuming power.
+            if (totalPower != 0 || u.getUid() == 0) {
+                //
+                // Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list.
+                //
+                final int uid = app.getUid();
+                final int userId = UserHandle.getUserId(uid);
+                if (uid == Process.WIFI_UID) {
+                    mWifiSippers.add(app);
+                } else if (uid == Process.BLUETOOTH_UID) {
+                    mBluetoothSippers.add(app);
+                } else if (!forAllUsers && asUsers.get(userId) == null
+                        && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
+                    // We are told to just report this user's apps as one large entry.
+                    List<BatterySipper> list = mUserSippers.get(userId);
+                    if (list == null) {
+                        list = new ArrayList<>();
+                        mUserSippers.put(userId, list);
+                    }
+                    list.add(app);
+                } else {
+                    mUsageList.add(app);
+                }
+
+                if (uid == 0) {
+                    osSipper = app;
+                }
+            }
+        }
+
+        if (osSipper != null) {
+            // The device has probably been awake for longer than the screen on
+            // time and application wake lock time would account for.  Assign
+            // this remainder to the OS, if possible.
+            mWakelockPowerCalculator.calculateRemaining(osSipper, mStats, mRawRealtimeUs,
+                    mRawUptimeUs, mStatsType);
+            osSipper.sumPower();
+        }
+    }
+
+    private void addPhoneUsage() {
+        long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtimeUs, mStatsType) / 1000;
+        double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
+                * phoneOnTimeMs / (60 * 60 * 1000);
+        if (phoneOnPower != 0) {
+            addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
+        }
+    }
+
+    /**
+     * Screen power is the additional power the screen takes while the device is running.
+     */
+    private void addScreenUsage() {
+        double power = 0;
+        long screenOnTimeMs = mStats.getScreenOnTime(mRawRealtimeUs, mStatsType) / 1000;
+        power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
+        final double screenFullPower =
+                mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
+        for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+            double screenBinPower = screenFullPower * (i + 0.5f)
+                    / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
+            long brightnessTime = mStats.getScreenBrightnessTime(i, mRawRealtimeUs, mStatsType)
+                    / 1000;
+            double p = screenBinPower * brightnessTime;
+            if (DEBUG && p != 0) {
+                Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
+                        + " power=" + makemAh(p / (60 * 60 * 1000)));
+            }
+            power += p;
+        }
+        power /= (60 * 60 * 1000); // To hours
+        if (power != 0) {
+            addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power);
+        }
+    }
+
+    private void addRadioUsage() {
+        BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0);
+        mMobileRadioPowerCalculator.calculateRemaining(radio, mStats, mRawRealtimeUs, mRawUptimeUs,
+                mStatsType);
+        radio.sumPower();
+        if (radio.totalPowerMah > 0) {
+            mUsageList.add(radio);
+        }
+    }
+
+    private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) {
+        for (int i = 0; i < from.size(); i++) {
+            BatterySipper wbs = from.get(i);
+            if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTimeMs);
+            bs.add(wbs);
+        }
+        bs.computeMobilemspp();
+        bs.sumPower();
+    }
+
+    /**
+     * Calculate the baseline power usage for the device when it is in suspend and idle.
+     * The device is drawing POWER_CPU_IDLE power at its lowest power state.
+     * The device is drawing POWER_CPU_IDLE + POWER_CPU_AWAKE power when a wakelock is held.
+     */
+    private void addIdleUsage() {
+        final double suspendPowerMaMs = (mTypeBatteryRealtimeUs / 1000) *
+                mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE);
+        final double idlePowerMaMs = (mTypeBatteryUptimeUs / 1000) *
+                mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE);
+        final double totalPowerMah = (suspendPowerMaMs + idlePowerMaMs) / (60 * 60 * 1000);
+        if (DEBUG && totalPowerMah != 0) {
+            Log.d(TAG, "Suspend: time=" + (mTypeBatteryRealtimeUs / 1000)
+                    + " power=" + makemAh(suspendPowerMaMs / (60 * 60 * 1000)));
+            Log.d(TAG, "Idle: time=" + (mTypeBatteryUptimeUs / 1000)
+                    + " power=" + makemAh(idlePowerMaMs / (60 * 60 * 1000)));
+        }
+
+        if (totalPowerMah != 0) {
+            addEntry(BatterySipper.DrainType.IDLE, mTypeBatteryRealtimeUs / 1000, totalPowerMah);
+        }
+    }
+
+    /**
+     * We do per-app blaming of WiFi activity. If energy info is reported from the controller,
+     * then only the WiFi process gets blamed here since we normalize power calculations and
+     * assign all the power drain to apps. If energy info is not reported, we attribute the
+     * difference between total running time of WiFi for all apps and the actual running time
+     * of WiFi to the WiFi subsystem.
+     */
+    private void addWiFiUsage() {
+        BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0);
+        mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs,
+                mStatsType);
+        aggregateSippers(bs, mWifiSippers, "WIFI");
+        if (bs.totalPowerMah > 0) {
+            mUsageList.add(bs);
+        }
+    }
+
+    /**
+     * Bluetooth usage is not attributed to any apps yet, so the entire blame goes to the
+     * Bluetooth Category.
+     */
+    private void addBluetoothUsage() {
+        BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0);
+        mBluetoothPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs,
+                mStatsType);
+        aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
+        if (bs.totalPowerMah > 0) {
+            mUsageList.add(bs);
+        }
+    }
+
+    private void addUserUsage() {
+        for (int i = 0; i < mUserSippers.size(); i++) {
+            final int userId = mUserSippers.keyAt(i);
+            BatterySipper bs = new BatterySipper(DrainType.USER, null, 0);
+            bs.userId = userId;
+            aggregateSippers(bs, mUserSippers.valueAt(i), "User");
+            mUsageList.add(bs);
+        }
+    }
+
+    private void addMemoryUsage() {
+        BatterySipper memory = new BatterySipper(DrainType.MEMORY, null, 0);
+        mMemoryPowerCalculator.calculateRemaining(memory, mStats, mRawRealtimeUs, mRawUptimeUs,
+                mStatsType);
+        memory.sumPower();
+        if (memory.totalPowerMah > 0) {
+            mUsageList.add(memory);
+        }
+    }
+
+    private void processMiscUsage() {
+        addUserUsage();
+        addPhoneUsage();
+        addScreenUsage();
+        addWiFiUsage();
+        addBluetoothUsage();
+        addMemoryUsage();
+        addIdleUsage(); // Not including cellular idle power
+        // Don't compute radio usage if it's a wifi-only device
+        if (!mWifiOnly) {
+            addRadioUsage();
+        }
+    }
+
+    private BatterySipper addEntry(DrainType drainType, long time, double power) {
+        BatterySipper bs = new BatterySipper(drainType, null, 0);
+        bs.usagePowerMah = power;
+        bs.usageTimeMs = time;
+        bs.sumPower();
+        mUsageList.add(bs);
+        return bs;
+    }
+
+    public List<BatterySipper> getUsageList() {
+        return mUsageList;
+    }
+
+    public List<BatterySipper> getMobilemsppList() {
+        return mMobilemsppList;
+    }
+
+    public long getStatsPeriod() {
+        return mStatsPeriod;
+    }
+
+    public int getStatsType() {
+        return mStatsType;
+    }
+
+    public double getMaxPower() {
+        return mMaxPower;
+    }
+
+    public double getMaxRealPower() {
+        return mMaxRealPower;
+    }
+
+    public double getTotalPower() {
+        return mTotalPower;
+    }
+
+    public double getComputedPower() {
+        return mComputedPower;
+    }
+
+    public double getMinDrainedPower() {
+        return mMinDrainedPower;
+    }
+
+    public double getMaxDrainedPower() {
+        return mMaxDrainedPower;
+    }
+
+    public static byte[] readFully(FileInputStream stream) throws java.io.IOException {
+        return readFully(stream, stream.available());
+    }
+
+    public static byte[] readFully(FileInputStream stream, int avail) throws java.io.IOException {
+        int pos = 0;
+        byte[] data = new byte[avail];
+        while (true) {
+            int amt = stream.read(data, pos, data.length - pos);
+            //Log.i("foo", "Read " + amt + " bytes at " + pos
+            //        + " of avail " + data.length);
+            if (amt <= 0) {
+                //Log.i("foo", "**** FINISHED READING: pos=" + pos
+                //        + " len=" + data.length);
+                return data;
+            }
+            pos += amt;
+            avail = stream.available();
+            if (avail > data.length - pos) {
+                byte[] newData = new byte[pos + avail];
+                System.arraycopy(data, 0, newData, 0, pos);
+                data = newData;
+            }
+        }
+    }
+
+    /**
+     * Mark the {@link BatterySipper} that we should hide and smear the screen usage based on
+     * foreground activity time.
+     *
+     * @param sippers sipper list that need to check and remove
+     * @return the total power of the hidden items of {@link BatterySipper}
+     * for proportional smearing
+     */
+    public double removeHiddenBatterySippers(List<BatterySipper> sippers) {
+        double proportionalSmearPowerMah = 0;
+        BatterySipper screenSipper = null;
+        for (int i = sippers.size() - 1; i >= 0; i--) {
+            final BatterySipper sipper = sippers.get(i);
+            sipper.shouldHide = shouldHideSipper(sipper);
+            if (sipper.shouldHide) {
+                if (sipper.drainType != BatterySipper.DrainType.OVERCOUNTED
+                        && sipper.drainType != BatterySipper.DrainType.SCREEN
+                        && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED
+                        && sipper.drainType != BatterySipper.DrainType.BLUETOOTH
+                        && sipper.drainType != BatterySipper.DrainType.WIFI
+                        && sipper.drainType != BatterySipper.DrainType.IDLE) {
+                    // Don't add it if it is overcounted, unaccounted or screen
+                    proportionalSmearPowerMah += sipper.totalPowerMah;
+                }
+            }
+
+            if (sipper.drainType == BatterySipper.DrainType.SCREEN) {
+                screenSipper = sipper;
+            }
+        }
+
+        smearScreenBatterySipper(sippers, screenSipper);
+
+        return proportionalSmearPowerMah;
+    }
+
+    /**
+     * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
+     * time.
+     */
+    public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper) {
+        long totalActivityTimeMs = 0;
+        final SparseLongArray activityTimeArray = new SparseLongArray();
+        for (int i = 0, size = sippers.size(); i < size; i++) {
+            final BatteryStats.Uid uid = sippers.get(i).uidObj;
+            if (uid != null) {
+                final long timeMs = getProcessForegroundTimeMs(uid,
+                        BatteryStats.STATS_SINCE_CHARGED);
+                activityTimeArray.put(uid.getUid(), timeMs);
+                totalActivityTimeMs += timeMs;
+            }
+        }
+
+        if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
+            final double screenPowerMah = screenSipper.totalPowerMah;
+            for (int i = 0, size = sippers.size(); i < size; i++) {
+                final BatterySipper sipper = sippers.get(i);
+                sipper.screenPowerMah = screenPowerMah * activityTimeArray.get(sipper.getUid(), 0)
+                        / totalActivityTimeMs;
+            }
+        }
+    }
+
+    /**
+     * Check whether we should hide the battery sipper.
+     */
+    public boolean shouldHideSipper(BatterySipper sipper) {
+        final BatterySipper.DrainType drainType = sipper.drainType;
+
+        return drainType == BatterySipper.DrainType.IDLE
+                || drainType == BatterySipper.DrainType.CELL
+                || drainType == BatterySipper.DrainType.SCREEN
+                || drainType == BatterySipper.DrainType.UNACCOUNTED
+                || drainType == BatterySipper.DrainType.OVERCOUNTED
+                || isTypeService(sipper)
+                || isTypeSystem(sipper);
+    }
+
+    /**
+     * Check whether {@code sipper} is type service
+     */
+    public boolean isTypeService(BatterySipper sipper) {
+        final String[] packages = mPackageManager.getPackagesForUid(sipper.getUid());
+        if (packages == null) {
+            return false;
+        }
+
+        for (String packageName : packages) {
+            if (ArrayUtils.contains(mServicepackageArray, packageName)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Check whether {@code sipper} is type system
+     */
+    public boolean isTypeSystem(BatterySipper sipper) {
+        final int uid = sipper.uidObj == null ? -1 : sipper.getUid();
+        sipper.mPackages = mPackageManager.getPackagesForUid(uid);
+        // Classify all the sippers to type system if the range of uid is 0...FIRST_APPLICATION_UID
+        if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) {
+            return true;
+        } else if (sipper.mPackages != null) {
+            for (final String packageName : sipper.mPackages) {
+                if (ArrayUtils.contains(mSystemPackageArray, packageName)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    public long convertUsToMs(long timeUs) {
+        return timeUs / 1000;
+    }
+
+    public long convertMsToUs(long timeMs) {
+        return timeMs * 1000;
+    }
+
+    @VisibleForTesting
+    public long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) {
+        final BatteryStats.Timer timer = uid.getForegroundActivityTimer();
+        if (timer != null) {
+            return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
+        }
+
+        return 0;
+    }
+
+    @VisibleForTesting
+    public long getProcessForegroundTimeMs(BatteryStats.Uid uid, int which) {
+        final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime());
+        final int foregroundTypes[] = {BatteryStats.Uid.PROCESS_STATE_TOP};
+
+        long timeUs = 0;
+        for (int type : foregroundTypes) {
+            final long localTime = uid.getProcessStateTime(type, rawRealTimeUs, which);
+            timeUs += localTime;
+        }
+
+        // Return the min value of STATE_TOP time and foreground activity time, since both of these
+        // time have some errors.
+        return convertUsToMs(
+                Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs)));
+    }
+
+    @VisibleForTesting
+    public void setPackageManager(PackageManager packageManager) {
+        mPackageManager = packageManager;
+    }
+
+    @VisibleForTesting
+    public void setSystemPackageArray(String[] array) {
+        mSystemPackageArray = array;
+    }
+
+    @VisibleForTesting
+    public void setServicePackageArray(String[] array) {
+        mServicepackageArray = array;
+    }
+
+    private void load() {
+        if (mBatteryInfo == null) {
+            return;
+        }
+        mStats = getStats(mBatteryInfo);
+        if (mCollectBatteryBroadcast) {
+            mBatteryBroadcast = mContext.registerReceiver(null,
+                    new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+        }
+    }
+
+    private static BatteryStatsImpl getStats(IBatteryStats service) {
+        try {
+            ParcelFileDescriptor pfd = service.getStatisticsStream();
+            if (pfd != null) {
+                try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+                    byte[] data = readFully(fis, MemoryFile.getSize(pfd.getFileDescriptor()));
+                    Parcel parcel = Parcel.obtain();
+                    parcel.unmarshall(data, 0, data.length);
+                    parcel.setDataPosition(0);
+                    BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
+                            .createFromParcel(parcel);
+                    return stats;
+                } catch (IOException e) {
+                    Log.w(TAG, "Unable to read statistics stream", e);
+                }
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException:", e);
+        }
+        return new BatteryStatsImpl();
+    }
+}
diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java
new file mode 100644
index 0000000..c58ff05
--- /dev/null
+++ b/com/android/internal/os/BatteryStatsImpl.java
@@ -0,0 +1,12873 @@
+/*
+ * Copyright (C) 2006-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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.UidTraffic;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkStats;
+import android.net.wifi.WifiActivityEnergyInfo;
+import android.net.wifi.WifiManager;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.Build;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IBatteryPropertiesRegistrar;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.ParcelFormatException;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.ModemActivityInfo;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.LogWriter;
+import android.util.LongSparseArray;
+import android.util.LongSparseLongArray;
+import android.util.MutableInt;
+import android.util.Pools;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.SparseLongArray;
+import android.util.TimeUtils;
+import android.util.Xml;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.JournaledFile;
+import com.android.internal.util.XmlUtils;
+
+import libcore.util.EmptyArray;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * All information we are collecting about things that can happen that impact
+ * battery life.  All times are represented in microseconds except where indicated
+ * otherwise.
+ */
+public class BatteryStatsImpl extends BatteryStats {
+    private static final String TAG = "BatteryStatsImpl";
+    private static final boolean DEBUG = false;
+    public static final boolean DEBUG_ENERGY = false;
+    private static final boolean DEBUG_ENERGY_CPU = DEBUG_ENERGY;
+    private static final boolean DEBUG_MEMORY = false;
+    private static final boolean DEBUG_HISTORY = false;
+    private static final boolean USE_OLD_HISTORY = false;   // for debugging.
+
+    // TODO: remove "tcp" from network methods, since we measure total stats.
+
+    // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
+    private static final int MAGIC = 0xBA757475; // 'BATSTATS'
+
+    // Current on-disk Parcel version
+    private static final int VERSION = 165 + (USE_OLD_HISTORY ? 1000 : 0);
+
+    // Maximum number of items we will record in the history.
+    private static final int MAX_HISTORY_ITEMS;
+
+    // No, really, THIS is the maximum number of items we will record in the history.
+    private static final int MAX_MAX_HISTORY_ITEMS;
+
+    // The maximum number of names wakelocks we will keep track of
+    // per uid; once the limit is reached, we batch the remaining wakelocks
+    // in to one common name.
+    private static final int MAX_WAKELOCKS_PER_UID;
+
+    static final int MAX_HISTORY_BUFFER; // 256KB
+    static final int MAX_MAX_HISTORY_BUFFER; // 320KB
+
+    static {
+        if (ActivityManager.isLowRamDeviceStatic()) {
+            MAX_HISTORY_ITEMS = 800;
+            MAX_MAX_HISTORY_ITEMS = 1200;
+            MAX_WAKELOCKS_PER_UID = 40;
+            MAX_HISTORY_BUFFER = 96*1024;  // 96KB
+            MAX_MAX_HISTORY_BUFFER = 128*1024; // 128KB
+        } else {
+            MAX_HISTORY_ITEMS = 2000;
+            MAX_MAX_HISTORY_ITEMS = 3000;
+            MAX_WAKELOCKS_PER_UID = 100;
+            MAX_HISTORY_BUFFER = 256*1024;  // 256KB
+            MAX_MAX_HISTORY_BUFFER = 320*1024;  // 256KB
+        }
+    }
+
+    // Number of transmit power states the Wifi controller can be in.
+    private static final int NUM_WIFI_TX_LEVELS = 1;
+
+    // Number of transmit power states the Bluetooth controller can be in.
+    private static final int NUM_BT_TX_LEVELS = 1;
+
+    /**
+     * Holding a wakelock costs more than just using the cpu.
+     * Currently, we assign only half the cpu time to an app that is running but
+     * not holding a wakelock. The apps holding wakelocks get the rest of the blame.
+     * If no app is holding a wakelock, then the distribution is normal.
+     */
+    @VisibleForTesting
+    public static final int WAKE_LOCK_WEIGHT = 50;
+
+    protected Clocks mClocks;
+
+    private final JournaledFile mFile;
+    public final AtomicFile mCheckinFile;
+    public final AtomicFile mDailyFile;
+
+    static final int MSG_UPDATE_WAKELOCKS = 1;
+    static final int MSG_REPORT_POWER_CHANGE = 2;
+    static final int MSG_REPORT_CHARGING = 3;
+    static final long DELAY_UPDATE_WAKELOCKS = 5*1000;
+
+    private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+    private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
+    @VisibleForTesting
+    protected KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
+    @VisibleForTesting
+    protected KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
+    @VisibleForTesting
+    protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
+            new KernelUidCpuFreqTimeReader();
+
+    private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
+            = new KernelMemoryBandwidthStats();
+    private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
+    public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
+        return mKernelMemoryStats;
+    }
+
+    public interface BatteryCallback {
+        public void batteryNeedsCpuUpdate();
+        public void batteryPowerChanged(boolean onBattery);
+        public void batterySendBroadcast(Intent intent);
+    }
+
+    public interface PlatformIdleStateCallback {
+        public String getPlatformLowPowerStats();
+        public String getSubsystemLowPowerStats();
+    }
+
+    public static abstract class UserInfoProvider {
+        private int[] userIds;
+        protected abstract @Nullable int[] getUserIds();
+        @VisibleForTesting
+        public final void refreshUserIds() {
+            userIds = getUserIds();
+        }
+        @VisibleForTesting
+        public boolean exists(int userId) {
+            return userIds != null ? ArrayUtils.contains(userIds, userId) : true;
+        }
+    }
+
+    private final PlatformIdleStateCallback mPlatformIdleStateCallback;
+
+    final class MyHandler extends Handler {
+        public MyHandler(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            BatteryCallback cb = mCallback;
+            switch (msg.what) {
+                case MSG_UPDATE_WAKELOCKS:
+                    synchronized (BatteryStatsImpl.this) {
+                        updateCpuTimeLocked();
+                    }
+                    if (cb != null) {
+                        cb.batteryNeedsCpuUpdate();
+                    }
+                    break;
+                case MSG_REPORT_POWER_CHANGE:
+                    if (cb != null) {
+                        cb.batteryPowerChanged(msg.arg1 != 0);
+                    }
+                    break;
+                case MSG_REPORT_CHARGING:
+                    if (cb != null) {
+                        final String action;
+                        synchronized (BatteryStatsImpl.this) {
+                            action = mCharging ? BatteryManager.ACTION_CHARGING
+                                    : BatteryManager.ACTION_DISCHARGING;
+                        }
+                        Intent intent = new Intent(action);
+                        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                        cb.batterySendBroadcast(intent);
+                    }
+                    break;
+            }
+        }
+    }
+
+    public interface Clocks {
+        public long elapsedRealtime();
+        public long uptimeMillis();
+    }
+
+    public static class SystemClocks implements Clocks {
+        public long elapsedRealtime() {
+            return SystemClock.elapsedRealtime();
+        }
+
+        public long uptimeMillis() {
+            return SystemClock.uptimeMillis();
+        }
+    }
+
+    public interface ExternalStatsSync {
+        int UPDATE_CPU = 0x01;
+        int UPDATE_WIFI = 0x02;
+        int UPDATE_RADIO = 0x04;
+        int UPDATE_BT = 0x08;
+        int UPDATE_ALL = UPDATE_CPU | UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT;
+
+        Future<?> scheduleSync(String reason, int flags);
+        Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
+    }
+
+    public final MyHandler mHandler;
+    private ExternalStatsSync mExternalSync = null;
+    @VisibleForTesting
+    protected UserInfoProvider mUserInfoProvider = null;
+
+    private BatteryCallback mCallback;
+
+    /**
+     * Mapping isolated uids to the actual owning app uid.
+     */
+    final SparseIntArray mIsolatedUids = new SparseIntArray();
+
+    /**
+     * The statistics we have collected organized by uids.
+     */
+    final SparseArray<BatteryStatsImpl.Uid> mUidStats = new SparseArray<>();
+
+    // A set of pools of currently active timers.  When a timer is queried, we will divide the
+    // elapsed time by the number of active timers to arrive at that timer's share of the time.
+    // In order to do this, we must refresh each timer whenever the number of active timers
+    // changes.
+    @VisibleForTesting
+    protected ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mDrawTimers = new ArrayList<>();
+    final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<>();
+    final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<>();
+    final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers = new SparseArray<>();
+    final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mFlashlightTurnedOnTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mCameraTurnedOnTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mBluetoothScanOnTimers = new ArrayList<>();
+
+    // Last partial timers we use for distributing CPU usage.
+    @VisibleForTesting
+    protected ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<>();
+
+    // These are the objects that will want to do something when the device
+    // is unplugged from power.
+    protected final TimeBase mOnBatteryTimeBase = new TimeBase();
+
+    // These are the objects that will want to do something when the device
+    // is unplugged from power *and* the screen is off.
+    final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase();
+
+    // Set to true when we want to distribute CPU across wakelocks for the next
+    // CPU update, even if we aren't currently running wake locks.
+    boolean mDistributeWakelockCpu;
+
+    boolean mShuttingDown;
+
+    final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
+
+    long mHistoryBaseTime;
+    boolean mHaveBatteryLevel = false;
+    boolean mRecordingHistory = false;
+    int mNumHistoryItems;
+
+    final Parcel mHistoryBuffer = Parcel.obtain();
+    final HistoryItem mHistoryLastWritten = new HistoryItem();
+    final HistoryItem mHistoryLastLastWritten = new HistoryItem();
+    final HistoryItem mHistoryReadTmp = new HistoryItem();
+    final HistoryItem mHistoryAddTmp = new HistoryItem();
+    final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>();
+    String[] mReadHistoryStrings;
+    int[] mReadHistoryUids;
+    int mReadHistoryChars;
+    int mNextHistoryTagIdx = 0;
+    int mNumHistoryTagChars = 0;
+    int mHistoryBufferLastPos = -1;
+    boolean mHistoryOverflow = false;
+    int mActiveHistoryStates = 0xffffffff;
+    int mActiveHistoryStates2 = 0xffffffff;
+    long mLastHistoryElapsedRealtime = 0;
+    long mTrackRunningHistoryElapsedRealtime = 0;
+    long mTrackRunningHistoryUptime = 0;
+
+    final HistoryItem mHistoryCur = new HistoryItem();
+
+    HistoryItem mHistory;
+    HistoryItem mHistoryEnd;
+    HistoryItem mHistoryLastEnd;
+    HistoryItem mHistoryCache;
+
+    // Used by computeHistoryStepDetails
+    HistoryStepDetails mLastHistoryStepDetails = null;
+    byte mLastHistoryStepLevel = 0;
+    final HistoryStepDetails mCurHistoryStepDetails = new HistoryStepDetails();
+    final HistoryStepDetails mReadHistoryStepDetails = new HistoryStepDetails();
+    final HistoryStepDetails mTmpHistoryStepDetails = new HistoryStepDetails();
+
+    /**
+     * Total time (in milliseconds) spent executing in user code.
+     */
+    long mLastStepCpuUserTime;
+    long mCurStepCpuUserTime;
+    /**
+     * Total time (in milliseconds) spent executing in kernel code.
+     */
+    long mLastStepCpuSystemTime;
+    long mCurStepCpuSystemTime;
+    /**
+     * Times from /proc/stat (but measured in milliseconds).
+     */
+    long mLastStepStatUserTime;
+    long mLastStepStatSystemTime;
+    long mLastStepStatIOWaitTime;
+    long mLastStepStatIrqTime;
+    long mLastStepStatSoftIrqTime;
+    long mLastStepStatIdleTime;
+    long mCurStepStatUserTime;
+    long mCurStepStatSystemTime;
+    long mCurStepStatIOWaitTime;
+    long mCurStepStatIrqTime;
+    long mCurStepStatSoftIrqTime;
+    long mCurStepStatIdleTime;
+
+    private HistoryItem mHistoryIterator;
+    private boolean mReadOverflow;
+    private boolean mIteratingHistory;
+
+    int mStartCount;
+
+    long mStartClockTime;
+    String mStartPlatformVersion;
+    String mEndPlatformVersion;
+
+    long mUptime;
+    long mUptimeStart;
+    long mRealtime;
+    long mRealtimeStart;
+
+    int mWakeLockNesting;
+    boolean mWakeLockImportant;
+    public boolean mRecordAllHistory;
+    boolean mNoAutoReset;
+
+    int mScreenState = Display.STATE_UNKNOWN;
+    StopwatchTimer mScreenOnTimer;
+
+    int mScreenBrightnessBin = -1;
+    final StopwatchTimer[] mScreenBrightnessTimer = new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
+
+    boolean mPretendScreenOff;
+
+    boolean mInteractive;
+    StopwatchTimer mInteractiveTimer;
+
+    boolean mPowerSaveModeEnabled;
+    StopwatchTimer mPowerSaveModeEnabledTimer;
+
+    boolean mDeviceIdling;
+    StopwatchTimer mDeviceIdlingTimer;
+
+    boolean mDeviceLightIdling;
+    StopwatchTimer mDeviceLightIdlingTimer;
+
+    int mDeviceIdleMode;
+    long mLastIdleTimeStart;
+    long mLongestLightIdleTime;
+    long mLongestFullIdleTime;
+    StopwatchTimer mDeviceIdleModeLightTimer;
+    StopwatchTimer mDeviceIdleModeFullTimer;
+
+    boolean mPhoneOn;
+    StopwatchTimer mPhoneOnTimer;
+
+    int mAudioOnNesting;
+    StopwatchTimer mAudioOnTimer;
+
+    int mVideoOnNesting;
+    StopwatchTimer mVideoOnTimer;
+
+    int mFlashlightOnNesting;
+    StopwatchTimer mFlashlightOnTimer;
+
+    int mCameraOnNesting;
+    StopwatchTimer mCameraOnTimer;
+
+    int mPhoneSignalStrengthBin = -1;
+    int mPhoneSignalStrengthBinRaw = -1;
+    final StopwatchTimer[] mPhoneSignalStrengthsTimer =
+            new StopwatchTimer[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+
+    StopwatchTimer mPhoneSignalScanningTimer;
+
+    int mPhoneDataConnectionType = -1;
+    final StopwatchTimer[] mPhoneDataConnectionsTimer =
+            new StopwatchTimer[NUM_DATA_CONNECTION_TYPES];
+
+    final LongSamplingCounter[] mNetworkByteActivityCounters =
+            new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+    final LongSamplingCounter[] mNetworkPacketActivityCounters =
+            new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+
+    /**
+     * The WiFi controller activity (time in tx, rx, idle, and power consumed) for the device.
+     */
+    ControllerActivityCounterImpl mWifiActivity;
+
+    /**
+     * The Bluetooth controller activity (time in tx, rx, idle, and power consumed) for the device.
+     */
+    ControllerActivityCounterImpl mBluetoothActivity;
+
+    /**
+     * The Modem controller activity (time in tx, rx, idle, and power consumed) for the device.
+     */
+    ControllerActivityCounterImpl mModemActivity;
+
+    /**
+     * Whether the device supports WiFi controller energy reporting. This is set to true on
+     * the first WiFi energy report. See {@link #mWifiActivity}.
+     */
+    boolean mHasWifiReporting = false;
+
+    /**
+     * Whether the device supports Bluetooth controller energy reporting. This is set to true on
+     * the first Bluetooth energy report. See {@link #mBluetoothActivity}.
+     */
+    boolean mHasBluetoothReporting = false;
+
+    /**
+     * Whether the device supports Modem controller energy reporting. This is set to true on
+     * the first Modem energy report. See {@link #mModemActivity}.
+     */
+    boolean mHasModemReporting = false;
+
+    boolean mWifiOn;
+    StopwatchTimer mWifiOnTimer;
+
+    boolean mGlobalWifiRunning;
+    StopwatchTimer mGlobalWifiRunningTimer;
+
+    int mWifiState = -1;
+    final StopwatchTimer[] mWifiStateTimer = new StopwatchTimer[NUM_WIFI_STATES];
+
+    int mWifiSupplState = -1;
+    final StopwatchTimer[] mWifiSupplStateTimer = new StopwatchTimer[NUM_WIFI_SUPPL_STATES];
+
+    int mWifiSignalStrengthBin = -1;
+    final StopwatchTimer[] mWifiSignalStrengthsTimer =
+            new StopwatchTimer[NUM_WIFI_SIGNAL_STRENGTH_BINS];
+
+    int mBluetoothScanNesting;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected StopwatchTimer mBluetoothScanTimer;
+
+    int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+    long mMobileRadioActiveStartTime;
+    StopwatchTimer mMobileRadioActiveTimer;
+    StopwatchTimer mMobileRadioActivePerAppTimer;
+    LongSamplingCounter mMobileRadioActiveAdjustedTime;
+    LongSamplingCounter mMobileRadioActiveUnknownTime;
+    LongSamplingCounter mMobileRadioActiveUnknownCount;
+
+    int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+
+    /**
+     * These provide time bases that discount the time the device is plugged
+     * in to power.
+     */
+    boolean mOnBattery;
+    @VisibleForTesting
+    protected boolean mOnBatteryInternal;
+
+    /**
+     * External reporting of whether the device is actually charging.
+     */
+    boolean mCharging = true;
+    int mLastChargingStateLevel;
+
+    /*
+     * These keep track of battery levels (1-100) at the last plug event and the last unplug event.
+     */
+    int mDischargeStartLevel;
+    int mDischargeUnplugLevel;
+    int mDischargePlugLevel;
+    int mDischargeCurrentLevel;
+    int mCurrentBatteryLevel;
+    int mLowDischargeAmountSinceCharge;
+    int mHighDischargeAmountSinceCharge;
+    int mDischargeScreenOnUnplugLevel;
+    int mDischargeScreenOffUnplugLevel;
+    int mDischargeAmountScreenOn;
+    int mDischargeAmountScreenOnSinceCharge;
+    int mDischargeAmountScreenOff;
+    int mDischargeAmountScreenOffSinceCharge;
+
+    private LongSamplingCounter mDischargeScreenOffCounter;
+    private LongSamplingCounter mDischargeCounter;
+
+    static final int MAX_LEVEL_STEPS = 200;
+
+    int mInitStepMode = 0;
+    int mCurStepMode = 0;
+    int mModStepMode = 0;
+
+    int mLastDischargeStepLevel;
+    int mMinDischargeStepLevel;
+    final LevelStepTracker mDischargeStepTracker = new LevelStepTracker(MAX_LEVEL_STEPS);
+    final LevelStepTracker mDailyDischargeStepTracker = new LevelStepTracker(MAX_LEVEL_STEPS*2);
+    ArrayList<PackageChange> mDailyPackageChanges;
+
+    int mLastChargeStepLevel;
+    int mMaxChargeStepLevel;
+    final LevelStepTracker mChargeStepTracker = new LevelStepTracker(MAX_LEVEL_STEPS);
+    final LevelStepTracker mDailyChargeStepTracker = new LevelStepTracker(MAX_LEVEL_STEPS*2);
+
+    static final int MAX_DAILY_ITEMS = 10;
+
+    long mDailyStartTime = 0;
+    long mNextMinDailyDeadline = 0;
+    long mNextMaxDailyDeadline = 0;
+
+    final ArrayList<DailyItem> mDailyItems = new ArrayList<>();
+
+    long mLastWriteTime = 0; // Milliseconds
+
+    private int mPhoneServiceState = -1;
+    private int mPhoneServiceStateRaw = -1;
+    private int mPhoneSimStateRaw = -1;
+
+    private int mNumConnectivityChange;
+    private int mLoadedNumConnectivityChange;
+    private int mUnpluggedNumConnectivityChange;
+
+    private int mEstimatedBatteryCapacity = -1;
+
+    private int mMinLearnedBatteryCapacity = -1;
+    private int mMaxLearnedBatteryCapacity = -1;
+
+    private long[] mCpuFreqs;
+
+    @VisibleForTesting
+    protected PowerProfile mPowerProfile;
+
+    /*
+     * Holds a SamplingTimer associated with each kernel wakelock name being tracked.
+     */
+    private final HashMap<String, SamplingTimer> mKernelWakelockStats = new HashMap<>();
+
+    public Map<String, ? extends Timer> getKernelWakelockStats() {
+        return mKernelWakelockStats;
+    }
+
+    String mLastWakeupReason = null;
+    long mLastWakeupUptimeMs = 0;
+    private final HashMap<String, SamplingTimer> mWakeupReasonStats = new HashMap<>();
+
+    public Map<String, ? extends Timer> getWakeupReasonStats() {
+        return mWakeupReasonStats;
+    }
+
+    @Override
+    public LongCounter getDischargeScreenOffCoulombCounter() {
+        return mDischargeScreenOffCounter;
+    }
+
+    @Override
+    public LongCounter getDischargeCoulombCounter() {
+        return mDischargeCounter;
+    }
+
+    @Override
+    public int getEstimatedBatteryCapacity() {
+        return mEstimatedBatteryCapacity;
+    }
+
+    @Override
+    public int getMinLearnedBatteryCapacity() {
+        return mMinLearnedBatteryCapacity;
+    }
+
+    @Override
+    public int getMaxLearnedBatteryCapacity() {
+        return mMaxLearnedBatteryCapacity;
+    }
+
+    public BatteryStatsImpl() {
+        this(new SystemClocks());
+    }
+
+    public BatteryStatsImpl(Clocks clocks) {
+        init(clocks);
+        mFile = null;
+        mCheckinFile = null;
+        mDailyFile = null;
+        mHandler = null;
+        mPlatformIdleStateCallback = null;
+        mUserInfoProvider = null;
+        clearHistoryLocked();
+    }
+
+    private void init(Clocks clocks) {
+        mClocks = clocks;
+    }
+
+    public interface TimeBaseObs {
+        void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime);
+        void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime);
+    }
+
+    // methods are protected not private to be VisibleForTesting
+    public static class TimeBase {
+        protected final ArrayList<TimeBaseObs> mObservers = new ArrayList<>();
+
+        protected long mUptime;
+        protected long mRealtime;
+
+        protected boolean mRunning;
+
+        protected long mPastUptime;
+        protected long mUptimeStart;
+        protected long mPastRealtime;
+        protected long mRealtimeStart;
+        protected long mUnpluggedUptime;
+        protected long mUnpluggedRealtime;
+
+        public void dump(PrintWriter pw, String prefix) {
+            StringBuilder sb = new StringBuilder(128);
+            pw.print(prefix); pw.print("mRunning="); pw.println(mRunning);
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("mUptime=");
+                    formatTimeMs(sb, mUptime / 1000);
+            pw.println(sb.toString());
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("mRealtime=");
+                    formatTimeMs(sb, mRealtime / 1000);
+            pw.println(sb.toString());
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("mPastUptime=");
+                    formatTimeMs(sb, mPastUptime / 1000); sb.append("mUptimeStart=");
+                    formatTimeMs(sb, mUptimeStart / 1000);
+                    sb.append("mUnpluggedUptime="); formatTimeMs(sb, mUnpluggedUptime / 1000);
+            pw.println(sb.toString());
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("mPastRealtime=");
+                    formatTimeMs(sb, mPastRealtime / 1000); sb.append("mRealtimeStart=");
+                    formatTimeMs(sb, mRealtimeStart / 1000);
+                    sb.append("mUnpluggedRealtime="); formatTimeMs(sb, mUnpluggedRealtime / 1000);
+            pw.println(sb.toString());
+        }
+
+        public void add(TimeBaseObs observer) {
+            mObservers.add(observer);
+        }
+
+        public void remove(TimeBaseObs observer) {
+            if (!mObservers.remove(observer)) {
+                Slog.wtf(TAG, "Removed unknown observer: " + observer);
+            }
+        }
+
+        public boolean hasObserver(TimeBaseObs observer) {
+            return mObservers.contains(observer);
+        }
+
+        public void init(long uptime, long realtime) {
+            mRealtime = 0;
+            mUptime = 0;
+            mPastUptime = 0;
+            mPastRealtime = 0;
+            mUptimeStart = uptime;
+            mRealtimeStart = realtime;
+            mUnpluggedUptime = getUptime(mUptimeStart);
+            mUnpluggedRealtime = getRealtime(mRealtimeStart);
+        }
+
+        public void reset(long uptime, long realtime) {
+            if (!mRunning) {
+                mPastUptime = 0;
+                mPastRealtime = 0;
+            } else {
+                mUptimeStart = uptime;
+                mRealtimeStart = realtime;
+                // TODO: Since mUptimeStart was just reset and we are running, getUptime will
+                // just return mPastUptime. Also, are we sure we don't want to reset that?
+                mUnpluggedUptime = getUptime(uptime);
+                // TODO: likewise.
+                mUnpluggedRealtime = getRealtime(realtime);
+            }
+        }
+
+        public long computeUptime(long curTime, int which) {
+            switch (which) {
+                case STATS_SINCE_CHARGED:
+                    return mUptime + getUptime(curTime);
+                case STATS_CURRENT:
+                    return getUptime(curTime);
+                case STATS_SINCE_UNPLUGGED:
+                    return getUptime(curTime) - mUnpluggedUptime;
+            }
+            return 0;
+        }
+
+        public long computeRealtime(long curTime, int which) {
+            switch (which) {
+                case STATS_SINCE_CHARGED:
+                    return mRealtime + getRealtime(curTime);
+                case STATS_CURRENT:
+                    return getRealtime(curTime);
+                case STATS_SINCE_UNPLUGGED:
+                    return getRealtime(curTime) - mUnpluggedRealtime;
+            }
+            return 0;
+        }
+
+        public long getUptime(long curTime) {
+            long time = mPastUptime;
+            if (mRunning) {
+                time += curTime - mUptimeStart;
+            }
+            return time;
+        }
+
+        public long getRealtime(long curTime) {
+            long time = mPastRealtime;
+            if (mRunning) {
+                time += curTime - mRealtimeStart;
+            }
+            return time;
+        }
+
+        public long getUptimeStart() {
+            return mUptimeStart;
+        }
+
+        public long getRealtimeStart() {
+            return mRealtimeStart;
+        }
+
+        public boolean isRunning() {
+            return mRunning;
+        }
+
+        public boolean setRunning(boolean running, long uptime, long realtime) {
+            if (mRunning != running) {
+                mRunning = running;
+                if (running) {
+                    mUptimeStart = uptime;
+                    mRealtimeStart = realtime;
+                    long batteryUptime = mUnpluggedUptime = getUptime(uptime);
+                    long batteryRealtime = mUnpluggedRealtime = getRealtime(realtime);
+
+                    for (int i = mObservers.size() - 1; i >= 0; i--) {
+                        mObservers.get(i).onTimeStarted(realtime, batteryUptime, batteryRealtime);
+                    }
+                } else {
+                    mPastUptime += uptime - mUptimeStart;
+                    mPastRealtime += realtime - mRealtimeStart;
+
+                    long batteryUptime = getUptime(uptime);
+                    long batteryRealtime = getRealtime(realtime);
+
+                    for (int i = mObservers.size() - 1; i >= 0; i--) {
+                        mObservers.get(i).onTimeStopped(realtime, batteryUptime, batteryRealtime);
+                    }
+                }
+                return true;
+            }
+            return false;
+        }
+
+        public void readSummaryFromParcel(Parcel in) {
+            mUptime = in.readLong();
+            mRealtime = in.readLong();
+        }
+
+        public void writeSummaryToParcel(Parcel out, long uptime, long realtime) {
+            out.writeLong(computeUptime(uptime, STATS_SINCE_CHARGED));
+            out.writeLong(computeRealtime(realtime, STATS_SINCE_CHARGED));
+        }
+
+        public void readFromParcel(Parcel in) {
+            mRunning = false;
+            mUptime = in.readLong();
+            mPastUptime = in.readLong();
+            mUptimeStart = in.readLong();
+            mRealtime = in.readLong();
+            mPastRealtime = in.readLong();
+            mRealtimeStart = in.readLong();
+            mUnpluggedUptime = in.readLong();
+            mUnpluggedRealtime = in.readLong();
+        }
+
+        public void writeToParcel(Parcel out, long uptime, long realtime) {
+            final long runningUptime = getUptime(uptime);
+            final long runningRealtime = getRealtime(realtime);
+            out.writeLong(mUptime);
+            out.writeLong(runningUptime);
+            out.writeLong(mUptimeStart);
+            out.writeLong(mRealtime);
+            out.writeLong(runningRealtime);
+            out.writeLong(mRealtimeStart);
+            out.writeLong(mUnpluggedUptime);
+            out.writeLong(mUnpluggedRealtime);
+        }
+    }
+
+    /**
+     * State for keeping track of counting information.
+     */
+    public static class Counter extends BatteryStats.Counter implements TimeBaseObs {
+        final AtomicInteger mCount = new AtomicInteger();
+        final TimeBase mTimeBase;
+        int mLoadedCount;
+        int mUnpluggedCount;
+        int mPluggedCount;
+
+        public Counter(TimeBase timeBase, Parcel in) {
+            mTimeBase = timeBase;
+            mPluggedCount = in.readInt();
+            mCount.set(mPluggedCount);
+            mLoadedCount = in.readInt();
+            mUnpluggedCount = in.readInt();
+            timeBase.add(this);
+        }
+
+        public Counter(TimeBase timeBase) {
+            mTimeBase = timeBase;
+            timeBase.add(this);
+        }
+
+        public void writeToParcel(Parcel out) {
+            out.writeInt(mCount.get());
+            out.writeInt(mLoadedCount);
+            out.writeInt(mUnpluggedCount);
+        }
+
+        @Override
+        public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
+            mUnpluggedCount = mPluggedCount;
+        }
+
+        @Override
+        public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
+            mPluggedCount = mCount.get();
+        }
+
+        /**
+         * Writes a possibly null Counter to a Parcel.
+         *
+         * @param out the Parcel to be written to.
+         * @param counter a Counter, or null.
+         */
+        public static void writeCounterToParcel(Parcel out, Counter counter) {
+            if (counter == null) {
+                out.writeInt(0); // indicates null
+                return;
+            }
+            out.writeInt(1); // indicates non-null
+
+            counter.writeToParcel(out);
+        }
+
+        @Override
+        public int getCountLocked(int which) {
+            int val = mCount.get();
+            if (which == STATS_SINCE_UNPLUGGED) {
+                val -= mUnpluggedCount;
+            } else if (which != STATS_SINCE_CHARGED) {
+                val -= mLoadedCount;
+            }
+
+            return val;
+        }
+
+        public void logState(Printer pw, String prefix) {
+            pw.println(prefix + "mCount=" + mCount.get()
+                    + " mLoadedCount=" + mLoadedCount
+                    + " mUnpluggedCount=" + mUnpluggedCount
+                    + " mPluggedCount=" + mPluggedCount);
+        }
+
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public void stepAtomic() {
+            if (mTimeBase.isRunning()) {
+                mCount.incrementAndGet();
+            }
+        }
+
+        void addAtomic(int delta) {
+            if (mTimeBase.isRunning()) {
+                mCount.addAndGet(delta);
+            }
+        }
+
+        /**
+         * Clear state of this counter.
+         */
+        void reset(boolean detachIfReset) {
+            mCount.set(0);
+            mLoadedCount = mPluggedCount = mUnpluggedCount = 0;
+            if (detachIfReset) {
+                detach();
+            }
+        }
+
+        void detach() {
+            mTimeBase.remove(this);
+        }
+
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public void writeSummaryFromParcelLocked(Parcel out) {
+            int count = mCount.get();
+            out.writeInt(count);
+        }
+
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public void readSummaryFromParcelLocked(Parcel in) {
+            mLoadedCount = in.readInt();
+            mCount.set(mLoadedCount);
+            mUnpluggedCount = mPluggedCount = mLoadedCount;
+        }
+    }
+
+    @VisibleForTesting
+    public static class LongSamplingCounterArray extends LongCounterArray implements TimeBaseObs {
+        final TimeBase mTimeBase;
+        public long[] mCounts;
+        public long[] mLoadedCounts;
+        public long[] mUnpluggedCounts;
+        public long[] mPluggedCounts;
+
+        private LongSamplingCounterArray(TimeBase timeBase, Parcel in) {
+            mTimeBase = timeBase;
+            mPluggedCounts = in.createLongArray();
+            mCounts = copyArray(mPluggedCounts, mCounts);
+            mLoadedCounts = in.createLongArray();
+            mUnpluggedCounts = in.createLongArray();
+            timeBase.add(this);
+        }
+
+        public LongSamplingCounterArray(TimeBase timeBase) {
+            mTimeBase = timeBase;
+            timeBase.add(this);
+        }
+
+        private void writeToParcel(Parcel out) {
+            out.writeLongArray(mCounts);
+            out.writeLongArray(mLoadedCounts);
+            out.writeLongArray(mUnpluggedCounts);
+        }
+
+        @Override
+        public void onTimeStarted(long elapsedRealTime, long baseUptime, long baseRealtime) {
+            mUnpluggedCounts = copyArray(mPluggedCounts, mUnpluggedCounts);
+        }
+
+        @Override
+        public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
+            mPluggedCounts = copyArray(mCounts, mPluggedCounts);
+        }
+
+        @Override
+        public long[] getCountsLocked(int which) {
+            long[] val = copyArray(mTimeBase.isRunning() ? mCounts : mPluggedCounts, null);
+            if (which == STATS_SINCE_UNPLUGGED) {
+                subtract(val, mUnpluggedCounts);
+            } else if (which != STATS_SINCE_CHARGED) {
+                subtract(val, mLoadedCounts);
+            }
+            return val;
+        }
+
+        @Override
+        public void logState(Printer pw, String prefix) {
+            pw.println(prefix + "mCounts=" + Arrays.toString(mCounts)
+                    + " mLoadedCounts=" + Arrays.toString(mLoadedCounts)
+                    + " mUnpluggedCounts=" + Arrays.toString(mUnpluggedCounts)
+                    + " mPluggedCounts=" + Arrays.toString(mPluggedCounts));
+        }
+
+        public void addCountLocked(long[] counts) {
+            if (counts == null) {
+                return;
+            }
+            if (mTimeBase.isRunning()) {
+                if (mCounts == null) {
+                    mCounts = new long[counts.length];
+                }
+                for (int i = 0; i < counts.length; ++i) {
+                    mCounts[i] += counts[i];
+                }
+            }
+        }
+
+        public int getSize() {
+            return mCounts == null ? 0 : mCounts.length;
+        }
+
+        /**
+         * Clear state of this counter.
+         */
+        public void reset(boolean detachIfReset) {
+            fillArray(mCounts, 0);
+            fillArray(mLoadedCounts, 0);
+            fillArray(mPluggedCounts, 0);
+            fillArray(mUnpluggedCounts, 0);
+            if (detachIfReset) {
+                detach();
+            }
+        }
+
+        public void detach() {
+            mTimeBase.remove(this);
+        }
+
+        private void writeSummaryToParcelLocked(Parcel out) {
+            out.writeLongArray(mCounts);
+        }
+
+        private void readSummaryFromParcelLocked(Parcel in) {
+            mCounts = in.createLongArray();
+            mLoadedCounts = copyArray(mCounts, mLoadedCounts);
+            mUnpluggedCounts = copyArray(mCounts, mUnpluggedCounts);
+            mPluggedCounts = copyArray(mCounts, mPluggedCounts);
+        }
+
+        public static void writeToParcel(Parcel out, LongSamplingCounterArray counterArray) {
+            if (counterArray != null) {
+                out.writeInt(1);
+                counterArray.writeToParcel(out);
+            } else {
+                out.writeInt(0);
+            }
+        }
+
+        public static LongSamplingCounterArray readFromParcel(Parcel in, TimeBase timeBase) {
+            if (in.readInt() != 0) {
+                return new LongSamplingCounterArray(timeBase, in);
+            } else {
+                return null;
+            }
+        }
+
+        public static void writeSummaryToParcelLocked(Parcel out,
+                LongSamplingCounterArray counterArray) {
+            if (counterArray != null) {
+                out.writeInt(1);
+                counterArray.writeSummaryToParcelLocked(out);
+            } else {
+                out.writeInt(0);
+            }
+        }
+
+        public static LongSamplingCounterArray readSummaryFromParcelLocked(Parcel in,
+                TimeBase timeBase) {
+            if (in.readInt() != 0) {
+                final LongSamplingCounterArray counterArray
+                        = new LongSamplingCounterArray(timeBase);
+                counterArray.readSummaryFromParcelLocked(in);
+                return counterArray;
+            } else {
+                return null;
+            }
+        }
+
+        private static void fillArray(long[] a, long val) {
+            if (a != null) {
+                Arrays.fill(a, val);
+            }
+        }
+
+        private static void subtract(@NonNull long[] val, long[] toSubtract) {
+            if (toSubtract == null) {
+                return;
+            }
+            for (int i = 0; i < val.length; i++) {
+                val[i] -= toSubtract[i];
+            }
+        }
+
+        private static long[] copyArray(long[] src, long[] dest) {
+            if (src == null) {
+                return null;
+            } else {
+                if (dest == null) {
+                    dest = new long[src.length];
+                }
+                System.arraycopy(src, 0, dest, 0, src.length);
+                return dest;
+            }
+        }
+    }
+
+    public static class LongSamplingCounter extends LongCounter implements TimeBaseObs {
+        final TimeBase mTimeBase;
+        long mCount;
+        long mLoadedCount;
+        long mUnpluggedCount;
+        long mPluggedCount;
+
+        LongSamplingCounter(TimeBase timeBase, Parcel in) {
+            mTimeBase = timeBase;
+            mPluggedCount = in.readLong();
+            mCount = mPluggedCount;
+            mLoadedCount = in.readLong();
+            mUnpluggedCount = in.readLong();
+            timeBase.add(this);
+        }
+
+        LongSamplingCounter(TimeBase timeBase) {
+            mTimeBase = timeBase;
+            timeBase.add(this);
+        }
+
+        public void writeToParcel(Parcel out) {
+            out.writeLong(mCount);
+            out.writeLong(mLoadedCount);
+            out.writeLong(mUnpluggedCount);
+        }
+
+        @Override
+        public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
+            mUnpluggedCount = mPluggedCount;
+        }
+
+        @Override
+        public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
+            mPluggedCount = mCount;
+        }
+
+        public long getCountLocked(int which) {
+            long val = mTimeBase.isRunning() ? mCount : mPluggedCount;
+            if (which == STATS_SINCE_UNPLUGGED) {
+                val -= mUnpluggedCount;
+            } else if (which != STATS_SINCE_CHARGED) {
+                val -= mLoadedCount;
+            }
+            return val;
+        }
+
+        @Override
+        public void logState(Printer pw, String prefix) {
+            pw.println(prefix + "mCount=" + mCount
+                    + " mLoadedCount=" + mLoadedCount
+                    + " mUnpluggedCount=" + mUnpluggedCount
+                    + " mPluggedCount=" + mPluggedCount);
+        }
+
+        void addCountLocked(long count) {
+            if (mTimeBase.isRunning()) {
+                mCount += count;
+            }
+        }
+
+        /**
+         * Clear state of this counter.
+         */
+        void reset(boolean detachIfReset) {
+            mCount = 0;
+            mLoadedCount = mPluggedCount = mUnpluggedCount = 0;
+            if (detachIfReset) {
+                detach();
+            }
+        }
+
+        void detach() {
+            mTimeBase.remove(this);
+        }
+
+        void writeSummaryFromParcelLocked(Parcel out) {
+            out.writeLong(mCount);
+        }
+
+        void readSummaryFromParcelLocked(Parcel in) {
+            mLoadedCount = in.readLong();
+            mCount = mLoadedCount;
+            mUnpluggedCount = mPluggedCount = mLoadedCount;
+        }
+    }
+
+    /**
+     * State for keeping track of timing information.
+     */
+    public static abstract class Timer extends BatteryStats.Timer implements TimeBaseObs {
+        protected final Clocks mClocks;
+        protected final int mType;
+        protected final TimeBase mTimeBase;
+
+        protected int mCount;
+        protected int mLoadedCount;
+        protected int mLastCount;
+        protected int mUnpluggedCount;
+
+        // Times are in microseconds for better accuracy when dividing by the
+        // lock count, and are in "battery realtime" units.
+
+        /**
+         * The total time we have accumulated since the start of the original
+         * boot, to the last time something interesting happened in the
+         * current run.
+         */
+        protected long mTotalTime;
+
+        /**
+         * The total time we loaded for the previous runs.  Subtract this from
+         * mTotalTime to find the time for the current run of the system.
+         */
+        protected long mLoadedTime;
+
+        /**
+         * The run time of the last run of the system, as loaded from the
+         * saved data.
+         */
+        protected long mLastTime;
+
+        /**
+         * The value of mTotalTime when unplug() was last called.  Subtract
+         * this from mTotalTime to find the time since the last unplug from
+         * power.
+         */
+        protected long mUnpluggedTime;
+
+        /**
+         * The total time this timer has been running until the latest mark has been set.
+         * Subtract this from mTotalTime to get the time spent running since the mark was set.
+         */
+        protected long mTimeBeforeMark;
+
+        /**
+         * Constructs from a parcel.
+         * @param type
+         * @param timeBase
+         * @param in
+         */
+        public Timer(Clocks clocks, int type, TimeBase timeBase, Parcel in) {
+            mClocks = clocks;
+            mType = type;
+            mTimeBase = timeBase;
+
+            mCount = in.readInt();
+            mLoadedCount = in.readInt();
+            mLastCount = 0;
+            mUnpluggedCount = in.readInt();
+            mTotalTime = in.readLong();
+            mLoadedTime = in.readLong();
+            mLastTime = 0;
+            mUnpluggedTime = in.readLong();
+            mTimeBeforeMark = in.readLong();
+            timeBase.add(this);
+            if (DEBUG) Log.i(TAG, "**** READ TIMER #" + mType + ": mTotalTime=" + mTotalTime);
+        }
+
+        public Timer(Clocks clocks, int type, TimeBase timeBase) {
+            mClocks = clocks;
+            mType = type;
+            mTimeBase = timeBase;
+            timeBase.add(this);
+        }
+
+        protected abstract long computeRunTimeLocked(long curBatteryRealtime);
+
+        protected abstract int computeCurrentCountLocked();
+
+        /**
+         * Clear state of this timer.  Returns true if the timer is inactive
+         * so can be completely dropped.
+         */
+        public boolean reset(boolean detachIfReset) {
+            mTotalTime = mLoadedTime = mLastTime = mTimeBeforeMark = 0;
+            mCount = mLoadedCount = mLastCount = 0;
+            if (detachIfReset) {
+                detach();
+            }
+            return true;
+        }
+
+        public void detach() {
+            mTimeBase.remove(this);
+        }
+
+        public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+            if (DEBUG) Log.i(TAG, "**** WRITING TIMER #" + mType + ": mTotalTime="
+                    + computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs)));
+            out.writeInt(computeCurrentCountLocked());
+            out.writeInt(mLoadedCount);
+            out.writeInt(mUnpluggedCount);
+            out.writeLong(computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs)));
+            out.writeLong(mLoadedTime);
+            out.writeLong(mUnpluggedTime);
+            out.writeLong(mTimeBeforeMark);
+        }
+
+        @Override
+        public void onTimeStarted(long elapsedRealtime, long timeBaseUptime, long baseRealtime) {
+            if (DEBUG && mType < 0) {
+                Log.v(TAG, "unplug #" + mType + ": realtime=" + baseRealtime
+                        + " old mUnpluggedTime=" + mUnpluggedTime
+                        + " old mUnpluggedCount=" + mUnpluggedCount);
+            }
+            mUnpluggedTime = computeRunTimeLocked(baseRealtime);
+            mUnpluggedCount = computeCurrentCountLocked();
+            if (DEBUG && mType < 0) {
+                Log.v(TAG, "unplug #" + mType
+                        + ": new mUnpluggedTime=" + mUnpluggedTime
+                        + " new mUnpluggedCount=" + mUnpluggedCount);
+            }
+        }
+
+        @Override
+        public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
+            if (DEBUG && mType < 0) {
+                Log.v(TAG, "plug #" + mType + ": realtime=" + baseRealtime
+                        + " old mTotalTime=" + mTotalTime);
+            }
+            mTotalTime = computeRunTimeLocked(baseRealtime);
+            mCount = computeCurrentCountLocked();
+            if (DEBUG && mType < 0) {
+                Log.v(TAG, "plug #" + mType
+                        + ": new mTotalTime=" + mTotalTime);
+            }
+        }
+
+        /**
+         * Writes a possibly null Timer to a Parcel.
+         *
+         * @param out the Parcel to be written to.
+         * @param timer a Timer, or null.
+         */
+        public static void writeTimerToParcel(Parcel out, Timer timer, long elapsedRealtimeUs) {
+            if (timer == null) {
+                out.writeInt(0); // indicates null
+                return;
+            }
+            out.writeInt(1); // indicates non-null
+
+            timer.writeToParcel(out, elapsedRealtimeUs);
+        }
+
+        @Override
+        public long getTotalTimeLocked(long elapsedRealtimeUs, int which) {
+            long val = computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs));
+            if (which == STATS_SINCE_UNPLUGGED) {
+                val -= mUnpluggedTime;
+            } else if (which != STATS_SINCE_CHARGED) {
+                val -= mLoadedTime;
+            }
+
+            return val;
+        }
+
+        @Override
+        public int getCountLocked(int which) {
+            int val = computeCurrentCountLocked();
+            if (which == STATS_SINCE_UNPLUGGED) {
+                val -= mUnpluggedCount;
+            } else if (which != STATS_SINCE_CHARGED) {
+                val -= mLoadedCount;
+            }
+
+            return val;
+        }
+
+        @Override
+        public long getTimeSinceMarkLocked(long elapsedRealtimeUs) {
+            long val = computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs));
+            return val - mTimeBeforeMark;
+        }
+
+        @Override
+        public void logState(Printer pw, String prefix) {
+            pw.println(prefix + "mCount=" + mCount
+                    + " mLoadedCount=" + mLoadedCount + " mLastCount=" + mLastCount
+                    + " mUnpluggedCount=" + mUnpluggedCount);
+            pw.println(prefix + "mTotalTime=" + mTotalTime
+                    + " mLoadedTime=" + mLoadedTime);
+            pw.println(prefix + "mLastTime=" + mLastTime
+                    + " mUnpluggedTime=" + mUnpluggedTime);
+        }
+
+
+        public void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) {
+            long runTime = computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs));
+            out.writeLong(runTime);
+            out.writeInt(computeCurrentCountLocked());
+        }
+
+        public void readSummaryFromParcelLocked(Parcel in) {
+            // Multiply by 1000 for backwards compatibility
+            mTotalTime = mLoadedTime = in.readLong();
+            mLastTime = 0;
+            mUnpluggedTime = mTotalTime;
+            mCount = mLoadedCount = in.readInt();
+            mLastCount = 0;
+            mUnpluggedCount = mCount;
+
+            // When reading the summary, we set the mark to be the latest information.
+            mTimeBeforeMark = mTotalTime;
+        }
+    }
+
+    /**
+     * A counter meant to accept monotonically increasing values to its {@link #update(long, int)}
+     * method. The state of the timer according to its {@link TimeBase} will determine how much
+     * of the value is recorded.
+     *
+     * If the value being recorded resets, {@link #endSample()} can be called in order to
+     * account for the change. If the value passed in to {@link #update(long, int)} decreased
+     * between calls, the {@link #endSample()} is automatically called and the new value is
+     * expected to increase monotonically from that point on.
+     */
+    public static class SamplingTimer extends Timer {
+
+        /**
+         * The most recent reported count from /proc/wakelocks.
+         */
+        int mCurrentReportedCount;
+
+        /**
+         * The reported count from /proc/wakelocks when unplug() was last
+         * called.
+         */
+        int mUnpluggedReportedCount;
+
+        /**
+         * The most recent reported total_time from /proc/wakelocks.
+         */
+        long mCurrentReportedTotalTime;
+
+
+        /**
+         * The reported total_time from /proc/wakelocks when unplug() was last
+         * called.
+         */
+        long mUnpluggedReportedTotalTime;
+
+        /**
+         * Whether we are currently in a discharge cycle.
+         */
+        boolean mTimeBaseRunning;
+
+        /**
+         * Whether we are currently recording reported values.
+         */
+        boolean mTrackingReportedValues;
+
+        /*
+         * A sequence counter, incremented once for each update of the stats.
+         */
+        int mUpdateVersion;
+
+        @VisibleForTesting
+        public SamplingTimer(Clocks clocks, TimeBase timeBase, Parcel in) {
+            super(clocks, 0, timeBase, in);
+            mCurrentReportedCount = in.readInt();
+            mUnpluggedReportedCount = in.readInt();
+            mCurrentReportedTotalTime = in.readLong();
+            mUnpluggedReportedTotalTime = in.readLong();
+            mTrackingReportedValues = in.readInt() == 1;
+            mTimeBaseRunning = timeBase.isRunning();
+        }
+
+        @VisibleForTesting
+        public SamplingTimer(Clocks clocks, TimeBase timeBase) {
+            super(clocks, 0, timeBase);
+            mTrackingReportedValues = false;
+            mTimeBaseRunning = timeBase.isRunning();
+        }
+
+        /**
+         * Ends the current sample, allowing subsequent values to {@link #update(long, int)} to
+         * be less than the values used for a previous invocation.
+         */
+        public void endSample() {
+            mTotalTime = computeRunTimeLocked(0 /* unused by us */);
+            mCount = computeCurrentCountLocked();
+            mUnpluggedReportedTotalTime = mCurrentReportedTotalTime = 0;
+            mUnpluggedReportedCount = mCurrentReportedCount = 0;
+        }
+
+        public void setUpdateVersion(int version) {
+            mUpdateVersion = version;
+        }
+
+        public int getUpdateVersion() {
+            return mUpdateVersion;
+        }
+
+        /**
+         * Updates the current recorded values. These are meant to be monotonically increasing
+         * and cumulative. If you are dealing with deltas, use {@link #add(long, int)}.
+         *
+         * If the values being recorded have been reset, the monotonically increasing requirement
+         * will be broken. In this case, {@link #endSample()} is automatically called and
+         * the total value of totalTime and count are recorded, starting a new monotonically
+         * increasing sample.
+         *
+         * @param totalTime total time of sample in microseconds.
+         * @param count total number of times the event being sampled occurred.
+         */
+        public void update(long totalTime, int count) {
+            if (mTimeBaseRunning && !mTrackingReportedValues) {
+                // Updating the reported value for the first time.
+                mUnpluggedReportedTotalTime = totalTime;
+                mUnpluggedReportedCount = count;
+            }
+
+            mTrackingReportedValues = true;
+
+            if (totalTime < mCurrentReportedTotalTime || count < mCurrentReportedCount) {
+                endSample();
+            }
+
+            mCurrentReportedTotalTime = totalTime;
+            mCurrentReportedCount = count;
+        }
+
+        /**
+         * Adds deltaTime and deltaCount to the current sample.
+         *
+         * @param deltaTime additional time recorded since the last sampled event, in microseconds.
+         * @param deltaCount additional number of times the event being sampled occurred.
+         */
+        public void add(long deltaTime, int deltaCount) {
+            update(mCurrentReportedTotalTime + deltaTime, mCurrentReportedCount + deltaCount);
+        }
+
+        @Override
+        public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
+            super.onTimeStarted(elapsedRealtime, baseUptime, baseRealtime);
+            if (mTrackingReportedValues) {
+                mUnpluggedReportedTotalTime = mCurrentReportedTotalTime;
+                mUnpluggedReportedCount = mCurrentReportedCount;
+            }
+            mTimeBaseRunning = true;
+        }
+
+        @Override
+        public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
+            super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime);
+            mTimeBaseRunning = false;
+        }
+
+        @Override
+        public void logState(Printer pw, String prefix) {
+            super.logState(pw, prefix);
+            pw.println(prefix + "mCurrentReportedCount=" + mCurrentReportedCount
+                    + " mUnpluggedReportedCount=" + mUnpluggedReportedCount
+                    + " mCurrentReportedTotalTime=" + mCurrentReportedTotalTime
+                    + " mUnpluggedReportedTotalTime=" + mUnpluggedReportedTotalTime);
+        }
+
+        @Override
+        protected long computeRunTimeLocked(long curBatteryRealtime) {
+            return mTotalTime + (mTimeBaseRunning && mTrackingReportedValues
+                    ? mCurrentReportedTotalTime - mUnpluggedReportedTotalTime : 0);
+        }
+
+        @Override
+        protected int computeCurrentCountLocked() {
+            return mCount + (mTimeBaseRunning && mTrackingReportedValues
+                    ? mCurrentReportedCount - mUnpluggedReportedCount : 0);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+            super.writeToParcel(out, elapsedRealtimeUs);
+            out.writeInt(mCurrentReportedCount);
+            out.writeInt(mUnpluggedReportedCount);
+            out.writeLong(mCurrentReportedTotalTime);
+            out.writeLong(mUnpluggedReportedTotalTime);
+            out.writeInt(mTrackingReportedValues ? 1 : 0);
+        }
+
+        @Override
+        public boolean reset(boolean detachIfReset) {
+            super.reset(detachIfReset);
+            mTrackingReportedValues = false;
+            mUnpluggedReportedTotalTime = 0;
+            mUnpluggedReportedCount = 0;
+            return true;
+        }
+    }
+
+    /**
+     * A timer that increments in batches.  It does not run for durations, but just jumps
+     * for a pre-determined amount.
+     */
+    public static class BatchTimer extends Timer {
+        final Uid mUid;
+
+        /**
+         * The last time at which we updated the timer.  This is in elapsed realtime microseconds.
+         */
+        long mLastAddedTime;
+
+        /**
+         * The last duration that we added to the timer.  This is in microseconds.
+         */
+        long mLastAddedDuration;
+
+        /**
+         * Whether we are currently in a discharge cycle.
+         */
+        boolean mInDischarge;
+
+        BatchTimer(Clocks clocks, Uid uid, int type, TimeBase timeBase, Parcel in) {
+            super(clocks, type, timeBase, in);
+            mUid = uid;
+            mLastAddedTime = in.readLong();
+            mLastAddedDuration = in.readLong();
+            mInDischarge = timeBase.isRunning();
+        }
+
+        BatchTimer(Clocks clocks, Uid uid, int type, TimeBase timeBase) {
+            super(clocks, type, timeBase);
+            mUid = uid;
+            mInDischarge = timeBase.isRunning();
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+            super.writeToParcel(out, elapsedRealtimeUs);
+            out.writeLong(mLastAddedTime);
+            out.writeLong(mLastAddedDuration);
+        }
+
+        @Override
+        public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
+            recomputeLastDuration(mClocks.elapsedRealtime() * 1000, false);
+            mInDischarge = false;
+            super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime);
+        }
+
+        @Override
+        public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
+            recomputeLastDuration(elapsedRealtime, false);
+            mInDischarge = true;
+            // If we are still within the last added duration, then re-added whatever remains.
+            if (mLastAddedTime == elapsedRealtime) {
+                mTotalTime += mLastAddedDuration;
+            }
+            super.onTimeStarted(elapsedRealtime, baseUptime, baseRealtime);
+        }
+
+        @Override
+        public void logState(Printer pw, String prefix) {
+            super.logState(pw, prefix);
+            pw.println(prefix + "mLastAddedTime=" + mLastAddedTime
+                    + " mLastAddedDuration=" + mLastAddedDuration);
+        }
+
+        private long computeOverage(long curTime) {
+            if (mLastAddedTime > 0) {
+                return mLastTime + mLastAddedDuration - curTime;
+            }
+            return 0;
+        }
+
+        private void recomputeLastDuration(long curTime, boolean abort) {
+            final long overage = computeOverage(curTime);
+            if (overage > 0) {
+                // Aborting before the duration ran out -- roll back the remaining
+                // duration.  Only do this if currently discharging; otherwise we didn't
+                // actually add the time.
+                if (mInDischarge) {
+                    mTotalTime -= overage;
+                }
+                if (abort) {
+                    mLastAddedTime = 0;
+                } else {
+                    mLastAddedTime = curTime;
+                    mLastAddedDuration -= overage;
+                }
+            }
+        }
+
+        public void addDuration(BatteryStatsImpl stats, long durationMillis) {
+            final long now = mClocks.elapsedRealtime() * 1000;
+            recomputeLastDuration(now, true);
+            mLastAddedTime = now;
+            mLastAddedDuration = durationMillis * 1000;
+            if (mInDischarge) {
+                mTotalTime += mLastAddedDuration;
+                mCount++;
+            }
+        }
+
+        public void abortLastDuration(BatteryStatsImpl stats) {
+            final long now = mClocks.elapsedRealtime() * 1000;
+            recomputeLastDuration(now, true);
+        }
+
+        @Override
+        protected int computeCurrentCountLocked() {
+            return mCount;
+        }
+
+        @Override
+        protected long computeRunTimeLocked(long curBatteryRealtime) {
+            final long overage = computeOverage(mClocks.elapsedRealtime() * 1000);
+            if (overage > 0) {
+                return mTotalTime = overage;
+            }
+            return mTotalTime;
+        }
+
+        @Override
+        public boolean reset(boolean detachIfReset) {
+            final long now = mClocks.elapsedRealtime() * 1000;
+            recomputeLastDuration(now, true);
+            boolean stillActive = mLastAddedTime == now;
+            super.reset(!stillActive && detachIfReset);
+            return !stillActive;
+        }
+    }
+
+
+    /**
+     * A StopwatchTimer that also tracks the total and max individual
+     * time spent active according to the given timebase.  Whereas
+     * StopwatchTimer apportions the time amongst all in the pool,
+     * the total and max durations are not apportioned.
+     */
+    public static class DurationTimer extends StopwatchTimer {
+        /**
+         * The time (in ms) that the timer was last acquired or the time base
+         * last (re-)started. Increasing the nesting depth does not reset this time.
+         *
+         * -1 if the timer is currently not running or the time base is not running.
+         *
+         * If written to a parcel, the start time is reset, as is mNesting in the base class
+         * StopwatchTimer.
+         */
+        long mStartTimeMs = -1;
+
+        /**
+         * The longest time period (in ms) that the timer has been active. Not pooled.
+         */
+        long mMaxDurationMs;
+
+        /**
+         * The time (in ms) that that the timer has been active since most recent
+         * stopRunningLocked() or reset(). Not pooled.
+         */
+        long mCurrentDurationMs;
+
+        /**
+         * The total time (in ms) that that the timer has been active since most recent reset()
+         * prior to the current startRunningLocked. This is the sum of all past currentDurations
+         * (but not including the present currentDuration) since reset. Not pooled.
+         */
+        long mTotalDurationMs;
+
+        public DurationTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
+                TimeBase timeBase, Parcel in) {
+            super(clocks, uid, type, timerPool, timeBase, in);
+            mMaxDurationMs = in.readLong();
+            mTotalDurationMs = in.readLong();
+            mCurrentDurationMs = in.readLong();
+        }
+
+        public DurationTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
+                TimeBase timeBase) {
+            super(clocks, uid, type, timerPool, timeBase);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+            super.writeToParcel(out, elapsedRealtimeUs);
+            out.writeLong(getMaxDurationMsLocked(elapsedRealtimeUs / 1000));
+            out.writeLong(mTotalDurationMs);
+            out.writeLong(getCurrentDurationMsLocked(elapsedRealtimeUs / 1000));
+        }
+
+        /**
+         * Write the summary to the parcel.
+         *
+         * Since the time base is probably meaningless after we come back, reading
+         * from this will have the effect of stopping the timer. So here all we write
+         * is the max and total durations.
+         */
+        @Override
+        public void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) {
+            super.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
+            out.writeLong(getMaxDurationMsLocked(elapsedRealtimeUs / 1000));
+            out.writeLong(getTotalDurationMsLocked(elapsedRealtimeUs / 1000));
+        }
+
+        /**
+         * Read the summary parcel.
+         *
+         * Has the side effect of stopping the timer.
+         */
+        @Override
+        public void readSummaryFromParcelLocked(Parcel in) {
+            super.readSummaryFromParcelLocked(in);
+            mMaxDurationMs = in.readLong();
+            mTotalDurationMs = in.readLong();
+            mStartTimeMs = -1;
+            mCurrentDurationMs = 0;
+        }
+
+        /**
+         * The TimeBase time started (again).
+         *
+         * If the timer is also running, store the start time.
+         */
+        public void onTimeStarted(long elapsedRealtimeUs, long baseUptime, long baseRealtime) {
+            super.onTimeStarted(elapsedRealtimeUs, baseUptime, baseRealtime);
+            if (mNesting > 0) {
+                mStartTimeMs = baseRealtime / 1000;
+            }
+        }
+
+        /**
+         * The TimeBase stopped running.
+         *
+         * If the timer is running, add the duration into mCurrentDurationMs.
+         */
+        @Override
+        public void onTimeStopped(long elapsedRealtimeUs, long baseUptime, long baseRealtimeUs) {
+            super.onTimeStopped(elapsedRealtimeUs, baseUptime, baseRealtimeUs);
+            if (mNesting > 0) {
+                // baseRealtimeUs has already been converted to the timebase's realtime.
+                mCurrentDurationMs += (baseRealtimeUs / 1000) - mStartTimeMs;
+            }
+            mStartTimeMs = -1;
+        }
+
+        @Override
+        public void logState(Printer pw, String prefix) {
+            super.logState(pw, prefix);
+        }
+
+        @Override
+        public void startRunningLocked(long elapsedRealtimeMs) {
+            super.startRunningLocked(elapsedRealtimeMs);
+            if (mNesting == 1 && mTimeBase.isRunning()) {
+                // Just started
+                mStartTimeMs = mTimeBase.getRealtime(elapsedRealtimeMs * 1000) / 1000;
+            }
+        }
+
+        /**
+         * Decrements the mNesting ref-count on this timer.
+         *
+         * If it actually stopped (mNesting went to 0), then possibly update
+         * mMaxDuration if the current duration was the longest ever.
+         */
+        @Override
+        public void stopRunningLocked(long elapsedRealtimeMs) {
+            if (mNesting == 1) {
+                final long durationMs = getCurrentDurationMsLocked(elapsedRealtimeMs);
+                mTotalDurationMs += durationMs;
+                if (durationMs > mMaxDurationMs) {
+                    mMaxDurationMs = durationMs;
+                }
+                mStartTimeMs = -1;
+                mCurrentDurationMs = 0;
+            }
+            // super method decrements mNesting, which getCurrentDurationMsLocked relies on,
+            // so call super.stopRunningLocked after calling getCurrentDurationMsLocked.
+            super.stopRunningLocked(elapsedRealtimeMs);
+        }
+
+        @Override
+        public boolean reset(boolean detachIfReset) {
+            boolean result = super.reset(detachIfReset);
+            mMaxDurationMs = 0;
+            mTotalDurationMs = 0;
+            mCurrentDurationMs = 0;
+            if (mNesting > 0) {
+                mStartTimeMs = mTimeBase.getRealtime(mClocks.elapsedRealtime()*1000) / 1000;
+            } else {
+                mStartTimeMs = -1;
+            }
+            return result;
+        }
+
+        /**
+         * Returns the max duration that this timer has ever seen.
+         *
+         * Note that this time is NOT split between the timers in the timer group that
+         * this timer is attached to.  It is the TOTAL time.
+         */
+        @Override
+        public long getMaxDurationMsLocked(long elapsedRealtimeMs) {
+            if (mNesting > 0) {
+                final long durationMs = getCurrentDurationMsLocked(elapsedRealtimeMs);
+                if (durationMs > mMaxDurationMs) {
+                    return durationMs;
+                }
+            }
+            return mMaxDurationMs;
+        }
+
+        /**
+         * Returns the time since the timer was started.
+         * Returns 0 if the timer is not currently running.
+         *
+         * Note that this time is NOT split between the timers in the timer group that
+         * this timer is attached to.  It is the TOTAL time.
+         *
+         * Note that if running timer is parceled and unparceled, this method will return
+         * current duration value at the time of parceling even though timer may not be
+         * currently running.
+         */
+        @Override
+        public long getCurrentDurationMsLocked(long elapsedRealtimeMs) {
+            long durationMs = mCurrentDurationMs;
+            if (mNesting > 0 && mTimeBase.isRunning()) {
+                durationMs += (mTimeBase.getRealtime(elapsedRealtimeMs*1000)/1000)
+                        - mStartTimeMs;
+            }
+            return durationMs;
+        }
+
+        /**
+         * Returns the total cumulative duration that this timer has been on since reset().
+         * If mTimerPool == null, this should be the same
+         * as getTotalTimeLocked(elapsedRealtimeMs*1000, STATS_SINCE_CHARGED)/1000.
+         *
+         * Note that this time is NOT split between the timers in the timer group that
+         * this timer is attached to.  It is the TOTAL time. For this reason, if mTimerPool != null,
+         * the result will not be equivalent to getTotalTimeLocked.
+         */
+        @Override
+        public long getTotalDurationMsLocked(long elapsedRealtimeMs) {
+            return mTotalDurationMs + getCurrentDurationMsLocked(elapsedRealtimeMs);
+        }
+    }
+
+    /**
+     * State for keeping track of timing information.
+     */
+    public static class StopwatchTimer extends Timer {
+        final Uid mUid;
+        final ArrayList<StopwatchTimer> mTimerPool;
+
+        int mNesting;
+
+        /**
+         * The last time at which we updated the timer.  If mNesting is > 0,
+         * subtract this from the current battery time to find the amount of
+         * time we have been running since we last computed an update.
+         */
+        long mUpdateTime;
+
+        /**
+         * The total time at which the timer was acquired, to determine if it
+         * was actually held for an interesting duration. If time base was not running when timer
+         * was acquired, will be -1.
+         */
+        long mAcquireTime = -1;
+
+        long mTimeout;
+
+        /**
+         * For partial wake locks, keep track of whether we are in the list
+         * to consume CPU cycles.
+         */
+        @VisibleForTesting
+        public boolean mInList;
+
+        public StopwatchTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
+                TimeBase timeBase, Parcel in) {
+            super(clocks, type, timeBase, in);
+            mUid = uid;
+            mTimerPool = timerPool;
+            mUpdateTime = in.readLong();
+        }
+
+        public StopwatchTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
+                TimeBase timeBase) {
+            super(clocks, type, timeBase);
+            mUid = uid;
+            mTimerPool = timerPool;
+        }
+
+        public void setTimeout(long timeout) {
+            mTimeout = timeout;
+        }
+
+        public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+            super.writeToParcel(out, elapsedRealtimeUs);
+            out.writeLong(mUpdateTime);
+        }
+
+        public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
+            if (mNesting > 0) {
+                if (DEBUG && mType < 0) {
+                    Log.v(TAG, "old mUpdateTime=" + mUpdateTime);
+                }
+                super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime);
+                mUpdateTime = baseRealtime;
+                if (DEBUG && mType < 0) {
+                    Log.v(TAG, "new mUpdateTime=" + mUpdateTime);
+                }
+            }
+        }
+
+        public void logState(Printer pw, String prefix) {
+            super.logState(pw, prefix);
+            pw.println(prefix + "mNesting=" + mNesting + " mUpdateTime=" + mUpdateTime
+                    + " mAcquireTime=" + mAcquireTime);
+        }
+
+        public void startRunningLocked(long elapsedRealtimeMs) {
+            if (mNesting++ == 0) {
+                final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
+                mUpdateTime = batteryRealtime;
+                if (mTimerPool != null) {
+                    // Accumulate time to all currently active timers before adding
+                    // this new one to the pool.
+                    refreshTimersLocked(batteryRealtime, mTimerPool, null);
+                    // Add this timer to the active pool
+                    mTimerPool.add(this);
+                }
+                if (mTimeBase.isRunning()) {
+                    // Increment the count
+                    mCount++;
+                    mAcquireTime = mTotalTime;
+                } else {
+                    mAcquireTime = -1;
+                }
+                if (DEBUG && mType < 0) {
+                    Log.v(TAG, "start #" + mType + ": mUpdateTime=" + mUpdateTime
+                            + " mTotalTime=" + mTotalTime + " mCount=" + mCount
+                            + " mAcquireTime=" + mAcquireTime);
+                }
+            }
+        }
+
+        public boolean isRunningLocked() {
+            return mNesting > 0;
+        }
+
+        public void stopRunningLocked(long elapsedRealtimeMs) {
+            // Ignore attempt to stop a timer that isn't running
+            if (mNesting == 0) {
+                return;
+            }
+            if (--mNesting == 0) {
+                final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
+                if (mTimerPool != null) {
+                    // Accumulate time to all active counters, scaled by the total
+                    // active in the pool, before taking this one out of the pool.
+                    refreshTimersLocked(batteryRealtime, mTimerPool, null);
+                    // Remove this timer from the active pool
+                    mTimerPool.remove(this);
+                } else {
+                    mNesting = 1;
+                    mTotalTime = computeRunTimeLocked(batteryRealtime);
+                    mNesting = 0;
+                }
+
+                if (DEBUG && mType < 0) {
+                    Log.v(TAG, "stop #" + mType + ": mUpdateTime=" + mUpdateTime
+                            + " mTotalTime=" + mTotalTime + " mCount=" + mCount
+                            + " mAcquireTime=" + mAcquireTime);
+                }
+
+                if (mAcquireTime >= 0 && mTotalTime == mAcquireTime) {
+                    // If there was no change in the time, then discard this
+                    // count.  A somewhat cheezy strategy, but hey.
+                    mCount--;
+                }
+            }
+        }
+
+        public void stopAllRunningLocked(long elapsedRealtimeMs) {
+            if (mNesting > 0) {
+                mNesting = 1;
+                stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        // Update the total time for all other running Timers with the same type as this Timer
+        // due to a change in timer count
+        private static long refreshTimersLocked(long batteryRealtime,
+                final ArrayList<StopwatchTimer> pool, StopwatchTimer self) {
+            long selfTime = 0;
+            final int N = pool.size();
+            for (int i=N-1; i>= 0; i--) {
+                final StopwatchTimer t = pool.get(i);
+                long heldTime = batteryRealtime - t.mUpdateTime;
+                if (heldTime > 0) {
+                    final long myTime = heldTime / N;
+                    if (t == self) {
+                        selfTime = myTime;
+                    }
+                    t.mTotalTime += myTime;
+                }
+                t.mUpdateTime = batteryRealtime;
+            }
+            return selfTime;
+        }
+
+        @Override
+        protected long computeRunTimeLocked(long curBatteryRealtime) {
+            if (mTimeout > 0 && curBatteryRealtime > mUpdateTime + mTimeout) {
+                curBatteryRealtime = mUpdateTime + mTimeout;
+            }
+            return mTotalTime + (mNesting > 0
+                    ? (curBatteryRealtime - mUpdateTime)
+                            / (mTimerPool != null ? mTimerPool.size() : 1)
+                    : 0);
+        }
+
+        @Override
+        protected int computeCurrentCountLocked() {
+            return mCount;
+        }
+
+        @Override
+        public boolean reset(boolean detachIfReset) {
+            boolean canDetach = mNesting <= 0;
+            super.reset(canDetach && detachIfReset);
+            if (mNesting > 0) {
+                mUpdateTime = mTimeBase.getRealtime(mClocks.elapsedRealtime() * 1000);
+            }
+            mAcquireTime = -1; // to ensure mCount isn't decreased to -1 if timer is stopped later.
+            return canDetach;
+        }
+
+        @Override
+        public void detach() {
+            super.detach();
+            if (mTimerPool != null) {
+                mTimerPool.remove(this);
+            }
+        }
+
+        @Override
+        public void readSummaryFromParcelLocked(Parcel in) {
+            super.readSummaryFromParcelLocked(in);
+            mNesting = 0;
+        }
+
+        /**
+         * Set the mark so that we can query later for the total time the timer has
+         * accumulated since this point. The timer can be running or not.
+         *
+         * @param elapsedRealtimeMs the current elapsed realtime in milliseconds.
+         */
+        public void setMark(long elapsedRealtimeMs) {
+            final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
+            if (mNesting > 0) {
+                // We are running.
+                if (mTimerPool != null) {
+                    refreshTimersLocked(batteryRealtime, mTimerPool, this);
+                } else {
+                    mTotalTime += batteryRealtime - mUpdateTime;
+                    mUpdateTime = batteryRealtime;
+                }
+            }
+            mTimeBeforeMark = mTotalTime;
+        }
+    }
+
+    /**
+     * State for keeping track of two DurationTimers with different TimeBases, presumably where one
+     * TimeBase is effectively a subset of the other.
+     */
+    public static class DualTimer extends DurationTimer {
+        // This class both is a DurationTimer and also holds a second DurationTimer.
+        // The main timer (this) typically tracks the total time. It may be pooled (but since it's a
+        // durationTimer, it also has the unpooled getTotalDurationMsLocked() for
+        // STATS_SINCE_CHARGED).
+        // mSubTimer typically tracks only part of the total time, such as background time, as
+        // determined by a subTimeBase. It is NOT pooled.
+        private final DurationTimer mSubTimer;
+
+        /**
+         * Creates a DualTimer to hold a main timer (this) and a mSubTimer.
+         * The main timer (this) is based on the given timeBase and timerPool.
+         * The mSubTimer is based on the given subTimeBase. The mSubTimer is not pooled, even if
+         * the main timer is.
+         */
+        public DualTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
+                TimeBase timeBase, TimeBase subTimeBase, Parcel in) {
+            super(clocks, uid, type, timerPool, timeBase, in);
+            mSubTimer = new DurationTimer(clocks, uid, type, null, subTimeBase, in);
+        }
+
+        /**
+         * Creates a DualTimer to hold a main timer (this) and a mSubTimer.
+         * The main timer (this) is based on the given timeBase and timerPool.
+         * The mSubTimer is based on the given subTimeBase. The mSubTimer is not pooled, even if
+         * the main timer is.
+         */
+        public DualTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
+                TimeBase timeBase, TimeBase subTimeBase) {
+            super(clocks, uid, type, timerPool, timeBase);
+            mSubTimer = new DurationTimer(clocks, uid, type, null, subTimeBase);
+        }
+
+        /** Get the secondary timer. */
+        @Override
+        public DurationTimer getSubTimer() {
+            return mSubTimer;
+        }
+
+        @Override
+        public void startRunningLocked(long elapsedRealtimeMs) {
+            super.startRunningLocked(elapsedRealtimeMs);
+            mSubTimer.startRunningLocked(elapsedRealtimeMs);
+        }
+
+        @Override
+        public void stopRunningLocked(long elapsedRealtimeMs) {
+            super.stopRunningLocked(elapsedRealtimeMs);
+            mSubTimer.stopRunningLocked(elapsedRealtimeMs);
+        }
+
+        @Override
+        public void stopAllRunningLocked(long elapsedRealtimeMs) {
+            super.stopAllRunningLocked(elapsedRealtimeMs);
+            mSubTimer.stopAllRunningLocked(elapsedRealtimeMs);
+        }
+
+        @Override
+        public boolean reset(boolean detachIfReset) {
+            boolean active = false;
+            // Do not detach the subTimer explicitly since that'll be done by DualTimer.detach().
+            active |= !mSubTimer.reset(false);
+            active |= !super.reset(detachIfReset);
+            return !active;
+        }
+
+        @Override
+        public void detach() {
+            mSubTimer.detach();
+            super.detach();
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+            super.writeToParcel(out, elapsedRealtimeUs);
+            mSubTimer.writeToParcel(out, elapsedRealtimeUs);
+        }
+
+        @Override
+        public void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) {
+            super.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
+            mSubTimer.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
+        }
+
+        @Override
+        public void readSummaryFromParcelLocked(Parcel in) {
+            super.readSummaryFromParcelLocked(in);
+            mSubTimer.readSummaryFromParcelLocked(in);
+        }
+    }
+
+
+    public abstract class OverflowArrayMap<T> {
+        private static final String OVERFLOW_NAME = "*overflow*";
+
+        final int mUid;
+        final ArrayMap<String, T> mMap = new ArrayMap<>();
+        T mCurOverflow;
+        ArrayMap<String, MutableInt> mActiveOverflow;
+        long mLastOverflowTime;
+        long mLastOverflowFinishTime;
+        long mLastClearTime;
+        long mLastCleanupTime;
+
+        public OverflowArrayMap(int uid) {
+            mUid = uid;
+        }
+
+        public ArrayMap<String, T> getMap() {
+            return mMap;
+        }
+
+        public void clear() {
+            mLastClearTime = SystemClock.elapsedRealtime();
+            mMap.clear();
+            mCurOverflow = null;
+            mActiveOverflow = null;
+        }
+
+        public void add(String name, T obj) {
+            if (name == null) {
+                name = "";
+            }
+            mMap.put(name, obj);
+            if (OVERFLOW_NAME.equals(name)) {
+                mCurOverflow = obj;
+            }
+        }
+
+        public void cleanup() {
+            mLastCleanupTime = SystemClock.elapsedRealtime();
+            if (mActiveOverflow != null) {
+                if (mActiveOverflow.size() == 0) {
+                    mActiveOverflow = null;
+                }
+            }
+            if (mActiveOverflow == null) {
+                // There is no currently active overflow, so we should no longer have
+                // an overflow entry.
+                if (mMap.containsKey(OVERFLOW_NAME)) {
+                    Slog.wtf(TAG, "Cleaning up with no active overflow, but have overflow entry "
+                            + mMap.get(OVERFLOW_NAME));
+                    mMap.remove(OVERFLOW_NAME);
+                }
+                mCurOverflow = null;
+            } else {
+                // There is currently active overflow, so we should still have an overflow entry.
+                if (mCurOverflow == null || !mMap.containsKey(OVERFLOW_NAME)) {
+                    Slog.wtf(TAG, "Cleaning up with active overflow, but no overflow entry: cur="
+                            + mCurOverflow + " map=" + mMap.get(OVERFLOW_NAME));
+                }
+            }
+        }
+
+        public T startObject(String name) {
+            if (name == null) {
+                name = "";
+            }
+            T obj = mMap.get(name);
+            if (obj != null) {
+                return obj;
+            }
+
+            // No object exists for the given name, but do we currently have it
+            // running as part of the overflow?
+            if (mActiveOverflow != null) {
+                MutableInt over = mActiveOverflow.get(name);
+                if (over != null) {
+                    // We are already actively counting this name in the overflow object.
+                    obj = mCurOverflow;
+                    if (obj == null) {
+                        // Shouldn't be here, but we'll try to recover.
+                        Slog.wtf(TAG, "Have active overflow " + name + " but null overflow");
+                        obj = mCurOverflow = instantiateObject();
+                        mMap.put(OVERFLOW_NAME, obj);
+                    }
+                    over.value++;
+                    return obj;
+                }
+            }
+
+            // No object exists for given name nor in the overflow; we need to make
+            // a new one.
+            final int N = mMap.size();
+            if (N >= MAX_WAKELOCKS_PER_UID) {
+                // Went over the limit on number of objects to track; this one goes
+                // in to the overflow.
+                obj = mCurOverflow;
+                if (obj == null) {
+                    // Need to start overflow now...
+                    obj = mCurOverflow = instantiateObject();
+                    mMap.put(OVERFLOW_NAME, obj);
+                }
+                if (mActiveOverflow == null) {
+                    mActiveOverflow = new ArrayMap<>();
+                }
+                mActiveOverflow.put(name, new MutableInt(1));
+                mLastOverflowTime = SystemClock.elapsedRealtime();
+                return obj;
+            }
+
+            // Normal case where we just need to make a new object.
+            obj = instantiateObject();
+            mMap.put(name, obj);
+            return obj;
+        }
+
+        public T stopObject(String name) {
+            if (name == null) {
+                name = "";
+            }
+            T obj = mMap.get(name);
+            if (obj != null) {
+                return obj;
+            }
+
+            // No object exists for the given name, but do we currently have it
+            // running as part of the overflow?
+            if (mActiveOverflow != null) {
+                MutableInt over = mActiveOverflow.get(name);
+                if (over != null) {
+                    // We are already actively counting this name in the overflow object.
+                    obj = mCurOverflow;
+                    if (obj != null) {
+                        over.value--;
+                        if (over.value <= 0) {
+                            mActiveOverflow.remove(name);
+                            mLastOverflowFinishTime = SystemClock.elapsedRealtime();
+                        }
+                        return obj;
+                    }
+                }
+            }
+
+            // Huh, they are stopping an active operation but we can't find one!
+            // That's not good.
+            StringBuilder sb = new StringBuilder();
+            sb.append("Unable to find object for ");
+            sb.append(name);
+            sb.append(" in uid ");
+            sb.append(mUid);
+            sb.append(" mapsize=");
+            sb.append(mMap.size());
+            sb.append(" activeoverflow=");
+            sb.append(mActiveOverflow);
+            sb.append(" curoverflow=");
+            sb.append(mCurOverflow);
+            long now = SystemClock.elapsedRealtime();
+            if (mLastOverflowTime != 0) {
+                sb.append(" lastOverflowTime=");
+                TimeUtils.formatDuration(mLastOverflowTime-now, sb);
+            }
+            if (mLastOverflowFinishTime != 0) {
+                sb.append(" lastOverflowFinishTime=");
+                TimeUtils.formatDuration(mLastOverflowFinishTime-now, sb);
+            }
+            if (mLastClearTime != 0) {
+                sb.append(" lastClearTime=");
+                TimeUtils.formatDuration(mLastClearTime-now, sb);
+            }
+            if (mLastCleanupTime != 0) {
+                sb.append(" lastCleanupTime=");
+                TimeUtils.formatDuration(mLastCleanupTime-now, sb);
+            }
+            Slog.wtf(TAG, sb.toString());
+            return null;
+        }
+
+        public abstract T instantiateObject();
+    }
+
+    public static class ControllerActivityCounterImpl extends ControllerActivityCounter
+            implements Parcelable {
+        private final LongSamplingCounter mIdleTimeMillis;
+        private final LongSamplingCounter mRxTimeMillis;
+        private final LongSamplingCounter[] mTxTimeMillis;
+        private final LongSamplingCounter mPowerDrainMaMs;
+
+        public ControllerActivityCounterImpl(TimeBase timeBase, int numTxStates) {
+            mIdleTimeMillis = new LongSamplingCounter(timeBase);
+            mRxTimeMillis = new LongSamplingCounter(timeBase);
+            mTxTimeMillis = new LongSamplingCounter[numTxStates];
+            for (int i = 0; i < numTxStates; i++) {
+                mTxTimeMillis[i] = new LongSamplingCounter(timeBase);
+            }
+            mPowerDrainMaMs = new LongSamplingCounter(timeBase);
+        }
+
+        public ControllerActivityCounterImpl(TimeBase timeBase, int numTxStates, Parcel in) {
+            mIdleTimeMillis = new LongSamplingCounter(timeBase, in);
+            mRxTimeMillis = new LongSamplingCounter(timeBase, in);
+            final int recordedTxStates = in.readInt();
+            if (recordedTxStates != numTxStates) {
+                throw new ParcelFormatException("inconsistent tx state lengths");
+            }
+
+            mTxTimeMillis = new LongSamplingCounter[numTxStates];
+            for (int i = 0; i < numTxStates; i++) {
+                mTxTimeMillis[i] = new LongSamplingCounter(timeBase, in);
+            }
+            mPowerDrainMaMs = new LongSamplingCounter(timeBase, in);
+        }
+
+        public void readSummaryFromParcel(Parcel in) {
+            mIdleTimeMillis.readSummaryFromParcelLocked(in);
+            mRxTimeMillis.readSummaryFromParcelLocked(in);
+            final int recordedTxStates = in.readInt();
+            if (recordedTxStates != mTxTimeMillis.length) {
+                throw new ParcelFormatException("inconsistent tx state lengths");
+            }
+            for (LongSamplingCounter counter : mTxTimeMillis) {
+                counter.readSummaryFromParcelLocked(in);
+            }
+            mPowerDrainMaMs.readSummaryFromParcelLocked(in);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeSummaryToParcel(Parcel dest) {
+            mIdleTimeMillis.writeSummaryFromParcelLocked(dest);
+            mRxTimeMillis.writeSummaryFromParcelLocked(dest);
+            dest.writeInt(mTxTimeMillis.length);
+            for (LongSamplingCounter counter : mTxTimeMillis) {
+                counter.writeSummaryFromParcelLocked(dest);
+            }
+            mPowerDrainMaMs.writeSummaryFromParcelLocked(dest);
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            mIdleTimeMillis.writeToParcel(dest);
+            mRxTimeMillis.writeToParcel(dest);
+            dest.writeInt(mTxTimeMillis.length);
+            for (LongSamplingCounter counter : mTxTimeMillis) {
+                counter.writeToParcel(dest);
+            }
+            mPowerDrainMaMs.writeToParcel(dest);
+        }
+
+        public void reset(boolean detachIfReset) {
+            mIdleTimeMillis.reset(detachIfReset);
+            mRxTimeMillis.reset(detachIfReset);
+            for (LongSamplingCounter counter : mTxTimeMillis) {
+                counter.reset(detachIfReset);
+            }
+            mPowerDrainMaMs.reset(detachIfReset);
+        }
+
+        public void detach() {
+            mIdleTimeMillis.detach();
+            mRxTimeMillis.detach();
+            for (LongSamplingCounter counter : mTxTimeMillis) {
+                counter.detach();
+            }
+            mPowerDrainMaMs.detach();
+        }
+
+        /**
+         * @return a LongSamplingCounter, measuring time spent in the idle state in
+         * milliseconds.
+         */
+        @Override
+        public LongSamplingCounter getIdleTimeCounter() {
+            return mIdleTimeMillis;
+        }
+
+        /**
+         * @return a LongSamplingCounter, measuring time spent in the receive state in
+         * milliseconds.
+         */
+        @Override
+        public LongSamplingCounter getRxTimeCounter() {
+            return mRxTimeMillis;
+        }
+
+        /**
+         * @return a LongSamplingCounter[], measuring time spent in various transmit states in
+         * milliseconds.
+         */
+        @Override
+        public LongSamplingCounter[] getTxTimeCounters() {
+            return mTxTimeMillis;
+        }
+
+        /**
+         * @return a LongSamplingCounter, measuring power use in milli-ampere milliseconds (mAmS).
+         */
+        @Override
+        public LongSamplingCounter getPowerCounter() {
+            return mPowerDrainMaMs;
+        }
+    }
+
+    /*
+     * Get the wakeup reason counter, and create a new one if one
+     * doesn't already exist.
+     */
+    public SamplingTimer getWakeupReasonTimerLocked(String name) {
+        SamplingTimer timer = mWakeupReasonStats.get(name);
+        if (timer == null) {
+            timer = new SamplingTimer(mClocks, mOnBatteryTimeBase);
+            mWakeupReasonStats.put(name, timer);
+        }
+        return timer;
+    }
+
+    /*
+     * Get the KernelWakelockTimer associated with name, and create a new one if one
+     * doesn't already exist.
+     */
+    public SamplingTimer getKernelWakelockTimerLocked(String name) {
+        SamplingTimer kwlt = mKernelWakelockStats.get(name);
+        if (kwlt == null) {
+            kwlt = new SamplingTimer(mClocks, mOnBatteryScreenOffTimeBase);
+            mKernelWakelockStats.put(name, kwlt);
+        }
+        return kwlt;
+    }
+
+    public SamplingTimer getKernelMemoryTimerLocked(long bucket) {
+        SamplingTimer kmt = mKernelMemoryStats.get(bucket);
+        if (kmt == null) {
+            kmt = new SamplingTimer(mClocks, mOnBatteryTimeBase);
+            mKernelMemoryStats.put(bucket, kmt);
+        }
+        return kmt;
+    }
+
+    private int writeHistoryTag(HistoryTag tag) {
+        Integer idxObj = mHistoryTagPool.get(tag);
+        int idx;
+        if (idxObj != null) {
+            idx = idxObj;
+        } else {
+            idx = mNextHistoryTagIdx;
+            HistoryTag key = new HistoryTag();
+            key.setTo(tag);
+            tag.poolIdx = idx;
+            mHistoryTagPool.put(key, idx);
+            mNextHistoryTagIdx++;
+            mNumHistoryTagChars += key.string.length() + 1;
+        }
+        return idx;
+    }
+
+    private void readHistoryTag(int index, HistoryTag tag) {
+        tag.string = mReadHistoryStrings[index];
+        tag.uid = mReadHistoryUids[index];
+        tag.poolIdx = index;
+    }
+
+    /*
+        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| | | | â–ˆ | | | | | | | â–ˆ |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.
+
+        Wakelock/wakereason struct: if D in the first token is set,
+        TODO(adamlesinski): describe wakelock/wakereason struct.
+
+        Event struct: if E in the first token is set,
+        TODO(adamlesinski): describe the event struct.
+
+        History step details struct: if D in the battery level int is set,
+        TODO(adamlesinski): describe the history step details struct.
+
+        Battery charge int: if F in the first token is set, an int representing the battery charge
+        in coulombs follows.
+     */
+
+    // 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_DELTA_LEVEL_FLAG   = 0x00000001;
+
+    public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
+        if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
+            dest.writeInt(DELTA_TIME_ABS);
+            cur.writeToParcel(dest, 0);
+            return;
+        }
+
+        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 = DELTA_TIME_LONG;
+        } else if (deltaTime >= DELTA_TIME_ABS) {
+            deltaTimeToken = DELTA_TIME_INT;
+        } else {
+            deltaTimeToken = (int)deltaTime;
+        }
+        int firstToken = deltaTimeToken | (cur.states&DELTA_STATE_MASK);
+        final int includeStepDetails = mLastHistoryStepLevel > cur.batteryLevel
+                ? BATTERY_DELTA_LEVEL_FLAG : 0;
+        final boolean computeStepDetails = includeStepDetails != 0
+                || mLastHistoryStepDetails == null;
+        final int batteryLevelInt = buildBatteryLevelInt(cur) | includeStepDetails;
+        final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
+        if (batteryLevelIntChanged) {
+            firstToken |= DELTA_BATTERY_LEVEL_FLAG;
+        }
+        final int stateInt = buildStateInt(cur);
+        final boolean stateIntChanged = stateInt != lastStateInt;
+        if (stateIntChanged) {
+            firstToken |= DELTA_STATE_FLAG;
+        }
+        final boolean state2IntChanged = cur.states2 != last.states2;
+        if (state2IntChanged) {
+            firstToken |= DELTA_STATE2_FLAG;
+        }
+        if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+            firstToken |= DELTA_WAKELOCK_FLAG;
+        }
+        if (cur.eventCode != HistoryItem.EVENT_NONE) {
+            firstToken |= DELTA_EVENT_FLAG;
+        }
+
+        final boolean batteryChargeChanged = cur.batteryChargeUAh != last.batteryChargeUAh;
+        if (batteryChargeChanged) {
+            firstToken |= DELTA_BATTERY_CHARGE_FLAG;
+        }
+        dest.writeInt(firstToken);
+        if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+                + " deltaTime=" + deltaTime);
+
+        if (deltaTimeToken >= DELTA_TIME_INT) {
+            if (deltaTimeToken == 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.eventCode != HistoryItem.EVENT_NONE) {
+            int index = writeHistoryTag(cur.eventTag);
+            int codeAndIndex = (cur.eventCode&0xffff) | (index<<16);
+            dest.writeInt(codeAndIndex);
+            if (DEBUG) Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#"
+                    + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
+                    + cur.eventTag.string);
+        }
+        if (computeStepDetails) {
+            if (mPlatformIdleStateCallback != null) {
+                mCurHistoryStepDetails.statPlatformIdleState =
+                        mPlatformIdleStateCallback.getPlatformLowPowerStats();
+                if (DEBUG) Slog.i(TAG, "WRITE PlatformIdleState:" +
+                        mCurHistoryStepDetails.statPlatformIdleState);
+
+                mCurHistoryStepDetails.statSubsystemPowerState =
+                        mPlatformIdleStateCallback.getSubsystemLowPowerStats();
+                if (DEBUG) Slog.i(TAG, "WRITE SubsystemPowerState:" +
+                        mCurHistoryStepDetails.statSubsystemPowerState);
+
+            }
+            computeHistoryStepDetails(mCurHistoryStepDetails, mLastHistoryStepDetails);
+            if (includeStepDetails != 0) {
+                mCurHistoryStepDetails.writeToParcel(dest);
+            }
+            cur.stepDetails = mCurHistoryStepDetails;
+            mLastHistoryStepDetails = mCurHistoryStepDetails;
+        } else {
+            cur.stepDetails = null;
+        }
+        if (mLastHistoryStepLevel < cur.batteryLevel) {
+            mLastHistoryStepDetails = null;
+        }
+        mLastHistoryStepLevel = cur.batteryLevel;
+
+        if (batteryChargeChanged) {
+            if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryChargeUAh=" + cur.batteryChargeUAh);
+            dest.writeInt(cur.batteryChargeUAh);
+        }
+    }
+
+    private int buildBatteryLevelInt(HistoryItem h) {
+        return ((((int)h.batteryLevel)<<25)&0xfe000000)
+                | ((((int)h.batteryTemperature)<<15)&0x01ff8000)
+                | ((((int)h.batteryVoltage)<<1)&0x00007ffe);
+    }
+
+    private void readBatteryLevelInt(int batteryLevelInt, HistoryItem out) {
+        out.batteryLevel = (byte)((batteryLevelInt & 0xfe000000) >>> 25);
+        out.batteryTemperature = (short)((batteryLevelInt & 0x01ff8000) >>> 15);
+        out.batteryVoltage = (char)((batteryLevelInt & 0x00007ffe) >>> 1);
+    }
+
+    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&STATE_BATTERY_STATUS_MASK)<<STATE_BATTERY_STATUS_SHIFT)
+                | ((h.batteryHealth&STATE_BATTERY_HEALTH_MASK)<<STATE_BATTERY_HEALTH_SHIFT)
+                | ((plugType&STATE_BATTERY_PLUG_MASK)<<STATE_BATTERY_PLUG_SHIFT)
+                | (h.states&(~STATE_BATTERY_MASK));
+    }
+
+    private void computeHistoryStepDetails(final HistoryStepDetails out,
+            final HistoryStepDetails last) {
+        final HistoryStepDetails tmp = last != null ? mTmpHistoryStepDetails : out;
+
+        // Perform a CPU update right after we do this collection, so we have started
+        // collecting good data for the next step.
+        requestImmediateCpuUpdate();
+
+        if (last == null) {
+            // We are not generating a delta, so all we need to do is reset the stats
+            // we will later be doing a delta from.
+            final int NU = mUidStats.size();
+            for (int i=0; i<NU; i++) {
+                final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+                uid.mLastStepUserTime = uid.mCurStepUserTime;
+                uid.mLastStepSystemTime = uid.mCurStepSystemTime;
+            }
+            mLastStepCpuUserTime = mCurStepCpuUserTime;
+            mLastStepCpuSystemTime = mCurStepCpuSystemTime;
+            mLastStepStatUserTime = mCurStepStatUserTime;
+            mLastStepStatSystemTime = mCurStepStatSystemTime;
+            mLastStepStatIOWaitTime = mCurStepStatIOWaitTime;
+            mLastStepStatIrqTime = mCurStepStatIrqTime;
+            mLastStepStatSoftIrqTime = mCurStepStatSoftIrqTime;
+            mLastStepStatIdleTime = mCurStepStatIdleTime;
+            tmp.clear();
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "Step stats last: user=" + mLastStepCpuUserTime + " sys="
+                    + mLastStepStatSystemTime + " io=" + mLastStepStatIOWaitTime
+                    + " irq=" + mLastStepStatIrqTime + " sirq="
+                    + mLastStepStatSoftIrqTime + " idle=" + mLastStepStatIdleTime);
+            Slog.d(TAG, "Step stats cur: user=" + mCurStepCpuUserTime + " sys="
+                    + mCurStepStatSystemTime + " io=" + mCurStepStatIOWaitTime
+                    + " irq=" + mCurStepStatIrqTime + " sirq="
+                    + mCurStepStatSoftIrqTime + " idle=" + mCurStepStatIdleTime);
+        }
+        out.userTime = (int)(mCurStepCpuUserTime - mLastStepCpuUserTime);
+        out.systemTime = (int)(mCurStepCpuSystemTime - mLastStepCpuSystemTime);
+        out.statUserTime = (int)(mCurStepStatUserTime - mLastStepStatUserTime);
+        out.statSystemTime = (int)(mCurStepStatSystemTime - mLastStepStatSystemTime);
+        out.statIOWaitTime = (int)(mCurStepStatIOWaitTime - mLastStepStatIOWaitTime);
+        out.statIrqTime = (int)(mCurStepStatIrqTime - mLastStepStatIrqTime);
+        out.statSoftIrqTime = (int)(mCurStepStatSoftIrqTime - mLastStepStatSoftIrqTime);
+        out.statIdlTime = (int)(mCurStepStatIdleTime - mLastStepStatIdleTime);
+        out.appCpuUid1 = out.appCpuUid2 = out.appCpuUid3 = -1;
+        out.appCpuUTime1 = out.appCpuUTime2 = out.appCpuUTime3 = 0;
+        out.appCpuSTime1 = out.appCpuSTime2 = out.appCpuSTime3 = 0;
+        final int NU = mUidStats.size();
+        for (int i=0; i<NU; i++) {
+            final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+            final int totalUTime = (int)(uid.mCurStepUserTime - uid.mLastStepUserTime);
+            final int totalSTime = (int)(uid.mCurStepSystemTime - uid.mLastStepSystemTime);
+            final int totalTime = totalUTime + totalSTime;
+            uid.mLastStepUserTime = uid.mCurStepUserTime;
+            uid.mLastStepSystemTime = uid.mCurStepSystemTime;
+            if (totalTime <= (out.appCpuUTime3+out.appCpuSTime3)) {
+                continue;
+            }
+            if (totalTime <= (out.appCpuUTime2+out.appCpuSTime2)) {
+                out.appCpuUid3 = uid.mUid;
+                out.appCpuUTime3 = totalUTime;
+                out.appCpuSTime3 = totalSTime;
+            } else {
+                out.appCpuUid3 = out.appCpuUid2;
+                out.appCpuUTime3 = out.appCpuUTime2;
+                out.appCpuSTime3 = out.appCpuSTime2;
+                if (totalTime <= (out.appCpuUTime1+out.appCpuSTime1)) {
+                    out.appCpuUid2 = uid.mUid;
+                    out.appCpuUTime2 = totalUTime;
+                    out.appCpuSTime2 = totalSTime;
+                } else {
+                    out.appCpuUid2 = out.appCpuUid1;
+                    out.appCpuUTime2 = out.appCpuUTime1;
+                    out.appCpuSTime2 = out.appCpuSTime1;
+                    out.appCpuUid1 = uid.mUid;
+                    out.appCpuUTime1 = totalUTime;
+                    out.appCpuSTime1 = totalSTime;
+                }
+            }
+        }
+        mLastStepCpuUserTime = mCurStepCpuUserTime;
+        mLastStepCpuSystemTime = mCurStepCpuSystemTime;
+        mLastStepStatUserTime = mCurStepStatUserTime;
+        mLastStepStatSystemTime = mCurStepStatSystemTime;
+        mLastStepStatIOWaitTime = mCurStepStatIOWaitTime;
+        mLastStepStatIrqTime = mCurStepStatIrqTime;
+        mLastStepStatSoftIrqTime = mCurStepStatSoftIrqTime;
+        mLastStepStatIdleTime = mCurStepStatIdleTime;
+    }
+
+    public void readHistoryDelta(Parcel src, HistoryItem cur) {
+        int firstToken = src.readInt();
+        int deltaTimeToken = firstToken&DELTA_TIME_MASK;
+        cur.cmd = HistoryItem.CMD_UPDATE;
+        cur.numReadInts = 1;
+        if (DEBUG) Slog.i(TAG, "READ DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+                + " deltaTimeToken=" + deltaTimeToken);
+
+        if (deltaTimeToken < DELTA_TIME_ABS) {
+            cur.time += deltaTimeToken;
+        } else if (deltaTimeToken == DELTA_TIME_ABS) {
+            cur.time = src.readLong();
+            cur.numReadInts += 2;
+            if (DEBUG) Slog.i(TAG, "READ DELTA: ABS time=" + cur.time);
+            cur.readFromParcel(src);
+            return;
+        } else if (deltaTimeToken == 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&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&DELTA_STATE_FLAG) != 0) {
+            int stateInt = src.readInt();
+            cur.states = (firstToken&DELTA_STATE_MASK) | (stateInt&(~STATE_BATTERY_MASK));
+            cur.batteryStatus = (byte)((stateInt>>STATE_BATTERY_STATUS_SHIFT)
+                    & STATE_BATTERY_STATUS_MASK);
+            cur.batteryHealth = (byte)((stateInt>>STATE_BATTERY_HEALTH_SHIFT)
+                    & STATE_BATTERY_HEALTH_MASK);
+            cur.batteryPlugType = (byte)((stateInt>>STATE_BATTERY_PLUG_SHIFT)
+                    & 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&DELTA_STATE_MASK) | (cur.states&(~STATE_BATTERY_MASK));
+        }
+
+        if ((firstToken&DELTA_STATE2_FLAG) != 0) {
+            cur.states2 = src.readInt();
+            if (DEBUG) Slog.i(TAG, "READ DELTA: states2=0x"
+                    + Integer.toHexString(cur.states2));
+        }
+
+        if ((firstToken&DELTA_WAKELOCK_FLAG) != 0) {
+            int indexes = src.readInt();
+            int wakeLockIndex = indexes&0xffff;
+            int wakeReasonIndex = (indexes>>16)&0xffff;
+            if (wakeLockIndex != 0xffff) {
+                cur.wakelockTag = cur.localWakelockTag;
+                readHistoryTag(wakeLockIndex, cur.wakelockTag);
+                if (DEBUG) Slog.i(TAG, "READ DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
+                    + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
+            } else {
+                cur.wakelockTag = null;
+            }
+            if (wakeReasonIndex != 0xffff) {
+                cur.wakeReasonTag = cur.localWakeReasonTag;
+                readHistoryTag(wakeReasonIndex, cur.wakeReasonTag);
+                if (DEBUG) Slog.i(TAG, "READ DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
+                    + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
+            } else {
+                cur.wakeReasonTag = null;
+            }
+            cur.numReadInts += 1;
+        } else {
+            cur.wakelockTag = null;
+            cur.wakeReasonTag = null;
+        }
+
+        if ((firstToken&DELTA_EVENT_FLAG) != 0) {
+            cur.eventTag = cur.localEventTag;
+            final int codeAndIndex = src.readInt();
+            cur.eventCode = (codeAndIndex&0xffff);
+            final int index = ((codeAndIndex>>16)&0xffff);
+            readHistoryTag(index, cur.eventTag);
+            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 = HistoryItem.EVENT_NONE;
+        }
+
+        if ((batteryLevelInt&BATTERY_DELTA_LEVEL_FLAG) != 0) {
+            cur.stepDetails = mReadHistoryStepDetails;
+            cur.stepDetails.readFromParcel(src);
+        } else {
+            cur.stepDetails = null;
+        }
+
+        if ((firstToken&DELTA_BATTERY_CHARGE_FLAG) != 0) {
+            cur.batteryChargeUAh = src.readInt();
+        }
+    }
+
+    @Override
+    public void commitCurrentHistoryBatchLocked() {
+        mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+    }
+
+    void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
+        if (!mHaveBatteryLevel || !mRecordingHistory) {
+            return;
+        }
+
+        final long timeDiff = (mHistoryBaseTime+elapsedRealtimeMs) - mHistoryLastWritten.time;
+        final int diffStates = mHistoryLastWritten.states^(cur.states&mActiveHistoryStates);
+        final int diffStates2 = mHistoryLastWritten.states2^(cur.states2&mActiveHistoryStates2);
+        final int lastDiffStates = mHistoryLastWritten.states^mHistoryLastLastWritten.states;
+        final int lastDiffStates2 = mHistoryLastWritten.states2^mHistoryLastLastWritten.states2;
+        if (DEBUG) Slog.i(TAG, "ADD: tdelta=" + timeDiff + " diff="
+                + Integer.toHexString(diffStates) + " lastDiff="
+                + Integer.toHexString(lastDiffStates) + " diff2="
+                + Integer.toHexString(diffStates2) + " lastDiff2="
+                + Integer.toHexString(lastDiffStates2));
+        if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
+                && timeDiff < 1000 && (diffStates&lastDiffStates) == 0
+                && (diffStates2&lastDiffStates2) == 0
+                && (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) {
+            // 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 = mHistoryLastWritten.time - mHistoryBaseTime;
+            // 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);
+        }
+
+        boolean recordResetDueToOverflow = false;
+        final int dataSize = mHistoryBuffer.dataSize();
+        if (dataSize >= MAX_MAX_HISTORY_BUFFER*3) {
+            // Clients can't deal with history buffers this large. This only
+            // really happens when the device is on charger and interacted with
+            // for long periods of time, like in retail mode. Since the device is
+            // most likely charged, when unplugged, stats would have reset anyways.
+            // Reset the stats and mark that we overflowed.
+            // b/32540341
+            resetAllStatsLocked();
+
+            // Mark that we want to set *OVERFLOW* event and the RESET:START
+            // events.
+            recordResetDueToOverflow = true;
+
+        } else if (dataSize >= MAX_HISTORY_BUFFER) {
+            if (!mHistoryOverflow) {
+                mHistoryOverflow = true;
+                addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur);
+                addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur);
+                return;
+            }
+
+            // After overflow, we allow various bit-wise states to settle to 0.
+            boolean writeAnyway = false;
+            final int curStates = cur.states & HistoryItem.SETTLE_TO_ZERO_STATES
+                    & mActiveHistoryStates;
+            if (mHistoryLastWritten.states != curStates) {
+                // mActiveHistoryStates keeps track of which bits in .states are now being
+                // forced to 0.
+                int old = mActiveHistoryStates;
+                mActiveHistoryStates &= curStates | ~HistoryItem.SETTLE_TO_ZERO_STATES;
+                writeAnyway |= old != mActiveHistoryStates;
+            }
+            final int curStates2 = cur.states2 & HistoryItem.SETTLE_TO_ZERO_STATES2
+                    & mActiveHistoryStates2;
+            if (mHistoryLastWritten.states2 != curStates2) {
+                // mActiveHistoryStates2 keeps track of which bits in .states2 are now being
+                // forced to 0.
+                int old = mActiveHistoryStates2;
+                mActiveHistoryStates2 &= curStates2 | ~HistoryItem.SETTLE_TO_ZERO_STATES2;
+                writeAnyway |= old != mActiveHistoryStates2;
+            }
+
+            // Once we've reached the maximum number of items, we only
+            // record changes to the battery level and the most interesting states.
+            // Once we've reached the maximum maximum number of items, we only
+            // record changes to the battery level.
+            if (!writeAnyway && mHistoryLastWritten.batteryLevel == cur.batteryLevel &&
+                    (dataSize >= MAX_MAX_HISTORY_BUFFER
+                            || ((mHistoryLastWritten.states^cur.states)
+                                    & HistoryItem.MOST_INTERESTING_STATES) == 0
+                            || ((mHistoryLastWritten.states2^cur.states2)
+                                    & HistoryItem.MOST_INTERESTING_STATES2) == 0)) {
+                return;
+            }
+
+            addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur);
+            return;
+        }
+
+        if (dataSize == 0 || recordResetDueToOverflow) {
+            // The history is currently empty; we need it to start with a time stamp.
+            cur.currentTime = System.currentTimeMillis();
+            if (recordResetDueToOverflow) {
+                addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur);
+            }
+            addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_RESET, cur);
+        }
+        addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur);
+    }
+
+    private void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, byte cmd,
+            HistoryItem cur) {
+        if (mIteratingHistory) {
+            throw new IllegalStateException("Can't do this while iterating history!");
+        }
+        mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
+        mHistoryLastLastWritten.setTo(mHistoryLastWritten);
+        mHistoryLastWritten.setTo(mHistoryBaseTime + elapsedRealtimeMs, cmd, cur);
+        mHistoryLastWritten.states &= mActiveHistoryStates;
+        mHistoryLastWritten.states2 &= mActiveHistoryStates2;
+        writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
+        mLastHistoryElapsedRealtime = elapsedRealtimeMs;
+        cur.wakelockTag = null;
+        cur.wakeReasonTag = null;
+        cur.eventCode = HistoryItem.EVENT_NONE;
+        cur.eventTag = null;
+        if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+                + " now " + mHistoryBuffer.dataPosition()
+                + " size is now " + mHistoryBuffer.dataSize());
+    }
+
+    int mChangedStates = 0;
+    int mChangedStates2 = 0;
+
+    void addHistoryRecordLocked(long elapsedRealtimeMs, long uptimeMs) {
+        if (mTrackRunningHistoryElapsedRealtime != 0) {
+            final long diffElapsed = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtime;
+            final long diffUptime = uptimeMs - mTrackRunningHistoryUptime;
+            if (diffUptime < (diffElapsed-20)) {
+                final long wakeElapsedTime = elapsedRealtimeMs - (diffElapsed - diffUptime);
+                mHistoryAddTmp.setTo(mHistoryLastWritten);
+                mHistoryAddTmp.wakelockTag = null;
+                mHistoryAddTmp.wakeReasonTag = null;
+                mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE;
+                mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG;
+                addHistoryRecordInnerLocked(wakeElapsedTime, uptimeMs, mHistoryAddTmp);
+            }
+        }
+        mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG;
+        mTrackRunningHistoryElapsedRealtime = elapsedRealtimeMs;
+        mTrackRunningHistoryUptime = uptimeMs;
+        addHistoryRecordInnerLocked(elapsedRealtimeMs, uptimeMs, mHistoryCur);
+    }
+
+    void addHistoryRecordInnerLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
+        addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, cur);
+
+        if (!USE_OLD_HISTORY) {
+            return;
+        }
+
+        if (!mHaveBatteryLevel || !mRecordingHistory) {
+            return;
+        }
+
+        // If the current time is basically the same as the last time,
+        // and no states have since the last recorded entry changed and
+        // are now resetting back to their original value, then just collapse
+        // into one record.
+        if (mHistoryEnd != null && mHistoryEnd.cmd == HistoryItem.CMD_UPDATE
+                && (mHistoryBaseTime+elapsedRealtimeMs) < (mHistoryEnd.time+1000)
+                && ((mHistoryEnd.states^cur.states)&mChangedStates&mActiveHistoryStates) == 0
+                && ((mHistoryEnd.states2^cur.states2)&mChangedStates2&mActiveHistoryStates2) == 0) {
+            // If the current is the same as the one before, then we no
+            // longer need the entry.
+            if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE
+                    && (mHistoryBaseTime+elapsedRealtimeMs) < (mHistoryEnd.time+500)
+                    && mHistoryLastEnd.sameNonEvent(cur)) {
+                mHistoryLastEnd.next = null;
+                mHistoryEnd.next = mHistoryCache;
+                mHistoryCache = mHistoryEnd;
+                mHistoryEnd = mHistoryLastEnd;
+                mHistoryLastEnd = null;
+            } else {
+                mChangedStates |= mHistoryEnd.states^(cur.states&mActiveHistoryStates);
+                mChangedStates2 |= mHistoryEnd.states^(cur.states2&mActiveHistoryStates2);
+                mHistoryEnd.setTo(mHistoryEnd.time, HistoryItem.CMD_UPDATE, cur);
+            }
+            return;
+        }
+
+        mChangedStates = 0;
+        mChangedStates2 = 0;
+
+        if (mNumHistoryItems == MAX_HISTORY_ITEMS
+                || mNumHistoryItems == MAX_MAX_HISTORY_ITEMS) {
+            addHistoryRecordLocked(elapsedRealtimeMs, HistoryItem.CMD_OVERFLOW);
+        }
+
+        if (mNumHistoryItems >= MAX_HISTORY_ITEMS) {
+            // Once we've reached the maximum number of items, we only
+            // record changes to the battery level and the most interesting states.
+            // Once we've reached the maximum maximum number of items, we only
+            // record changes to the battery level.
+            if (mHistoryEnd != null && mHistoryEnd.batteryLevel
+                    == cur.batteryLevel &&
+                    (mNumHistoryItems >= MAX_MAX_HISTORY_ITEMS
+                            || ((mHistoryEnd.states^(cur.states&mActiveHistoryStates))
+                                    & HistoryItem.MOST_INTERESTING_STATES) == 0)) {
+                return;
+            }
+        }
+
+        addHistoryRecordLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE);
+    }
+
+    public void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code,
+            String name, int uid) {
+        mHistoryCur.eventCode = code;
+        mHistoryCur.eventTag = mHistoryCur.localEventTag;
+        mHistoryCur.eventTag.string = name;
+        mHistoryCur.eventTag.uid = uid;
+        addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+    }
+
+    void addHistoryRecordLocked(long elapsedRealtimeMs, long uptimeMs, byte cmd, HistoryItem cur) {
+        HistoryItem rec = mHistoryCache;
+        if (rec != null) {
+            mHistoryCache = rec.next;
+        } else {
+            rec = new HistoryItem();
+        }
+        rec.setTo(mHistoryBaseTime + elapsedRealtimeMs, cmd, cur);
+
+        addHistoryRecordLocked(rec);
+    }
+
+    void addHistoryRecordLocked(HistoryItem rec) {
+        mNumHistoryItems++;
+        rec.next = null;
+        mHistoryLastEnd = mHistoryEnd;
+        if (mHistoryEnd != null) {
+            mHistoryEnd.next = rec;
+            mHistoryEnd = rec;
+        } else {
+            mHistory = mHistoryEnd = rec;
+        }
+    }
+
+    void clearHistoryLocked() {
+        if (DEBUG_HISTORY) Slog.i(TAG, "********** CLEARING HISTORY!");
+        if (USE_OLD_HISTORY) {
+            if (mHistory != null) {
+                mHistoryEnd.next = mHistoryCache;
+                mHistoryCache = mHistory;
+                mHistory = mHistoryLastEnd = mHistoryEnd = null;
+            }
+            mNumHistoryItems = 0;
+        }
+
+        mHistoryBaseTime = 0;
+        mLastHistoryElapsedRealtime = 0;
+        mTrackRunningHistoryElapsedRealtime = 0;
+        mTrackRunningHistoryUptime = 0;
+
+        mHistoryBuffer.setDataSize(0);
+        mHistoryBuffer.setDataPosition(0);
+        mHistoryBuffer.setDataCapacity(MAX_HISTORY_BUFFER / 2);
+        mHistoryLastLastWritten.clear();
+        mHistoryLastWritten.clear();
+        mHistoryTagPool.clear();
+        mNextHistoryTagIdx = 0;
+        mNumHistoryTagChars = 0;
+        mHistoryBufferLastPos = -1;
+        mHistoryOverflow = false;
+        mActiveHistoryStates = 0xffffffff;
+        mActiveHistoryStates2 = 0xffffffff;
+    }
+
+    public void updateTimeBasesLocked(boolean unplugged, boolean screenOff, long uptime,
+            long realtime) {
+        final boolean updateOnBatteryTimeBase = unplugged != mOnBatteryTimeBase.isRunning();
+        final boolean updateOnBatteryScreenOffTimeBase =
+                (unplugged && screenOff) != mOnBatteryScreenOffTimeBase.isRunning();
+
+        if (updateOnBatteryScreenOffTimeBase || updateOnBatteryTimeBase) {
+            if (updateOnBatteryScreenOffTimeBase) {
+                updateKernelWakelocksLocked();
+                updateBatteryPropertiesLocked();
+            }
+            if (DEBUG_ENERGY_CPU) {
+                Slog.d(TAG, "Updating cpu time because screen is now " + (screenOff ? "off" : "on")
+                        + " and battery is " + (unplugged ? "on" : "off"));
+            }
+            updateCpuTimeLocked();
+
+            mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
+            mOnBatteryScreenOffTimeBase.setRunning(unplugged && screenOff, uptime, realtime);
+            for (int i = mUidStats.size() - 1; i >= 0; --i) {
+                final Uid u = mUidStats.valueAt(i);
+                if (updateOnBatteryTimeBase) {
+                    u.updateOnBatteryBgTimeBase(uptime, realtime);
+                }
+                if (updateOnBatteryScreenOffTimeBase) {
+                    u.updateOnBatteryScreenOffBgTimeBase(uptime, realtime);
+                }
+            }
+        }
+    }
+
+    private void updateBatteryPropertiesLocked() {
+        try {
+            IBatteryPropertiesRegistrar registrar = IBatteryPropertiesRegistrar.Stub.asInterface(
+                    ServiceManager.getService("batteryproperties"));
+            registrar.scheduleUpdate();
+        } catch (RemoteException e) {
+            // Ignore.
+        }
+    }
+
+    public void addIsolatedUidLocked(int isolatedUid, int appUid) {
+        mIsolatedUids.put(isolatedUid, appUid);
+    }
+
+    /**
+     * Schedules a read of the latest cpu times before removing the isolated UID.
+     * @see #removeIsolatedUidLocked(int)
+     */
+    public void scheduleRemoveIsolatedUidLocked(int isolatedUid, int appUid) {
+        int curUid = mIsolatedUids.get(isolatedUid, -1);
+        if (curUid == appUid) {
+            if (mExternalSync != null) {
+                mExternalSync.scheduleCpuSyncDueToRemovedUid(isolatedUid);
+            }
+        }
+    }
+
+    /**
+     * This should only be called after the cpu times have been read.
+     * @see #scheduleRemoveIsolatedUidLocked(int, int)
+     */
+    public void removeIsolatedUidLocked(int isolatedUid) {
+        mIsolatedUids.delete(isolatedUid);
+        mKernelUidCpuTimeReader.removeUid(isolatedUid);
+        mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
+    }
+
+    public int mapUid(int uid) {
+        int isolated = mIsolatedUids.get(uid, -1);
+        return isolated > 0 ? isolated : uid;
+    }
+
+    public void noteEventLocked(int code, String name, int uid) {
+        uid = mapUid(uid);
+        if (!mActiveEvents.updateState(code, name, uid, 0)) {
+            return;
+        }
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        addHistoryEventLocked(elapsedRealtime, uptime, code, name, uid);
+    }
+
+    boolean ensureStartClockTime(final long currentTime) {
+        final long ABOUT_ONE_YEAR = 365*24*60*60*1000L;
+        if (currentTime > ABOUT_ONE_YEAR && mStartClockTime < (currentTime-ABOUT_ONE_YEAR)) {
+            // If the start clock time has changed by more than a year, then presumably
+            // the previous time was completely bogus.  So we are going to figure out a
+            // new time based on how much time has elapsed since we started counting.
+            mStartClockTime = currentTime - (mClocks.elapsedRealtime()-(mRealtimeStart/1000));
+            return true;
+        }
+        return false;
+    }
+
+    public void noteCurrentTimeChangedLocked() {
+        final long currentTime = System.currentTimeMillis();
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        recordCurrentTimeChangeLocked(currentTime, elapsedRealtime, uptime);
+        ensureStartClockTime(currentTime);
+    }
+
+    public void noteProcessStartLocked(String name, int uid) {
+        uid = mapUid(uid);
+        if (isOnBattery()) {
+            Uid u = getUidStatsLocked(uid);
+            u.getProcessStatsLocked(name).incStartsLocked();
+        }
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_PROC_START, name, uid, 0)) {
+            return;
+        }
+        if (!mRecordAllHistory) {
+            return;
+        }
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PROC_START, name, uid);
+    }
+
+    public void noteProcessCrashLocked(String name, int uid) {
+        uid = mapUid(uid);
+        if (isOnBattery()) {
+            Uid u = getUidStatsLocked(uid);
+            u.getProcessStatsLocked(name).incNumCrashesLocked();
+        }
+    }
+
+    public void noteProcessAnrLocked(String name, int uid) {
+        uid = mapUid(uid);
+        if (isOnBattery()) {
+            Uid u = getUidStatsLocked(uid);
+            u.getProcessStatsLocked(name).incNumAnrsLocked();
+        }
+    }
+
+    public void noteUidProcessStateLocked(int uid, int state) {
+        int parentUid = mapUid(uid);
+        if (uid != parentUid) {
+            // Isolated UIDs process state is already rolled up into parent, so no need to track
+            // Otherwise the parent's process state will get downgraded incorrectly
+            return;
+        }
+        getUidStatsLocked(uid).updateUidProcessStateLocked(state);
+    }
+
+    public void noteProcessFinishLocked(String name, int uid) {
+        uid = mapUid(uid);
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_PROC_FINISH, name, uid, 0)) {
+            return;
+        }
+        if (!mRecordAllHistory) {
+            return;
+        }
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PROC_FINISH, name, uid);
+    }
+
+    public void noteSyncStartLocked(String name, int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        getUidStatsLocked(uid).noteStartSyncLocked(name, elapsedRealtime);
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_START, name, uid, 0)) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_SYNC_START, name, uid);
+    }
+
+    public void noteSyncFinishLocked(String name, int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        getUidStatsLocked(uid).noteStopSyncLocked(name, elapsedRealtime);
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_FINISH, name, uid, 0)) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_SYNC_FINISH, name, uid);
+    }
+
+    public void noteJobStartLocked(String name, int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        getUidStatsLocked(uid).noteStartJobLocked(name, elapsedRealtime);
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_START, name, uid, 0)) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_JOB_START, name, uid);
+    }
+
+    public void noteJobFinishLocked(String name, int uid, int stopReason) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        getUidStatsLocked(uid).noteStopJobLocked(name, elapsedRealtime, stopReason);
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_FINISH, name, uid, 0)) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_JOB_FINISH, name, uid);
+    }
+
+    public void noteAlarmStartLocked(String name, int uid) {
+        if (!mRecordAllHistory) {
+            return;
+        }
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_ALARM_START, name, uid, 0)) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ALARM_START, name, uid);
+    }
+
+    public void noteAlarmFinishLocked(String name, int uid) {
+        if (!mRecordAllHistory) {
+            return;
+        }
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_ALARM_FINISH, name, uid, 0)) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ALARM_FINISH, name, uid);
+    }
+
+    private void requestWakelockCpuUpdate() {
+        if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
+            Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
+            mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS);
+        }
+    }
+
+    private void requestImmediateCpuUpdate() {
+        mHandler.removeMessages(MSG_UPDATE_WAKELOCKS);
+        mHandler.sendEmptyMessage(MSG_UPDATE_WAKELOCKS);
+    }
+
+    public void setRecordAllHistoryLocked(boolean enabled) {
+        mRecordAllHistory = enabled;
+        if (!enabled) {
+            // Clear out any existing state.
+            mActiveEvents.removeEvents(HistoryItem.EVENT_WAKE_LOCK);
+            mActiveEvents.removeEvents(HistoryItem.EVENT_ALARM);
+            // Record the currently running processes as stopping, now that we are no
+            // longer tracking them.
+            HashMap<String, SparseIntArray> active = mActiveEvents.getStateForEvent(
+                    HistoryItem.EVENT_PROC);
+            if (active != null) {
+                long mSecRealtime = mClocks.elapsedRealtime();
+                final long mSecUptime = mClocks.uptimeMillis();
+                for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
+                    SparseIntArray uids = ent.getValue();
+                    for (int j=0; j<uids.size(); j++) {
+                        addHistoryEventLocked(mSecRealtime, mSecUptime,
+                                HistoryItem.EVENT_PROC_FINISH, ent.getKey(), uids.keyAt(j));
+                    }
+                }
+            }
+        } else {
+            // Record the currently running processes as starting, now that we are tracking them.
+            HashMap<String, SparseIntArray> active = mActiveEvents.getStateForEvent(
+                    HistoryItem.EVENT_PROC);
+            if (active != null) {
+                long mSecRealtime = mClocks.elapsedRealtime();
+                final long mSecUptime = mClocks.uptimeMillis();
+                for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
+                    SparseIntArray uids = ent.getValue();
+                    for (int j=0; j<uids.size(); j++) {
+                        addHistoryEventLocked(mSecRealtime, mSecUptime,
+                                HistoryItem.EVENT_PROC_START, ent.getKey(), uids.keyAt(j));
+                    }
+                }
+            }
+        }
+    }
+
+    public void setNoAutoReset(boolean enabled) {
+        mNoAutoReset = enabled;
+    }
+
+    public void setPretendScreenOff(boolean pretendScreenOff) {
+        mPretendScreenOff = pretendScreenOff;
+        noteScreenStateLocked(pretendScreenOff ? Display.STATE_OFF : Display.STATE_ON);
+    }
+
+    private String mInitialAcquireWakeName;
+    private int mInitialAcquireWakeUid = -1;
+
+    public void noteStartWakeLocked(int uid, int pid, String name, String historyName, int type,
+            boolean unimportantForLogging, long elapsedRealtime, long uptime) {
+        uid = mapUid(uid);
+        if (type == WAKE_TYPE_PARTIAL) {
+            // Only care about partial wake locks, since full wake locks
+            // will be canceled when the user puts the screen to sleep.
+            aggregateLastWakeupUptimeLocked(uptime);
+            if (historyName == null) {
+                historyName = name;
+            }
+            if (mRecordAllHistory) {
+                if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName,
+                        uid, 0)) {
+                    addHistoryEventLocked(elapsedRealtime, uptime,
+                            HistoryItem.EVENT_WAKE_LOCK_START, historyName, uid);
+                }
+            }
+            if (mWakeLockNesting == 0) {
+                mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: "
+                        + Integer.toHexString(mHistoryCur.states));
+                mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+                mHistoryCur.wakelockTag.string = mInitialAcquireWakeName = historyName;
+                mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid;
+                mWakeLockImportant = !unimportantForLogging;
+                addHistoryRecordLocked(elapsedRealtime, uptime);
+            } else if (!mWakeLockImportant && !unimportantForLogging
+                    && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE) {
+                if (mHistoryLastWritten.wakelockTag != null) {
+                    // We'll try to update the last tag.
+                    mHistoryLastWritten.wakelockTag = null;
+                    mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+                    mHistoryCur.wakelockTag.string = mInitialAcquireWakeName = historyName;
+                    mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid;
+                    addHistoryRecordLocked(elapsedRealtime, uptime);
+                }
+                mWakeLockImportant = true;
+            }
+            mWakeLockNesting++;
+        }
+        if (uid >= 0) {
+            if (mOnBatteryScreenOffTimeBase.isRunning()) {
+                // We only update the cpu time when a wake lock is acquired if the screen is off.
+                // If the screen is on, we don't distribute the power amongst partial wakelocks.
+                if (DEBUG_ENERGY_CPU) {
+                    Slog.d(TAG, "Updating cpu time because of +wake_lock");
+                }
+                requestWakelockCpuUpdate();
+            }
+            getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type, elapsedRealtime);
+        }
+    }
+
+    public void noteStopWakeLocked(int uid, int pid, String name, String historyName, int type,
+            long elapsedRealtime, long uptime) {
+        uid = mapUid(uid);
+        if (type == WAKE_TYPE_PARTIAL) {
+            mWakeLockNesting--;
+            if (mRecordAllHistory) {
+                if (historyName == null) {
+                    historyName = name;
+                }
+                if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName,
+                        uid, 0)) {
+                    addHistoryEventLocked(elapsedRealtime, uptime,
+                            HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, uid);
+                }
+            }
+            if (mWakeLockNesting == 0) {
+                mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: "
+                        + Integer.toHexString(mHistoryCur.states));
+                mInitialAcquireWakeName = null;
+                mInitialAcquireWakeUid = -1;
+                addHistoryRecordLocked(elapsedRealtime, uptime);
+            }
+        }
+        if (uid >= 0) {
+            if (mOnBatteryScreenOffTimeBase.isRunning()) {
+                if (DEBUG_ENERGY_CPU) {
+                    Slog.d(TAG, "Updating cpu time because of -wake_lock");
+                }
+                requestWakelockCpuUpdate();
+            }
+            getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type, elapsedRealtime);
+        }
+    }
+
+    public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name,
+            String historyName, int type, boolean unimportantForLogging) {
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        final int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteStartWakeLocked(ws.get(i), pid, name, historyName, type, unimportantForLogging,
+                    elapsedRealtime, uptime);
+        }
+    }
+
+    public void noteChangeWakelockFromSourceLocked(WorkSource ws, int pid, String name,
+            String historyName, int type, WorkSource newWs, int newPid, String newName,
+            String newHistoryName, int newType, boolean newUnimportantForLogging) {
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        // For correct semantics, we start the need worksources first, so that we won't
+        // make inappropriate history items as if all wake locks went away and new ones
+        // appeared.  This is okay because tracking of wake locks allows nesting.
+        final int NN = newWs.size();
+        for (int i=0; i<NN; i++) {
+            noteStartWakeLocked(newWs.get(i), newPid, newName, newHistoryName, newType,
+                    newUnimportantForLogging, elapsedRealtime, uptime);
+        }
+        final int NO = ws.size();
+        for (int i=0; i<NO; i++) {
+            noteStopWakeLocked(ws.get(i), pid, name, historyName, type, elapsedRealtime, uptime);
+        }
+    }
+
+    public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name,
+            String historyName, int type) {
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        final int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteStopWakeLocked(ws.get(i), pid, name, historyName, type, elapsedRealtime, uptime);
+        }
+    }
+
+    public void noteLongPartialWakelockStart(String name, String historyName, int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (historyName == null) {
+            historyName = name;
+        }
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_LONG_WAKE_LOCK_START, historyName, uid,
+                0)) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_LONG_WAKE_LOCK_START,
+                historyName, uid);
+    }
+
+    public void noteLongPartialWakelockFinish(String name, String historyName, int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (historyName == null) {
+            historyName = name;
+        }
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH, historyName, uid,
+                0)) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH,
+                historyName, uid);
+    }
+
+    void aggregateLastWakeupUptimeLocked(long uptimeMs) {
+        if (mLastWakeupReason != null) {
+            long deltaUptime = uptimeMs - mLastWakeupUptimeMs;
+            SamplingTimer timer = getWakeupReasonTimerLocked(mLastWakeupReason);
+            timer.add(deltaUptime * 1000, 1); // time in in microseconds
+            mLastWakeupReason = null;
+        }
+    }
+
+    public void noteWakeupReasonLocked(String reason) {
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (DEBUG_HISTORY) Slog.v(TAG, "Wakeup reason \"" + reason +"\": "
+                + Integer.toHexString(mHistoryCur.states));
+        aggregateLastWakeupUptimeLocked(uptime);
+        mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
+        mHistoryCur.wakeReasonTag.string = reason;
+        mHistoryCur.wakeReasonTag.uid = 0;
+        mLastWakeupReason = reason;
+        mLastWakeupUptimeMs = uptime;
+        addHistoryRecordLocked(elapsedRealtime, uptime);
+    }
+
+    public boolean startAddingCpuLocked() {
+        mHandler.removeMessages(MSG_UPDATE_WAKELOCKS);
+        return mOnBatteryInternal;
+    }
+
+    public void finishAddingCpuLocked(int totalUTime, int totalSTime, int statUserTime,
+                                      int statSystemTime, int statIOWaitTime, int statIrqTime,
+                                      int statSoftIrqTime, int statIdleTime) {
+        if (DEBUG) Slog.d(TAG, "Adding cpu: tuser=" + totalUTime + " tsys=" + totalSTime
+                + " user=" + statUserTime + " sys=" + statSystemTime
+                + " io=" + statIOWaitTime + " irq=" + statIrqTime
+                + " sirq=" + statSoftIrqTime + " idle=" + statIdleTime);
+        mCurStepCpuUserTime += totalUTime;
+        mCurStepCpuSystemTime += totalSTime;
+        mCurStepStatUserTime += statUserTime;
+        mCurStepStatSystemTime += statSystemTime;
+        mCurStepStatIOWaitTime += statIOWaitTime;
+        mCurStepStatIrqTime += statIrqTime;
+        mCurStepStatSoftIrqTime += statSoftIrqTime;
+        mCurStepStatIdleTime += statIdleTime;
+    }
+
+    public void noteProcessDiedLocked(int uid, int pid) {
+        uid = mapUid(uid);
+        Uid u = mUidStats.get(uid);
+        if (u != null) {
+            u.mPids.remove(pid);
+        }
+    }
+
+    public long getProcessWakeTime(int uid, int pid, long realtime) {
+        uid = mapUid(uid);
+        Uid u = mUidStats.get(uid);
+        if (u != null) {
+            Uid.Pid p = u.mPids.get(pid);
+            if (p != null) {
+                return p.mWakeSumMs + (p.mWakeNesting > 0 ? (realtime - p.mWakeStartMs) : 0);
+            }
+        }
+        return 0;
+    }
+
+    public void reportExcessiveCpuLocked(int uid, String proc, long overTime, long usedTime) {
+        uid = mapUid(uid);
+        Uid u = mUidStats.get(uid);
+        if (u != null) {
+            u.reportExcessiveCpuLocked(proc, overTime, usedTime);
+        }
+    }
+
+    int mSensorNesting;
+
+    public void noteStartSensorLocked(int uid, int sensor) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (mSensorNesting == 0) {
+            mHistoryCur.states |= HistoryItem.STATE_SENSOR_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Start sensor to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+        mSensorNesting++;
+        getUidStatsLocked(uid).noteStartSensor(sensor, elapsedRealtime);
+    }
+
+    public void noteStopSensorLocked(int uid, int sensor) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        mSensorNesting--;
+        if (mSensorNesting == 0) {
+            mHistoryCur.states &= ~HistoryItem.STATE_SENSOR_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Stop sensor to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+        getUidStatsLocked(uid).noteStopSensor(sensor, elapsedRealtime);
+    }
+
+    int mGpsNesting;
+
+    public void noteStartGpsLocked(int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (mGpsNesting == 0) {
+            mHistoryCur.states |= HistoryItem.STATE_GPS_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Start GPS to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+        mGpsNesting++;
+        getUidStatsLocked(uid).noteStartGps(elapsedRealtime);
+    }
+
+    public void noteStopGpsLocked(int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        mGpsNesting--;
+        if (mGpsNesting == 0) {
+            mHistoryCur.states &= ~HistoryItem.STATE_GPS_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Stop GPS to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+        getUidStatsLocked(uid).noteStopGps(elapsedRealtime);
+    }
+
+    public void noteScreenStateLocked(int state) {
+        state = mPretendScreenOff ? Display.STATE_OFF : state;
+
+        // Battery stats relies on there being 4 states. To accommodate this, new states beyond the
+        // original 4 are mapped to one of the originals.
+        if (state > MAX_TRACKED_SCREEN_STATE) {
+            switch (state) {
+                case Display.STATE_VR:
+                    state = Display.STATE_ON;
+                    break;
+                default:
+                    Slog.wtf(TAG, "Unknown screen state (not mapped): " + state);
+                    break;
+            }
+        }
+
+        if (mScreenState != state) {
+            recordDailyStatsIfNeededLocked(true);
+            final int oldState = mScreenState;
+            mScreenState = state;
+            if (DEBUG) Slog.v(TAG, "Screen state: oldState=" + Display.stateToString(oldState)
+                    + ", newState=" + Display.stateToString(state));
+
+            if (state != Display.STATE_UNKNOWN) {
+                int stepState = state-1;
+                if ((stepState & STEP_LEVEL_MODE_SCREEN_STATE) == stepState) {
+                    mModStepMode |= (mCurStepMode & STEP_LEVEL_MODE_SCREEN_STATE) ^ stepState;
+                    mCurStepMode = (mCurStepMode & ~STEP_LEVEL_MODE_SCREEN_STATE) | stepState;
+                } else {
+                    Slog.wtf(TAG, "Unexpected screen state: " + state);
+                }
+            }
+
+            if (state == Display.STATE_ON) {
+                // Screen turning on.
+                final long elapsedRealtime = mClocks.elapsedRealtime();
+                final long uptime = mClocks.uptimeMillis();
+                mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: "
+                        + Integer.toHexString(mHistoryCur.states));
+                addHistoryRecordLocked(elapsedRealtime, uptime);
+                mScreenOnTimer.startRunningLocked(elapsedRealtime);
+                if (mScreenBrightnessBin >= 0) {
+                    mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(elapsedRealtime);
+                }
+
+                updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), false,
+                        mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
+
+                // Fake a wake lock, so we consider the device waked as long
+                // as the screen is on.
+                noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false,
+                        elapsedRealtime, uptime);
+
+                // Update discharge amounts.
+                if (mOnBatteryInternal) {
+                    updateDischargeScreenLevelsLocked(false, true);
+                }
+            } else if (oldState == Display.STATE_ON) {
+                // Screen turning off or dozing.
+                final long elapsedRealtime = mClocks.elapsedRealtime();
+                final long uptime = mClocks.uptimeMillis();
+                mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: "
+                        + Integer.toHexString(mHistoryCur.states));
+                addHistoryRecordLocked(elapsedRealtime, uptime);
+                mScreenOnTimer.stopRunningLocked(elapsedRealtime);
+                if (mScreenBrightnessBin >= 0) {
+                    mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime);
+                }
+
+                noteStopWakeLocked(-1, -1, "screen", "screen", WAKE_TYPE_PARTIAL,
+                        elapsedRealtime, uptime);
+
+                updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), true,
+                        mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
+
+                // Update discharge amounts.
+                if (mOnBatteryInternal) {
+                    updateDischargeScreenLevelsLocked(true, false);
+                }
+            }
+        }
+    }
+
+    public void noteScreenBrightnessLocked(int brightness) {
+        // Bin the brightness.
+        int bin = brightness / (256/NUM_SCREEN_BRIGHTNESS_BINS);
+        if (bin < 0) bin = 0;
+        else if (bin >= NUM_SCREEN_BRIGHTNESS_BINS) bin = NUM_SCREEN_BRIGHTNESS_BINS-1;
+        if (mScreenBrightnessBin != bin) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_BRIGHTNESS_MASK)
+                    | (bin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
+            if (DEBUG_HISTORY) Slog.v(TAG, "Screen brightness " + bin + " to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            if (mScreenState == Display.STATE_ON) {
+                if (mScreenBrightnessBin >= 0) {
+                    mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime);
+                }
+                mScreenBrightnessTimer[bin].startRunningLocked(elapsedRealtime);
+            }
+            mScreenBrightnessBin = bin;
+        }
+    }
+
+    public void noteUserActivityLocked(int uid, int event) {
+        if (mOnBatteryInternal) {
+            uid = mapUid(uid);
+            getUidStatsLocked(uid).noteUserActivityLocked(event);
+        }
+    }
+
+    public void noteWakeUpLocked(String reason, int reasonUid) {
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_SCREEN_WAKE_UP,
+                reason, reasonUid);
+    }
+
+    public void noteInteractiveLocked(boolean interactive) {
+        if (mInteractive != interactive) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            mInteractive = interactive;
+            if (DEBUG) Slog.v(TAG, "Interactive: " + interactive);
+            if (interactive) {
+                mInteractiveTimer.startRunningLocked(elapsedRealtime);
+            } else {
+                mInteractiveTimer.stopRunningLocked(elapsedRealtime);
+            }
+        }
+    }
+
+    public void noteConnectivityChangedLocked(int type, String extra) {
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_CONNECTIVITY_CHANGED,
+                extra, type);
+        mNumConnectivityChange++;
+    }
+
+    private void noteMobileRadioApWakeupLocked(final long elapsedRealtimeMillis,
+            final long uptimeMillis, int uid) {
+        uid = mapUid(uid);
+        addHistoryEventLocked(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
+                uid);
+        getUidStatsLocked(uid).noteMobileRadioApWakeupLocked();
+    }
+
+    /**
+     * Updates the radio power state and returns true if an external stats collection should occur.
+     */
+    public boolean noteMobileRadioPowerStateLocked(int powerState, long timestampNs, int uid) {
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (mMobileRadioPowerState != powerState) {
+            long realElapsedRealtimeMs;
+            final boolean active =
+                    powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM
+                            || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
+            if (active) {
+                if (uid > 0) {
+                    noteMobileRadioApWakeupLocked(elapsedRealtime, uptime, uid);
+                }
+
+                mMobileRadioActiveStartTime = realElapsedRealtimeMs = timestampNs / (1000 * 1000);
+                mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
+            } else {
+                realElapsedRealtimeMs = timestampNs / (1000*1000);
+                long lastUpdateTimeMs = mMobileRadioActiveStartTime;
+                if (realElapsedRealtimeMs < lastUpdateTimeMs) {
+                    Slog.wtf(TAG, "Data connection inactive timestamp " + realElapsedRealtimeMs
+                            + " is before start time " + lastUpdateTimeMs);
+                    realElapsedRealtimeMs = elapsedRealtime;
+                } else if (realElapsedRealtimeMs < elapsedRealtime) {
+                    mMobileRadioActiveAdjustedTime.addCountLocked(elapsedRealtime
+                            - realElapsedRealtimeMs);
+                }
+                mHistoryCur.states &= ~HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
+            }
+            if (DEBUG_HISTORY) Slog.v(TAG, "Mobile network active " + active + " to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mMobileRadioPowerState = powerState;
+            if (active) {
+                mMobileRadioActiveTimer.startRunningLocked(elapsedRealtime);
+                mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtime);
+            } else {
+                mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs);
+                mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs);
+                // Tell the caller to collect radio network/power stats.
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void notePowerSaveModeLocked(boolean enabled) {
+        if (mPowerSaveModeEnabled != enabled) {
+            int stepState = enabled ? STEP_LEVEL_MODE_POWER_SAVE : 0;
+            mModStepMode |= (mCurStepMode&STEP_LEVEL_MODE_POWER_SAVE) ^ stepState;
+            mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_POWER_SAVE) | stepState;
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            mPowerSaveModeEnabled = enabled;
+            if (enabled) {
+                mHistoryCur.states2 |= HistoryItem.STATE2_POWER_SAVE_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode enabled to: "
+                        + Integer.toHexString(mHistoryCur.states2));
+                mPowerSaveModeEnabledTimer.startRunningLocked(elapsedRealtime);
+            } else {
+                mHistoryCur.states2 &= ~HistoryItem.STATE2_POWER_SAVE_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode disabled to: "
+                        + Integer.toHexString(mHistoryCur.states2));
+                mPowerSaveModeEnabledTimer.stopRunningLocked(elapsedRealtime);
+            }
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+    }
+
+    public void noteDeviceIdleModeLocked(int mode, String activeReason, int activeUid) {
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        boolean nowIdling = mode == DEVICE_IDLE_MODE_DEEP;
+        if (mDeviceIdling && !nowIdling && activeReason == null) {
+            // We don't go out of general idling mode until explicitly taken out of
+            // device idle through going active or significant motion.
+            nowIdling = true;
+        }
+        boolean nowLightIdling = mode == DEVICE_IDLE_MODE_LIGHT;
+        if (mDeviceLightIdling && !nowLightIdling && !nowIdling && activeReason == null) {
+            // We don't go out of general light idling mode until explicitly taken out of
+            // device idle through going active or significant motion.
+            nowLightIdling = true;
+        }
+        if (activeReason != null && (mDeviceIdling || mDeviceLightIdling)) {
+            addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_ACTIVE,
+                    activeReason, activeUid);
+        }
+        if (mDeviceIdling != nowIdling) {
+            mDeviceIdling = nowIdling;
+            int stepState = nowIdling ? STEP_LEVEL_MODE_DEVICE_IDLE : 0;
+            mModStepMode |= (mCurStepMode&STEP_LEVEL_MODE_DEVICE_IDLE) ^ stepState;
+            mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_DEVICE_IDLE) | stepState;
+            if (nowIdling) {
+                mDeviceIdlingTimer.startRunningLocked(elapsedRealtime);
+            } else {
+                mDeviceIdlingTimer.stopRunningLocked(elapsedRealtime);
+            }
+        }
+        if (mDeviceLightIdling != nowLightIdling) {
+            mDeviceLightIdling = nowLightIdling;
+            if (nowLightIdling) {
+                mDeviceLightIdlingTimer.startRunningLocked(elapsedRealtime);
+            } else {
+                mDeviceLightIdlingTimer.stopRunningLocked(elapsedRealtime);
+            }
+        }
+        if (mDeviceIdleMode != mode) {
+            mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_DEVICE_IDLE_MASK)
+                    | (mode << HistoryItem.STATE2_DEVICE_IDLE_SHIFT);
+            if (DEBUG_HISTORY) Slog.v(TAG, "Device idle mode changed to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            long lastDuration = elapsedRealtime - mLastIdleTimeStart;
+            mLastIdleTimeStart = elapsedRealtime;
+            if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
+                if (lastDuration > mLongestLightIdleTime) {
+                    mLongestLightIdleTime = lastDuration;
+                }
+                mDeviceIdleModeLightTimer.stopRunningLocked(elapsedRealtime);
+            } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) {
+                if (lastDuration > mLongestFullIdleTime) {
+                    mLongestFullIdleTime = lastDuration;
+                }
+                mDeviceIdleModeFullTimer.stopRunningLocked(elapsedRealtime);
+            }
+            if (mode == DEVICE_IDLE_MODE_LIGHT) {
+                mDeviceIdleModeLightTimer.startRunningLocked(elapsedRealtime);
+            } else if (mode == DEVICE_IDLE_MODE_DEEP) {
+                mDeviceIdleModeFullTimer.startRunningLocked(elapsedRealtime);
+            }
+            mDeviceIdleMode = mode;
+        }
+    }
+
+    public void notePackageInstalledLocked(String pkgName, int versionCode) {
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PACKAGE_INSTALLED,
+                pkgName, versionCode);
+        PackageChange pc = new PackageChange();
+        pc.mPackageName = pkgName;
+        pc.mUpdate = true;
+        pc.mVersionCode = versionCode;
+        addPackageChange(pc);
+    }
+
+    public void notePackageUninstalledLocked(String pkgName) {
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PACKAGE_UNINSTALLED,
+                pkgName, 0);
+        PackageChange pc = new PackageChange();
+        pc.mPackageName = pkgName;
+        pc.mUpdate = true;
+        addPackageChange(pc);
+    }
+
+    private void addPackageChange(PackageChange pc) {
+        if (mDailyPackageChanges == null) {
+            mDailyPackageChanges = new ArrayList<>();
+        }
+        mDailyPackageChanges.add(pc);
+    }
+
+    public void notePhoneOnLocked() {
+        if (!mPhoneOn) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            mHistoryCur.states2 |= HistoryItem.STATE2_PHONE_IN_CALL_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mPhoneOn = true;
+            mPhoneOnTimer.startRunningLocked(elapsedRealtime);
+        }
+    }
+
+    public void notePhoneOffLocked() {
+        if (mPhoneOn) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_PHONE_IN_CALL_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mPhoneOn = false;
+            mPhoneOnTimer.stopRunningLocked(elapsedRealtime);
+        }
+    }
+
+    void stopAllPhoneSignalStrengthTimersLocked(int except) {
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
+            if (i == except) {
+                continue;
+            }
+            while (mPhoneSignalStrengthsTimer[i].isRunningLocked()) {
+                mPhoneSignalStrengthsTimer[i].stopRunningLocked(elapsedRealtime);
+            }
+        }
+    }
+
+    private int fixPhoneServiceState(int state, int signalBin) {
+        if (mPhoneSimStateRaw == TelephonyManager.SIM_STATE_ABSENT) {
+            // In this case we will always be STATE_OUT_OF_SERVICE, so need
+            // to infer that we are scanning from other data.
+            if (state == ServiceState.STATE_OUT_OF_SERVICE
+                    && signalBin > SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+                state = ServiceState.STATE_IN_SERVICE;
+            }
+        }
+
+        return state;
+    }
+
+    private void updateAllPhoneStateLocked(int state, int simState, int strengthBin) {
+        boolean scanning = false;
+        boolean newHistory = false;
+
+        mPhoneServiceStateRaw = state;
+        mPhoneSimStateRaw = simState;
+        mPhoneSignalStrengthBinRaw = strengthBin;
+
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+
+        if (simState == TelephonyManager.SIM_STATE_ABSENT) {
+            // In this case we will always be STATE_OUT_OF_SERVICE, so need
+            // to infer that we are scanning from other data.
+            if (state == ServiceState.STATE_OUT_OF_SERVICE
+                    && strengthBin > SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+                state = ServiceState.STATE_IN_SERVICE;
+            }
+        }
+
+        // If the phone is powered off, stop all timers.
+        if (state == ServiceState.STATE_POWER_OFF) {
+            strengthBin = -1;
+
+        // If we are in service, make sure the correct signal string timer is running.
+        } else if (state == ServiceState.STATE_IN_SERVICE) {
+            // Bin will be changed below.
+
+        // If we're out of service, we are in the lowest signal strength
+        // bin and have the scanning bit set.
+        } else if (state == ServiceState.STATE_OUT_OF_SERVICE) {
+            scanning = true;
+            strengthBin = SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+            if (!mPhoneSignalScanningTimer.isRunningLocked()) {
+                mHistoryCur.states |= HistoryItem.STATE_PHONE_SCANNING_FLAG;
+                newHistory = true;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Phone started scanning to: "
+                        + Integer.toHexString(mHistoryCur.states));
+                mPhoneSignalScanningTimer.startRunningLocked(elapsedRealtime);
+            }
+        }
+
+        if (!scanning) {
+            // If we are no longer scanning, then stop the scanning timer.
+            if (mPhoneSignalScanningTimer.isRunningLocked()) {
+                mHistoryCur.states &= ~HistoryItem.STATE_PHONE_SCANNING_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Phone stopped scanning to: "
+                        + Integer.toHexString(mHistoryCur.states));
+                newHistory = true;
+                mPhoneSignalScanningTimer.stopRunningLocked(elapsedRealtime);
+            }
+        }
+
+        if (mPhoneServiceState != state) {
+            mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_PHONE_STATE_MASK)
+                    | (state << HistoryItem.STATE_PHONE_STATE_SHIFT);
+            if (DEBUG_HISTORY) Slog.v(TAG, "Phone state " + state + " to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            newHistory = true;
+            mPhoneServiceState = state;
+        }
+
+        if (mPhoneSignalStrengthBin != strengthBin) {
+            if (mPhoneSignalStrengthBin >= 0) {
+                mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].stopRunningLocked(
+                        elapsedRealtime);
+            }
+            if (strengthBin >= 0) {
+                if (!mPhoneSignalStrengthsTimer[strengthBin].isRunningLocked()) {
+                    mPhoneSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime);
+                }
+                mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
+                        | (strengthBin << HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT);
+                if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: "
+                        + Integer.toHexString(mHistoryCur.states));
+                newHistory = true;
+            } else {
+                stopAllPhoneSignalStrengthTimersLocked(-1);
+            }
+            mPhoneSignalStrengthBin = strengthBin;
+        }
+
+        if (newHistory) {
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+    }
+
+    /**
+     * Telephony stack updates the phone state.
+     * @param state phone state from ServiceState.getState()
+     */
+    public void notePhoneStateLocked(int state, int simState) {
+        updateAllPhoneStateLocked(state, simState, mPhoneSignalStrengthBinRaw);
+    }
+
+    public void notePhoneSignalStrengthLocked(SignalStrength signalStrength) {
+        // Bin the strength.
+        int bin = signalStrength.getLevel();
+        updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, bin);
+    }
+
+    public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData) {
+        int bin = DATA_CONNECTION_NONE;
+        if (hasData) {
+            switch (dataType) {
+                case TelephonyManager.NETWORK_TYPE_EDGE:
+                    bin = DATA_CONNECTION_EDGE;
+                    break;
+                case TelephonyManager.NETWORK_TYPE_GPRS:
+                    bin = DATA_CONNECTION_GPRS;
+                    break;
+                case TelephonyManager.NETWORK_TYPE_UMTS:
+                    bin = DATA_CONNECTION_UMTS;
+                    break;
+                case TelephonyManager.NETWORK_TYPE_CDMA:
+                    bin = DATA_CONNECTION_CDMA;
+                    break;
+                case TelephonyManager.NETWORK_TYPE_EVDO_0:
+                    bin = DATA_CONNECTION_EVDO_0;
+                    break;
+                case TelephonyManager.NETWORK_TYPE_EVDO_A:
+                    bin = DATA_CONNECTION_EVDO_A;
+                    break;
+                case TelephonyManager.NETWORK_TYPE_1xRTT:
+                    bin = DATA_CONNECTION_1xRTT;
+                    break;
+                case TelephonyManager.NETWORK_TYPE_HSDPA:
+                    bin = DATA_CONNECTION_HSDPA;
+                    break;
+                case TelephonyManager.NETWORK_TYPE_HSUPA:
+                    bin = DATA_CONNECTION_HSUPA;
+                    break;
+                case TelephonyManager.NETWORK_TYPE_HSPA:
+                    bin = DATA_CONNECTION_HSPA;
+                    break;
+                case TelephonyManager.NETWORK_TYPE_IDEN:
+                    bin = DATA_CONNECTION_IDEN;
+                    break;
+                case TelephonyManager.NETWORK_TYPE_EVDO_B:
+                    bin = DATA_CONNECTION_EVDO_B;
+                    break;
+                case TelephonyManager.NETWORK_TYPE_LTE:
+                    bin = DATA_CONNECTION_LTE;
+                    break;
+                case TelephonyManager.NETWORK_TYPE_EHRPD:
+                    bin = DATA_CONNECTION_EHRPD;
+                    break;
+                case TelephonyManager.NETWORK_TYPE_HSPAP:
+                    bin = DATA_CONNECTION_HSPAP;
+                    break;
+                default:
+                    bin = DATA_CONNECTION_OTHER;
+                    break;
+            }
+        }
+        if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
+        if (mPhoneDataConnectionType != bin) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK)
+                    | (bin << HistoryItem.STATE_DATA_CONNECTION_SHIFT);
+            if (DEBUG_HISTORY) Slog.v(TAG, "Data connection " + bin + " to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            if (mPhoneDataConnectionType >= 0) {
+                mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(
+                        elapsedRealtime);
+            }
+            mPhoneDataConnectionType = bin;
+            mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtime);
+        }
+    }
+
+    public void noteWifiOnLocked() {
+        if (!mWifiOn) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mWifiOn = true;
+            mWifiOnTimer.startRunningLocked(elapsedRealtime);
+            scheduleSyncExternalStatsLocked("wifi-off", ExternalStatsSync.UPDATE_WIFI);
+        }
+    }
+
+    public void noteWifiOffLocked() {
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (mWifiOn) {
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mWifiOn = false;
+            mWifiOnTimer.stopRunningLocked(elapsedRealtime);
+            scheduleSyncExternalStatsLocked("wifi-on", ExternalStatsSync.UPDATE_WIFI);
+        }
+    }
+
+    public void noteAudioOnLocked(int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (mAudioOnNesting == 0) {
+            mHistoryCur.states |= HistoryItem.STATE_AUDIO_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Audio on to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mAudioOnTimer.startRunningLocked(elapsedRealtime);
+        }
+        mAudioOnNesting++;
+        getUidStatsLocked(uid).noteAudioTurnedOnLocked(elapsedRealtime);
+    }
+
+    public void noteAudioOffLocked(int uid) {
+        if (mAudioOnNesting == 0) {
+            return;
+        }
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (--mAudioOnNesting == 0) {
+            mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mAudioOnTimer.stopRunningLocked(elapsedRealtime);
+        }
+        getUidStatsLocked(uid).noteAudioTurnedOffLocked(elapsedRealtime);
+    }
+
+    public void noteVideoOnLocked(int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (mVideoOnNesting == 0) {
+            mHistoryCur.states2 |= HistoryItem.STATE2_VIDEO_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Video on to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mVideoOnTimer.startRunningLocked(elapsedRealtime);
+        }
+        mVideoOnNesting++;
+        getUidStatsLocked(uid).noteVideoTurnedOnLocked(elapsedRealtime);
+    }
+
+    public void noteVideoOffLocked(int uid) {
+        if (mVideoOnNesting == 0) {
+            return;
+        }
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (--mVideoOnNesting == 0) {
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_VIDEO_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mVideoOnTimer.stopRunningLocked(elapsedRealtime);
+        }
+        getUidStatsLocked(uid).noteVideoTurnedOffLocked(elapsedRealtime);
+    }
+
+    public void noteResetAudioLocked() {
+        if (mAudioOnNesting > 0) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            mAudioOnNesting = 0;
+            mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mAudioOnTimer.stopAllRunningLocked(elapsedRealtime);
+            for (int i=0; i<mUidStats.size(); i++) {
+                BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+                uid.noteResetAudioLocked(elapsedRealtime);
+            }
+        }
+    }
+
+    public void noteResetVideoLocked() {
+        if (mVideoOnNesting > 0) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            mAudioOnNesting = 0;
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_VIDEO_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mVideoOnTimer.stopAllRunningLocked(elapsedRealtime);
+            for (int i=0; i<mUidStats.size(); i++) {
+                BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+                uid.noteResetVideoLocked(elapsedRealtime);
+            }
+        }
+    }
+
+    public void noteActivityResumedLocked(int uid) {
+        uid = mapUid(uid);
+        getUidStatsLocked(uid).noteActivityResumedLocked(mClocks.elapsedRealtime());
+    }
+
+    public void noteActivityPausedLocked(int uid) {
+        uid = mapUid(uid);
+        getUidStatsLocked(uid).noteActivityPausedLocked(mClocks.elapsedRealtime());
+    }
+
+    public void noteVibratorOnLocked(int uid, long durationMillis) {
+        uid = mapUid(uid);
+        getUidStatsLocked(uid).noteVibratorOnLocked(durationMillis);
+    }
+
+    public void noteVibratorOffLocked(int uid) {
+        uid = mapUid(uid);
+        getUidStatsLocked(uid).noteVibratorOffLocked();
+    }
+
+    public void noteFlashlightOnLocked(int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (mFlashlightOnNesting++ == 0) {
+            mHistoryCur.states2 |= HistoryItem.STATE2_FLASHLIGHT_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight on to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mFlashlightOnTimer.startRunningLocked(elapsedRealtime);
+        }
+        getUidStatsLocked(uid).noteFlashlightTurnedOnLocked(elapsedRealtime);
+    }
+
+    public void noteFlashlightOffLocked(int uid) {
+        if (mFlashlightOnNesting == 0) {
+            return;
+        }
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (--mFlashlightOnNesting == 0) {
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_FLASHLIGHT_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight off to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mFlashlightOnTimer.stopRunningLocked(elapsedRealtime);
+        }
+        getUidStatsLocked(uid).noteFlashlightTurnedOffLocked(elapsedRealtime);
+    }
+
+    public void noteCameraOnLocked(int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (mCameraOnNesting++ == 0) {
+            mHistoryCur.states2 |= HistoryItem.STATE2_CAMERA_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Camera on to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mCameraOnTimer.startRunningLocked(elapsedRealtime);
+        }
+        getUidStatsLocked(uid).noteCameraTurnedOnLocked(elapsedRealtime);
+    }
+
+    public void noteCameraOffLocked(int uid) {
+        if (mCameraOnNesting == 0) {
+            return;
+        }
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (--mCameraOnNesting == 0) {
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_CAMERA_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Camera off to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mCameraOnTimer.stopRunningLocked(elapsedRealtime);
+        }
+        getUidStatsLocked(uid).noteCameraTurnedOffLocked(elapsedRealtime);
+    }
+
+    public void noteResetCameraLocked() {
+        if (mCameraOnNesting > 0) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            mCameraOnNesting = 0;
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_CAMERA_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Camera off to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mCameraOnTimer.stopAllRunningLocked(elapsedRealtime);
+            for (int i=0; i<mUidStats.size(); i++) {
+                BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+                uid.noteResetCameraLocked(elapsedRealtime);
+            }
+        }
+    }
+
+    public void noteResetFlashlightLocked() {
+        if (mFlashlightOnNesting > 0) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            mFlashlightOnNesting = 0;
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_FLASHLIGHT_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight off to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mFlashlightOnTimer.stopAllRunningLocked(elapsedRealtime);
+            for (int i=0; i<mUidStats.size(); i++) {
+                BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+                uid.noteResetFlashlightLocked(elapsedRealtime);
+            }
+        }
+    }
+
+    private void noteBluetoothScanStartedLocked(int uid, boolean isUnoptimized) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (mBluetoothScanNesting == 0) {
+            mHistoryCur.states2 |= HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan started for: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mBluetoothScanTimer.startRunningLocked(elapsedRealtime);
+        }
+        mBluetoothScanNesting++;
+        getUidStatsLocked(uid).noteBluetoothScanStartedLocked(elapsedRealtime, isUnoptimized);
+    }
+
+    public void noteBluetoothScanStartedFromSourceLocked(WorkSource ws, boolean isUnoptimized) {
+        final int N = ws.size();
+        for (int i = 0; i < N; i++) {
+            noteBluetoothScanStartedLocked(ws.get(i), isUnoptimized);
+        }
+    }
+
+    private void noteBluetoothScanStoppedLocked(int uid, boolean isUnoptimized) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        mBluetoothScanNesting--;
+        if (mBluetoothScanNesting == 0) {
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan stopped for: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mBluetoothScanTimer.stopRunningLocked(elapsedRealtime);
+        }
+        getUidStatsLocked(uid).noteBluetoothScanStoppedLocked(elapsedRealtime, isUnoptimized);
+    }
+
+    public void noteBluetoothScanStoppedFromSourceLocked(WorkSource ws, boolean isUnoptimized) {
+        final int N = ws.size();
+        for (int i = 0; i < N; i++) {
+            noteBluetoothScanStoppedLocked(ws.get(i), isUnoptimized);
+        }
+    }
+
+    public void noteResetBluetoothScanLocked() {
+        if (mBluetoothScanNesting > 0) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            mBluetoothScanNesting = 0;
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "BLE can stopped for: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mBluetoothScanTimer.stopAllRunningLocked(elapsedRealtime);
+            for (int i=0; i<mUidStats.size(); i++) {
+                BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+                uid.noteResetBluetoothScanLocked(elapsedRealtime);
+            }
+        }
+    }
+
+    public void noteBluetoothScanResultsFromSourceLocked(WorkSource ws, int numNewResults) {
+        final int N = ws.size();
+        for (int i = 0; i < N; i++) {
+            int uid = mapUid(ws.get(i));
+            getUidStatsLocked(uid).noteBluetoothScanResultsLocked(numNewResults);
+        }
+    }
+
+    private void noteWifiRadioApWakeupLocked(final long elapsedRealtimeMillis,
+            final long uptimeMillis, int uid) {
+        uid = mapUid(uid);
+        addHistoryEventLocked(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
+                uid);
+        getUidStatsLocked(uid).noteWifiRadioApWakeupLocked();
+    }
+
+    public void noteWifiRadioPowerState(int powerState, long timestampNs, int uid) {
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (mWifiRadioPowerState != powerState) {
+            final boolean active =
+                    powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM
+                            || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
+            if (active) {
+                if (uid > 0) {
+                    noteWifiRadioApWakeupLocked(elapsedRealtime, uptime, uid);
+                }
+                mHistoryCur.states |= HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG;
+            } else {
+                mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG;
+            }
+            if (DEBUG_HISTORY) Slog.v(TAG, "Wifi network active " + active + " to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mWifiRadioPowerState = powerState;
+        }
+    }
+
+    public void noteWifiRunningLocked(WorkSource ws) {
+        if (!mGlobalWifiRunning) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_RUNNING_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mGlobalWifiRunning = true;
+            mGlobalWifiRunningTimer.startRunningLocked(elapsedRealtime);
+            int N = ws.size();
+            for (int i=0; i<N; i++) {
+                int uid = mapUid(ws.get(i));
+                getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime);
+            }
+            scheduleSyncExternalStatsLocked("wifi-running", ExternalStatsSync.UPDATE_WIFI);
+        } else {
+            Log.w(TAG, "noteWifiRunningLocked -- called while WIFI running");
+        }
+    }
+
+    public void noteWifiRunningChangedLocked(WorkSource oldWs, WorkSource newWs) {
+        if (mGlobalWifiRunning) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            int N = oldWs.size();
+            for (int i=0; i<N; i++) {
+                int uid = mapUid(oldWs.get(i));
+                getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime);
+            }
+            N = newWs.size();
+            for (int i=0; i<N; i++) {
+                int uid = mapUid(newWs.get(i));
+                getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime);
+            }
+        } else {
+            Log.w(TAG, "noteWifiRunningChangedLocked -- called while WIFI not running");
+        }
+    }
+
+    public void noteWifiStoppedLocked(WorkSource ws) {
+        if (mGlobalWifiRunning) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_RUNNING_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+            mGlobalWifiRunning = false;
+            mGlobalWifiRunningTimer.stopRunningLocked(elapsedRealtime);
+            int N = ws.size();
+            for (int i=0; i<N; i++) {
+                int uid = mapUid(ws.get(i));
+                getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime);
+            }
+            scheduleSyncExternalStatsLocked("wifi-stopped", ExternalStatsSync.UPDATE_WIFI);
+        } else {
+            Log.w(TAG, "noteWifiStoppedLocked -- called while WIFI not running");
+        }
+    }
+
+    public void noteWifiStateLocked(int wifiState, String accessPoint) {
+        if (DEBUG) Log.i(TAG, "WiFi state -> " + wifiState);
+        if (mWifiState != wifiState) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            if (mWifiState >= 0) {
+                mWifiStateTimer[mWifiState].stopRunningLocked(elapsedRealtime);
+            }
+            mWifiState = wifiState;
+            mWifiStateTimer[wifiState].startRunningLocked(elapsedRealtime);
+            scheduleSyncExternalStatsLocked("wifi-state", ExternalStatsSync.UPDATE_WIFI);
+        }
+    }
+
+    public void noteWifiSupplicantStateChangedLocked(int supplState, boolean failedAuth) {
+        if (DEBUG) Log.i(TAG, "WiFi suppl state -> " + supplState);
+        if (mWifiSupplState != supplState) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            if (mWifiSupplState >= 0) {
+                mWifiSupplStateTimer[mWifiSupplState].stopRunningLocked(elapsedRealtime);
+            }
+            mWifiSupplState = supplState;
+            mWifiSupplStateTimer[supplState].startRunningLocked(elapsedRealtime);
+            mHistoryCur.states2 =
+                    (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
+                    | (supplState << HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
+            if (DEBUG_HISTORY) Slog.v(TAG, "Wifi suppl state " + supplState + " to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+    }
+
+    void stopAllWifiSignalStrengthTimersLocked(int except) {
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            if (i == except) {
+                continue;
+            }
+            while (mWifiSignalStrengthsTimer[i].isRunningLocked()) {
+                mWifiSignalStrengthsTimer[i].stopRunningLocked(elapsedRealtime);
+            }
+        }
+    }
+
+    public void noteWifiRssiChangedLocked(int newRssi) {
+        int strengthBin = WifiManager.calculateSignalLevel(newRssi, NUM_WIFI_SIGNAL_STRENGTH_BINS);
+        if (DEBUG) Log.i(TAG, "WiFi rssi -> " + newRssi + " bin=" + strengthBin);
+        if (mWifiSignalStrengthBin != strengthBin) {
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            if (mWifiSignalStrengthBin >= 0) {
+                mWifiSignalStrengthsTimer[mWifiSignalStrengthBin].stopRunningLocked(
+                        elapsedRealtime);
+            }
+            if (strengthBin >= 0) {
+                if (!mWifiSignalStrengthsTimer[strengthBin].isRunningLocked()) {
+                    mWifiSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime);
+                }
+                mHistoryCur.states2 =
+                        (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
+                        | (strengthBin << HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT);
+                if (DEBUG_HISTORY) Slog.v(TAG, "Wifi signal strength " + strengthBin + " to: "
+                        + Integer.toHexString(mHistoryCur.states2));
+                addHistoryRecordLocked(elapsedRealtime, uptime);
+            } else {
+                stopAllWifiSignalStrengthTimersLocked(-1);
+            }
+            mWifiSignalStrengthBin = strengthBin;
+        }
+    }
+
+    int mWifiFullLockNesting = 0;
+
+    public void noteFullWifiLockAcquiredLocked(int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (mWifiFullLockNesting == 0) {
+            mHistoryCur.states |= HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+        mWifiFullLockNesting++;
+        getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked(elapsedRealtime);
+    }
+
+    public void noteFullWifiLockReleasedLocked(int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        mWifiFullLockNesting--;
+        if (mWifiFullLockNesting == 0) {
+            mHistoryCur.states &= ~HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+        getUidStatsLocked(uid).noteFullWifiLockReleasedLocked(elapsedRealtime);
+    }
+
+    int mWifiScanNesting = 0;
+
+    public void noteWifiScanStartedLocked(int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (mWifiScanNesting == 0) {
+            mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan started for: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+        mWifiScanNesting++;
+        getUidStatsLocked(uid).noteWifiScanStartedLocked(elapsedRealtime);
+    }
+
+    public void noteWifiScanStoppedLocked(int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        mWifiScanNesting--;
+        if (mWifiScanNesting == 0) {
+            mHistoryCur.states &= ~HistoryItem.STATE_WIFI_SCAN_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan stopped for: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+        getUidStatsLocked(uid).noteWifiScanStoppedLocked(elapsedRealtime);
+    }
+
+    public void noteWifiBatchedScanStartedLocked(int uid, int csph) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        getUidStatsLocked(uid).noteWifiBatchedScanStartedLocked(csph, elapsedRealtime);
+    }
+
+    public void noteWifiBatchedScanStoppedLocked(int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        getUidStatsLocked(uid).noteWifiBatchedScanStoppedLocked(elapsedRealtime);
+    }
+
+    int mWifiMulticastNesting = 0;
+
+    public void noteWifiMulticastEnabledLocked(int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        if (mWifiMulticastNesting == 0) {
+            mHistoryCur.states |= HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+        mWifiMulticastNesting++;
+        getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(elapsedRealtime);
+    }
+
+    public void noteWifiMulticastDisabledLocked(int uid) {
+        uid = mapUid(uid);
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
+        mWifiMulticastNesting--;
+        if (mWifiMulticastNesting == 0) {
+            mHistoryCur.states &= ~HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtime, uptime);
+        }
+        getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(elapsedRealtime);
+    }
+
+    public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteFullWifiLockAcquiredLocked(ws.get(i));
+        }
+    }
+
+    public void noteFullWifiLockReleasedFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteFullWifiLockReleasedLocked(ws.get(i));
+        }
+    }
+
+    public void noteWifiScanStartedFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteWifiScanStartedLocked(ws.get(i));
+        }
+    }
+
+    public void noteWifiScanStoppedFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteWifiScanStoppedLocked(ws.get(i));
+        }
+    }
+
+    public void noteWifiBatchedScanStartedFromSourceLocked(WorkSource ws, int csph) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteWifiBatchedScanStartedLocked(ws.get(i), csph);
+        }
+    }
+
+    public void noteWifiBatchedScanStoppedFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteWifiBatchedScanStoppedLocked(ws.get(i));
+        }
+    }
+
+    public void noteWifiMulticastEnabledFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteWifiMulticastEnabledLocked(ws.get(i));
+        }
+    }
+
+    public void noteWifiMulticastDisabledFromSourceLocked(WorkSource ws) {
+        int N = ws.size();
+        for (int i=0; i<N; i++) {
+            noteWifiMulticastDisabledLocked(ws.get(i));
+        }
+    }
+
+    private static String[] includeInStringArray(String[] array, String str) {
+        if (ArrayUtils.indexOf(array, str) >= 0) {
+            return array;
+        }
+        String[] newArray = new String[array.length+1];
+        System.arraycopy(array, 0, newArray, 0, array.length);
+        newArray[array.length] = str;
+        return newArray;
+    }
+
+    private static String[] excludeFromStringArray(String[] array, String str) {
+        int index = ArrayUtils.indexOf(array, str);
+        if (index >= 0) {
+            String[] newArray = new String[array.length-1];
+            if (index > 0) {
+                System.arraycopy(array, 0, newArray, 0, index);
+            }
+            if (index < array.length-1) {
+                System.arraycopy(array, index+1, newArray, index, array.length-index-1);
+            }
+            return newArray;
+        }
+        return array;
+    }
+
+    public void noteNetworkInterfaceTypeLocked(String iface, int networkType) {
+        if (TextUtils.isEmpty(iface)) return;
+
+        synchronized (mModemNetworkLock) {
+            if (ConnectivityManager.isNetworkTypeMobile(networkType)) {
+                mModemIfaces = includeInStringArray(mModemIfaces, iface);
+                if (DEBUG) Slog.d(TAG, "Note mobile iface " + iface + ": " + mModemIfaces);
+            } else {
+                mModemIfaces = excludeFromStringArray(mModemIfaces, iface);
+                if (DEBUG) Slog.d(TAG, "Note non-mobile iface " + iface + ": " + mModemIfaces);
+            }
+        }
+
+        synchronized (mWifiNetworkLock) {
+            if (ConnectivityManager.isNetworkTypeWifi(networkType)) {
+                mWifiIfaces = includeInStringArray(mWifiIfaces, iface);
+                if (DEBUG) Slog.d(TAG, "Note wifi iface " + iface + ": " + mWifiIfaces);
+            } else {
+                mWifiIfaces = excludeFromStringArray(mWifiIfaces, iface);
+                if (DEBUG) Slog.d(TAG, "Note non-wifi iface " + iface + ": " + mWifiIfaces);
+            }
+        }
+    }
+
+    @Override public long getScreenOnTime(long elapsedRealtimeUs, int which) {
+        return mScreenOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    }
+
+    @Override public int getScreenOnCount(int which) {
+        return mScreenOnTimer.getCountLocked(which);
+    }
+
+    @Override public long getScreenBrightnessTime(int brightnessBin,
+            long elapsedRealtimeUs, int which) {
+        return mScreenBrightnessTimer[brightnessBin].getTotalTimeLocked(
+                elapsedRealtimeUs, which);
+    }
+
+    @Override public long getInteractiveTime(long elapsedRealtimeUs, int which) {
+        return mInteractiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    }
+
+    @Override public long getPowerSaveModeEnabledTime(long elapsedRealtimeUs, int which) {
+        return mPowerSaveModeEnabledTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    }
+
+    @Override public int getPowerSaveModeEnabledCount(int which) {
+        return mPowerSaveModeEnabledTimer.getCountLocked(which);
+    }
+
+    @Override public long getDeviceIdleModeTime(int mode, long elapsedRealtimeUs,
+            int which) {
+        switch (mode) {
+            case DEVICE_IDLE_MODE_LIGHT:
+                return mDeviceIdleModeLightTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+            case DEVICE_IDLE_MODE_DEEP:
+                return mDeviceIdleModeFullTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+        }
+        return 0;
+    }
+
+    @Override public int getDeviceIdleModeCount(int mode, int which) {
+        switch (mode) {
+            case DEVICE_IDLE_MODE_LIGHT:
+                return mDeviceIdleModeLightTimer.getCountLocked(which);
+            case DEVICE_IDLE_MODE_DEEP:
+                return mDeviceIdleModeFullTimer.getCountLocked(which);
+        }
+        return 0;
+    }
+
+    @Override public long getLongestDeviceIdleModeTime(int mode) {
+        switch (mode) {
+            case DEVICE_IDLE_MODE_LIGHT:
+                return mLongestLightIdleTime;
+            case DEVICE_IDLE_MODE_DEEP:
+                return mLongestFullIdleTime;
+        }
+        return 0;
+    }
+
+    @Override public long getDeviceIdlingTime(int mode, long elapsedRealtimeUs, int which) {
+        switch (mode) {
+            case DEVICE_IDLE_MODE_LIGHT:
+                return mDeviceLightIdlingTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+            case DEVICE_IDLE_MODE_DEEP:
+                return mDeviceIdlingTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+        }
+        return 0;
+    }
+
+    @Override public int getDeviceIdlingCount(int mode, int which) {
+        switch (mode) {
+            case DEVICE_IDLE_MODE_LIGHT:
+                return mDeviceLightIdlingTimer.getCountLocked(which);
+            case DEVICE_IDLE_MODE_DEEP:
+                return mDeviceIdlingTimer.getCountLocked(which);
+        }
+        return 0;
+    }
+
+    @Override public int getNumConnectivityChange(int which) {
+        int val = mNumConnectivityChange;
+        if (which == STATS_CURRENT) {
+            val -= mLoadedNumConnectivityChange;
+        } else if (which == STATS_SINCE_UNPLUGGED) {
+            val -= mUnpluggedNumConnectivityChange;
+        }
+        return val;
+    }
+
+    @Override public long getPhoneOnTime(long elapsedRealtimeUs, int which) {
+        return mPhoneOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    }
+
+    @Override public int getPhoneOnCount(int which) {
+        return mPhoneOnTimer.getCountLocked(which);
+    }
+
+    @Override public long getPhoneSignalStrengthTime(int strengthBin,
+            long elapsedRealtimeUs, int which) {
+        return mPhoneSignalStrengthsTimer[strengthBin].getTotalTimeLocked(
+                elapsedRealtimeUs, which);
+    }
+
+    @Override public long getPhoneSignalScanningTime(
+            long elapsedRealtimeUs, int which) {
+        return mPhoneSignalScanningTimer.getTotalTimeLocked(
+                elapsedRealtimeUs, which);
+    }
+
+    @Override public int getPhoneSignalStrengthCount(int strengthBin, int which) {
+        return mPhoneSignalStrengthsTimer[strengthBin].getCountLocked(which);
+    }
+
+    @Override public long getPhoneDataConnectionTime(int dataType,
+            long elapsedRealtimeUs, int which) {
+        return mPhoneDataConnectionsTimer[dataType].getTotalTimeLocked(
+                elapsedRealtimeUs, which);
+    }
+
+    @Override public int getPhoneDataConnectionCount(int dataType, int which) {
+        return mPhoneDataConnectionsTimer[dataType].getCountLocked(which);
+    }
+
+    @Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) {
+        return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    }
+
+    @Override public int getMobileRadioActiveCount(int which) {
+        return mMobileRadioActiveTimer.getCountLocked(which);
+    }
+
+    @Override public long getMobileRadioActiveAdjustedTime(int which) {
+        return mMobileRadioActiveAdjustedTime.getCountLocked(which);
+    }
+
+    @Override public long getMobileRadioActiveUnknownTime(int which) {
+        return mMobileRadioActiveUnknownTime.getCountLocked(which);
+    }
+
+    @Override public int getMobileRadioActiveUnknownCount(int which) {
+        return (int)mMobileRadioActiveUnknownCount.getCountLocked(which);
+    }
+
+    @Override public long getWifiOnTime(long elapsedRealtimeUs, int which) {
+        return mWifiOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    }
+
+    @Override public long getGlobalWifiRunningTime(long elapsedRealtimeUs, int which) {
+        return mGlobalWifiRunningTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    }
+
+    @Override public long getWifiStateTime(int wifiState,
+            long elapsedRealtimeUs, int which) {
+        return mWifiStateTimer[wifiState].getTotalTimeLocked(
+                elapsedRealtimeUs, which);
+    }
+
+    @Override public int getWifiStateCount(int wifiState, int which) {
+        return mWifiStateTimer[wifiState].getCountLocked(which);
+    }
+
+    @Override public long getWifiSupplStateTime(int state,
+            long elapsedRealtimeUs, int which) {
+        return mWifiSupplStateTimer[state].getTotalTimeLocked(
+                elapsedRealtimeUs, which);
+    }
+
+    @Override public int getWifiSupplStateCount(int state, int which) {
+        return mWifiSupplStateTimer[state].getCountLocked(which);
+    }
+
+    @Override public long getWifiSignalStrengthTime(int strengthBin,
+            long elapsedRealtimeUs, int which) {
+        return mWifiSignalStrengthsTimer[strengthBin].getTotalTimeLocked(
+                elapsedRealtimeUs, which);
+    }
+
+    @Override public int getWifiSignalStrengthCount(int strengthBin, int which) {
+        return mWifiSignalStrengthsTimer[strengthBin].getCountLocked(which);
+    }
+
+    @Override
+    public ControllerActivityCounter getBluetoothControllerActivity() {
+        return mBluetoothActivity;
+    }
+
+    @Override
+    public ControllerActivityCounter getWifiControllerActivity() {
+        return mWifiActivity;
+    }
+
+    @Override
+    public ControllerActivityCounter getModemControllerActivity() {
+        return mModemActivity;
+    }
+
+    @Override
+    public boolean hasBluetoothActivityReporting() {
+        return mHasBluetoothReporting;
+    }
+
+    @Override
+    public boolean hasWifiActivityReporting() {
+        return mHasWifiReporting;
+    }
+
+    @Override
+    public boolean hasModemActivityReporting() {
+        return mHasModemReporting;
+    }
+
+    @Override
+    public long getFlashlightOnTime(long elapsedRealtimeUs, int which) {
+        return mFlashlightOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    }
+
+    @Override
+    public long getFlashlightOnCount(int which) {
+        return mFlashlightOnTimer.getCountLocked(which);
+    }
+
+    @Override
+    public long getCameraOnTime(long elapsedRealtimeUs, int which) {
+        return mCameraOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    }
+
+    @Override
+    public long getBluetoothScanTime(long elapsedRealtimeUs, int which) {
+        return mBluetoothScanTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    }
+
+    @Override
+    public long getNetworkActivityBytes(int type, int which) {
+        if (type >= 0 && type < mNetworkByteActivityCounters.length) {
+            return mNetworkByteActivityCounters[type].getCountLocked(which);
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public long getNetworkActivityPackets(int type, int which) {
+        if (type >= 0 && type < mNetworkPacketActivityCounters.length) {
+            return mNetworkPacketActivityCounters[type].getCountLocked(which);
+        } else {
+            return 0;
+        }
+    }
+
+    @Override public long getStartClockTime() {
+        final long currentTime = System.currentTimeMillis();
+        if (ensureStartClockTime(currentTime)) {
+            recordCurrentTimeChangeLocked(currentTime, mClocks.elapsedRealtime(),
+                    mClocks.uptimeMillis());
+        }
+        return mStartClockTime;
+    }
+
+    @Override public String getStartPlatformVersion() {
+        return mStartPlatformVersion;
+    }
+
+    @Override public String getEndPlatformVersion() {
+        return mEndPlatformVersion;
+    }
+
+    @Override public int getParcelVersion() {
+        return VERSION;
+    }
+
+    @Override public boolean getIsOnBattery() {
+        return mOnBattery;
+    }
+
+    @Override public SparseArray<? extends BatteryStats.Uid> getUidStats() {
+        return mUidStats;
+    }
+
+    private static void detachTimerIfNotNull(BatteryStatsImpl.Timer timer) {
+        if (timer != null) {
+            timer.detach();
+        }
+    }
+
+    private static boolean resetTimerIfNotNull(BatteryStatsImpl.Timer timer,
+            boolean detachIfReset) {
+        if (timer != null) {
+            return timer.reset(detachIfReset);
+        }
+        return true;
+    }
+
+    private static boolean resetTimerIfNotNull(DualTimer timer, boolean detachIfReset) {
+        if (timer != null) {
+            return timer.reset(detachIfReset);
+        }
+        return true;
+    }
+
+    private static void detachLongCounterIfNotNull(LongSamplingCounter counter) {
+        if (counter != null) {
+            counter.detach();
+        }
+    }
+
+    private static void resetLongCounterIfNotNull(LongSamplingCounter counter,
+            boolean detachIfReset) {
+        if (counter != null) {
+            counter.reset(detachIfReset);
+        }
+    }
+
+    /**
+     * The statistics associated with a particular uid.
+     */
+    public static class Uid extends BatteryStats.Uid {
+        /**
+         * BatteryStatsImpl that we are associated with.
+         */
+        protected BatteryStatsImpl mBsi;
+
+        final int mUid;
+
+        /** TimeBase for when uid is in background and device is on battery. */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public final TimeBase mOnBatteryBackgroundTimeBase;
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public final TimeBase mOnBatteryScreenOffBackgroundTimeBase;
+
+        boolean mWifiRunning;
+        StopwatchTimer mWifiRunningTimer;
+
+        boolean mFullWifiLockOut;
+        StopwatchTimer mFullWifiLockTimer;
+
+        boolean mWifiScanStarted;
+        DualTimer mWifiScanTimer;
+
+        static final int NO_BATCHED_SCAN_STARTED = -1;
+        int mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED;
+        StopwatchTimer[] mWifiBatchedScanTimer;
+
+        boolean mWifiMulticastEnabled;
+        StopwatchTimer mWifiMulticastTimer;
+
+        StopwatchTimer mAudioTurnedOnTimer;
+        StopwatchTimer mVideoTurnedOnTimer;
+        StopwatchTimer mFlashlightTurnedOnTimer;
+        StopwatchTimer mCameraTurnedOnTimer;
+        StopwatchTimer mForegroundActivityTimer;
+        StopwatchTimer mForegroundServiceTimer;
+        /** Total time spent by the uid holding any partial wakelocks. */
+        DualTimer mAggregatedPartialWakelockTimer;
+        DualTimer mBluetoothScanTimer;
+        DualTimer mBluetoothUnoptimizedScanTimer;
+        Counter mBluetoothScanResultCounter;
+        Counter mBluetoothScanResultBgCounter;
+
+        int mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+        StopwatchTimer[] mProcessStateTimer;
+
+        boolean mInForegroundService = false;
+
+        BatchTimer mVibratorOnTimer;
+
+        Counter[] mUserActivityCounters;
+
+        LongSamplingCounter[] mNetworkByteActivityCounters;
+        LongSamplingCounter[] mNetworkPacketActivityCounters;
+        LongSamplingCounter mMobileRadioActiveTime;
+        LongSamplingCounter mMobileRadioActiveCount;
+
+        /**
+         * How many times this UID woke up the Application Processor due to a Mobile radio packet.
+         */
+        private LongSamplingCounter mMobileRadioApWakeupCount;
+
+        /**
+         * How many times this UID woke up the Application Processor due to a Wifi packet.
+         */
+        private LongSamplingCounter mWifiRadioApWakeupCount;
+
+        /**
+         * The amount of time this uid has kept the WiFi controller in idle, tx, and rx mode.
+         * Can be null if the UID has had no such activity.
+         */
+        private ControllerActivityCounterImpl mWifiControllerActivity;
+
+        /**
+         * The amount of time this uid has kept the Bluetooth controller in idle, tx, and rx mode.
+         * Can be null if the UID has had no such activity.
+         */
+        private ControllerActivityCounterImpl mBluetoothControllerActivity;
+
+        /**
+         * The amount of time this uid has kept the Modem controller in idle, tx, and rx mode.
+         * Can be null if the UID has had no such activity.
+         */
+        private ControllerActivityCounterImpl mModemControllerActivity;
+
+        /**
+         * The CPU times we had at the last history details update.
+         */
+        long mLastStepUserTime;
+        long mLastStepSystemTime;
+        long mCurStepUserTime;
+        long mCurStepSystemTime;
+
+        LongSamplingCounter mUserCpuTime;
+        LongSamplingCounter mSystemCpuTime;
+        LongSamplingCounter[][] mCpuClusterSpeedTimesUs;
+
+        LongSamplingCounterArray mCpuFreqTimeMs;
+        LongSamplingCounterArray mScreenOffCpuFreqTimeMs;
+
+        /**
+         * The statistics we have collected for this uid's wake locks.
+         */
+        final OverflowArrayMap<Wakelock> mWakelockStats;
+
+        /**
+         * The statistics we have collected for this uid's syncs.
+         */
+        final OverflowArrayMap<DualTimer> mSyncStats;
+
+        /**
+         * The statistics we have collected for this uid's jobs.
+         */
+        final OverflowArrayMap<DualTimer> mJobStats;
+
+        /**
+         * Count of the jobs that have completed and the reasons why they completed.
+         */
+        final ArrayMap<String, SparseIntArray> mJobCompletions = new ArrayMap<>();
+
+        /**
+         * The statistics we have collected for this uid's sensor activations.
+         */
+        final SparseArray<Sensor> mSensorStats = new SparseArray<>();
+
+        /**
+         * The statistics we have collected for this uid's processes.
+         */
+        final ArrayMap<String, Proc> mProcessStats = new ArrayMap<>();
+
+        /**
+         * The statistics we have collected for this uid's processes.
+         */
+        final ArrayMap<String, Pkg> mPackageStats = new ArrayMap<>();
+
+        /**
+         * The transient wake stats we have collected for this uid's pids.
+         */
+        final SparseArray<Pid> mPids = new SparseArray<>();
+
+        public Uid(BatteryStatsImpl bsi, int uid) {
+            mBsi = bsi;
+            mUid = uid;
+
+            mOnBatteryBackgroundTimeBase = new TimeBase();
+            mOnBatteryBackgroundTimeBase.init(mBsi.mClocks.uptimeMillis() * 1000,
+                    mBsi.mClocks.elapsedRealtime() * 1000);
+
+            mOnBatteryScreenOffBackgroundTimeBase = new TimeBase();
+            mOnBatteryScreenOffBackgroundTimeBase.init(mBsi.mClocks.uptimeMillis() * 1000,
+                    mBsi.mClocks.elapsedRealtime() * 1000);
+
+            mUserCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
+            mSystemCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
+
+            mWakelockStats = mBsi.new OverflowArrayMap<Wakelock>(uid) {
+                @Override public Wakelock instantiateObject() {
+                    return new Wakelock(mBsi, Uid.this);
+                }
+            };
+            mSyncStats = mBsi.new OverflowArrayMap<DualTimer>(uid) {
+                @Override public DualTimer instantiateObject() {
+                    return new DualTimer(mBsi.mClocks, Uid.this, SYNC, null,
+                            mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase);
+                }
+            };
+            mJobStats = mBsi.new OverflowArrayMap<DualTimer>(uid) {
+                @Override public DualTimer instantiateObject() {
+                    return new DualTimer(mBsi.mClocks, Uid.this, JOB, null,
+                            mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase);
+                }
+            };
+
+            mWifiRunningTimer = new StopwatchTimer(mBsi.mClocks, this, WIFI_RUNNING,
+                    mBsi.mWifiRunningTimers, mBsi.mOnBatteryTimeBase);
+            mFullWifiLockTimer = new StopwatchTimer(mBsi.mClocks, this, FULL_WIFI_LOCK,
+                    mBsi.mFullWifiLockTimers, mBsi.mOnBatteryTimeBase);
+            mWifiScanTimer = new DualTimer(mBsi.mClocks, this, WIFI_SCAN,
+                    mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase);
+            mWifiBatchedScanTimer = new StopwatchTimer[NUM_WIFI_BATCHED_SCAN_BINS];
+            mWifiMulticastTimer = new StopwatchTimer(mBsi.mClocks, this, WIFI_MULTICAST_ENABLED,
+                    mBsi.mWifiMulticastTimers, mBsi.mOnBatteryTimeBase);
+            mProcessStateTimer = new StopwatchTimer[NUM_PROCESS_STATE];
+        }
+
+        @Override
+        public long[] getCpuFreqTimes(int which) {
+            if (mCpuFreqTimeMs == null) {
+                return null;
+            }
+            final long[] cpuFreqTimes = mCpuFreqTimeMs.getCountsLocked(which);
+            if (cpuFreqTimes == null) {
+                return null;
+            }
+            // Return cpuFreqTimes only if atleast one of the elements in non-zero.
+            for (int i = 0; i < cpuFreqTimes.length; ++i) {
+                if (cpuFreqTimes[i] != 0) {
+                    return cpuFreqTimes;
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public long[] getScreenOffCpuFreqTimes(int which) {
+            if (mScreenOffCpuFreqTimeMs == null) {
+                return null;
+            }
+            final long[] cpuFreqTimes = mScreenOffCpuFreqTimeMs.getCountsLocked(which);
+            if (cpuFreqTimes == null) {
+                return null;
+            }
+            // Return cpuFreqTimes only if atleast one of the elements in non-zero.
+            for (int i = 0; i < cpuFreqTimes.length; ++i) {
+                if (cpuFreqTimes[i] != 0) {
+                    return cpuFreqTimes;
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public Timer getAggregatedPartialWakelockTimer() {
+            return mAggregatedPartialWakelockTimer;
+        }
+
+        @Override
+        public ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> getWakelockStats() {
+            return mWakelockStats.getMap();
+        }
+
+        @Override
+        public ArrayMap<String, ? extends BatteryStats.Timer> getSyncStats() {
+            return mSyncStats.getMap();
+        }
+
+        @Override
+        public ArrayMap<String, ? extends BatteryStats.Timer> getJobStats() {
+            return mJobStats.getMap();
+        }
+
+        @Override
+        public ArrayMap<String, SparseIntArray> getJobCompletionStats() {
+            return mJobCompletions;
+        }
+
+        @Override
+        public SparseArray<? extends BatteryStats.Uid.Sensor> getSensorStats() {
+            return mSensorStats;
+        }
+
+        @Override
+        public ArrayMap<String, ? extends BatteryStats.Uid.Proc> getProcessStats() {
+            return mProcessStats;
+        }
+
+        @Override
+        public ArrayMap<String, ? extends BatteryStats.Uid.Pkg> getPackageStats() {
+            return mPackageStats;
+        }
+
+        @Override
+        public int getUid() {
+            return mUid;
+        }
+
+        @Override
+        public void noteWifiRunningLocked(long elapsedRealtimeMs) {
+            if (!mWifiRunning) {
+                mWifiRunning = true;
+                if (mWifiRunningTimer == null) {
+                    mWifiRunningTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, WIFI_RUNNING,
+                            mBsi.mWifiRunningTimers, mBsi.mOnBatteryTimeBase);
+                }
+                mWifiRunningTimer.startRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        @Override
+        public void noteWifiStoppedLocked(long elapsedRealtimeMs) {
+            if (mWifiRunning) {
+                mWifiRunning = false;
+                mWifiRunningTimer.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        @Override
+        public void noteFullWifiLockAcquiredLocked(long elapsedRealtimeMs) {
+            if (!mFullWifiLockOut) {
+                mFullWifiLockOut = true;
+                if (mFullWifiLockTimer == null) {
+                    mFullWifiLockTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, FULL_WIFI_LOCK,
+                            mBsi.mFullWifiLockTimers, mBsi.mOnBatteryTimeBase);
+                }
+                mFullWifiLockTimer.startRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        @Override
+        public void noteFullWifiLockReleasedLocked(long elapsedRealtimeMs) {
+            if (mFullWifiLockOut) {
+                mFullWifiLockOut = false;
+                mFullWifiLockTimer.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        @Override
+        public void noteWifiScanStartedLocked(long elapsedRealtimeMs) {
+            if (!mWifiScanStarted) {
+                mWifiScanStarted = true;
+                if (mWifiScanTimer == null) {
+                    mWifiScanTimer = new DualTimer(mBsi.mClocks, Uid.this, WIFI_SCAN,
+                            mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase,
+                            mOnBatteryBackgroundTimeBase);
+                }
+                mWifiScanTimer.startRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        @Override
+        public void noteWifiScanStoppedLocked(long elapsedRealtimeMs) {
+            if (mWifiScanStarted) {
+                mWifiScanStarted = false;
+                mWifiScanTimer.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        @Override
+        public void noteWifiBatchedScanStartedLocked(int csph, long elapsedRealtimeMs) {
+            int bin = 0;
+            while (csph > 8 && bin < NUM_WIFI_BATCHED_SCAN_BINS-1) {
+                csph = csph >> 3;
+                bin++;
+            }
+
+            if (mWifiBatchedScanBinStarted == bin) return;
+
+            if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) {
+                mWifiBatchedScanTimer[mWifiBatchedScanBinStarted].
+                        stopRunningLocked(elapsedRealtimeMs);
+            }
+            mWifiBatchedScanBinStarted = bin;
+            if (mWifiBatchedScanTimer[bin] == null) {
+                makeWifiBatchedScanBin(bin, null);
+            }
+            mWifiBatchedScanTimer[bin].startRunningLocked(elapsedRealtimeMs);
+        }
+
+        @Override
+        public void noteWifiBatchedScanStoppedLocked(long elapsedRealtimeMs) {
+            if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) {
+                mWifiBatchedScanTimer[mWifiBatchedScanBinStarted].
+                        stopRunningLocked(elapsedRealtimeMs);
+                mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED;
+            }
+        }
+
+        @Override
+        public void noteWifiMulticastEnabledLocked(long elapsedRealtimeMs) {
+            if (!mWifiMulticastEnabled) {
+                mWifiMulticastEnabled = true;
+                if (mWifiMulticastTimer == null) {
+                    mWifiMulticastTimer = new StopwatchTimer(mBsi.mClocks, Uid.this,
+                            WIFI_MULTICAST_ENABLED, mBsi.mWifiMulticastTimers, mBsi.mOnBatteryTimeBase);
+                }
+                mWifiMulticastTimer.startRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        @Override
+        public void noteWifiMulticastDisabledLocked(long elapsedRealtimeMs) {
+            if (mWifiMulticastEnabled) {
+                mWifiMulticastEnabled = false;
+                mWifiMulticastTimer.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        @Override
+        public ControllerActivityCounter getWifiControllerActivity() {
+            return mWifiControllerActivity;
+        }
+
+        @Override
+        public ControllerActivityCounter getBluetoothControllerActivity() {
+            return mBluetoothControllerActivity;
+        }
+
+        @Override
+        public ControllerActivityCounter getModemControllerActivity() {
+            return mModemControllerActivity;
+        }
+
+        public ControllerActivityCounterImpl getOrCreateWifiControllerActivityLocked() {
+            if (mWifiControllerActivity == null) {
+                mWifiControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase,
+                        NUM_BT_TX_LEVELS);
+            }
+            return mWifiControllerActivity;
+        }
+
+        public ControllerActivityCounterImpl getOrCreateBluetoothControllerActivityLocked() {
+            if (mBluetoothControllerActivity == null) {
+                mBluetoothControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase,
+                        NUM_BT_TX_LEVELS);
+            }
+            return mBluetoothControllerActivity;
+        }
+
+        public ControllerActivityCounterImpl getOrCreateModemControllerActivityLocked() {
+            if (mModemControllerActivity == null) {
+                mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase,
+                        ModemActivityInfo.TX_POWER_LEVELS);
+            }
+            return mModemControllerActivity;
+        }
+
+        public StopwatchTimer createAudioTurnedOnTimerLocked() {
+            if (mAudioTurnedOnTimer == null) {
+                mAudioTurnedOnTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, AUDIO_TURNED_ON,
+                        mBsi.mAudioTurnedOnTimers, mBsi.mOnBatteryTimeBase);
+            }
+            return mAudioTurnedOnTimer;
+        }
+
+        public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) {
+            createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+        }
+
+        public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) {
+            if (mAudioTurnedOnTimer != null) {
+                mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public void noteResetAudioLocked(long elapsedRealtimeMs) {
+            if (mAudioTurnedOnTimer != null) {
+                mAudioTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public StopwatchTimer createVideoTurnedOnTimerLocked() {
+            if (mVideoTurnedOnTimer == null) {
+                mVideoTurnedOnTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, VIDEO_TURNED_ON,
+                        mBsi.mVideoTurnedOnTimers, mBsi.mOnBatteryTimeBase);
+            }
+            return mVideoTurnedOnTimer;
+        }
+
+        public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) {
+            createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+        }
+
+        public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) {
+            if (mVideoTurnedOnTimer != null) {
+                mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public void noteResetVideoLocked(long elapsedRealtimeMs) {
+            if (mVideoTurnedOnTimer != null) {
+                mVideoTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public StopwatchTimer createFlashlightTurnedOnTimerLocked() {
+            if (mFlashlightTurnedOnTimer == null) {
+                mFlashlightTurnedOnTimer = new StopwatchTimer(mBsi.mClocks, Uid.this,
+                        FLASHLIGHT_TURNED_ON, mBsi.mFlashlightTurnedOnTimers, mBsi.mOnBatteryTimeBase);
+            }
+            return mFlashlightTurnedOnTimer;
+        }
+
+        public void noteFlashlightTurnedOnLocked(long elapsedRealtimeMs) {
+            createFlashlightTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+        }
+
+        public void noteFlashlightTurnedOffLocked(long elapsedRealtimeMs) {
+            if (mFlashlightTurnedOnTimer != null) {
+                mFlashlightTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public void noteResetFlashlightLocked(long elapsedRealtimeMs) {
+            if (mFlashlightTurnedOnTimer != null) {
+                mFlashlightTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public StopwatchTimer createCameraTurnedOnTimerLocked() {
+            if (mCameraTurnedOnTimer == null) {
+                mCameraTurnedOnTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, CAMERA_TURNED_ON,
+                        mBsi.mCameraTurnedOnTimers, mBsi.mOnBatteryTimeBase);
+            }
+            return mCameraTurnedOnTimer;
+        }
+
+        public void noteCameraTurnedOnLocked(long elapsedRealtimeMs) {
+            createCameraTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+        }
+
+        public void noteCameraTurnedOffLocked(long elapsedRealtimeMs) {
+            if (mCameraTurnedOnTimer != null) {
+                mCameraTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public void noteResetCameraLocked(long elapsedRealtimeMs) {
+            if (mCameraTurnedOnTimer != null) {
+                mCameraTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public StopwatchTimer createForegroundActivityTimerLocked() {
+            if (mForegroundActivityTimer == null) {
+                mForegroundActivityTimer = new StopwatchTimer(mBsi.mClocks, Uid.this,
+                        FOREGROUND_ACTIVITY, null, mBsi.mOnBatteryTimeBase);
+            }
+            return mForegroundActivityTimer;
+        }
+
+        public StopwatchTimer createForegroundServiceTimerLocked() {
+            if (mForegroundServiceTimer == null) {
+                mForegroundServiceTimer = new StopwatchTimer(mBsi.mClocks, Uid.this,
+                        FOREGROUND_SERVICE, null, mBsi.mOnBatteryTimeBase);
+            }
+            return mForegroundServiceTimer;
+        }
+
+        public DualTimer createAggregatedPartialWakelockTimerLocked() {
+            if (mAggregatedPartialWakelockTimer == null) {
+                mAggregatedPartialWakelockTimer = new DualTimer(mBsi.mClocks, this,
+                        AGGREGATED_WAKE_TYPE_PARTIAL, null,
+                        mBsi.mOnBatteryScreenOffTimeBase, mOnBatteryScreenOffBackgroundTimeBase);
+            }
+            return mAggregatedPartialWakelockTimer;
+        }
+
+        public DualTimer createBluetoothScanTimerLocked() {
+            if (mBluetoothScanTimer == null) {
+                mBluetoothScanTimer = new DualTimer(mBsi.mClocks, Uid.this, BLUETOOTH_SCAN_ON,
+                        mBsi.mBluetoothScanOnTimers, mBsi.mOnBatteryTimeBase,
+                        mOnBatteryBackgroundTimeBase);
+            }
+            return mBluetoothScanTimer;
+        }
+
+        public DualTimer createBluetoothUnoptimizedScanTimerLocked() {
+            if (mBluetoothUnoptimizedScanTimer == null) {
+                mBluetoothUnoptimizedScanTimer = new DualTimer(mBsi.mClocks, Uid.this,
+                        BLUETOOTH_UNOPTIMIZED_SCAN_ON, null,
+                        mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase);
+            }
+            return mBluetoothUnoptimizedScanTimer;
+        }
+
+        public void noteBluetoothScanStartedLocked(long elapsedRealtimeMs, boolean isUnoptimized) {
+            createBluetoothScanTimerLocked().startRunningLocked(elapsedRealtimeMs);
+            if (isUnoptimized) {
+                createBluetoothUnoptimizedScanTimerLocked().startRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public void noteBluetoothScanStoppedLocked(long elapsedRealtimeMs, boolean isUnoptimized) {
+            if (mBluetoothScanTimer != null) {
+                mBluetoothScanTimer.stopRunningLocked(elapsedRealtimeMs);
+            }
+            if (isUnoptimized && mBluetoothUnoptimizedScanTimer != null) {
+                mBluetoothUnoptimizedScanTimer.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public void noteResetBluetoothScanLocked(long elapsedRealtimeMs) {
+            if (mBluetoothScanTimer != null) {
+                mBluetoothScanTimer.stopAllRunningLocked(elapsedRealtimeMs);
+            }
+            if (mBluetoothUnoptimizedScanTimer != null) {
+                mBluetoothUnoptimizedScanTimer.stopAllRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public Counter createBluetoothScanResultCounterLocked() {
+            if (mBluetoothScanResultCounter == null) {
+                mBluetoothScanResultCounter = new Counter(mBsi.mOnBatteryTimeBase);
+            }
+            return mBluetoothScanResultCounter;
+        }
+
+        public Counter createBluetoothScanResultBgCounterLocked() {
+            if (mBluetoothScanResultBgCounter == null) {
+                mBluetoothScanResultBgCounter = new Counter(mOnBatteryBackgroundTimeBase);
+            }
+            return mBluetoothScanResultBgCounter;
+        }
+
+        public void noteBluetoothScanResultsLocked(int numNewResults) {
+            createBluetoothScanResultCounterLocked().addAtomic(numNewResults);
+            // Uses background timebase, so the count will only be incremented if uid in background.
+            createBluetoothScanResultBgCounterLocked().addAtomic(numNewResults);
+        }
+
+        @Override
+        public void noteActivityResumedLocked(long elapsedRealtimeMs) {
+            // We always start, since we want multiple foreground PIDs to nest
+            createForegroundActivityTimerLocked().startRunningLocked(elapsedRealtimeMs);
+        }
+
+        @Override
+        public void noteActivityPausedLocked(long elapsedRealtimeMs) {
+            if (mForegroundActivityTimer != null) {
+                mForegroundActivityTimer.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public void noteForegroundServiceResumedLocked(long elapsedRealtimeMs) {
+            createForegroundServiceTimerLocked().startRunningLocked(elapsedRealtimeMs);
+        }
+
+        public void noteForegroundServicePausedLocked(long elapsedRealtimeMs) {
+            if (mForegroundServiceTimer != null) {
+                mForegroundServiceTimer.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public BatchTimer createVibratorOnTimerLocked() {
+            if (mVibratorOnTimer == null) {
+                mVibratorOnTimer = new BatchTimer(mBsi.mClocks, Uid.this, VIBRATOR_ON,
+                        mBsi.mOnBatteryTimeBase);
+            }
+            return mVibratorOnTimer;
+        }
+
+        public void noteVibratorOnLocked(long durationMillis) {
+            createVibratorOnTimerLocked().addDuration(mBsi, durationMillis);
+        }
+
+        public void noteVibratorOffLocked() {
+            if (mVibratorOnTimer != null) {
+                mVibratorOnTimer.abortLastDuration(mBsi);
+            }
+        }
+
+        @Override
+        public long getWifiRunningTime(long elapsedRealtimeUs, int which) {
+            if (mWifiRunningTimer == null) {
+                return 0;
+            }
+            return mWifiRunningTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+        }
+
+        @Override
+        public long getFullWifiLockTime(long elapsedRealtimeUs, int which) {
+            if (mFullWifiLockTimer == null) {
+                return 0;
+            }
+            return mFullWifiLockTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+        }
+
+        @Override
+        public long getWifiScanTime(long elapsedRealtimeUs, int which) {
+            if (mWifiScanTimer == null) {
+                return 0;
+            }
+            return mWifiScanTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+        }
+
+        @Override
+        public int getWifiScanCount(int which) {
+            if (mWifiScanTimer == null) {
+                return 0;
+            }
+            return mWifiScanTimer.getCountLocked(which);
+        }
+
+        @Override
+        public int getWifiScanBackgroundCount(int which) {
+            if (mWifiScanTimer == null || mWifiScanTimer.getSubTimer() == null) {
+                return 0;
+            }
+            return mWifiScanTimer.getSubTimer().getCountLocked(which);
+        }
+
+        @Override
+        public long getWifiScanActualTime(final long elapsedRealtimeUs) {
+            if (mWifiScanTimer == null) {
+                return 0;
+            }
+            final long elapsedRealtimeMs = (elapsedRealtimeUs + 500) / 1000;
+            return mWifiScanTimer.getTotalDurationMsLocked(elapsedRealtimeMs) * 1000;
+        }
+
+        @Override
+        public long getWifiScanBackgroundTime(final long elapsedRealtimeUs) {
+            if (mWifiScanTimer == null || mWifiScanTimer.getSubTimer() == null) {
+                return 0;
+            }
+            final long elapsedRealtimeMs = (elapsedRealtimeUs + 500) / 1000;
+            return mWifiScanTimer.getSubTimer().getTotalDurationMsLocked(elapsedRealtimeMs) * 1000;
+        }
+
+        @Override
+        public long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which) {
+            if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0;
+            if (mWifiBatchedScanTimer[csphBin] == null) {
+                return 0;
+            }
+            return mWifiBatchedScanTimer[csphBin].getTotalTimeLocked(elapsedRealtimeUs, which);
+        }
+
+        @Override
+        public int getWifiBatchedScanCount(int csphBin, int which) {
+            if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0;
+            if (mWifiBatchedScanTimer[csphBin] == null) {
+                return 0;
+            }
+            return mWifiBatchedScanTimer[csphBin].getCountLocked(which);
+        }
+
+        @Override
+        public long getWifiMulticastTime(long elapsedRealtimeUs, int which) {
+            if (mWifiMulticastTimer == null) {
+                return 0;
+            }
+            return mWifiMulticastTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+        }
+
+        @Override
+        public Timer getAudioTurnedOnTimer() {
+            return mAudioTurnedOnTimer;
+        }
+
+        @Override
+        public Timer getVideoTurnedOnTimer() {
+            return mVideoTurnedOnTimer;
+        }
+
+        @Override
+        public Timer getFlashlightTurnedOnTimer() {
+            return mFlashlightTurnedOnTimer;
+        }
+
+        @Override
+        public Timer getCameraTurnedOnTimer() {
+            return mCameraTurnedOnTimer;
+        }
+
+        @Override
+        public Timer getForegroundActivityTimer() {
+            return mForegroundActivityTimer;
+        }
+
+        @Override
+        public Timer getForegroundServiceTimer() {
+            return mForegroundServiceTimer;
+        }
+
+        @Override
+        public Timer getBluetoothScanTimer() {
+            return mBluetoothScanTimer;
+        }
+
+        @Override
+        public Timer getBluetoothScanBackgroundTimer() {
+            if (mBluetoothScanTimer == null) {
+                return null;
+            }
+            return mBluetoothScanTimer.getSubTimer();
+        }
+
+        @Override
+        public Timer getBluetoothUnoptimizedScanTimer() {
+            return mBluetoothUnoptimizedScanTimer;
+        }
+
+        @Override
+        public Timer getBluetoothUnoptimizedScanBackgroundTimer() {
+            if (mBluetoothUnoptimizedScanTimer == null) {
+                return null;
+            }
+            return mBluetoothUnoptimizedScanTimer.getSubTimer();
+        }
+
+        @Override
+        public Counter getBluetoothScanResultCounter() {
+            return mBluetoothScanResultCounter;
+        }
+
+        @Override
+        public Counter getBluetoothScanResultBgCounter() {
+            return mBluetoothScanResultBgCounter;
+        }
+
+        void makeProcessState(int i, Parcel in) {
+            if (i < 0 || i >= NUM_PROCESS_STATE) return;
+
+            if (in == null) {
+                mProcessStateTimer[i] = new StopwatchTimer(mBsi.mClocks, this, PROCESS_STATE, null,
+                        mBsi.mOnBatteryTimeBase);
+            } else {
+                mProcessStateTimer[i] = new StopwatchTimer(mBsi.mClocks, this, PROCESS_STATE, null,
+                        mBsi.mOnBatteryTimeBase, in);
+            }
+        }
+
+        @Override
+        public long getProcessStateTime(int state, long elapsedRealtimeUs, int which) {
+            if (state < 0 || state >= NUM_PROCESS_STATE) return 0;
+            if (mProcessStateTimer[state] == null) {
+                return 0;
+            }
+            return mProcessStateTimer[state].getTotalTimeLocked(elapsedRealtimeUs, which);
+        }
+
+        @Override
+        public Timer getProcessStateTimer(int state) {
+            if (state < 0 || state >= NUM_PROCESS_STATE) return null;
+            return mProcessStateTimer[state];
+        }
+
+        @Override
+        public Timer getVibratorOnTimer() {
+            return mVibratorOnTimer;
+        }
+
+        @Override
+        public void noteUserActivityLocked(int type) {
+            if (mUserActivityCounters == null) {
+                initUserActivityLocked();
+            }
+            if (type >= 0 && type < NUM_USER_ACTIVITY_TYPES) {
+                mUserActivityCounters[type].stepAtomic();
+            } else {
+                Slog.w(TAG, "Unknown user activity type " + type + " was specified.",
+                        new Throwable());
+            }
+        }
+
+        @Override
+        public boolean hasUserActivity() {
+            return mUserActivityCounters != null;
+        }
+
+        @Override
+        public int getUserActivityCount(int type, int which) {
+            if (mUserActivityCounters == null) {
+                return 0;
+            }
+            return mUserActivityCounters[type].getCountLocked(which);
+        }
+
+        void makeWifiBatchedScanBin(int i, Parcel in) {
+            if (i < 0 || i >= NUM_WIFI_BATCHED_SCAN_BINS) return;
+
+            ArrayList<StopwatchTimer> collected = mBsi.mWifiBatchedScanTimers.get(i);
+            if (collected == null) {
+                collected = new ArrayList<StopwatchTimer>();
+                mBsi.mWifiBatchedScanTimers.put(i, collected);
+            }
+            if (in == null) {
+                mWifiBatchedScanTimer[i] = new StopwatchTimer(mBsi.mClocks, this, WIFI_BATCHED_SCAN,
+                        collected, mBsi.mOnBatteryTimeBase);
+            } else {
+                mWifiBatchedScanTimer[i] = new StopwatchTimer(mBsi.mClocks, this, WIFI_BATCHED_SCAN,
+                        collected, mBsi.mOnBatteryTimeBase, in);
+            }
+        }
+
+
+        void initUserActivityLocked() {
+            mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES];
+            for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
+                mUserActivityCounters[i] = new Counter(mBsi.mOnBatteryTimeBase);
+            }
+        }
+
+        void noteNetworkActivityLocked(int type, long deltaBytes, long deltaPackets) {
+            if (mNetworkByteActivityCounters == null) {
+                initNetworkActivityLocked();
+            }
+            if (type >= 0 && type < NUM_NETWORK_ACTIVITY_TYPES) {
+                mNetworkByteActivityCounters[type].addCountLocked(deltaBytes);
+                mNetworkPacketActivityCounters[type].addCountLocked(deltaPackets);
+            } else {
+                Slog.w(TAG, "Unknown network activity type " + type + " was specified.",
+                        new Throwable());
+            }
+        }
+
+        void noteMobileRadioActiveTimeLocked(long batteryUptime) {
+            if (mNetworkByteActivityCounters == null) {
+                initNetworkActivityLocked();
+            }
+            mMobileRadioActiveTime.addCountLocked(batteryUptime);
+            mMobileRadioActiveCount.addCountLocked(1);
+        }
+
+        @Override
+        public boolean hasNetworkActivity() {
+            return mNetworkByteActivityCounters != null;
+        }
+
+        @Override
+        public long getNetworkActivityBytes(int type, int which) {
+            if (mNetworkByteActivityCounters != null && type >= 0
+                    && type < mNetworkByteActivityCounters.length) {
+                return mNetworkByteActivityCounters[type].getCountLocked(which);
+            } else {
+                return 0;
+            }
+        }
+
+        @Override
+        public long getNetworkActivityPackets(int type, int which) {
+            if (mNetworkPacketActivityCounters != null && type >= 0
+                    && type < mNetworkPacketActivityCounters.length) {
+                return mNetworkPacketActivityCounters[type].getCountLocked(which);
+            } else {
+                return 0;
+            }
+        }
+
+        @Override
+        public long getMobileRadioActiveTime(int which) {
+            return mMobileRadioActiveTime != null
+                    ? mMobileRadioActiveTime.getCountLocked(which) : 0;
+        }
+
+        @Override
+        public int getMobileRadioActiveCount(int which) {
+            return mMobileRadioActiveCount != null
+                    ? (int)mMobileRadioActiveCount.getCountLocked(which) : 0;
+        }
+
+        @Override
+        public long getUserCpuTimeUs(int which) {
+            return mUserCpuTime.getCountLocked(which);
+        }
+
+        @Override
+        public long getSystemCpuTimeUs(int which) {
+            return mSystemCpuTime.getCountLocked(which);
+        }
+
+        @Override
+        public long getTimeAtCpuSpeed(int cluster, int step, int which) {
+            if (mCpuClusterSpeedTimesUs != null) {
+                if (cluster >= 0 && cluster < mCpuClusterSpeedTimesUs.length) {
+                    final LongSamplingCounter[] cpuSpeedTimesUs = mCpuClusterSpeedTimesUs[cluster];
+                    if (cpuSpeedTimesUs != null) {
+                        if (step >= 0 && step < cpuSpeedTimesUs.length) {
+                            final LongSamplingCounter c = cpuSpeedTimesUs[step];
+                            if (c != null) {
+                                return c.getCountLocked(which);
+                            }
+                        }
+                    }
+                }
+            }
+            return 0;
+        }
+
+        public void noteMobileRadioApWakeupLocked() {
+            if (mMobileRadioApWakeupCount == null) {
+                mMobileRadioApWakeupCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
+            }
+            mMobileRadioApWakeupCount.addCountLocked(1);
+        }
+
+        @Override
+        public long getMobileRadioApWakeupCount(int which) {
+            if (mMobileRadioApWakeupCount != null) {
+                return mMobileRadioApWakeupCount.getCountLocked(which);
+            }
+            return 0;
+        }
+
+        public void noteWifiRadioApWakeupLocked() {
+            if (mWifiRadioApWakeupCount == null) {
+                mWifiRadioApWakeupCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
+            }
+            mWifiRadioApWakeupCount.addCountLocked(1);
+        }
+
+        @Override
+        public long getWifiRadioApWakeupCount(int which) {
+            if (mWifiRadioApWakeupCount != null) {
+                return mWifiRadioApWakeupCount.getCountLocked(which);
+            }
+            return 0;
+        }
+
+        void initNetworkActivityLocked() {
+            mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+            mNetworkPacketActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+            for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
+                mNetworkByteActivityCounters[i] = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
+                mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
+            }
+            mMobileRadioActiveTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
+            mMobileRadioActiveCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
+        }
+
+        /**
+         * Clear all stats for this uid.  Returns true if the uid is completely
+         * inactive so can be dropped.
+         */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public boolean reset(long uptime, long realtime) {
+            boolean active = false;
+
+            mOnBatteryBackgroundTimeBase.init(uptime, realtime);
+            mOnBatteryScreenOffBackgroundTimeBase.init(uptime, realtime);
+
+            if (mWifiRunningTimer != null) {
+                active |= !mWifiRunningTimer.reset(false);
+                active |= mWifiRunning;
+            }
+            if (mFullWifiLockTimer != null) {
+                active |= !mFullWifiLockTimer.reset(false);
+                active |= mFullWifiLockOut;
+            }
+            if (mWifiScanTimer != null) {
+                active |= !mWifiScanTimer.reset(false);
+                active |= mWifiScanStarted;
+            }
+            if (mWifiBatchedScanTimer != null) {
+                for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) {
+                    if (mWifiBatchedScanTimer[i] != null) {
+                        active |= !mWifiBatchedScanTimer[i].reset(false);
+                    }
+                }
+                active |= (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED);
+            }
+            if (mWifiMulticastTimer != null) {
+                active |= !mWifiMulticastTimer.reset(false);
+                active |= mWifiMulticastEnabled;
+            }
+
+            active |= !resetTimerIfNotNull(mAudioTurnedOnTimer, false);
+            active |= !resetTimerIfNotNull(mVideoTurnedOnTimer, false);
+            active |= !resetTimerIfNotNull(mFlashlightTurnedOnTimer, false);
+            active |= !resetTimerIfNotNull(mCameraTurnedOnTimer, false);
+            active |= !resetTimerIfNotNull(mForegroundActivityTimer, false);
+            active |= !resetTimerIfNotNull(mForegroundServiceTimer, false);
+            active |= !resetTimerIfNotNull(mAggregatedPartialWakelockTimer, false);
+            active |= !resetTimerIfNotNull(mBluetoothScanTimer, false);
+            active |= !resetTimerIfNotNull(mBluetoothUnoptimizedScanTimer, false);
+            if (mBluetoothScanResultCounter != null) {
+                mBluetoothScanResultCounter.reset(false);
+            }
+            if (mBluetoothScanResultBgCounter != null) {
+                mBluetoothScanResultBgCounter.reset(false);
+            }
+
+            if (mProcessStateTimer != null) {
+                for (int i = 0; i < NUM_PROCESS_STATE; i++) {
+                    if (mProcessStateTimer[i] != null) {
+                        active |= !mProcessStateTimer[i].reset(false);
+                    }
+                }
+                active |= (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT);
+            }
+            if (mVibratorOnTimer != null) {
+                if (mVibratorOnTimer.reset(false)) {
+                    mVibratorOnTimer.detach();
+                    mVibratorOnTimer = null;
+                } else {
+                    active = true;
+                }
+            }
+
+            if (mUserActivityCounters != null) {
+                for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
+                    mUserActivityCounters[i].reset(false);
+                }
+            }
+
+            if (mNetworkByteActivityCounters != null) {
+                for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
+                    mNetworkByteActivityCounters[i].reset(false);
+                    mNetworkPacketActivityCounters[i].reset(false);
+                }
+                mMobileRadioActiveTime.reset(false);
+                mMobileRadioActiveCount.reset(false);
+            }
+
+            if (mWifiControllerActivity != null) {
+                mWifiControllerActivity.reset(false);
+            }
+
+            if (mBluetoothControllerActivity != null) {
+                mBluetoothControllerActivity.reset(false);
+            }
+
+            if (mModemControllerActivity != null) {
+                mModemControllerActivity.reset(false);
+            }
+
+            mUserCpuTime.reset(false);
+            mSystemCpuTime.reset(false);
+
+            if (mCpuClusterSpeedTimesUs != null) {
+                for (LongSamplingCounter[] speeds : mCpuClusterSpeedTimesUs) {
+                    if (speeds != null) {
+                        for (LongSamplingCounter speed : speeds) {
+                            if (speed != null) {
+                                speed.reset(false);
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (mCpuFreqTimeMs != null) {
+                mCpuFreqTimeMs.reset(false);
+            }
+            if (mScreenOffCpuFreqTimeMs != null) {
+                mScreenOffCpuFreqTimeMs.reset(false);
+            }
+
+            resetLongCounterIfNotNull(mMobileRadioApWakeupCount, false);
+            resetLongCounterIfNotNull(mWifiRadioApWakeupCount, false);
+
+            final ArrayMap<String, Wakelock> wakeStats = mWakelockStats.getMap();
+            for (int iw=wakeStats.size()-1; iw>=0; iw--) {
+                Wakelock wl = wakeStats.valueAt(iw);
+                if (wl.reset()) {
+                    wakeStats.removeAt(iw);
+                } else {
+                    active = true;
+                }
+            }
+            mWakelockStats.cleanup();
+            final ArrayMap<String, DualTimer> syncStats = mSyncStats.getMap();
+            for (int is=syncStats.size()-1; is>=0; is--) {
+                DualTimer timer = syncStats.valueAt(is);
+                if (timer.reset(false)) {
+                    syncStats.removeAt(is);
+                    timer.detach();
+                } else {
+                    active = true;
+                }
+            }
+            mSyncStats.cleanup();
+            final ArrayMap<String, DualTimer> jobStats = mJobStats.getMap();
+            for (int ij=jobStats.size()-1; ij>=0; ij--) {
+                DualTimer timer = jobStats.valueAt(ij);
+                if (timer.reset(false)) {
+                    jobStats.removeAt(ij);
+                    timer.detach();
+                } else {
+                    active = true;
+                }
+            }
+            mJobStats.cleanup();
+            mJobCompletions.clear();
+            for (int ise=mSensorStats.size()-1; ise>=0; ise--) {
+                Sensor s = mSensorStats.valueAt(ise);
+                if (s.reset()) {
+                    mSensorStats.removeAt(ise);
+                } else {
+                    active = true;
+                }
+            }
+            for (int ip=mProcessStats.size()-1; ip>=0; ip--) {
+                Proc proc = mProcessStats.valueAt(ip);
+                proc.detach();
+            }
+            mProcessStats.clear();
+            if (mPids.size() > 0) {
+                for (int i=mPids.size()-1; i>=0; i--) {
+                    Pid pid = mPids.valueAt(i);
+                    if (pid.mWakeNesting > 0) {
+                        active = true;
+                    } else {
+                        mPids.removeAt(i);
+                    }
+                }
+            }
+            if (mPackageStats.size() > 0) {
+                Iterator<Map.Entry<String, Pkg>> it = mPackageStats.entrySet().iterator();
+                while (it.hasNext()) {
+                    Map.Entry<String, Pkg> pkgEntry = it.next();
+                    Pkg p = pkgEntry.getValue();
+                    p.detach();
+                    if (p.mServiceStats.size() > 0) {
+                        Iterator<Map.Entry<String, Pkg.Serv>> it2
+                                = p.mServiceStats.entrySet().iterator();
+                        while (it2.hasNext()) {
+                            Map.Entry<String, Pkg.Serv> servEntry = it2.next();
+                            servEntry.getValue().detach();
+                        }
+                    }
+                }
+                mPackageStats.clear();
+            }
+
+            mLastStepUserTime = mLastStepSystemTime = 0;
+            mCurStepUserTime = mCurStepSystemTime = 0;
+
+            if (!active) {
+                if (mWifiRunningTimer != null) {
+                    mWifiRunningTimer.detach();
+                }
+                if (mFullWifiLockTimer != null) {
+                    mFullWifiLockTimer.detach();
+                }
+                if (mWifiScanTimer != null) {
+                    mWifiScanTimer.detach();
+                }
+                for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) {
+                    if (mWifiBatchedScanTimer[i] != null) {
+                        mWifiBatchedScanTimer[i].detach();
+                    }
+                }
+                if (mWifiMulticastTimer != null) {
+                    mWifiMulticastTimer.detach();
+                }
+                if (mAudioTurnedOnTimer != null) {
+                    mAudioTurnedOnTimer.detach();
+                    mAudioTurnedOnTimer = null;
+                }
+                if (mVideoTurnedOnTimer != null) {
+                    mVideoTurnedOnTimer.detach();
+                    mVideoTurnedOnTimer = null;
+                }
+                if (mFlashlightTurnedOnTimer != null) {
+                    mFlashlightTurnedOnTimer.detach();
+                    mFlashlightTurnedOnTimer = null;
+                }
+                if (mCameraTurnedOnTimer != null) {
+                    mCameraTurnedOnTimer.detach();
+                    mCameraTurnedOnTimer = null;
+                }
+                if (mForegroundActivityTimer != null) {
+                    mForegroundActivityTimer.detach();
+                    mForegroundActivityTimer = null;
+                }
+                if (mForegroundServiceTimer != null) {
+                    mForegroundServiceTimer.detach();
+                    mForegroundServiceTimer = null;
+                }
+                if (mAggregatedPartialWakelockTimer != null) {
+                    mAggregatedPartialWakelockTimer.detach();
+                    mAggregatedPartialWakelockTimer = null;
+                }
+                if (mBluetoothScanTimer != null) {
+                    mBluetoothScanTimer.detach();
+                    mBluetoothScanTimer = null;
+                }
+                if (mBluetoothUnoptimizedScanTimer != null) {
+                    mBluetoothUnoptimizedScanTimer.detach();
+                    mBluetoothUnoptimizedScanTimer = null;
+                }
+                if (mBluetoothScanResultCounter != null) {
+                    mBluetoothScanResultCounter.detach();
+                    mBluetoothScanResultCounter = null;
+                }
+                if (mBluetoothScanResultBgCounter != null) {
+                    mBluetoothScanResultBgCounter.detach();
+                    mBluetoothScanResultBgCounter = null;
+                }
+                if (mUserActivityCounters != null) {
+                    for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
+                        mUserActivityCounters[i].detach();
+                    }
+                }
+                if (mNetworkByteActivityCounters != null) {
+                    for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
+                        mNetworkByteActivityCounters[i].detach();
+                        mNetworkPacketActivityCounters[i].detach();
+                    }
+                }
+
+                if (mWifiControllerActivity != null) {
+                    mWifiControllerActivity.detach();
+                }
+
+                if (mBluetoothControllerActivity != null) {
+                    mBluetoothControllerActivity.detach();
+                }
+
+                if (mModemControllerActivity != null) {
+                    mModemControllerActivity.detach();
+                }
+
+                mPids.clear();
+
+                mUserCpuTime.detach();
+                mSystemCpuTime.detach();
+
+                if (mCpuClusterSpeedTimesUs != null) {
+                    for (LongSamplingCounter[] cpuSpeeds : mCpuClusterSpeedTimesUs) {
+                        if (cpuSpeeds != null) {
+                            for (LongSamplingCounter c : cpuSpeeds) {
+                                if (c != null) {
+                                    c.detach();
+                                }
+                            }
+                        }
+                    }
+                }
+
+                if (mCpuFreqTimeMs != null) {
+                    mCpuFreqTimeMs.detach();
+                }
+                if (mScreenOffCpuFreqTimeMs != null) {
+                    mScreenOffCpuFreqTimeMs.detach();
+                }
+
+                detachLongCounterIfNotNull(mMobileRadioApWakeupCount);
+                detachLongCounterIfNotNull(mWifiRadioApWakeupCount);
+            }
+
+            return !active;
+        }
+
+        void writeJobCompletionsToParcelLocked(Parcel out) {
+            int NJC = mJobCompletions.size();
+            out.writeInt(NJC);
+            for (int ijc=0; ijc<NJC; ijc++) {
+                out.writeString(mJobCompletions.keyAt(ijc));
+                SparseIntArray types = mJobCompletions.valueAt(ijc);
+                int NT = types.size();
+                out.writeInt(NT);
+                for (int it=0; it<NT; it++) {
+                    out.writeInt(types.keyAt(it));
+                    out.writeInt(types.valueAt(it));
+                }
+            }
+        }
+
+        void writeToParcelLocked(Parcel out, long uptimeUs, long elapsedRealtimeUs) {
+            mOnBatteryBackgroundTimeBase.writeToParcel(out, uptimeUs, elapsedRealtimeUs);
+            mOnBatteryScreenOffBackgroundTimeBase.writeToParcel(out, uptimeUs, elapsedRealtimeUs);
+
+            final ArrayMap<String, Wakelock> wakeStats = mWakelockStats.getMap();
+            int NW = wakeStats.size();
+            out.writeInt(NW);
+            for (int iw=0; iw<NW; iw++) {
+                out.writeString(wakeStats.keyAt(iw));
+                Uid.Wakelock wakelock = wakeStats.valueAt(iw);
+                wakelock.writeToParcelLocked(out, elapsedRealtimeUs);
+            }
+
+            final ArrayMap<String, DualTimer> syncStats = mSyncStats.getMap();
+            int NS = syncStats.size();
+            out.writeInt(NS);
+            for (int is=0; is<NS; is++) {
+                out.writeString(syncStats.keyAt(is));
+                DualTimer timer = syncStats.valueAt(is);
+                Timer.writeTimerToParcel(out, timer, elapsedRealtimeUs);
+            }
+
+            final ArrayMap<String, DualTimer> jobStats = mJobStats.getMap();
+            int NJ = jobStats.size();
+            out.writeInt(NJ);
+            for (int ij=0; ij<NJ; ij++) {
+                out.writeString(jobStats.keyAt(ij));
+                DualTimer timer = jobStats.valueAt(ij);
+                Timer.writeTimerToParcel(out, timer, elapsedRealtimeUs);
+            }
+
+            writeJobCompletionsToParcelLocked(out);
+
+            int NSE = mSensorStats.size();
+            out.writeInt(NSE);
+            for (int ise=0; ise<NSE; ise++) {
+                out.writeInt(mSensorStats.keyAt(ise));
+                Uid.Sensor sensor = mSensorStats.valueAt(ise);
+                sensor.writeToParcelLocked(out, elapsedRealtimeUs);
+            }
+
+            int NP = mProcessStats.size();
+            out.writeInt(NP);
+            for (int ip=0; ip<NP; ip++) {
+                out.writeString(mProcessStats.keyAt(ip));
+                Uid.Proc proc = mProcessStats.valueAt(ip);
+                proc.writeToParcelLocked(out);
+            }
+
+            out.writeInt(mPackageStats.size());
+            for (Map.Entry<String, Uid.Pkg> pkgEntry : mPackageStats.entrySet()) {
+                out.writeString(pkgEntry.getKey());
+                Uid.Pkg pkg = pkgEntry.getValue();
+                pkg.writeToParcelLocked(out);
+            }
+
+            if (mWifiRunningTimer != null) {
+                out.writeInt(1);
+                mWifiRunningTimer.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+            if (mFullWifiLockTimer != null) {
+                out.writeInt(1);
+                mFullWifiLockTimer.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+            if (mWifiScanTimer != null) {
+                out.writeInt(1);
+                mWifiScanTimer.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+            for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) {
+                if (mWifiBatchedScanTimer[i] != null) {
+                    out.writeInt(1);
+                    mWifiBatchedScanTimer[i].writeToParcel(out, elapsedRealtimeUs);
+                } else {
+                    out.writeInt(0);
+                }
+            }
+            if (mWifiMulticastTimer != null) {
+                out.writeInt(1);
+                mWifiMulticastTimer.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+
+            if (mAudioTurnedOnTimer != null) {
+                out.writeInt(1);
+                mAudioTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+            if (mVideoTurnedOnTimer != null) {
+                out.writeInt(1);
+                mVideoTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+            if (mFlashlightTurnedOnTimer != null) {
+                out.writeInt(1);
+                mFlashlightTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+            if (mCameraTurnedOnTimer != null) {
+                out.writeInt(1);
+                mCameraTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+            if (mForegroundActivityTimer != null) {
+                out.writeInt(1);
+                mForegroundActivityTimer.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+            if (mForegroundServiceTimer != null) {
+                out.writeInt(1);
+                mForegroundServiceTimer.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+            if (mAggregatedPartialWakelockTimer != null) {
+                out.writeInt(1);
+                mAggregatedPartialWakelockTimer.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+            if (mBluetoothScanTimer != null) {
+                out.writeInt(1);
+                mBluetoothScanTimer.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+            if (mBluetoothUnoptimizedScanTimer != null) {
+                out.writeInt(1);
+                mBluetoothUnoptimizedScanTimer.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+            if (mBluetoothScanResultCounter != null) {
+                out.writeInt(1);
+                mBluetoothScanResultCounter.writeToParcel(out);
+            } else {
+                out.writeInt(0);
+            }
+            if (mBluetoothScanResultBgCounter != null) {
+                out.writeInt(1);
+                mBluetoothScanResultBgCounter.writeToParcel(out);
+            } else {
+                out.writeInt(0);
+            }
+            for (int i = 0; i < NUM_PROCESS_STATE; i++) {
+                if (mProcessStateTimer[i] != null) {
+                    out.writeInt(1);
+                    mProcessStateTimer[i].writeToParcel(out, elapsedRealtimeUs);
+                } else {
+                    out.writeInt(0);
+                }
+            }
+            if (mVibratorOnTimer != null) {
+                out.writeInt(1);
+                mVibratorOnTimer.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+            if (mUserActivityCounters != null) {
+                out.writeInt(1);
+                for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
+                    mUserActivityCounters[i].writeToParcel(out);
+                }
+            } else {
+                out.writeInt(0);
+            }
+            if (mNetworkByteActivityCounters != null) {
+                out.writeInt(1);
+                for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
+                    mNetworkByteActivityCounters[i].writeToParcel(out);
+                    mNetworkPacketActivityCounters[i].writeToParcel(out);
+                }
+                mMobileRadioActiveTime.writeToParcel(out);
+                mMobileRadioActiveCount.writeToParcel(out);
+            } else {
+                out.writeInt(0);
+            }
+
+            if (mWifiControllerActivity != null) {
+                out.writeInt(1);
+                mWifiControllerActivity.writeToParcel(out, 0);
+            } else {
+                out.writeInt(0);
+            }
+
+            if (mBluetoothControllerActivity != null) {
+                out.writeInt(1);
+                mBluetoothControllerActivity.writeToParcel(out, 0);
+            } else {
+                out.writeInt(0);
+            }
+
+            if (mModemControllerActivity != null) {
+                out.writeInt(1);
+                mModemControllerActivity.writeToParcel(out, 0);
+            } else {
+                out.writeInt(0);
+            }
+
+            mUserCpuTime.writeToParcel(out);
+            mSystemCpuTime.writeToParcel(out);
+
+            if (mCpuClusterSpeedTimesUs != null) {
+                out.writeInt(1);
+                out.writeInt(mCpuClusterSpeedTimesUs.length);
+                for (LongSamplingCounter[] cpuSpeeds : mCpuClusterSpeedTimesUs) {
+                    if (cpuSpeeds != null) {
+                        out.writeInt(1);
+                        out.writeInt(cpuSpeeds.length);
+                        for (LongSamplingCounter c : cpuSpeeds) {
+                            if (c != null) {
+                                out.writeInt(1);
+                                c.writeToParcel(out);
+                            } else {
+                                out.writeInt(0);
+                            }
+                        }
+                    } else {
+                        out.writeInt(0);
+                    }
+                }
+            } else {
+                out.writeInt(0);
+            }
+
+            LongSamplingCounterArray.writeToParcel(out, mCpuFreqTimeMs);
+            LongSamplingCounterArray.writeToParcel(out, mScreenOffCpuFreqTimeMs);
+
+            if (mMobileRadioApWakeupCount != null) {
+                out.writeInt(1);
+                mMobileRadioApWakeupCount.writeToParcel(out);
+            } else {
+                out.writeInt(0);
+            }
+
+            if (mWifiRadioApWakeupCount != null) {
+                out.writeInt(1);
+                mWifiRadioApWakeupCount.writeToParcel(out);
+            } else {
+                out.writeInt(0);
+            }
+        }
+
+        void readJobCompletionsFromParcelLocked(Parcel in) {
+            int numJobCompletions = in.readInt();
+            mJobCompletions.clear();
+            for (int j = 0; j < numJobCompletions; j++) {
+                String jobName = in.readString();
+                int numTypes = in.readInt();
+                if (numTypes > 0) {
+                    SparseIntArray types = new SparseIntArray();
+                    for (int k = 0; k < numTypes; k++) {
+                        int type = in.readInt();
+                        int count = in.readInt();
+                        types.put(type, count);
+                    }
+                    mJobCompletions.put(jobName, types);
+                }
+            }
+        }
+
+        void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
+            mOnBatteryBackgroundTimeBase.readFromParcel(in);
+            mOnBatteryScreenOffBackgroundTimeBase.readFromParcel(in);
+
+            int numWakelocks = in.readInt();
+            mWakelockStats.clear();
+            for (int j = 0; j < numWakelocks; j++) {
+                String wakelockName = in.readString();
+                Uid.Wakelock wakelock = new Wakelock(mBsi, this);
+                wakelock.readFromParcelLocked(
+                        timeBase, screenOffTimeBase, mOnBatteryScreenOffBackgroundTimeBase, in);
+                mWakelockStats.add(wakelockName, wakelock);
+            }
+
+            int numSyncs = in.readInt();
+            mSyncStats.clear();
+            for (int j = 0; j < numSyncs; j++) {
+                String syncName = in.readString();
+                if (in.readInt() != 0) {
+                    mSyncStats.add(syncName, new DualTimer(mBsi.mClocks, Uid.this, SYNC, null,
+                            mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase, in));
+                }
+            }
+
+            int numJobs = in.readInt();
+            mJobStats.clear();
+            for (int j = 0; j < numJobs; j++) {
+                String jobName = in.readString();
+                if (in.readInt() != 0) {
+                    mJobStats.add(jobName, new DualTimer(mBsi.mClocks, Uid.this, JOB, null,
+                            mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase, in));
+                }
+            }
+
+            readJobCompletionsFromParcelLocked(in);
+
+            int numSensors = in.readInt();
+            mSensorStats.clear();
+            for (int k = 0; k < numSensors; k++) {
+                int sensorNumber = in.readInt();
+                Uid.Sensor sensor = new Sensor(mBsi, this, sensorNumber);
+                sensor.readFromParcelLocked(mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase,
+                        in);
+                mSensorStats.put(sensorNumber, sensor);
+            }
+
+            int numProcs = in.readInt();
+            mProcessStats.clear();
+            for (int k = 0; k < numProcs; k++) {
+                String processName = in.readString();
+                Uid.Proc proc = new Proc(mBsi, processName);
+                proc.readFromParcelLocked(in);
+                mProcessStats.put(processName, proc);
+            }
+
+            int numPkgs = in.readInt();
+            mPackageStats.clear();
+            for (int l = 0; l < numPkgs; l++) {
+                String packageName = in.readString();
+                Uid.Pkg pkg = new Pkg(mBsi);
+                pkg.readFromParcelLocked(in);
+                mPackageStats.put(packageName, pkg);
+            }
+
+            mWifiRunning = false;
+            if (in.readInt() != 0) {
+                mWifiRunningTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, WIFI_RUNNING,
+                        mBsi.mWifiRunningTimers, mBsi.mOnBatteryTimeBase, in);
+            } else {
+                mWifiRunningTimer = null;
+            }
+            mFullWifiLockOut = false;
+            if (in.readInt() != 0) {
+                mFullWifiLockTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, FULL_WIFI_LOCK,
+                        mBsi.mFullWifiLockTimers, mBsi.mOnBatteryTimeBase, in);
+            } else {
+                mFullWifiLockTimer = null;
+            }
+            mWifiScanStarted = false;
+            if (in.readInt() != 0) {
+                mWifiScanTimer = new DualTimer(mBsi.mClocks, Uid.this, WIFI_SCAN,
+                        mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase,
+                        in);
+            } else {
+                mWifiScanTimer = null;
+            }
+            mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED;
+            for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) {
+                if (in.readInt() != 0) {
+                    makeWifiBatchedScanBin(i, in);
+                } else {
+                    mWifiBatchedScanTimer[i] = null;
+                }
+            }
+            mWifiMulticastEnabled = false;
+            if (in.readInt() != 0) {
+                mWifiMulticastTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, WIFI_MULTICAST_ENABLED,
+                        mBsi.mWifiMulticastTimers, mBsi.mOnBatteryTimeBase, in);
+            } else {
+                mWifiMulticastTimer = null;
+            }
+            if (in.readInt() != 0) {
+                mAudioTurnedOnTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, AUDIO_TURNED_ON,
+                        mBsi.mAudioTurnedOnTimers, mBsi.mOnBatteryTimeBase, in);
+            } else {
+                mAudioTurnedOnTimer = null;
+            }
+            if (in.readInt() != 0) {
+                mVideoTurnedOnTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, VIDEO_TURNED_ON,
+                        mBsi.mVideoTurnedOnTimers, mBsi.mOnBatteryTimeBase, in);
+            } else {
+                mVideoTurnedOnTimer = null;
+            }
+            if (in.readInt() != 0) {
+                mFlashlightTurnedOnTimer = new StopwatchTimer(mBsi.mClocks, Uid.this,
+                        FLASHLIGHT_TURNED_ON, mBsi.mFlashlightTurnedOnTimers, mBsi.mOnBatteryTimeBase, in);
+            } else {
+                mFlashlightTurnedOnTimer = null;
+            }
+            if (in.readInt() != 0) {
+                mCameraTurnedOnTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, CAMERA_TURNED_ON,
+                        mBsi.mCameraTurnedOnTimers, mBsi.mOnBatteryTimeBase, in);
+            } else {
+                mCameraTurnedOnTimer = null;
+            }
+            if (in.readInt() != 0) {
+                mForegroundActivityTimer = new StopwatchTimer(mBsi.mClocks, Uid.this,
+                        FOREGROUND_ACTIVITY, null, mBsi.mOnBatteryTimeBase, in);
+            } else {
+                mForegroundActivityTimer = null;
+            }
+            if (in.readInt() != 0) {
+                mForegroundServiceTimer = new StopwatchTimer(mBsi.mClocks, Uid.this,
+                        FOREGROUND_SERVICE, null, mBsi.mOnBatteryTimeBase, in);
+            } else {
+                mForegroundServiceTimer = null;
+            }
+            if (in.readInt() != 0) {
+                mAggregatedPartialWakelockTimer = new DualTimer(mBsi.mClocks, this,
+                        AGGREGATED_WAKE_TYPE_PARTIAL, null,
+                        mBsi.mOnBatteryScreenOffTimeBase, mOnBatteryScreenOffBackgroundTimeBase,
+                        in);
+            } else {
+                mAggregatedPartialWakelockTimer = null;
+            }
+            if (in.readInt() != 0) {
+                mBluetoothScanTimer = new DualTimer(mBsi.mClocks, Uid.this, BLUETOOTH_SCAN_ON,
+                        mBsi.mBluetoothScanOnTimers, mBsi.mOnBatteryTimeBase,
+                        mOnBatteryBackgroundTimeBase, in);
+            } else {
+                mBluetoothScanTimer = null;
+            }
+            if (in.readInt() != 0) {
+                mBluetoothUnoptimizedScanTimer = new DualTimer(mBsi.mClocks, Uid.this,
+                        BLUETOOTH_UNOPTIMIZED_SCAN_ON, null,
+                        mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase, in);
+            } else {
+                mBluetoothUnoptimizedScanTimer = null;
+            }
+            if (in.readInt() != 0) {
+                mBluetoothScanResultCounter = new Counter(mBsi.mOnBatteryTimeBase, in);
+            } else {
+                mBluetoothScanResultCounter = null;
+            }
+            if (in.readInt() != 0) {
+                mBluetoothScanResultBgCounter = new Counter(mOnBatteryBackgroundTimeBase, in);
+            } else {
+                mBluetoothScanResultBgCounter = null;
+            }
+            mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+            for (int i = 0; i < NUM_PROCESS_STATE; i++) {
+                if (in.readInt() != 0) {
+                    makeProcessState(i, in);
+                } else {
+                    mProcessStateTimer[i] = null;
+                }
+            }
+            if (in.readInt() != 0) {
+                mVibratorOnTimer = new BatchTimer(mBsi.mClocks, Uid.this, VIBRATOR_ON,
+                        mBsi.mOnBatteryTimeBase, in);
+            } else {
+                mVibratorOnTimer = null;
+            }
+            if (in.readInt() != 0) {
+                mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES];
+                for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
+                    mUserActivityCounters[i] = new Counter(mBsi.mOnBatteryTimeBase, in);
+                }
+            } else {
+                mUserActivityCounters = null;
+            }
+            if (in.readInt() != 0) {
+                mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+                mNetworkPacketActivityCounters
+                        = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+                for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
+                    mNetworkByteActivityCounters[i]
+                            = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
+                    mNetworkPacketActivityCounters[i]
+                            = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
+                }
+                mMobileRadioActiveTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
+                mMobileRadioActiveCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
+            } else {
+                mNetworkByteActivityCounters = null;
+                mNetworkPacketActivityCounters = null;
+            }
+
+            if (in.readInt() != 0) {
+                mWifiControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase,
+                        NUM_WIFI_TX_LEVELS, in);
+            } else {
+                mWifiControllerActivity = null;
+            }
+
+            if (in.readInt() != 0) {
+                mBluetoothControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase,
+                        NUM_BT_TX_LEVELS, in);
+            } else {
+                mBluetoothControllerActivity = null;
+            }
+
+            if (in.readInt() != 0) {
+                mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase,
+                        ModemActivityInfo.TX_POWER_LEVELS, in);
+            } else {
+                mModemControllerActivity = null;
+            }
+
+            mUserCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
+            mSystemCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
+
+            if (in.readInt() != 0) {
+                int numCpuClusters = in.readInt();
+                if (mBsi.mPowerProfile != null && mBsi.mPowerProfile.getNumCpuClusters() != numCpuClusters) {
+                    throw new ParcelFormatException("Incompatible number of cpu clusters");
+                }
+
+                mCpuClusterSpeedTimesUs = new LongSamplingCounter[numCpuClusters][];
+                for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+                    if (in.readInt() != 0) {
+                        int numSpeeds = in.readInt();
+                        if (mBsi.mPowerProfile != null &&
+                                mBsi.mPowerProfile.getNumSpeedStepsInCpuCluster(cluster) != numSpeeds) {
+                            throw new ParcelFormatException("Incompatible number of cpu speeds");
+                        }
+
+                        final LongSamplingCounter[] cpuSpeeds = new LongSamplingCounter[numSpeeds];
+                        mCpuClusterSpeedTimesUs[cluster] = cpuSpeeds;
+                        for (int speed = 0; speed < numSpeeds; speed++) {
+                            if (in.readInt() != 0) {
+                                cpuSpeeds[speed] = new LongSamplingCounter(
+                                        mBsi.mOnBatteryTimeBase, in);
+                            }
+                        }
+                    } else {
+                        mCpuClusterSpeedTimesUs[cluster] = null;
+                    }
+                }
+            } else {
+                mCpuClusterSpeedTimesUs = null;
+            }
+
+            mCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(in, mBsi.mOnBatteryTimeBase);
+            mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(
+                    in, mBsi.mOnBatteryScreenOffTimeBase);
+
+            if (in.readInt() != 0) {
+                mMobileRadioApWakeupCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
+            } else {
+                mMobileRadioApWakeupCount = null;
+            }
+
+            if (in.readInt() != 0) {
+                mWifiRadioApWakeupCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
+            } else {
+                mWifiRadioApWakeupCount = null;
+            }
+        }
+
+        /**
+         * The statistics associated with a particular wake lock.
+         */
+        public static class Wakelock extends BatteryStats.Uid.Wakelock {
+            /**
+             * BatteryStatsImpl that we are associated with.
+             */
+            protected BatteryStatsImpl mBsi;
+
+            /**
+             * BatteryStatsImpl that we are associated with.
+             */
+            protected Uid mUid;
+
+            /**
+             * How long (in ms) this uid has been keeping the device partially awake.
+             * Tracks both the total time and the time while the app was in the background.
+             */
+            DualTimer mTimerPartial;
+
+            /**
+             * How long (in ms) this uid has been keeping the device fully awake.
+             */
+            StopwatchTimer mTimerFull;
+
+            /**
+             * How long (in ms) this uid has had a window keeping the device awake.
+             */
+            StopwatchTimer mTimerWindow;
+
+            /**
+             * How long (in ms) this uid has had a draw wake lock.
+             */
+            StopwatchTimer mTimerDraw;
+
+            public Wakelock(BatteryStatsImpl bsi, Uid uid) {
+                mBsi = bsi;
+                mUid = uid;
+            }
+
+            /**
+             * Reads a possibly null Timer from a Parcel.  The timer is associated with the
+             * proper timer pool from the given BatteryStatsImpl object.
+             *
+             * @param in the Parcel to be read from.
+             * return a new Timer, or null.
+             */
+            private StopwatchTimer readStopwatchTimerFromParcel(int type,
+                    ArrayList<StopwatchTimer> pool, TimeBase timeBase, Parcel in) {
+                if (in.readInt() == 0) {
+                    return null;
+                }
+
+                return new StopwatchTimer(mBsi.mClocks, mUid, type, pool, timeBase, in);
+            }
+
+            /**
+             * Reads a possibly null Timer from a Parcel.  The timer is associated with the
+             * proper timer pool from the given BatteryStatsImpl object.
+             *
+             * @param in the Parcel to be read from.
+             * return a new Timer, or null.
+             */
+            private DualTimer readDualTimerFromParcel(int type, ArrayList<StopwatchTimer> pool,
+                    TimeBase timeBase, TimeBase bgTimeBase, Parcel in) {
+                if (in.readInt() == 0) {
+                    return null;
+                }
+
+                return new DualTimer(mBsi.mClocks, mUid, type, pool, timeBase, bgTimeBase, in);
+            }
+
+            boolean reset() {
+                boolean wlactive = false;
+                if (mTimerFull != null) {
+                    wlactive |= !mTimerFull.reset(false);
+                }
+                if (mTimerPartial != null) {
+                    wlactive |= !mTimerPartial.reset(false);
+                }
+                if (mTimerWindow != null) {
+                    wlactive |= !mTimerWindow.reset(false);
+                }
+                if (mTimerDraw != null) {
+                    wlactive |= !mTimerDraw.reset(false);
+                }
+                if (!wlactive) {
+                    if (mTimerFull != null) {
+                        mTimerFull.detach();
+                        mTimerFull = null;
+                    }
+                    if (mTimerPartial != null) {
+                        mTimerPartial.detach();
+                        mTimerPartial = null;
+                    }
+                    if (mTimerWindow != null) {
+                        mTimerWindow.detach();
+                        mTimerWindow = null;
+                    }
+                    if (mTimerDraw != null) {
+                        mTimerDraw.detach();
+                        mTimerDraw = null;
+                    }
+                }
+                return !wlactive;
+            }
+
+            void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase,
+                    TimeBase screenOffBgTimeBase, Parcel in) {
+                mTimerPartial = readDualTimerFromParcel(WAKE_TYPE_PARTIAL,
+                        mBsi.mPartialTimers, screenOffTimeBase, screenOffBgTimeBase, in);
+                mTimerFull = readStopwatchTimerFromParcel(WAKE_TYPE_FULL,
+                        mBsi.mFullTimers, timeBase, in);
+                mTimerWindow = readStopwatchTimerFromParcel(WAKE_TYPE_WINDOW,
+                        mBsi.mWindowTimers, timeBase, in);
+                mTimerDraw = readStopwatchTimerFromParcel(WAKE_TYPE_DRAW,
+                        mBsi.mDrawTimers, timeBase, in);
+            }
+
+            void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
+                Timer.writeTimerToParcel(out, mTimerPartial, elapsedRealtimeUs);
+                Timer.writeTimerToParcel(out, mTimerFull, elapsedRealtimeUs);
+                Timer.writeTimerToParcel(out, mTimerWindow, elapsedRealtimeUs);
+                Timer.writeTimerToParcel(out, mTimerDraw, elapsedRealtimeUs);
+            }
+
+            @Override
+            public Timer getWakeTime(int type) {
+                switch (type) {
+                case WAKE_TYPE_FULL: return mTimerFull;
+                case WAKE_TYPE_PARTIAL: return mTimerPartial;
+                case WAKE_TYPE_WINDOW: return mTimerWindow;
+                case WAKE_TYPE_DRAW: return mTimerDraw;
+                default: throw new IllegalArgumentException("type = " + type);
+                }
+            }
+        }
+
+        public static class Sensor extends BatteryStats.Uid.Sensor {
+            /**
+             * BatteryStatsImpl that we are associated with.
+             */
+            protected BatteryStatsImpl mBsi;
+
+            /**
+             * Uid that we are associated with.
+             */
+            protected Uid mUid;
+
+            final int mHandle;
+            DualTimer mTimer;
+
+            public Sensor(BatteryStatsImpl bsi, Uid uid, int handle) {
+                mBsi = bsi;
+                mUid = uid;
+                mHandle = handle;
+            }
+
+            private DualTimer readTimersFromParcel(
+                    TimeBase timeBase, TimeBase bgTimeBase, Parcel in) {
+                if (in.readInt() == 0) {
+                    return null;
+                }
+
+                ArrayList<StopwatchTimer> pool = mBsi.mSensorTimers.get(mHandle);
+                if (pool == null) {
+                    pool = new ArrayList<StopwatchTimer>();
+                    mBsi.mSensorTimers.put(mHandle, pool);
+                }
+                return new DualTimer(mBsi.mClocks, mUid, 0, pool, timeBase, bgTimeBase, in);
+            }
+
+            boolean reset() {
+                if (mTimer.reset(true)) {
+                    mTimer = null;
+                    return true;
+                }
+                return false;
+            }
+
+            void readFromParcelLocked(TimeBase timeBase, TimeBase bgTimeBase, Parcel in) {
+                mTimer = readTimersFromParcel(timeBase, bgTimeBase, in);
+            }
+
+            void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
+                Timer.writeTimerToParcel(out, mTimer, elapsedRealtimeUs);
+            }
+
+            @Override
+            public Timer getSensorTime() {
+                return mTimer;
+            }
+
+            @Override
+            public Timer getSensorBackgroundTime() {
+                if (mTimer == null) {
+                    return null;
+                }
+                return mTimer.getSubTimer();
+            }
+
+            @Override
+            public int getHandle() {
+                return mHandle;
+            }
+        }
+
+        /**
+         * The statistics associated with a particular process.
+         */
+        public static class Proc extends BatteryStats.Uid.Proc implements TimeBaseObs {
+            /**
+             * BatteryStatsImpl that we are associated with.
+             */
+            protected BatteryStatsImpl mBsi;
+
+            /**
+             * The name of this process.
+             */
+            final String mName;
+
+            /**
+             * Remains true until removed from the stats.
+             */
+            boolean mActive = true;
+
+            /**
+             * Total time (in ms) spent executing in user code.
+             */
+            long mUserTime;
+
+            /**
+             * Total time (in ms) spent executing in kernel code.
+             */
+            long mSystemTime;
+
+            /**
+             * Amount of time (in ms) the process was running in the foreground.
+             */
+            long mForegroundTime;
+
+            /**
+             * Number of times the process has been started.
+             */
+            int mStarts;
+
+            /**
+             * Number of times the process has crashed.
+             */
+            int mNumCrashes;
+
+            /**
+             * Number of times the process has had an ANR.
+             */
+            int mNumAnrs;
+
+            /**
+             * The amount of user time loaded from a previous save.
+             */
+            long mLoadedUserTime;
+
+            /**
+             * The amount of system time loaded from a previous save.
+             */
+            long mLoadedSystemTime;
+
+            /**
+             * The amount of foreground time loaded from a previous save.
+             */
+            long mLoadedForegroundTime;
+
+            /**
+             * The number of times the process has started from a previous save.
+             */
+            int mLoadedStarts;
+
+            /**
+             * Number of times the process has crashed from a previous save.
+             */
+            int mLoadedNumCrashes;
+
+            /**
+             * Number of times the process has had an ANR from a previous save.
+             */
+            int mLoadedNumAnrs;
+
+            /**
+             * The amount of user time when last unplugged.
+             */
+            long mUnpluggedUserTime;
+
+            /**
+             * The amount of system time when last unplugged.
+             */
+            long mUnpluggedSystemTime;
+
+            /**
+             * The amount of foreground time since unplugged.
+             */
+            long mUnpluggedForegroundTime;
+
+            /**
+             * The number of times the process has started before unplugged.
+             */
+            int mUnpluggedStarts;
+
+            /**
+             * Number of times the process has crashed before unplugged.
+             */
+            int mUnpluggedNumCrashes;
+
+            /**
+             * Number of times the process has had an ANR before unplugged.
+             */
+            int mUnpluggedNumAnrs;
+
+            ArrayList<ExcessivePower> mExcessivePower;
+
+            public Proc(BatteryStatsImpl bsi, String name) {
+                mBsi = bsi;
+                mName = name;
+                mBsi.mOnBatteryTimeBase.add(this);
+            }
+
+            public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
+                mUnpluggedUserTime = mUserTime;
+                mUnpluggedSystemTime = mSystemTime;
+                mUnpluggedForegroundTime = mForegroundTime;
+                mUnpluggedStarts = mStarts;
+                mUnpluggedNumCrashes = mNumCrashes;
+                mUnpluggedNumAnrs = mNumAnrs;
+            }
+
+            public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
+            }
+
+            void detach() {
+                mActive = false;
+                mBsi.mOnBatteryTimeBase.remove(this);
+            }
+
+            public int countExcessivePowers() {
+                return mExcessivePower != null ? mExcessivePower.size() : 0;
+            }
+
+            public ExcessivePower getExcessivePower(int i) {
+                if (mExcessivePower != null) {
+                    return mExcessivePower.get(i);
+                }
+                return null;
+            }
+
+            public void addExcessiveCpu(long overTime, long usedTime) {
+                if (mExcessivePower == null) {
+                    mExcessivePower = new ArrayList<ExcessivePower>();
+                }
+                ExcessivePower ew = new ExcessivePower();
+                ew.type = ExcessivePower.TYPE_CPU;
+                ew.overTime = overTime;
+                ew.usedTime = usedTime;
+                mExcessivePower.add(ew);
+            }
+
+            void writeExcessivePowerToParcelLocked(Parcel out) {
+                if (mExcessivePower == null) {
+                    out.writeInt(0);
+                    return;
+                }
+
+                final int N = mExcessivePower.size();
+                out.writeInt(N);
+                for (int i=0; i<N; i++) {
+                    ExcessivePower ew = mExcessivePower.get(i);
+                    out.writeInt(ew.type);
+                    out.writeLong(ew.overTime);
+                    out.writeLong(ew.usedTime);
+                }
+            }
+
+            void readExcessivePowerFromParcelLocked(Parcel in) {
+                final int N = in.readInt();
+                if (N == 0) {
+                    mExcessivePower = null;
+                    return;
+                }
+
+                if (N > 10000) {
+                    throw new ParcelFormatException(
+                            "File corrupt: too many excessive power entries " + N);
+                }
+
+                mExcessivePower = new ArrayList<>();
+                for (int i=0; i<N; i++) {
+                    ExcessivePower ew = new ExcessivePower();
+                    ew.type = in.readInt();
+                    ew.overTime = in.readLong();
+                    ew.usedTime = in.readLong();
+                    mExcessivePower.add(ew);
+                }
+            }
+
+            void writeToParcelLocked(Parcel out) {
+                out.writeLong(mUserTime);
+                out.writeLong(mSystemTime);
+                out.writeLong(mForegroundTime);
+                out.writeInt(mStarts);
+                out.writeInt(mNumCrashes);
+                out.writeInt(mNumAnrs);
+                out.writeLong(mLoadedUserTime);
+                out.writeLong(mLoadedSystemTime);
+                out.writeLong(mLoadedForegroundTime);
+                out.writeInt(mLoadedStarts);
+                out.writeInt(mLoadedNumCrashes);
+                out.writeInt(mLoadedNumAnrs);
+                out.writeLong(mUnpluggedUserTime);
+                out.writeLong(mUnpluggedSystemTime);
+                out.writeLong(mUnpluggedForegroundTime);
+                out.writeInt(mUnpluggedStarts);
+                out.writeInt(mUnpluggedNumCrashes);
+                out.writeInt(mUnpluggedNumAnrs);
+                writeExcessivePowerToParcelLocked(out);
+            }
+
+            void readFromParcelLocked(Parcel in) {
+                mUserTime = in.readLong();
+                mSystemTime = in.readLong();
+                mForegroundTime = in.readLong();
+                mStarts = in.readInt();
+                mNumCrashes = in.readInt();
+                mNumAnrs = in.readInt();
+                mLoadedUserTime = in.readLong();
+                mLoadedSystemTime = in.readLong();
+                mLoadedForegroundTime = in.readLong();
+                mLoadedStarts = in.readInt();
+                mLoadedNumCrashes = in.readInt();
+                mLoadedNumAnrs = in.readInt();
+                mUnpluggedUserTime = in.readLong();
+                mUnpluggedSystemTime = in.readLong();
+                mUnpluggedForegroundTime = in.readLong();
+                mUnpluggedStarts = in.readInt();
+                mUnpluggedNumCrashes = in.readInt();
+                mUnpluggedNumAnrs = in.readInt();
+                readExcessivePowerFromParcelLocked(in);
+            }
+
+            public void addCpuTimeLocked(int utime, int stime) {
+                mUserTime += utime;
+                mSystemTime += stime;
+            }
+
+            public void addForegroundTimeLocked(long ttime) {
+                mForegroundTime += ttime;
+            }
+
+            public void incStartsLocked() {
+                mStarts++;
+            }
+
+            public void incNumCrashesLocked() {
+                mNumCrashes++;
+            }
+
+            public void incNumAnrsLocked() {
+                mNumAnrs++;
+            }
+
+            @Override
+            public boolean isActive() {
+                return mActive;
+            }
+
+            @Override
+            public long getUserTime(int which) {
+                long val = mUserTime;
+                if (which == STATS_CURRENT) {
+                    val -= mLoadedUserTime;
+                } else if (which == STATS_SINCE_UNPLUGGED) {
+                    val -= mUnpluggedUserTime;
+                }
+                return val;
+            }
+
+            @Override
+            public long getSystemTime(int which) {
+                long val = mSystemTime;
+                if (which == STATS_CURRENT) {
+                    val -= mLoadedSystemTime;
+                } else if (which == STATS_SINCE_UNPLUGGED) {
+                    val -= mUnpluggedSystemTime;
+                }
+                return val;
+            }
+
+            @Override
+            public long getForegroundTime(int which) {
+                long val = mForegroundTime;
+                if (which == STATS_CURRENT) {
+                    val -= mLoadedForegroundTime;
+                } else if (which == STATS_SINCE_UNPLUGGED) {
+                    val -= mUnpluggedForegroundTime;
+                }
+                return val;
+            }
+
+            @Override
+            public int getStarts(int which) {
+                int val = mStarts;
+                if (which == STATS_CURRENT) {
+                    val -= mLoadedStarts;
+                } else if (which == STATS_SINCE_UNPLUGGED) {
+                    val -= mUnpluggedStarts;
+                }
+                return val;
+            }
+
+            @Override
+            public int getNumCrashes(int which) {
+                int val = mNumCrashes;
+                if (which == STATS_CURRENT) {
+                    val -= mLoadedNumCrashes;
+                } else if (which == STATS_SINCE_UNPLUGGED) {
+                    val -= mUnpluggedNumCrashes;
+                }
+                return val;
+            }
+
+            @Override
+            public int getNumAnrs(int which) {
+                int val = mNumAnrs;
+                if (which == STATS_CURRENT) {
+                    val -= mLoadedNumAnrs;
+                } else if (which == STATS_SINCE_UNPLUGGED) {
+                    val -= mUnpluggedNumAnrs;
+                }
+                return val;
+            }
+        }
+
+        /**
+         * The statistics associated with a particular package.
+         */
+        public static class Pkg extends BatteryStats.Uid.Pkg implements TimeBaseObs {
+            /**
+             * BatteryStatsImpl that we are associated with.
+             */
+            protected BatteryStatsImpl mBsi;
+
+            /**
+             * Number of times wakeup alarms have occurred for this app.
+             * On screen-off timebase starting in report v25.
+             */
+            ArrayMap<String, Counter> mWakeupAlarms = new ArrayMap<>();
+
+            /**
+             * The statics we have collected for this package's services.
+             */
+            final ArrayMap<String, Serv> mServiceStats = new ArrayMap<>();
+
+            public Pkg(BatteryStatsImpl bsi) {
+                mBsi = bsi;
+                mBsi.mOnBatteryScreenOffTimeBase.add(this);
+            }
+
+            public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
+            }
+
+            public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
+            }
+
+            void detach() {
+                mBsi.mOnBatteryScreenOffTimeBase.remove(this);
+            }
+
+            void readFromParcelLocked(Parcel in) {
+                int numWA = in.readInt();
+                mWakeupAlarms.clear();
+                for (int i=0; i<numWA; i++) {
+                    String tag = in.readString();
+                    mWakeupAlarms.put(tag, new Counter(mBsi.mOnBatteryScreenOffTimeBase, in));
+                }
+
+                int numServs = in.readInt();
+                mServiceStats.clear();
+                for (int m = 0; m < numServs; m++) {
+                    String serviceName = in.readString();
+                    Uid.Pkg.Serv serv = new Serv(mBsi);
+                    mServiceStats.put(serviceName, serv);
+
+                    serv.readFromParcelLocked(in);
+                }
+            }
+
+            void writeToParcelLocked(Parcel out) {
+                int numWA = mWakeupAlarms.size();
+                out.writeInt(numWA);
+                for (int i=0; i<numWA; i++) {
+                    out.writeString(mWakeupAlarms.keyAt(i));
+                    mWakeupAlarms.valueAt(i).writeToParcel(out);
+                }
+
+                final int NS = mServiceStats.size();
+                out.writeInt(NS);
+                for (int i=0; i<NS; i++) {
+                    out.writeString(mServiceStats.keyAt(i));
+                    Uid.Pkg.Serv serv = mServiceStats.valueAt(i);
+                    serv.writeToParcelLocked(out);
+                }
+            }
+
+            @Override
+            public ArrayMap<String, ? extends BatteryStats.Counter> getWakeupAlarmStats() {
+                return mWakeupAlarms;
+            }
+
+            public void noteWakeupAlarmLocked(String tag) {
+                Counter c = mWakeupAlarms.get(tag);
+                if (c == null) {
+                    c = new Counter(mBsi.mOnBatteryScreenOffTimeBase);
+                    mWakeupAlarms.put(tag, c);
+                }
+                c.stepAtomic();
+            }
+
+            @Override
+            public ArrayMap<String, ? extends BatteryStats.Uid.Pkg.Serv> getServiceStats() {
+                return mServiceStats;
+            }
+
+            /**
+             * The statistics associated with a particular service.
+             */
+            public static class Serv extends BatteryStats.Uid.Pkg.Serv implements TimeBaseObs {
+                /**
+                 * BatteryStatsImpl that we are associated with.
+                 */
+                protected BatteryStatsImpl mBsi;
+
+                /**
+                 * The android package in which this service resides.
+                 */
+                protected Pkg mPkg;
+
+                /**
+                 * Total time (ms in battery uptime) the service has been left started.
+                 */
+                protected long mStartTime;
+
+                /**
+                 * If service has been started and not yet stopped, this is
+                 * when it was started.
+                 */
+                protected long mRunningSince;
+
+                /**
+                 * True if we are currently running.
+                 */
+                protected boolean mRunning;
+
+                /**
+                 * Total number of times startService() has been called.
+                 */
+                protected int mStarts;
+
+                /**
+                 * Total time (ms in battery uptime) the service has been left launched.
+                 */
+                protected long mLaunchedTime;
+
+                /**
+                 * If service has been launched and not yet exited, this is
+                 * when it was launched (ms in battery uptime).
+                 */
+                protected long mLaunchedSince;
+
+                /**
+                 * True if we are currently launched.
+                 */
+                protected boolean mLaunched;
+
+                /**
+                 * Total number times the service has been launched.
+                 */
+                protected int mLaunches;
+
+                /**
+                 * The amount of time spent started loaded from a previous save
+                 * (ms in battery uptime).
+                 */
+                protected long mLoadedStartTime;
+
+                /**
+                 * The number of starts loaded from a previous save.
+                 */
+                protected int mLoadedStarts;
+
+                /**
+                 * The number of launches loaded from a previous save.
+                 */
+                protected int mLoadedLaunches;
+
+                /**
+                 * The amount of time spent started as of the last run (ms
+                 * in battery uptime).
+                 */
+                protected long mLastStartTime;
+
+                /**
+                 * The number of starts as of the last run.
+                 */
+                protected int mLastStarts;
+
+                /**
+                 * The number of launches as of the last run.
+                 */
+                protected int mLastLaunches;
+
+                /**
+                 * The amount of time spent started when last unplugged (ms
+                 * in battery uptime).
+                 */
+                protected long mUnpluggedStartTime;
+
+                /**
+                 * The number of starts when last unplugged.
+                 */
+                protected int mUnpluggedStarts;
+
+                /**
+                 * The number of launches when last unplugged.
+                 */
+                protected int mUnpluggedLaunches;
+
+                /**
+                 * Construct a Serv. Also adds it to the on-battery time base as a listener.
+                 */
+                public Serv(BatteryStatsImpl bsi) {
+                    mBsi = bsi;
+                    mBsi.mOnBatteryTimeBase.add(this);
+                }
+
+                public void onTimeStarted(long elapsedRealtime, long baseUptime,
+                        long baseRealtime) {
+                    mUnpluggedStartTime = getStartTimeToNowLocked(baseUptime);
+                    mUnpluggedStarts = mStarts;
+                    mUnpluggedLaunches = mLaunches;
+                }
+
+                public void onTimeStopped(long elapsedRealtime, long baseUptime,
+                        long baseRealtime) {
+                }
+
+                /**
+                 * Remove this Serv as a listener from the time base.
+                 */
+                public void detach() {
+                    mBsi.mOnBatteryTimeBase.remove(this);
+                }
+
+                public void readFromParcelLocked(Parcel in) {
+                    mStartTime = in.readLong();
+                    mRunningSince = in.readLong();
+                    mRunning = in.readInt() != 0;
+                    mStarts = in.readInt();
+                    mLaunchedTime = in.readLong();
+                    mLaunchedSince = in.readLong();
+                    mLaunched = in.readInt() != 0;
+                    mLaunches = in.readInt();
+                    mLoadedStartTime = in.readLong();
+                    mLoadedStarts = in.readInt();
+                    mLoadedLaunches = in.readInt();
+                    mLastStartTime = 0;
+                    mLastStarts = 0;
+                    mLastLaunches = 0;
+                    mUnpluggedStartTime = in.readLong();
+                    mUnpluggedStarts = in.readInt();
+                    mUnpluggedLaunches = in.readInt();
+                }
+
+                public void writeToParcelLocked(Parcel out) {
+                    out.writeLong(mStartTime);
+                    out.writeLong(mRunningSince);
+                    out.writeInt(mRunning ? 1 : 0);
+                    out.writeInt(mStarts);
+                    out.writeLong(mLaunchedTime);
+                    out.writeLong(mLaunchedSince);
+                    out.writeInt(mLaunched ? 1 : 0);
+                    out.writeInt(mLaunches);
+                    out.writeLong(mLoadedStartTime);
+                    out.writeInt(mLoadedStarts);
+                    out.writeInt(mLoadedLaunches);
+                    out.writeLong(mUnpluggedStartTime);
+                    out.writeInt(mUnpluggedStarts);
+                    out.writeInt(mUnpluggedLaunches);
+                }
+
+                public long getLaunchTimeToNowLocked(long batteryUptime) {
+                    if (!mLaunched) return mLaunchedTime;
+                    return mLaunchedTime + batteryUptime - mLaunchedSince;
+                }
+
+                public long getStartTimeToNowLocked(long batteryUptime) {
+                    if (!mRunning) return mStartTime;
+                    return mStartTime + batteryUptime - mRunningSince;
+                }
+
+                public void startLaunchedLocked() {
+                    if (!mLaunched) {
+                        mLaunches++;
+                        mLaunchedSince = mBsi.getBatteryUptimeLocked();
+                        mLaunched = true;
+                    }
+                }
+
+                public void stopLaunchedLocked() {
+                    if (mLaunched) {
+                        long time = mBsi.getBatteryUptimeLocked() - mLaunchedSince;
+                        if (time > 0) {
+                            mLaunchedTime += time;
+                        } else {
+                            mLaunches--;
+                        }
+                        mLaunched = false;
+                    }
+                }
+
+                public void startRunningLocked() {
+                    if (!mRunning) {
+                        mStarts++;
+                        mRunningSince = mBsi.getBatteryUptimeLocked();
+                        mRunning = true;
+                    }
+                }
+
+                public void stopRunningLocked() {
+                    if (mRunning) {
+                        long time = mBsi.getBatteryUptimeLocked() - mRunningSince;
+                        if (time > 0) {
+                            mStartTime += time;
+                        } else {
+                            mStarts--;
+                        }
+                        mRunning = false;
+                    }
+                }
+
+                public BatteryStatsImpl getBatteryStats() {
+                    return mBsi;
+                }
+
+                @Override
+                public int getLaunches(int which) {
+                    int val = mLaunches;
+                    if (which == STATS_CURRENT) {
+                        val -= mLoadedLaunches;
+                    } else if (which == STATS_SINCE_UNPLUGGED) {
+                        val -= mUnpluggedLaunches;
+                    }
+                    return val;
+                }
+
+                @Override
+                public long getStartTime(long now, int which) {
+                    long val = getStartTimeToNowLocked(now);
+                    if (which == STATS_CURRENT) {
+                        val -= mLoadedStartTime;
+                    } else if (which == STATS_SINCE_UNPLUGGED) {
+                        val -= mUnpluggedStartTime;
+                    }
+                    return val;
+                }
+
+                @Override
+                public int getStarts(int which) {
+                    int val = mStarts;
+                    if (which == STATS_CURRENT) {
+                        val -= mLoadedStarts;
+                    } else if (which == STATS_SINCE_UNPLUGGED) {
+                        val -= mUnpluggedStarts;
+                    }
+
+                    return val;
+                }
+            }
+
+            final Serv newServiceStatsLocked() {
+                return new Serv(mBsi);
+            }
+        }
+
+        /**
+         * Retrieve the statistics object for a particular process, creating
+         * if needed.
+         */
+        public Proc getProcessStatsLocked(String name) {
+            Proc ps = mProcessStats.get(name);
+            if (ps == null) {
+                ps = new Proc(mBsi, name);
+                mProcessStats.put(name, ps);
+            }
+
+            return ps;
+        }
+
+        public void updateUidProcessStateLocked(int procState) {
+            int uidRunningState;
+            // Make special note of Foreground Services
+            final boolean userAwareService =
+                    (procState == ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+            if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+                uidRunningState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+            } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
+                uidRunningState = PROCESS_STATE_TOP;
+            } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+                // Persistent and other foreground states go here.
+                uidRunningState = PROCESS_STATE_FOREGROUND_SERVICE;
+            } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+                uidRunningState = PROCESS_STATE_TOP_SLEEPING;
+            } else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                // Persistent and other foreground states go here.
+                uidRunningState = PROCESS_STATE_FOREGROUND;
+            } else if (procState <= ActivityManager.PROCESS_STATE_RECEIVER) {
+                uidRunningState = PROCESS_STATE_BACKGROUND;
+            } else {
+                uidRunningState = PROCESS_STATE_CACHED;
+            }
+
+            if (mProcessState == uidRunningState && userAwareService == mInForegroundService) {
+                return;
+            }
+
+            final long elapsedRealtimeMs = mBsi.mClocks.elapsedRealtime();
+            if (mProcessState != uidRunningState) {
+                final long uptimeMs = mBsi.mClocks.uptimeMillis();
+
+                if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+                    mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
+                }
+                mProcessState = uidRunningState;
+                if (uidRunningState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+                    if (mProcessStateTimer[uidRunningState] == null) {
+                        makeProcessState(uidRunningState, null);
+                    }
+                    mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtimeMs);
+                }
+
+                updateOnBatteryBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
+                updateOnBatteryScreenOffBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
+            }
+
+            if (userAwareService != mInForegroundService) {
+                if (userAwareService) {
+                    noteForegroundServiceResumedLocked(elapsedRealtimeMs);
+                } else {
+                    noteForegroundServicePausedLocked(elapsedRealtimeMs);
+                }
+                mInForegroundService = userAwareService;
+            }
+        }
+
+        /** Whether to consider Uid to be in the background for background timebase purposes. */
+        public boolean isInBackground() {
+            // Note that PROCESS_STATE_CACHED and ActivityManager.PROCESS_STATE_NONEXISTENT is
+            // also considered to be 'background' for our purposes, because it's not foreground.
+            return mProcessState >= PROCESS_STATE_BACKGROUND;
+        }
+
+        public boolean updateOnBatteryBgTimeBase(long uptimeUs, long realtimeUs) {
+            boolean on = mBsi.mOnBatteryTimeBase.isRunning() && isInBackground();
+            return mOnBatteryBackgroundTimeBase.setRunning(on, uptimeUs, realtimeUs);
+        }
+
+        public boolean updateOnBatteryScreenOffBgTimeBase(long uptimeUs, long realtimeUs) {
+            boolean on = mBsi.mOnBatteryScreenOffTimeBase.isRunning() && isInBackground();
+            return mOnBatteryScreenOffBackgroundTimeBase.setRunning(on, uptimeUs, realtimeUs);
+        }
+
+        public SparseArray<? extends Pid> getPidStats() {
+            return mPids;
+        }
+
+        public Pid getPidStatsLocked(int pid) {
+            Pid p = mPids.get(pid);
+            if (p == null) {
+                p = new Pid();
+                mPids.put(pid, p);
+            }
+            return p;
+        }
+
+        /**
+         * Retrieve the statistics object for a particular service, creating
+         * if needed.
+         */
+        public Pkg getPackageStatsLocked(String name) {
+            Pkg ps = mPackageStats.get(name);
+            if (ps == null) {
+                ps = new Pkg(mBsi);
+                mPackageStats.put(name, ps);
+            }
+
+            return ps;
+        }
+
+        /**
+         * Retrieve the statistics object for a particular service, creating
+         * if needed.
+         */
+        public Pkg.Serv getServiceStatsLocked(String pkg, String serv) {
+            Pkg ps = getPackageStatsLocked(pkg);
+            Pkg.Serv ss = ps.mServiceStats.get(serv);
+            if (ss == null) {
+                ss = ps.newServiceStatsLocked();
+                ps.mServiceStats.put(serv, ss);
+            }
+
+            return ss;
+        }
+
+        public void readSyncSummaryFromParcelLocked(String name, Parcel in) {
+            DualTimer timer = mSyncStats.instantiateObject();
+            timer.readSummaryFromParcelLocked(in);
+            mSyncStats.add(name, timer);
+        }
+
+        public void readJobSummaryFromParcelLocked(String name, Parcel in) {
+            DualTimer timer = mJobStats.instantiateObject();
+            timer.readSummaryFromParcelLocked(in);
+            mJobStats.add(name, timer);
+        }
+
+        public void readWakeSummaryFromParcelLocked(String wlName, Parcel in) {
+            Wakelock wl = new Wakelock(mBsi, this);
+            mWakelockStats.add(wlName, wl);
+            if (in.readInt() != 0) {
+                getWakelockTimerLocked(wl, WAKE_TYPE_FULL).readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                getWakelockTimerLocked(wl, WAKE_TYPE_PARTIAL).readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                getWakelockTimerLocked(wl, WAKE_TYPE_WINDOW).readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                getWakelockTimerLocked(wl, WAKE_TYPE_DRAW).readSummaryFromParcelLocked(in);
+            }
+        }
+
+        public DualTimer getSensorTimerLocked(int sensor, boolean create) {
+            Sensor se = mSensorStats.get(sensor);
+            if (se == null) {
+                if (!create) {
+                    return null;
+                }
+                se = new Sensor(mBsi, this, sensor);
+                mSensorStats.put(sensor, se);
+            }
+            DualTimer t = se.mTimer;
+            if (t != null) {
+                return t;
+            }
+            ArrayList<StopwatchTimer> timers = mBsi.mSensorTimers.get(sensor);
+            if (timers == null) {
+                timers = new ArrayList<StopwatchTimer>();
+                mBsi.mSensorTimers.put(sensor, timers);
+            }
+            t = new DualTimer(mBsi.mClocks, this, BatteryStats.SENSOR, timers,
+                    mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase);
+            se.mTimer = t;
+            return t;
+        }
+
+        public void noteStartSyncLocked(String name, long elapsedRealtimeMs) {
+            DualTimer t = mSyncStats.startObject(name);
+            if (t != null) {
+                t.startRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public void noteStopSyncLocked(String name, long elapsedRealtimeMs) {
+            DualTimer t = mSyncStats.stopObject(name);
+            if (t != null) {
+                t.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public void noteStartJobLocked(String name, long elapsedRealtimeMs) {
+            DualTimer t = mJobStats.startObject(name);
+            if (t != null) {
+                t.startRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public void noteStopJobLocked(String name, long elapsedRealtimeMs, int stopReason) {
+            DualTimer t = mJobStats.stopObject(name);
+            if (t != null) {
+                t.stopRunningLocked(elapsedRealtimeMs);
+            }
+            if (mBsi.mOnBatteryTimeBase.isRunning()) {
+                SparseIntArray types = mJobCompletions.get(name);
+                if (types == null) {
+                    types = new SparseIntArray();
+                    mJobCompletions.put(name, types);
+                }
+                int last = types.get(stopReason, 0);
+                types.put(stopReason, last + 1);
+            }
+        }
+
+        public StopwatchTimer getWakelockTimerLocked(Wakelock wl, int type) {
+            if (wl == null) {
+                return null;
+            }
+            switch (type) {
+                case WAKE_TYPE_PARTIAL: {
+                    DualTimer t = wl.mTimerPartial;
+                    if (t == null) {
+                        t = new DualTimer(mBsi.mClocks, this, WAKE_TYPE_PARTIAL,
+                                mBsi.mPartialTimers, mBsi.mOnBatteryScreenOffTimeBase,
+                                mOnBatteryScreenOffBackgroundTimeBase);
+                        wl.mTimerPartial = t;
+                    }
+                    return t;
+                }
+                case WAKE_TYPE_FULL: {
+                    StopwatchTimer t = wl.mTimerFull;
+                    if (t == null) {
+                        t = new StopwatchTimer(mBsi.mClocks, this, WAKE_TYPE_FULL,
+                                mBsi.mFullTimers, mBsi.mOnBatteryTimeBase);
+                        wl.mTimerFull = t;
+                    }
+                    return t;
+                }
+                case WAKE_TYPE_WINDOW: {
+                    StopwatchTimer t = wl.mTimerWindow;
+                    if (t == null) {
+                        t = new StopwatchTimer(mBsi.mClocks, this, WAKE_TYPE_WINDOW,
+                                mBsi.mWindowTimers, mBsi.mOnBatteryTimeBase);
+                        wl.mTimerWindow = t;
+                    }
+                    return t;
+                }
+                case WAKE_TYPE_DRAW: {
+                    StopwatchTimer t = wl.mTimerDraw;
+                    if (t == null) {
+                        t = new StopwatchTimer(mBsi.mClocks, this, WAKE_TYPE_DRAW,
+                                mBsi.mDrawTimers, mBsi.mOnBatteryTimeBase);
+                        wl.mTimerDraw = t;
+                    }
+                    return t;
+                }
+                default:
+                    throw new IllegalArgumentException("type=" + type);
+            }
+        }
+
+        public void noteStartWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) {
+            Wakelock wl = mWakelockStats.startObject(name);
+            if (wl != null) {
+                getWakelockTimerLocked(wl, type).startRunningLocked(elapsedRealtimeMs);
+            }
+            if (type == WAKE_TYPE_PARTIAL) {
+                createAggregatedPartialWakelockTimerLocked().startRunningLocked(elapsedRealtimeMs);
+                if (pid >= 0) {
+                    Pid p = getPidStatsLocked(pid);
+                    if (p.mWakeNesting++ == 0) {
+                        p.mWakeStartMs = elapsedRealtimeMs;
+                    }
+                }
+            }
+        }
+
+        public void noteStopWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) {
+            Wakelock wl = mWakelockStats.stopObject(name);
+            if (wl != null) {
+                getWakelockTimerLocked(wl, type).stopRunningLocked(elapsedRealtimeMs);
+            }
+            if (type == WAKE_TYPE_PARTIAL) {
+                if (mAggregatedPartialWakelockTimer != null) {
+                    mAggregatedPartialWakelockTimer.stopRunningLocked(elapsedRealtimeMs);
+                }
+                if (pid >= 0) {
+                    Pid p = mPids.get(pid);
+                    if (p != null && p.mWakeNesting > 0) {
+                        if (p.mWakeNesting-- == 1) {
+                            p.mWakeSumMs += elapsedRealtimeMs - p.mWakeStartMs;
+                            p.mWakeStartMs = 0;
+                        }
+                    }
+                }
+            }
+        }
+
+        public void reportExcessiveCpuLocked(String proc, long overTime, long usedTime) {
+            Proc p = getProcessStatsLocked(proc);
+            if (p != null) {
+                p.addExcessiveCpu(overTime, usedTime);
+            }
+        }
+
+        public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
+            DualTimer t = getSensorTimerLocked(sensor, /* create= */ true);
+            t.startRunningLocked(elapsedRealtimeMs);
+        }
+
+        public void noteStopSensor(int sensor, long elapsedRealtimeMs) {
+            // Don't create a timer if one doesn't already exist
+            DualTimer t = getSensorTimerLocked(sensor, false);
+            if (t != null) {
+                t.stopRunningLocked(elapsedRealtimeMs);
+            }
+        }
+
+        public void noteStartGps(long elapsedRealtimeMs) {
+            noteStartSensor(Sensor.GPS, elapsedRealtimeMs);
+        }
+
+        public void noteStopGps(long elapsedRealtimeMs) {
+            noteStopSensor(Sensor.GPS, elapsedRealtimeMs);
+        }
+
+        public BatteryStatsImpl getBatteryStats() {
+            return mBsi;
+        }
+    }
+
+    public long[] getCpuFreqs() {
+        return mCpuFreqs;
+    }
+
+    public BatteryStatsImpl(File systemDir, Handler handler, PlatformIdleStateCallback cb,
+            UserInfoProvider userInfoProvider) {
+        this(new SystemClocks(), systemDir, handler, cb, userInfoProvider);
+    }
+
+    private BatteryStatsImpl(Clocks clocks, File systemDir, Handler handler,
+            PlatformIdleStateCallback cb,
+            UserInfoProvider userInfoProvider) {
+        init(clocks);
+
+        if (systemDir != null) {
+            mFile = new JournaledFile(new File(systemDir, "batterystats.bin"),
+                    new File(systemDir, "batterystats.bin.tmp"));
+        } else {
+            mFile = null;
+        }
+        mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
+        mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
+        mHandler = new MyHandler(handler.getLooper());
+        mStartCount++;
+        mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
+        for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+            mScreenBrightnessTimer[i] = new StopwatchTimer(mClocks, null, -100-i, null,
+                    mOnBatteryTimeBase);
+        }
+        mInteractiveTimer = new StopwatchTimer(mClocks, null, -10, null, mOnBatteryTimeBase);
+        mPowerSaveModeEnabledTimer = new StopwatchTimer(mClocks, null, -2, null,
+                mOnBatteryTimeBase);
+        mDeviceIdleModeLightTimer = new StopwatchTimer(mClocks, null, -11, null,
+                mOnBatteryTimeBase);
+        mDeviceIdleModeFullTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
+        mDeviceLightIdlingTimer = new StopwatchTimer(mClocks, null, -15, null, mOnBatteryTimeBase);
+        mDeviceIdlingTimer = new StopwatchTimer(mClocks, null, -12, null, mOnBatteryTimeBase);
+        mPhoneOnTimer = new StopwatchTimer(mClocks, null, -3, null, mOnBatteryTimeBase);
+        for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
+            mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(mClocks, null, -200-i, null,
+                    mOnBatteryTimeBase);
+        }
+        mPhoneSignalScanningTimer = new StopwatchTimer(mClocks, null, -200+1, null,
+                mOnBatteryTimeBase);
+        for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
+            mPhoneDataConnectionsTimer[i] = new StopwatchTimer(mClocks, null, -300-i, null,
+                    mOnBatteryTimeBase);
+        }
+        for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
+            mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+            mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+        }
+        mWifiActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, NUM_WIFI_TX_LEVELS);
+        mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
+                NUM_BT_TX_LEVELS);
+        mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
+                ModemActivityInfo.TX_POWER_LEVELS);
+        mMobileRadioActiveTimer = new StopwatchTimer(mClocks, null, -400, null, mOnBatteryTimeBase);
+        mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null,
+                mOnBatteryTimeBase);
+        mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase);
+        mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase);
+        mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase);
+        mWifiOnTimer = new StopwatchTimer(mClocks, null, -4, null, mOnBatteryTimeBase);
+        mGlobalWifiRunningTimer = new StopwatchTimer(mClocks, null, -5, null, mOnBatteryTimeBase);
+        for (int i=0; i<NUM_WIFI_STATES; i++) {
+            mWifiStateTimer[i] = new StopwatchTimer(mClocks, null, -600-i, null,
+                    mOnBatteryTimeBase);
+        }
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            mWifiSupplStateTimer[i] = new StopwatchTimer(mClocks, null, -700-i, null,
+                    mOnBatteryTimeBase);
+        }
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            mWifiSignalStrengthsTimer[i] = new StopwatchTimer(mClocks, null, -800-i, null,
+                    mOnBatteryTimeBase);
+        }
+        mAudioOnTimer = new StopwatchTimer(mClocks, null, -7, null, mOnBatteryTimeBase);
+        mVideoOnTimer = new StopwatchTimer(mClocks, null, -8, null, mOnBatteryTimeBase);
+        mFlashlightOnTimer = new StopwatchTimer(mClocks, null, -9, null, mOnBatteryTimeBase);
+        mCameraOnTimer = new StopwatchTimer(mClocks, null, -13, null, mOnBatteryTimeBase);
+        mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
+        mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase);
+        mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
+        mOnBattery = mOnBatteryInternal = false;
+        long uptime = mClocks.uptimeMillis() * 1000;
+        long realtime = mClocks.elapsedRealtime() * 1000;
+        initTimes(uptime, realtime);
+        mStartPlatformVersion = mEndPlatformVersion = Build.ID;
+        mDischargeStartLevel = 0;
+        mDischargeUnplugLevel = 0;
+        mDischargePlugLevel = -1;
+        mDischargeCurrentLevel = 0;
+        mCurrentBatteryLevel = 0;
+        initDischarge();
+        clearHistoryLocked();
+        updateDailyDeadlineLocked();
+        mPlatformIdleStateCallback = cb;
+        mUserInfoProvider = userInfoProvider;
+    }
+
+    public BatteryStatsImpl(Parcel p) {
+        this(new SystemClocks(), p);
+    }
+
+    public BatteryStatsImpl(Clocks clocks, Parcel p) {
+        init(clocks);
+        mFile = null;
+        mCheckinFile = null;
+        mDailyFile = null;
+        mHandler = null;
+        mExternalSync = null;
+        clearHistoryLocked();
+        readFromParcel(p);
+        mPlatformIdleStateCallback = null;
+    }
+
+    public void setPowerProfileLocked(PowerProfile profile) {
+        mPowerProfile = profile;
+
+        // We need to initialize the KernelCpuSpeedReaders to read from
+        // the first cpu of each core. Once we have the PowerProfile, we have access to this
+        // information.
+        final int numClusters = mPowerProfile.getNumCpuClusters();
+        mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
+        int firstCpuOfCluster = 0;
+        for (int i = 0; i < numClusters; i++) {
+            final int numSpeedSteps = mPowerProfile.getNumSpeedStepsInCpuCluster(i);
+            mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
+                    numSpeedSteps);
+            firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
+        }
+
+        if (mEstimatedBatteryCapacity == -1) {
+            // Initialize the estimated battery capacity to a known preset one.
+            mEstimatedBatteryCapacity = (int) mPowerProfile.getBatteryCapacity();
+        }
+    }
+
+    public void setCallback(BatteryCallback cb) {
+        mCallback = cb;
+    }
+
+    public void setRadioScanningTimeoutLocked(long timeout) {
+        if (mPhoneSignalScanningTimer != null) {
+            mPhoneSignalScanningTimer.setTimeout(timeout);
+        }
+    }
+
+    public void setExternalStatsSyncLocked(ExternalStatsSync sync) {
+        mExternalSync = sync;
+    }
+
+    public void updateDailyDeadlineLocked() {
+        // Get the current time.
+        long currentTime = mDailyStartTime = System.currentTimeMillis();
+        Calendar calDeadline = Calendar.getInstance();
+        calDeadline.setTimeInMillis(currentTime);
+
+        // Move time up to the next day, ranging from 1am to 3pm.
+        calDeadline.set(Calendar.DAY_OF_YEAR, calDeadline.get(Calendar.DAY_OF_YEAR) + 1);
+        calDeadline.set(Calendar.MILLISECOND, 0);
+        calDeadline.set(Calendar.SECOND, 0);
+        calDeadline.set(Calendar.MINUTE, 0);
+        calDeadline.set(Calendar.HOUR_OF_DAY, 1);
+        mNextMinDailyDeadline = calDeadline.getTimeInMillis();
+        calDeadline.set(Calendar.HOUR_OF_DAY, 3);
+        mNextMaxDailyDeadline = calDeadline.getTimeInMillis();
+    }
+
+    public void recordDailyStatsIfNeededLocked(boolean settled) {
+        long currentTime = System.currentTimeMillis();
+        if (currentTime >= mNextMaxDailyDeadline) {
+            recordDailyStatsLocked();
+        } else if (settled && currentTime >= mNextMinDailyDeadline) {
+            recordDailyStatsLocked();
+        } else if (currentTime < (mDailyStartTime-(1000*60*60*24))) {
+            recordDailyStatsLocked();
+        }
+    }
+
+    public void recordDailyStatsLocked() {
+        DailyItem item = new DailyItem();
+        item.mStartTime = mDailyStartTime;
+        item.mEndTime = System.currentTimeMillis();
+        boolean hasData = false;
+        if (mDailyDischargeStepTracker.mNumStepDurations > 0) {
+            hasData = true;
+            item.mDischargeSteps = new LevelStepTracker(
+                    mDailyDischargeStepTracker.mNumStepDurations,
+                    mDailyDischargeStepTracker.mStepDurations);
+        }
+        if (mDailyChargeStepTracker.mNumStepDurations > 0) {
+            hasData = true;
+            item.mChargeSteps = new LevelStepTracker(
+                    mDailyChargeStepTracker.mNumStepDurations,
+                    mDailyChargeStepTracker.mStepDurations);
+        }
+        if (mDailyPackageChanges != null) {
+            hasData = true;
+            item.mPackageChanges = mDailyPackageChanges;
+            mDailyPackageChanges = null;
+        }
+        mDailyDischargeStepTracker.init();
+        mDailyChargeStepTracker.init();
+        updateDailyDeadlineLocked();
+
+        if (hasData) {
+            mDailyItems.add(item);
+            while (mDailyItems.size() > MAX_DAILY_ITEMS) {
+                mDailyItems.remove(0);
+            }
+            final ByteArrayOutputStream memStream = new ByteArrayOutputStream();
+            try {
+                XmlSerializer out = new FastXmlSerializer();
+                out.setOutput(memStream, StandardCharsets.UTF_8.name());
+                writeDailyItemsLocked(out);
+                BackgroundThread.getHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        synchronized (mCheckinFile) {
+                            FileOutputStream stream = null;
+                            try {
+                                stream = mDailyFile.startWrite();
+                                memStream.writeTo(stream);
+                                stream.flush();
+                                FileUtils.sync(stream);
+                                stream.close();
+                                mDailyFile.finishWrite(stream);
+                            } catch (IOException e) {
+                                Slog.w("BatteryStats",
+                                        "Error writing battery daily items", e);
+                                mDailyFile.failWrite(stream);
+                            }
+                        }
+                    }
+                });
+            } catch (IOException e) {
+            }
+        }
+    }
+
+    private void writeDailyItemsLocked(XmlSerializer out) throws IOException {
+        StringBuilder sb = new StringBuilder(64);
+        out.startDocument(null, true);
+        out.startTag(null, "daily-items");
+        for (int i=0; i<mDailyItems.size(); i++) {
+            final DailyItem dit = mDailyItems.get(i);
+            out.startTag(null, "item");
+            out.attribute(null, "start", Long.toString(dit.mStartTime));
+            out.attribute(null, "end", Long.toString(dit.mEndTime));
+            writeDailyLevelSteps(out, "dis", dit.mDischargeSteps, sb);
+            writeDailyLevelSteps(out, "chg", dit.mChargeSteps, sb);
+            if (dit.mPackageChanges != null) {
+                for (int j=0; j<dit.mPackageChanges.size(); j++) {
+                    PackageChange pc = dit.mPackageChanges.get(j);
+                    if (pc.mUpdate) {
+                        out.startTag(null, "upd");
+                        out.attribute(null, "pkg", pc.mPackageName);
+                        out.attribute(null, "ver", Integer.toString(pc.mVersionCode));
+                        out.endTag(null, "upd");
+                    } else {
+                        out.startTag(null, "rem");
+                        out.attribute(null, "pkg", pc.mPackageName);
+                        out.endTag(null, "rem");
+                    }
+                }
+            }
+            out.endTag(null, "item");
+        }
+        out.endTag(null, "daily-items");
+        out.endDocument();
+    }
+
+    private void writeDailyLevelSteps(XmlSerializer out, String tag, LevelStepTracker steps,
+            StringBuilder tmpBuilder) throws IOException {
+        if (steps != null) {
+            out.startTag(null, tag);
+            out.attribute(null, "n", Integer.toString(steps.mNumStepDurations));
+            for (int i=0; i<steps.mNumStepDurations; i++) {
+                out.startTag(null, "s");
+                tmpBuilder.setLength(0);
+                steps.encodeEntryAt(i, tmpBuilder);
+                out.attribute(null, "v", tmpBuilder.toString());
+                out.endTag(null, "s");
+            }
+            out.endTag(null, tag);
+        }
+    }
+
+    public void readDailyStatsLocked() {
+        Slog.d(TAG, "Reading daily items from " + mDailyFile.getBaseFile());
+        mDailyItems.clear();
+        FileInputStream stream;
+        try {
+            stream = mDailyFile.openRead();
+        } catch (FileNotFoundException e) {
+            return;
+        }
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(stream, StandardCharsets.UTF_8.name());
+            readDailyItemsLocked(parser);
+        } catch (XmlPullParserException e) {
+        } finally {
+            try {
+                stream.close();
+            } catch (IOException e) {
+            }
+        }
+    }
+
+    private void readDailyItemsLocked(XmlPullParser parser) {
+        try {
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                ;
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                throw new IllegalStateException("no start tag found");
+            }
+
+            int outerDepth = parser.getDepth();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                String tagName = parser.getName();
+                if (tagName.equals("item")) {
+                    readDailyItemTagLocked(parser);
+                } else {
+                    Slog.w(TAG, "Unknown element under <daily-items>: "
+                            + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+
+        } catch (IllegalStateException e) {
+            Slog.w(TAG, "Failed parsing daily " + e);
+        } catch (NullPointerException e) {
+            Slog.w(TAG, "Failed parsing daily " + e);
+        } catch (NumberFormatException e) {
+            Slog.w(TAG, "Failed parsing daily " + e);
+        } catch (XmlPullParserException e) {
+            Slog.w(TAG, "Failed parsing daily " + e);
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed parsing daily " + e);
+        } catch (IndexOutOfBoundsException e) {
+            Slog.w(TAG, "Failed parsing daily " + e);
+        }
+    }
+
+    void readDailyItemTagLocked(XmlPullParser parser) throws NumberFormatException,
+            XmlPullParserException, IOException {
+        DailyItem dit = new DailyItem();
+        String attr = parser.getAttributeValue(null, "start");
+        if (attr != null) {
+            dit.mStartTime = Long.parseLong(attr);
+        }
+        attr = parser.getAttributeValue(null, "end");
+        if (attr != null) {
+            dit.mEndTime = Long.parseLong(attr);
+        }
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("dis")) {
+                readDailyItemTagDetailsLocked(parser, dit, false, "dis");
+            } else if (tagName.equals("chg")) {
+                readDailyItemTagDetailsLocked(parser, dit, true, "chg");
+            } else if (tagName.equals("upd")) {
+                if (dit.mPackageChanges == null) {
+                    dit.mPackageChanges = new ArrayList<>();
+                }
+                PackageChange pc = new PackageChange();
+                pc.mUpdate = true;
+                pc.mPackageName = parser.getAttributeValue(null, "pkg");
+                String verStr = parser.getAttributeValue(null, "ver");
+                pc.mVersionCode = verStr != null ? Integer.parseInt(verStr) : 0;
+                dit.mPackageChanges.add(pc);
+                XmlUtils.skipCurrentTag(parser);
+            } else if (tagName.equals("rem")) {
+                if (dit.mPackageChanges == null) {
+                    dit.mPackageChanges = new ArrayList<>();
+                }
+                PackageChange pc = new PackageChange();
+                pc.mUpdate = false;
+                pc.mPackageName = parser.getAttributeValue(null, "pkg");
+                dit.mPackageChanges.add(pc);
+                XmlUtils.skipCurrentTag(parser);
+            } else {
+                Slog.w(TAG, "Unknown element under <item>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+        mDailyItems.add(dit);
+    }
+
+    void readDailyItemTagDetailsLocked(XmlPullParser parser, DailyItem dit, boolean isCharge,
+            String tag)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        final String numAttr = parser.getAttributeValue(null, "n");
+        if (numAttr == null) {
+            Slog.w(TAG, "Missing 'n' attribute at " + parser.getPositionDescription());
+            XmlUtils.skipCurrentTag(parser);
+            return;
+        }
+        final int num = Integer.parseInt(numAttr);
+        LevelStepTracker steps = new LevelStepTracker(num);
+        if (isCharge) {
+            dit.mChargeSteps = steps;
+        } else {
+            dit.mDischargeSteps = steps;
+        }
+        int i = 0;
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if ("s".equals(tagName)) {
+                if (i < num) {
+                    String valueAttr = parser.getAttributeValue(null, "v");
+                    if (valueAttr != null) {
+                        steps.decodeEntryAt(i, valueAttr);
+                        i++;
+                    }
+                }
+            } else {
+                Slog.w(TAG, "Unknown element under <" + tag + ">: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+        steps.mNumStepDurations = i;
+    }
+
+    @Override
+    public DailyItem getDailyItemLocked(int daysAgo) {
+        int index = mDailyItems.size()-1-daysAgo;
+        return index >= 0 ? mDailyItems.get(index) : null;
+    }
+
+    @Override
+    public long getCurrentDailyStartTime() {
+        return mDailyStartTime;
+    }
+
+    @Override
+    public long getNextMinDailyDeadline() {
+        return mNextMinDailyDeadline;
+    }
+
+    @Override
+    public long getNextMaxDailyDeadline() {
+        return mNextMaxDailyDeadline;
+    }
+
+    @Override
+    public boolean startIteratingOldHistoryLocked() {
+        if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize()
+                + " pos=" + mHistoryBuffer.dataPosition());
+        if ((mHistoryIterator = mHistory) == null) {
+            return false;
+        }
+        mHistoryBuffer.setDataPosition(0);
+        mHistoryReadTmp.clear();
+        mReadOverflow = false;
+        mIteratingHistory = true;
+        return true;
+    }
+
+    @Override
+    public boolean getNextOldHistoryLocked(HistoryItem out) {
+        boolean end = mHistoryBuffer.dataPosition() >= mHistoryBuffer.dataSize();
+        if (!end) {
+            readHistoryDelta(mHistoryBuffer, mHistoryReadTmp);
+            mReadOverflow |= mHistoryReadTmp.cmd == HistoryItem.CMD_OVERFLOW;
+        }
+        HistoryItem cur = mHistoryIterator;
+        if (cur == null) {
+            if (!mReadOverflow && !end) {
+                Slog.w(TAG, "Old history ends before new history!");
+            }
+            return false;
+        }
+        out.setTo(cur);
+        mHistoryIterator = cur.next;
+        if (!mReadOverflow) {
+            if (end) {
+                Slog.w(TAG, "New history ends before old history!");
+            } else if (!out.same(mHistoryReadTmp)) {
+                PrintWriter pw = new FastPrintWriter(new LogWriter(android.util.Log.WARN, TAG));
+                pw.println("Histories differ!");
+                pw.println("Old history:");
+                (new HistoryPrinter()).printNextItem(pw, out, 0, false, true);
+                pw.println("New history:");
+                (new HistoryPrinter()).printNextItem(pw, mHistoryReadTmp, 0, false,
+                        true);
+                pw.flush();
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void finishIteratingOldHistoryLocked() {
+        mIteratingHistory = false;
+        mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+        mHistoryIterator = null;
+    }
+
+    public int getHistoryTotalSize() {
+        return MAX_HISTORY_BUFFER;
+    }
+
+    public int getHistoryUsedSize() {
+        return mHistoryBuffer.dataSize();
+    }
+
+    @Override
+    public boolean startIteratingHistoryLocked() {
+        if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize()
+                + " pos=" + mHistoryBuffer.dataPosition());
+        if (mHistoryBuffer.dataSize() <= 0) {
+            return false;
+        }
+        mHistoryBuffer.setDataPosition(0);
+        mReadOverflow = false;
+        mIteratingHistory = true;
+        mReadHistoryStrings = new String[mHistoryTagPool.size()];
+        mReadHistoryUids = new int[mHistoryTagPool.size()];
+        mReadHistoryChars = 0;
+        for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
+            final HistoryTag tag = ent.getKey();
+            final int idx = ent.getValue();
+            mReadHistoryStrings[idx] = tag.string;
+            mReadHistoryUids[idx] = tag.uid;
+            mReadHistoryChars += tag.string.length() + 1;
+        }
+        return true;
+    }
+
+    @Override
+    public int getHistoryStringPoolSize() {
+        return mReadHistoryStrings.length;
+    }
+
+    @Override
+    public int getHistoryStringPoolBytes() {
+        // Each entry is a fixed 12 bytes: 4 for index, 4 for uid, 4 for string size
+        // Each string character is 2 bytes.
+        return (mReadHistoryStrings.length * 12) + (mReadHistoryChars * 2);
+    }
+
+    @Override
+    public String getHistoryTagPoolString(int index) {
+        return mReadHistoryStrings[index];
+    }
+
+    @Override
+    public int getHistoryTagPoolUid(int index) {
+        return mReadHistoryUids[index];
+    }
+
+    @Override
+    public boolean getNextHistoryLocked(HistoryItem out) {
+        final int pos = mHistoryBuffer.dataPosition();
+        if (pos == 0) {
+            out.clear();
+        }
+        boolean end = pos >= mHistoryBuffer.dataSize();
+        if (end) {
+            return false;
+        }
+
+        final long lastRealtime = out.time;
+        final long lastWalltime = out.currentTime;
+        readHistoryDelta(mHistoryBuffer, out);
+        if (out.cmd != HistoryItem.CMD_CURRENT_TIME
+                && out.cmd != HistoryItem.CMD_RESET && lastWalltime != 0) {
+            out.currentTime = lastWalltime + (out.time - lastRealtime);
+        }
+        return true;
+    }
+
+    @Override
+    public void finishIteratingHistoryLocked() {
+        mIteratingHistory = false;
+        mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+        mReadHistoryStrings = null;
+    }
+
+    @Override
+    public long getHistoryBaseTime() {
+        return mHistoryBaseTime;
+    }
+
+    @Override
+    public int getStartCount() {
+        return mStartCount;
+    }
+
+    public boolean isOnBattery() {
+        return mOnBattery;
+    }
+
+    public boolean isCharging() {
+        return mCharging;
+    }
+
+    public boolean isScreenOn() {
+        return mScreenState == Display.STATE_ON;
+    }
+
+    void initTimes(long uptime, long realtime) {
+        mStartClockTime = System.currentTimeMillis();
+        mOnBatteryTimeBase.init(uptime, realtime);
+        mOnBatteryScreenOffTimeBase.init(uptime, realtime);
+        mRealtime = 0;
+        mUptime = 0;
+        mRealtimeStart = realtime;
+        mUptimeStart = uptime;
+    }
+
+    void initDischarge() {
+        mLowDischargeAmountSinceCharge = 0;
+        mHighDischargeAmountSinceCharge = 0;
+        mDischargeAmountScreenOn = 0;
+        mDischargeAmountScreenOnSinceCharge = 0;
+        mDischargeAmountScreenOff = 0;
+        mDischargeAmountScreenOffSinceCharge = 0;
+        mDischargeStepTracker.init();
+        mChargeStepTracker.init();
+        mDischargeScreenOffCounter.reset(false);
+        mDischargeCounter.reset(false);
+    }
+
+    public void resetAllStatsCmdLocked() {
+        resetAllStatsLocked();
+        final long mSecUptime = mClocks.uptimeMillis();
+        long uptime = mSecUptime * 1000;
+        long mSecRealtime = mClocks.elapsedRealtime();
+        long realtime = mSecRealtime * 1000;
+        mDischargeStartLevel = mHistoryCur.batteryLevel;
+        pullPendingStateUpdatesLocked();
+        addHistoryRecordLocked(mSecRealtime, mSecUptime);
+        mDischargeCurrentLevel = mDischargeUnplugLevel = mDischargePlugLevel
+                = mCurrentBatteryLevel = mHistoryCur.batteryLevel;
+        mOnBatteryTimeBase.reset(uptime, realtime);
+        mOnBatteryScreenOffTimeBase.reset(uptime, realtime);
+        if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) {
+            if (mScreenState == Display.STATE_ON) {
+                mDischargeScreenOnUnplugLevel = mHistoryCur.batteryLevel;
+                mDischargeScreenOffUnplugLevel = 0;
+            } else {
+                mDischargeScreenOnUnplugLevel = 0;
+                mDischargeScreenOffUnplugLevel = mHistoryCur.batteryLevel;
+            }
+            mDischargeAmountScreenOn = 0;
+            mDischargeAmountScreenOff = 0;
+        }
+        initActiveHistoryEventsLocked(mSecRealtime, mSecUptime);
+    }
+
+    private void resetAllStatsLocked() {
+        final long uptimeMillis = mClocks.uptimeMillis();
+        final long elapsedRealtimeMillis = mClocks.elapsedRealtime();
+        mStartCount = 0;
+        initTimes(uptimeMillis * 1000, elapsedRealtimeMillis * 1000);
+        mScreenOnTimer.reset(false);
+        for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+            mScreenBrightnessTimer[i].reset(false);
+        }
+
+        if (mPowerProfile != null) {
+            mEstimatedBatteryCapacity = (int) mPowerProfile.getBatteryCapacity();
+        } else {
+            mEstimatedBatteryCapacity = -1;
+        }
+        mMinLearnedBatteryCapacity = -1;
+        mMaxLearnedBatteryCapacity = -1;
+        mInteractiveTimer.reset(false);
+        mPowerSaveModeEnabledTimer.reset(false);
+        mLastIdleTimeStart = elapsedRealtimeMillis;
+        mLongestLightIdleTime = 0;
+        mLongestFullIdleTime = 0;
+        mDeviceIdleModeLightTimer.reset(false);
+        mDeviceIdleModeFullTimer.reset(false);
+        mDeviceLightIdlingTimer.reset(false);
+        mDeviceIdlingTimer.reset(false);
+        mPhoneOnTimer.reset(false);
+        mAudioOnTimer.reset(false);
+        mVideoOnTimer.reset(false);
+        mFlashlightOnTimer.reset(false);
+        mCameraOnTimer.reset(false);
+        mBluetoothScanTimer.reset(false);
+        for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
+            mPhoneSignalStrengthsTimer[i].reset(false);
+        }
+        mPhoneSignalScanningTimer.reset(false);
+        for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
+            mPhoneDataConnectionsTimer[i].reset(false);
+        }
+        for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
+            mNetworkByteActivityCounters[i].reset(false);
+            mNetworkPacketActivityCounters[i].reset(false);
+        }
+        mMobileRadioActiveTimer.reset(false);
+        mMobileRadioActivePerAppTimer.reset(false);
+        mMobileRadioActiveAdjustedTime.reset(false);
+        mMobileRadioActiveUnknownTime.reset(false);
+        mMobileRadioActiveUnknownCount.reset(false);
+        mWifiOnTimer.reset(false);
+        mGlobalWifiRunningTimer.reset(false);
+        for (int i=0; i<NUM_WIFI_STATES; i++) {
+            mWifiStateTimer[i].reset(false);
+        }
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            mWifiSupplStateTimer[i].reset(false);
+        }
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            mWifiSignalStrengthsTimer[i].reset(false);
+        }
+        mWifiActivity.reset(false);
+        mBluetoothActivity.reset(false);
+        mModemActivity.reset(false);
+        mNumConnectivityChange = mLoadedNumConnectivityChange = mUnpluggedNumConnectivityChange = 0;
+
+        for (int i=0; i<mUidStats.size(); i++) {
+            if (mUidStats.valueAt(i).reset(uptimeMillis * 1000, elapsedRealtimeMillis * 1000)) {
+                mUidStats.remove(mUidStats.keyAt(i));
+                i--;
+            }
+        }
+
+        if (mKernelWakelockStats.size() > 0) {
+            for (SamplingTimer timer : mKernelWakelockStats.values()) {
+                mOnBatteryScreenOffTimeBase.remove(timer);
+            }
+            mKernelWakelockStats.clear();
+        }
+
+        if (mKernelMemoryStats.size() > 0) {
+            for (int i = 0; i < mKernelMemoryStats.size(); i++) {
+                mOnBatteryTimeBase.remove(mKernelMemoryStats.valueAt(i));
+            }
+            mKernelMemoryStats.clear();
+        }
+
+        if (mWakeupReasonStats.size() > 0) {
+            for (SamplingTimer timer : mWakeupReasonStats.values()) {
+                mOnBatteryTimeBase.remove(timer);
+            }
+            mWakeupReasonStats.clear();
+        }
+
+        mLastHistoryStepDetails = null;
+        mLastStepCpuUserTime = mLastStepCpuSystemTime = 0;
+        mCurStepCpuUserTime = mCurStepCpuSystemTime = 0;
+        mLastStepCpuUserTime = mCurStepCpuUserTime = 0;
+        mLastStepCpuSystemTime = mCurStepCpuSystemTime = 0;
+        mLastStepStatUserTime = mCurStepStatUserTime = 0;
+        mLastStepStatSystemTime = mCurStepStatSystemTime = 0;
+        mLastStepStatIOWaitTime = mCurStepStatIOWaitTime = 0;
+        mLastStepStatIrqTime = mCurStepStatIrqTime = 0;
+        mLastStepStatSoftIrqTime = mCurStepStatSoftIrqTime = 0;
+        mLastStepStatIdleTime = mCurStepStatIdleTime = 0;
+
+        initDischarge();
+
+        clearHistoryLocked();
+    }
+
+    private void initActiveHistoryEventsLocked(long elapsedRealtimeMs, long uptimeMs) {
+        for (int i=0; i<HistoryItem.EVENT_COUNT; i++) {
+            if (!mRecordAllHistory && i == HistoryItem.EVENT_PROC) {
+                // Not recording process starts/stops.
+                continue;
+            }
+            HashMap<String, SparseIntArray> active = mActiveEvents.getStateForEvent(i);
+            if (active == null) {
+                continue;
+            }
+            for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
+                SparseIntArray uids = ent.getValue();
+                for (int j=0; j<uids.size(); j++) {
+                    addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, i, ent.getKey(),
+                            uids.keyAt(j));
+                }
+            }
+        }
+    }
+
+    void updateDischargeScreenLevelsLocked(boolean oldScreenOn, boolean newScreenOn) {
+        if (oldScreenOn) {
+            int diff = mDischargeScreenOnUnplugLevel - mDischargeCurrentLevel;
+            if (diff > 0) {
+                mDischargeAmountScreenOn += diff;
+                mDischargeAmountScreenOnSinceCharge += diff;
+            }
+        } else {
+            int diff = mDischargeScreenOffUnplugLevel - mDischargeCurrentLevel;
+            if (diff > 0) {
+                mDischargeAmountScreenOff += diff;
+                mDischargeAmountScreenOffSinceCharge += diff;
+            }
+        }
+        if (newScreenOn) {
+            mDischargeScreenOnUnplugLevel = mDischargeCurrentLevel;
+            mDischargeScreenOffUnplugLevel = 0;
+        } else {
+            mDischargeScreenOnUnplugLevel = 0;
+            mDischargeScreenOffUnplugLevel = mDischargeCurrentLevel;
+        }
+    }
+
+    public void pullPendingStateUpdatesLocked() {
+        if (mOnBatteryInternal) {
+            final boolean screenOn = mScreenState == Display.STATE_ON;
+            updateDischargeScreenLevelsLocked(screenOn, screenOn);
+        }
+    }
+
+    private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory();
+    private final Pools.Pool<NetworkStats> mNetworkStatsPool = new Pools.SynchronizedPool<>(6);
+
+    private final Object mWifiNetworkLock = new Object();
+
+    @GuardedBy("mWifiNetworkLock")
+    private String[] mWifiIfaces = EmptyArray.STRING;
+
+    @GuardedBy("mWifiNetworkLock")
+    private NetworkStats mLastWifiNetworkStats = new NetworkStats(0, -1);
+
+    private final Object mModemNetworkLock = new Object();
+
+    @GuardedBy("mModemNetworkLock")
+    private String[] mModemIfaces = EmptyArray.STRING;
+
+    @GuardedBy("mModemNetworkLock")
+    private NetworkStats mLastModemNetworkStats = new NetworkStats(0, -1);
+
+    private NetworkStats readNetworkStatsLocked(String[] ifaces) {
+        try {
+            if (!ArrayUtils.isEmpty(ifaces)) {
+                return mNetworkStatsFactory.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+                        NetworkStats.TAG_NONE, mNetworkStatsPool.acquire());
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "failed to read network stats for ifaces: " + Arrays.toString(ifaces));
+        }
+        return null;
+    }
+
+    /**
+     * Distribute WiFi energy info and network traffic to apps.
+     * @param info The energy information from the WiFi controller.
+     */
+    public void updateWifiState(@Nullable final WifiActivityEnergyInfo info) {
+        if (DEBUG_ENERGY) {
+            Slog.d(TAG, "Updating wifi stats: " + Arrays.toString(mWifiIfaces));
+        }
+
+        // Grab a separate lock to acquire the network stats, which may do I/O.
+        NetworkStats delta = null;
+        synchronized (mWifiNetworkLock) {
+            final NetworkStats latestStats = readNetworkStatsLocked(mWifiIfaces);
+            if (latestStats != null) {
+                delta = NetworkStats.subtract(latestStats, mLastWifiNetworkStats, null, null,
+                        mNetworkStatsPool.acquire());
+                mNetworkStatsPool.release(mLastWifiNetworkStats);
+                mLastWifiNetworkStats = latestStats;
+            }
+        }
+
+        synchronized (this) {
+            if (!mOnBatteryInternal) {
+                if (delta != null) {
+                    mNetworkStatsPool.release(delta);
+                }
+                return;
+            }
+
+            final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+            SparseLongArray rxPackets = new SparseLongArray();
+            SparseLongArray txPackets = new SparseLongArray();
+            long totalTxPackets = 0;
+            long totalRxPackets = 0;
+            if (delta != null) {
+                NetworkStats.Entry entry = new NetworkStats.Entry();
+                final int size = delta.size();
+                for (int i = 0; i < size; i++) {
+                    entry = delta.getValues(i, entry);
+
+                    if (DEBUG_ENERGY) {
+                        Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes
+                                + " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets
+                                + " txPackets=" + entry.txPackets);
+                    }
+
+                    if (entry.rxBytes == 0 && entry.txBytes == 0) {
+                        // Skip the lookup below since there is no work to do.
+                        continue;
+                    }
+
+                    final Uid u = getUidStatsLocked(mapUid(entry.uid));
+                    if (entry.rxBytes != 0) {
+                        u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes,
+                                entry.rxPackets);
+                        if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers
+                            u.noteNetworkActivityLocked(NETWORK_WIFI_BG_RX_DATA, entry.rxBytes,
+                                    entry.rxPackets);
+                        }
+                        mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
+                                entry.rxBytes);
+                        mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
+                                entry.rxPackets);
+
+                        rxPackets.put(u.getUid(), entry.rxPackets);
+
+                        // Sum the total number of packets so that the Rx Power can
+                        // be evenly distributed amongst the apps.
+                        totalRxPackets += entry.rxPackets;
+                    }
+
+                    if (entry.txBytes != 0) {
+                        u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes,
+                                entry.txPackets);
+                        if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers
+                            u.noteNetworkActivityLocked(NETWORK_WIFI_BG_TX_DATA, entry.txBytes,
+                                    entry.txPackets);
+                        }
+                        mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
+                                entry.txBytes);
+                        mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
+                                entry.txPackets);
+
+                        txPackets.put(u.getUid(), entry.txPackets);
+
+                        // Sum the total number of packets so that the Tx Power can
+                        // be evenly distributed amongst the apps.
+                        totalTxPackets += entry.txPackets;
+                    }
+                }
+                mNetworkStatsPool.release(delta);
+                delta = null;
+            }
+
+            if (info != null) {
+                mHasWifiReporting = true;
+
+                // Measured in mAms
+                final long txTimeMs = info.getControllerTxTimeMillis();
+                final long rxTimeMs = info.getControllerRxTimeMillis();
+                final long idleTimeMs = info.getControllerIdleTimeMillis();
+                final long totalTimeMs = txTimeMs + rxTimeMs + idleTimeMs;
+
+                long leftOverRxTimeMs = rxTimeMs;
+                long leftOverTxTimeMs = txTimeMs;
+
+                if (DEBUG_ENERGY) {
+                    Slog.d(TAG, "------ BEGIN WiFi power blaming ------");
+                    Slog.d(TAG, "  Tx Time:    " + txTimeMs + " ms");
+                    Slog.d(TAG, "  Rx Time:    " + rxTimeMs + " ms");
+                    Slog.d(TAG, "  Idle Time:  " + idleTimeMs + " ms");
+                    Slog.d(TAG, "  Total Time: " + totalTimeMs + " ms");
+                }
+
+                long totalWifiLockTimeMs = 0;
+                long totalScanTimeMs = 0;
+
+                // On the first pass, collect some totals so that we can normalize power
+                // calculations if we need to.
+                final int uidStatsSize = mUidStats.size();
+                for (int i = 0; i < uidStatsSize; i++) {
+                    final Uid uid = mUidStats.valueAt(i);
+
+                    // Sum the total scan power for all apps.
+                    totalScanTimeMs += uid.mWifiScanTimer.getTimeSinceMarkLocked(
+                            elapsedRealtimeMs * 1000) / 1000;
+
+                    // Sum the total time holding wifi lock for all apps.
+                    totalWifiLockTimeMs += uid.mFullWifiLockTimer.getTimeSinceMarkLocked(
+                            elapsedRealtimeMs * 1000) / 1000;
+                }
+
+                if (DEBUG_ENERGY && totalScanTimeMs > rxTimeMs) {
+                    Slog.d(TAG,
+                            "  !Estimated scan time > Actual rx time (" + totalScanTimeMs + " ms > "
+                                    + rxTimeMs + " ms). Normalizing scan time.");
+                }
+                if (DEBUG_ENERGY && totalScanTimeMs > txTimeMs) {
+                    Slog.d(TAG,
+                            "  !Estimated scan time > Actual tx time (" + totalScanTimeMs + " ms > "
+                                    + txTimeMs + " ms). Normalizing scan time.");
+                }
+
+                // Actually assign and distribute power usage to apps.
+                for (int i = 0; i < uidStatsSize; i++) {
+                    final Uid uid = mUidStats.valueAt(i);
+
+                    long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked(
+                            elapsedRealtimeMs * 1000) / 1000;
+                    if (scanTimeSinceMarkMs > 0) {
+                        // Set the new mark so that next time we get new data since this point.
+                        uid.mWifiScanTimer.setMark(elapsedRealtimeMs);
+
+                        long scanRxTimeSinceMarkMs = scanTimeSinceMarkMs;
+                        long scanTxTimeSinceMarkMs = scanTimeSinceMarkMs;
+
+                        // Our total scan time is more than the reported Tx/Rx time.
+                        // This is possible because the cost of a scan is approximate.
+                        // Let's normalize the result so that we evenly blame each app
+                        // scanning.
+                        //
+                        // This means that we may have apps that transmitted/received packets not be
+                        // blamed for this, but this is fine as scans are relatively more expensive.
+                        if (totalScanTimeMs > rxTimeMs) {
+                            scanRxTimeSinceMarkMs = (rxTimeMs * scanRxTimeSinceMarkMs) /
+                                    totalScanTimeMs;
+                        }
+                        if (totalScanTimeMs > txTimeMs) {
+                            scanTxTimeSinceMarkMs = (txTimeMs * scanTxTimeSinceMarkMs) /
+                                    totalScanTimeMs;
+                        }
+
+                        if (DEBUG_ENERGY) {
+                            Slog.d(TAG, "  ScanTime for UID " + uid.getUid() + ": Rx:"
+                                    + scanRxTimeSinceMarkMs + " ms  Tx:"
+                                    + scanTxTimeSinceMarkMs + " ms)");
+                        }
+
+                        ControllerActivityCounterImpl activityCounter =
+                                uid.getOrCreateWifiControllerActivityLocked();
+                        activityCounter.getRxTimeCounter().addCountLocked(scanRxTimeSinceMarkMs);
+                        activityCounter.getTxTimeCounters()[0].addCountLocked(
+                                scanTxTimeSinceMarkMs);
+                        leftOverRxTimeMs -= scanRxTimeSinceMarkMs;
+                        leftOverTxTimeMs -= scanTxTimeSinceMarkMs;
+                    }
+
+                    // Distribute evenly the power consumed while Idle to each app holding a WiFi
+                    // lock.
+                    final long wifiLockTimeSinceMarkMs =
+                            uid.mFullWifiLockTimer.getTimeSinceMarkLocked(
+                                    elapsedRealtimeMs * 1000) / 1000;
+                    if (wifiLockTimeSinceMarkMs > 0) {
+                        // Set the new mark so that next time we get new data since this point.
+                        uid.mFullWifiLockTimer.setMark(elapsedRealtimeMs);
+
+                        final long myIdleTimeMs = (wifiLockTimeSinceMarkMs * idleTimeMs)
+                                / totalWifiLockTimeMs;
+                        if (DEBUG_ENERGY) {
+                            Slog.d(TAG, "  IdleTime for UID " + uid.getUid() + ": "
+                                    + myIdleTimeMs + " ms");
+                        }
+                        uid.getOrCreateWifiControllerActivityLocked().getIdleTimeCounter()
+                                .addCountLocked(myIdleTimeMs);
+                    }
+                }
+
+                if (DEBUG_ENERGY) {
+                    Slog.d(TAG, "  New RxPower: " + leftOverRxTimeMs + " ms");
+                    Slog.d(TAG, "  New TxPower: " + leftOverTxTimeMs + " ms");
+                }
+
+                // Distribute the remaining Tx power appropriately between all apps that transmitted
+                // packets.
+                for (int i = 0; i < txPackets.size(); i++) {
+                    final Uid uid = getUidStatsLocked(txPackets.keyAt(i));
+                    final long myTxTimeMs = (txPackets.valueAt(i) * leftOverTxTimeMs)
+                            / totalTxPackets;
+                    if (DEBUG_ENERGY) {
+                        Slog.d(TAG, "  TxTime for UID " + uid.getUid() + ": " + myTxTimeMs + " ms");
+                    }
+                    uid.getOrCreateWifiControllerActivityLocked().getTxTimeCounters()[0]
+                            .addCountLocked(myTxTimeMs);
+                }
+
+                // Distribute the remaining Rx power appropriately between all apps that received
+                // packets.
+                for (int i = 0; i < rxPackets.size(); i++) {
+                    final Uid uid = getUidStatsLocked(rxPackets.keyAt(i));
+                    final long myRxTimeMs = (rxPackets.valueAt(i) * leftOverRxTimeMs)
+                            / totalRxPackets;
+                    if (DEBUG_ENERGY) {
+                        Slog.d(TAG, "  RxTime for UID " + uid.getUid() + ": " + myRxTimeMs + " ms");
+                    }
+                    uid.getOrCreateWifiControllerActivityLocked().getRxTimeCounter()
+                            .addCountLocked(myRxTimeMs);
+                }
+
+                // Any left over power use will be picked up by the WiFi category in BatteryStatsHelper.
+
+
+                // Update WiFi controller stats.
+                mWifiActivity.getRxTimeCounter().addCountLocked(info.getControllerRxTimeMillis());
+                mWifiActivity.getTxTimeCounters()[0].addCountLocked(
+                        info.getControllerTxTimeMillis());
+                mWifiActivity.getIdleTimeCounter().addCountLocked(
+                        info.getControllerIdleTimeMillis());
+
+                // POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
+                final double opVolt = mPowerProfile.getAveragePower(
+                        PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE) / 1000.0;
+                if (opVolt != 0) {
+                    // We store the power drain as mAms.
+                    mWifiActivity.getPowerCounter().addCountLocked(
+                            (long) (info.getControllerEnergyUsed() / opVolt));
+                }
+            }
+        }
+    }
+
+    /**
+     * Distribute Cell radio energy info and network traffic to apps.
+     */
+    public void updateMobileRadioState(@Nullable final ModemActivityInfo activityInfo) {
+        if (DEBUG_ENERGY) {
+            Slog.d(TAG, "Updating mobile radio stats with " + activityInfo);
+        }
+
+        // Grab a separate lock to acquire the network stats, which may do I/O.
+        NetworkStats delta = null;
+        synchronized (mModemNetworkLock) {
+            final NetworkStats latestStats = readNetworkStatsLocked(mModemIfaces);
+            if (latestStats != null) {
+                delta = NetworkStats.subtract(latestStats, mLastModemNetworkStats, null, null,
+                        mNetworkStatsPool.acquire());
+                mNetworkStatsPool.release(mLastModemNetworkStats);
+                mLastModemNetworkStats = latestStats;
+            }
+        }
+
+        synchronized (this) {
+            if (!mOnBatteryInternal) {
+                if (delta != null) {
+                    mNetworkStatsPool.release(delta);
+                }
+                return;
+            }
+
+            final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+            long radioTime = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked(
+                    elapsedRealtimeMs * 1000);
+            mMobileRadioActivePerAppTimer.setMark(elapsedRealtimeMs);
+
+            long totalRxPackets = 0;
+            long totalTxPackets = 0;
+            if (delta != null) {
+                NetworkStats.Entry entry = new NetworkStats.Entry();
+                final int size = delta.size();
+                for (int i = 0; i < size; i++) {
+                    entry = delta.getValues(i, entry);
+                    if (entry.rxPackets == 0 && entry.txPackets == 0) {
+                        continue;
+                    }
+
+                    if (DEBUG_ENERGY) {
+                        Slog.d(TAG, "Mobile uid " + entry.uid + ": delta rx=" + entry.rxBytes
+                                + " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets
+                                + " txPackets=" + entry.txPackets);
+                    }
+
+                    totalRxPackets += entry.rxPackets;
+                    totalTxPackets += entry.txPackets;
+
+                    final Uid u = getUidStatsLocked(mapUid(entry.uid));
+                    u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes,
+                            entry.rxPackets);
+                    u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_DATA, entry.txBytes,
+                            entry.txPackets);
+                    if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers
+                        u.noteNetworkActivityLocked(NETWORK_MOBILE_BG_RX_DATA,
+                                entry.rxBytes, entry.rxPackets);
+                        u.noteNetworkActivityLocked(NETWORK_MOBILE_BG_TX_DATA,
+                                entry.txBytes, entry.txPackets);
+                    }
+
+                    mNetworkByteActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
+                            entry.rxBytes);
+                    mNetworkByteActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
+                            entry.txBytes);
+                    mNetworkPacketActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
+                            entry.rxPackets);
+                    mNetworkPacketActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
+                            entry.txPackets);
+                }
+
+                // Now distribute proportional blame to the apps that did networking.
+                long totalPackets = totalRxPackets + totalTxPackets;
+                if (totalPackets > 0) {
+                    for (int i = 0; i < size; i++) {
+                        entry = delta.getValues(i, entry);
+                        if (entry.rxPackets == 0 && entry.txPackets == 0) {
+                            continue;
+                        }
+
+                        final Uid u = getUidStatsLocked(mapUid(entry.uid));
+
+                        // Distribute total radio active time in to this app.
+                        final long appPackets = entry.rxPackets + entry.txPackets;
+                        final long appRadioTime = (radioTime * appPackets) / totalPackets;
+                        u.noteMobileRadioActiveTimeLocked(appRadioTime);
+
+                        // Remove this app from the totals, so that we don't lose any time
+                        // due to rounding.
+                        radioTime -= appRadioTime;
+                        totalPackets -= appPackets;
+
+                        if (activityInfo != null) {
+                            ControllerActivityCounterImpl activityCounter =
+                                    u.getOrCreateModemControllerActivityLocked();
+                            if (totalRxPackets > 0 && entry.rxPackets > 0) {
+                                final long rxMs = (entry.rxPackets * activityInfo.getRxTimeMillis())
+                                        / totalRxPackets;
+                                activityCounter.getRxTimeCounter().addCountLocked(rxMs);
+                            }
+
+                            if (totalTxPackets > 0 && entry.txPackets > 0) {
+                                for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
+                                    long txMs =
+                                            entry.txPackets * activityInfo.getTxTimeMillis()[lvl];
+                                    txMs /= totalTxPackets;
+                                    activityCounter.getTxTimeCounters()[lvl].addCountLocked(txMs);
+                                }
+                            }
+                        }
+                    }
+                }
+
+                if (radioTime > 0) {
+                    // Whoops, there is some radio time we can't blame on an app!
+                    mMobileRadioActiveUnknownTime.addCountLocked(radioTime);
+                    mMobileRadioActiveUnknownCount.addCountLocked(1);
+                }
+
+                mNetworkStatsPool.release(delta);
+                delta = null;
+            }
+
+            if (activityInfo != null) {
+                mHasModemReporting = true;
+                mModemActivity.getIdleTimeCounter().addCountLocked(
+                        activityInfo.getIdleTimeMillis());
+                mModemActivity.getRxTimeCounter().addCountLocked(activityInfo.getRxTimeMillis());
+                for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
+                    mModemActivity.getTxTimeCounters()[lvl]
+                            .addCountLocked(activityInfo.getTxTimeMillis()[lvl]);
+                }
+
+                // POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
+                final double opVolt = mPowerProfile.getAveragePower(
+                        PowerProfile.POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE) / 1000.0;
+                if (opVolt != 0) {
+                    // We store the power drain as mAms.
+                    mModemActivity.getPowerCounter().addCountLocked(
+                            (long) (activityInfo.getEnergyUsed() / opVolt));
+                }
+            }
+        }
+    }
+
+    /**
+     * Distribute Bluetooth energy info and network traffic to apps.
+     * @param info The energy information from the bluetooth controller.
+     */
+    public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info) {
+        if (DEBUG_ENERGY) {
+            Slog.d(TAG, "Updating bluetooth stats: " + info);
+        }
+
+        if (info == null || !mOnBatteryInternal) {
+            return;
+        }
+
+        mHasBluetoothReporting = true;
+
+        final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+        final long rxTimeMs = info.getControllerRxTimeMillis();
+        final long txTimeMs = info.getControllerTxTimeMillis();
+
+        if (DEBUG_ENERGY) {
+            Slog.d(TAG, "------ BEGIN BLE power blaming ------");
+            Slog.d(TAG, "  Tx Time:    " + txTimeMs + " ms");
+            Slog.d(TAG, "  Rx Time:    " + rxTimeMs + " ms");
+            Slog.d(TAG, "  Idle Time:  " + info.getControllerIdleTimeMillis() + " ms");
+        }
+
+        long totalScanTimeMs = 0;
+
+        final int uidCount = mUidStats.size();
+        for (int i = 0; i < uidCount; i++) {
+            final Uid u = mUidStats.valueAt(i);
+            if (u.mBluetoothScanTimer == null) {
+                continue;
+            }
+
+            totalScanTimeMs += u.mBluetoothScanTimer.getTimeSinceMarkLocked(
+                    elapsedRealtimeMs * 1000) / 1000;
+        }
+
+        final boolean normalizeScanRxTime = (totalScanTimeMs > rxTimeMs);
+        final boolean normalizeScanTxTime = (totalScanTimeMs > txTimeMs);
+
+        if (DEBUG_ENERGY) {
+            Slog.d(TAG, "Normalizing scan power for RX=" + normalizeScanRxTime
+                    + " TX=" + normalizeScanTxTime);
+        }
+
+        long leftOverRxTimeMs = rxTimeMs;
+        long leftOverTxTimeMs = txTimeMs;
+
+        for (int i = 0; i < uidCount; i++) {
+            final Uid u = mUidStats.valueAt(i);
+            if (u.mBluetoothScanTimer == null) {
+                continue;
+            }
+
+            long scanTimeSinceMarkMs = u.mBluetoothScanTimer.getTimeSinceMarkLocked(
+                    elapsedRealtimeMs * 1000) / 1000;
+            if (scanTimeSinceMarkMs > 0) {
+                // Set the new mark so that next time we get new data since this point.
+                u.mBluetoothScanTimer.setMark(elapsedRealtimeMs);
+
+                long scanTimeRxSinceMarkMs = scanTimeSinceMarkMs;
+                long scanTimeTxSinceMarkMs = scanTimeSinceMarkMs;
+
+                if (normalizeScanRxTime) {
+                    // Scan time is longer than the total rx time in the controller,
+                    // so distribute the scan time proportionately. This means regular traffic
+                    // will not blamed, but scans are more expensive anyways.
+                    scanTimeRxSinceMarkMs = (rxTimeMs * scanTimeRxSinceMarkMs) / totalScanTimeMs;
+                }
+
+                if (normalizeScanTxTime) {
+                    // Scan time is longer than the total tx time in the controller,
+                    // so distribute the scan time proportionately. This means regular traffic
+                    // will not blamed, but scans are more expensive anyways.
+                    scanTimeTxSinceMarkMs = (txTimeMs * scanTimeTxSinceMarkMs) / totalScanTimeMs;
+                }
+
+                final ControllerActivityCounterImpl counter =
+                        u.getOrCreateBluetoothControllerActivityLocked();
+                counter.getRxTimeCounter().addCountLocked(scanTimeRxSinceMarkMs);
+                counter.getTxTimeCounters()[0].addCountLocked(scanTimeTxSinceMarkMs);
+
+                leftOverRxTimeMs -= scanTimeRxSinceMarkMs;
+                leftOverTxTimeMs -= scanTimeTxSinceMarkMs;
+            }
+        }
+
+        if (DEBUG_ENERGY) {
+            Slog.d(TAG, "Left over time for traffic RX=" + leftOverRxTimeMs
+                    + " TX=" + leftOverTxTimeMs);
+        }
+
+        //
+        // Now distribute blame to apps that did bluetooth traffic.
+        //
+
+        long totalTxBytes = 0;
+        long totalRxBytes = 0;
+
+        final UidTraffic[] uidTraffic = info.getUidTraffic();
+        final int numUids = uidTraffic != null ? uidTraffic.length : 0;
+        for (int i = 0; i < numUids; i++) {
+            final UidTraffic traffic = uidTraffic[i];
+
+            // Add to the global counters.
+            mNetworkByteActivityCounters[NETWORK_BT_RX_DATA].addCountLocked(
+                    traffic.getRxBytes());
+            mNetworkByteActivityCounters[NETWORK_BT_TX_DATA].addCountLocked(
+                    traffic.getTxBytes());
+
+            // Add to the UID counters.
+            final Uid u = getUidStatsLocked(mapUid(traffic.getUid()));
+            u.noteNetworkActivityLocked(NETWORK_BT_RX_DATA, traffic.getRxBytes(), 0);
+            u.noteNetworkActivityLocked(NETWORK_BT_TX_DATA, traffic.getTxBytes(), 0);
+
+            // Calculate the total traffic.
+            totalTxBytes += traffic.getTxBytes();
+            totalRxBytes += traffic.getRxBytes();
+        }
+
+        if ((totalTxBytes != 0 || totalRxBytes != 0) &&
+                (leftOverRxTimeMs != 0 || leftOverTxTimeMs != 0)) {
+            for (int i = 0; i < numUids; i++) {
+                final UidTraffic traffic = uidTraffic[i];
+
+                final Uid u = getUidStatsLocked(mapUid(traffic.getUid()));
+                final ControllerActivityCounterImpl counter =
+                        u.getOrCreateBluetoothControllerActivityLocked();
+
+                if (totalRxBytes > 0 && traffic.getRxBytes() > 0) {
+                    final long timeRxMs = (leftOverRxTimeMs * traffic.getRxBytes()) / totalRxBytes;
+
+                    if (DEBUG_ENERGY) {
+                        Slog.d(TAG, "UID=" + traffic.getUid() + " rx_bytes=" + traffic.getRxBytes()
+                                + " rx_time=" + timeRxMs);
+                    }
+                    counter.getRxTimeCounter().addCountLocked(timeRxMs);
+                    leftOverRxTimeMs -= timeRxMs;
+                }
+
+                if (totalTxBytes > 0 && traffic.getTxBytes() > 0) {
+                    final long timeTxMs = (leftOverTxTimeMs * traffic.getTxBytes()) / totalTxBytes;
+
+                    if (DEBUG_ENERGY) {
+                        Slog.d(TAG, "UID=" + traffic.getUid() + " tx_bytes=" + traffic.getTxBytes()
+                                + " tx_time=" + timeTxMs);
+                    }
+
+                    counter.getTxTimeCounters()[0].addCountLocked(timeTxMs);
+                    leftOverTxTimeMs -= timeTxMs;
+                }
+            }
+        }
+
+        mBluetoothActivity.getRxTimeCounter().addCountLocked(
+                info.getControllerRxTimeMillis());
+        mBluetoothActivity.getTxTimeCounters()[0].addCountLocked(
+                info.getControllerTxTimeMillis());
+        mBluetoothActivity.getIdleTimeCounter().addCountLocked(
+                info.getControllerIdleTimeMillis());
+
+        // POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
+        final double opVolt = mPowerProfile.getAveragePower(
+                PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE) / 1000.0;
+        if (opVolt != 0) {
+            // We store the power drain as mAms.
+            mBluetoothActivity.getPowerCounter().addCountLocked(
+                    (long) (info.getControllerEnergyUsed() / opVolt));
+        }
+    }
+
+    /**
+     * Read and distribute kernel wake lock use across apps.
+     */
+    public void updateKernelWakelocksLocked() {
+        final KernelWakelockStats wakelockStats = mKernelWakelockReader.readKernelWakelockStats(
+                mTmpWakelockStats);
+        if (wakelockStats == null) {
+            // Not crashing might make board bringup easier.
+            Slog.w(TAG, "Couldn't get kernel wake lock stats");
+            return;
+        }
+
+        for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+            String name = ent.getKey();
+            KernelWakelockStats.Entry kws = ent.getValue();
+
+            SamplingTimer kwlt = mKernelWakelockStats.get(name);
+            if (kwlt == null) {
+                kwlt = new SamplingTimer(mClocks, mOnBatteryScreenOffTimeBase);
+                mKernelWakelockStats.put(name, kwlt);
+            }
+
+            kwlt.update(kws.mTotalTime, kws.mCount);
+            kwlt.setUpdateVersion(kws.mVersion);
+        }
+
+        int numWakelocksSetStale = 0;
+        // Set timers to stale if they didn't appear in /d/wakeup_sources (or /proc/wakelocks)
+        // this time.
+        for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
+            SamplingTimer st = ent.getValue();
+            if (st.getUpdateVersion() != wakelockStats.kernelWakelockVersion) {
+                st.endSample();
+                numWakelocksSetStale++;
+            }
+        }
+
+        // Record whether we've seen a non-zero time (for debugging b/22716723).
+        if (wakelockStats.isEmpty()) {
+            Slog.wtf(TAG, "All kernel wakelocks had time of zero");
+        }
+
+        if (numWakelocksSetStale == mKernelWakelockStats.size()) {
+            Slog.wtf(TAG, "All kernel wakelocks were set stale. new version=" +
+                    wakelockStats.kernelWakelockVersion);
+        }
+    }
+
+    // We use an anonymous class to access these variables,
+    // so they can't live on the stack or they'd have to be
+    // final MutableLong objects (more allocations).
+    // Used in updateCpuTimeLocked().
+    long mTempTotalCpuUserTimeUs;
+    long mTempTotalCpuSystemTimeUs;
+    long[][] mWakeLockAllocationsUs;
+
+    /**
+     * Reads the newest memory stats from the kernel.
+     */
+    public void updateKernelMemoryBandwidthLocked() {
+        mKernelMemoryBandwidthStats.updateStats();
+        LongSparseLongArray bandwidthEntries = mKernelMemoryBandwidthStats.getBandwidthEntries();
+        final int bandwidthEntryCount = bandwidthEntries.size();
+        int index;
+        for (int i = 0; i < bandwidthEntryCount; i++) {
+            SamplingTimer timer;
+            if ((index = mKernelMemoryStats.indexOfKey(bandwidthEntries.keyAt(i))) >= 0) {
+                timer = mKernelMemoryStats.valueAt(index);
+            } else {
+                timer = new SamplingTimer(mClocks, mOnBatteryTimeBase);
+                mKernelMemoryStats.put(bandwidthEntries.keyAt(i), timer);
+            }
+            timer.update(bandwidthEntries.valueAt(i), 1);
+            if (DEBUG_MEMORY) {
+                Slog.d(TAG, String.format("Added entry %d and updated timer to: "
+                        + "mUnpluggedReportedTotalTime %d size %d", bandwidthEntries.keyAt(i),
+                        mKernelMemoryStats.get(
+                                bandwidthEntries.keyAt(i)).mUnpluggedReportedTotalTime,
+                        mKernelMemoryStats.size()));
+            }
+        }
+    }
+
+    /**
+     * Read and distribute CPU usage across apps. If their are partial wakelocks being held
+     * and we are on battery with screen off, we give more of the cpu time to those apps holding
+     * wakelocks. If the screen is on, we just assign the actual cpu time an app used.
+     */
+    public void updateCpuTimeLocked() {
+        if (mPowerProfile == null) {
+            return;
+        }
+
+        if (DEBUG_ENERGY_CPU) {
+            Slog.d(TAG, "!Cpu updating!");
+        }
+
+        if (mCpuFreqs == null) {
+            mCpuFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
+        }
+
+        // Calculate the wakelocks we have to distribute amongst. The system is excluded as it is
+        // usually holding the wakelock on behalf of an app.
+        // And Only distribute cpu power to wakelocks if the screen is off and we're on battery.
+        ArrayList<StopwatchTimer> partialTimersToConsider = null;
+        if (mOnBatteryScreenOffTimeBase.isRunning()) {
+            partialTimersToConsider = new ArrayList<>();
+            for (int i = mPartialTimers.size() - 1; i >= 0; --i) {
+                final StopwatchTimer timer = mPartialTimers.get(i);
+                // Since the collection and blaming of wakelocks can be scheduled to run after
+                // some delay, the mPartialTimers list may have new entries. We can't blame
+                // the newly added timer for past cpu time, so we only consider timers that
+                // were present for one round of collection. Once a timer has gone through
+                // a round of collection, its mInList field is set to true.
+                if (timer.mInList && timer.mUid != null && timer.mUid.mUid != Process.SYSTEM_UID) {
+                    partialTimersToConsider.add(timer);
+                }
+            }
+        }
+        markPartialTimersAsEligible();
+
+        // When the battery is not on, we don't attribute the cpu times to any timers but we still
+        // need to take the snapshots.
+        if (!mOnBatteryInternal) {
+            mKernelUidCpuTimeReader.readDelta(null);
+            mKernelUidCpuFreqTimeReader.readDelta(null);
+            for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) {
+                mKernelCpuSpeedReaders[cluster].readDelta();
+            }
+            return;
+        }
+
+        mUserInfoProvider.refreshUserIds();
+        final SparseLongArray updatedUids = mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()
+                ? null : new SparseLongArray();
+        readKernelUidCpuTimesLocked(partialTimersToConsider, updatedUids);
+        // updatedUids=null means /proc/uid_time_in_state provides snapshots of per-cluster cpu
+        // freqs, so no need to approximate these values.
+        if (updatedUids != null) {
+            updateClusterSpeedTimes(updatedUids);
+        }
+        readKernelUidCpuFreqTimesLocked(partialTimersToConsider);
+    }
+
+    /**
+     * Mark the current partial timers as gone through a collection so that they will be
+     * considered in the next cpu times distribution to wakelock holders.
+     */
+    @VisibleForTesting
+    public void markPartialTimersAsEligible() {
+        if (ArrayUtils.referenceEquals(mPartialTimers, mLastPartialTimers)) {
+            // No difference, so each timer is now considered for the next collection.
+            for (int i = mPartialTimers.size() - 1; i >= 0; --i) {
+                mPartialTimers.get(i).mInList = true;
+            }
+        } else {
+            // The lists are different, meaning we added (or removed a timer) since the last
+            // collection.
+            for (int i = mLastPartialTimers.size() - 1; i >= 0; --i) {
+                mLastPartialTimers.get(i).mInList = false;
+            }
+            mLastPartialTimers.clear();
+
+            // Mark the current timers as gone through a collection.
+            final int numPartialTimers = mPartialTimers.size();
+            for (int i = 0; i < numPartialTimers; ++i) {
+                final StopwatchTimer timer = mPartialTimers.get(i);
+                timer.mInList = true;
+                mLastPartialTimers.add(timer);
+            }
+        }
+    }
+
+    /**
+     * Take snapshot of cpu times (aggregated over all uids) at different frequencies and
+     * calculate cpu times spent by each uid at different frequencies.
+     *
+     * @param updatedUids The uids for which times spent at different frequencies are calculated.
+     */
+    @VisibleForTesting
+    public void updateClusterSpeedTimes(@NonNull SparseLongArray updatedUids) {
+        long totalCpuClustersTimeMs = 0;
+        // Read the time spent for each cluster at various cpu frequencies.
+        final long[][] clusterSpeedTimesMs = new long[mKernelCpuSpeedReaders.length][];
+        for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
+            clusterSpeedTimesMs[cluster] = mKernelCpuSpeedReaders[cluster].readDelta();
+            if (clusterSpeedTimesMs[cluster] != null) {
+                for (int speed = clusterSpeedTimesMs[cluster].length - 1; speed >= 0; --speed) {
+                    totalCpuClustersTimeMs += clusterSpeedTimesMs[cluster][speed];
+                }
+            }
+        }
+        if (totalCpuClustersTimeMs != 0) {
+            // We have cpu times per freq aggregated over all uids but we need the times per uid.
+            // So, we distribute total time spent by an uid to different cpu freqs based on the
+            // amount of time cpu was running at that freq.
+            final int updatedUidsCount = updatedUids.size();
+            for (int i = 0; i < updatedUidsCount; ++i) {
+                final Uid u = getUidStatsLocked(updatedUids.keyAt(i));
+                final long appCpuTimeUs = updatedUids.valueAt(i);
+                // Add the cpu speeds to this UID.
+                final int numClusters = mPowerProfile.getNumCpuClusters();
+                if (u.mCpuClusterSpeedTimesUs == null ||
+                        u.mCpuClusterSpeedTimesUs.length != numClusters) {
+                    u.mCpuClusterSpeedTimesUs = new LongSamplingCounter[numClusters][];
+                }
+
+                for (int cluster = 0; cluster < clusterSpeedTimesMs.length; cluster++) {
+                    final int speedsInCluster = clusterSpeedTimesMs[cluster].length;
+                    if (u.mCpuClusterSpeedTimesUs[cluster] == null || speedsInCluster !=
+                            u.mCpuClusterSpeedTimesUs[cluster].length) {
+                        u.mCpuClusterSpeedTimesUs[cluster]
+                                = new LongSamplingCounter[speedsInCluster];
+                    }
+
+                    final LongSamplingCounter[] cpuSpeeds = u.mCpuClusterSpeedTimesUs[cluster];
+                    for (int speed = 0; speed < speedsInCluster; speed++) {
+                        if (cpuSpeeds[speed] == null) {
+                            cpuSpeeds[speed] = new LongSamplingCounter(mOnBatteryTimeBase);
+                        }
+                        cpuSpeeds[speed].addCountLocked(appCpuTimeUs
+                                * clusterSpeedTimesMs[cluster][speed]
+                                / totalCpuClustersTimeMs);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Take a snapshot of the cpu times spent by each uid and update the corresponding counters.
+     * If {@param partialTimers} is not null and empty, then we assign a portion of cpu times to
+     * wakelock holders.
+     *
+     * @param partialTimers The wakelock holders among which the cpu times will be distributed.
+     * @param updatedUids If not null, then the uids found in the snapshot will be added to this.
+     */
+    @VisibleForTesting
+    public void readKernelUidCpuTimesLocked(@Nullable ArrayList<StopwatchTimer> partialTimers,
+            @Nullable SparseLongArray updatedUids) {
+        mTempTotalCpuUserTimeUs = mTempTotalCpuSystemTimeUs = 0;
+        final int numWakelocks = partialTimers == null ? 0 : partialTimers.size();
+        final long startTimeMs = mClocks.uptimeMillis();
+
+        mKernelUidCpuTimeReader.readDelta((uid, userTimeUs, systemTimeUs) -> {
+            uid = mapUid(uid);
+            if (Process.isIsolated(uid)) {
+                // This could happen if the isolated uid mapping was removed before that process
+                // was actually killed.
+                mKernelUidCpuTimeReader.removeUid(uid);
+                Slog.d(TAG, "Got readings for an isolated uid with no mapping: " + uid);
+                return;
+            }
+            if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
+                Slog.d(TAG, "Got readings for an invalid user's uid " + uid);
+                mKernelUidCpuTimeReader.removeUid(uid);
+                return;
+            }
+            final Uid u = getUidStatsLocked(uid);
+
+            // Accumulate the total system and user time.
+            mTempTotalCpuUserTimeUs += userTimeUs;
+            mTempTotalCpuSystemTimeUs += systemTimeUs;
+
+            StringBuilder sb = null;
+            if (DEBUG_ENERGY_CPU) {
+                sb = new StringBuilder();
+                sb.append("  got time for uid=").append(u.mUid).append(": u=");
+                TimeUtils.formatDuration(userTimeUs / 1000, sb);
+                sb.append(" s=");
+                TimeUtils.formatDuration(systemTimeUs / 1000, sb);
+                sb.append("\n");
+            }
+
+            if (numWakelocks > 0) {
+                // We have wakelocks being held, so only give a portion of the
+                // time to the process. The rest will be distributed among wakelock
+                // holders.
+                userTimeUs = (userTimeUs * WAKE_LOCK_WEIGHT) / 100;
+                systemTimeUs = (systemTimeUs * WAKE_LOCK_WEIGHT) / 100;
+            }
+
+            if (sb != null) {
+                sb.append("  adding to uid=").append(u.mUid).append(": u=");
+                TimeUtils.formatDuration(userTimeUs / 1000, sb);
+                sb.append(" s=");
+                TimeUtils.formatDuration(systemTimeUs / 1000, sb);
+                Slog.d(TAG, sb.toString());
+            }
+
+            u.mUserCpuTime.addCountLocked(userTimeUs);
+            u.mSystemCpuTime.addCountLocked(systemTimeUs);
+            if (updatedUids != null) {
+                updatedUids.put(u.getUid(), userTimeUs + systemTimeUs);
+            }
+        });
+
+        final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
+        if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
+            Slog.d(TAG, "Reading cpu stats took " + elapsedTimeMs + "ms");
+        }
+
+        if (numWakelocks > 0) {
+            // Distribute a portion of the total cpu time to wakelock holders.
+            mTempTotalCpuUserTimeUs = (mTempTotalCpuUserTimeUs * (100 - WAKE_LOCK_WEIGHT)) / 100;
+            mTempTotalCpuSystemTimeUs =
+                    (mTempTotalCpuSystemTimeUs * (100 - WAKE_LOCK_WEIGHT)) / 100;
+
+            for (int i = 0; i < numWakelocks; ++i) {
+                final StopwatchTimer timer = partialTimers.get(i);
+                final int userTimeUs = (int) (mTempTotalCpuUserTimeUs / (numWakelocks - i));
+                final int systemTimeUs = (int) (mTempTotalCpuSystemTimeUs / (numWakelocks - i));
+
+                if (DEBUG_ENERGY_CPU) {
+                    final StringBuilder sb = new StringBuilder();
+                    sb.append("  Distributing wakelock uid=").append(timer.mUid.mUid)
+                            .append(": u=");
+                    TimeUtils.formatDuration(userTimeUs / 1000, sb);
+                    sb.append(" s=");
+                    TimeUtils.formatDuration(systemTimeUs / 1000, sb);
+                    Slog.d(TAG, sb.toString());
+                }
+
+                timer.mUid.mUserCpuTime.addCountLocked(userTimeUs);
+                timer.mUid.mSystemCpuTime.addCountLocked(systemTimeUs);
+                if (updatedUids != null) {
+                    final int uid = timer.mUid.getUid();
+                    updatedUids.put(uid, updatedUids.get(uid, 0) + userTimeUs + systemTimeUs);
+                }
+
+                final Uid.Proc proc = timer.mUid.getProcessStatsLocked("*wakelock*");
+                proc.addCpuTimeLocked(userTimeUs / 1000, systemTimeUs / 1000);
+
+                mTempTotalCpuUserTimeUs -= userTimeUs;
+                mTempTotalCpuSystemTimeUs -= systemTimeUs;
+            }
+        }
+    }
+
+    /**
+     * Take a snapshot of the cpu times spent by each uid in each freq and update the
+     * corresponding counters.
+     *
+     * @param partialTimers The wakelock holders among which the cpu freq times will be distributed.
+     */
+    @VisibleForTesting
+    public void readKernelUidCpuFreqTimesLocked(@Nullable ArrayList<StopwatchTimer> partialTimers) {
+        final boolean perClusterTimesAvailable =
+                mKernelUidCpuFreqTimeReader.perClusterTimesAvailable();
+        final int numWakelocks = partialTimers == null ? 0 : partialTimers.size();
+        final int numClusters = mPowerProfile.getNumCpuClusters();
+        mWakeLockAllocationsUs = null;
+        mKernelUidCpuFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> {
+            uid = mapUid(uid);
+            if (Process.isIsolated(uid)) {
+                mKernelUidCpuFreqTimeReader.removeUid(uid);
+                Slog.d(TAG, "Got freq readings for an isolated uid with no mapping: " + uid);
+                return;
+            }
+            if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
+                Slog.d(TAG, "Got freq readings for an invalid user's uid " + uid);
+                mKernelUidCpuFreqTimeReader.removeUid(uid);
+                return;
+            }
+            final Uid u = getUidStatsLocked(uid);
+            if (u.mCpuFreqTimeMs == null || u.mCpuFreqTimeMs.getSize() != cpuFreqTimeMs.length) {
+                u.mCpuFreqTimeMs = new LongSamplingCounterArray(mOnBatteryTimeBase);
+            }
+            u.mCpuFreqTimeMs.addCountLocked(cpuFreqTimeMs);
+            if (u.mScreenOffCpuFreqTimeMs == null ||
+                    u.mScreenOffCpuFreqTimeMs.getSize() != cpuFreqTimeMs.length) {
+                u.mScreenOffCpuFreqTimeMs = new LongSamplingCounterArray(
+                        mOnBatteryScreenOffTimeBase);
+            }
+            u.mScreenOffCpuFreqTimeMs.addCountLocked(cpuFreqTimeMs);
+
+            if (perClusterTimesAvailable) {
+                if (u.mCpuClusterSpeedTimesUs == null ||
+                        u.mCpuClusterSpeedTimesUs.length != numClusters) {
+                    u.mCpuClusterSpeedTimesUs = new LongSamplingCounter[numClusters][];
+                }
+                if (numWakelocks > 0 && mWakeLockAllocationsUs == null) {
+                    mWakeLockAllocationsUs = new long[numClusters][];
+                }
+
+                int freqIndex = 0;
+                for (int cluster = 0; cluster < numClusters; ++cluster) {
+                    final int speedsInCluster = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+                    if (u.mCpuClusterSpeedTimesUs[cluster] == null ||
+                            u.mCpuClusterSpeedTimesUs[cluster].length != speedsInCluster) {
+                        u.mCpuClusterSpeedTimesUs[cluster]
+                                = new LongSamplingCounter[speedsInCluster];
+                    }
+                    if (numWakelocks > 0 && mWakeLockAllocationsUs[cluster] == null) {
+                        mWakeLockAllocationsUs[cluster] = new long[speedsInCluster];
+                    }
+                    final LongSamplingCounter[] cpuTimesUs = u.mCpuClusterSpeedTimesUs[cluster];
+                    for (int speed = 0; speed < speedsInCluster; ++speed) {
+                        if (cpuTimesUs[speed] == null) {
+                            cpuTimesUs[speed] = new LongSamplingCounter(mOnBatteryTimeBase);
+                        }
+                        final long appAllocationUs;
+                        if (mWakeLockAllocationsUs != null) {
+                            appAllocationUs =
+                                    (cpuFreqTimeMs[freqIndex] * 1000 * WAKE_LOCK_WEIGHT) / 100;
+                            mWakeLockAllocationsUs[cluster][speed] +=
+                                    (cpuFreqTimeMs[freqIndex] * 1000 - appAllocationUs);
+                        } else {
+                            appAllocationUs = cpuFreqTimeMs[freqIndex] * 1000;
+                        }
+                        cpuTimesUs[speed].addCountLocked(appAllocationUs);
+                        freqIndex++;
+                    }
+                }
+            }
+        });
+
+        if (mWakeLockAllocationsUs != null) {
+            for (int i = 0; i < numWakelocks; ++i) {
+                final Uid u = partialTimers.get(i).mUid;
+                if (u.mCpuClusterSpeedTimesUs == null ||
+                        u.mCpuClusterSpeedTimesUs.length != numClusters) {
+                    u.mCpuClusterSpeedTimesUs = new LongSamplingCounter[numClusters][];
+                }
+
+                for (int cluster = 0; cluster < numClusters; ++cluster) {
+                    final int speedsInCluster = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+                    if (u.mCpuClusterSpeedTimesUs[cluster] == null ||
+                            u.mCpuClusterSpeedTimesUs[cluster].length != speedsInCluster) {
+                        u.mCpuClusterSpeedTimesUs[cluster]
+                                = new LongSamplingCounter[speedsInCluster];
+                    }
+                    final LongSamplingCounter[] cpuTimeUs = u.mCpuClusterSpeedTimesUs[cluster];
+                    for (int speed = 0; speed < speedsInCluster; ++speed) {
+                        if (cpuTimeUs[speed] == null) {
+                            cpuTimeUs[speed] = new LongSamplingCounter(mOnBatteryTimeBase);
+                        }
+                        final long allocationUs =
+                                mWakeLockAllocationsUs[cluster][speed] / (numWakelocks - i);
+                        cpuTimeUs[speed].addCountLocked(allocationUs);
+                        mWakeLockAllocationsUs[cluster][speed] -= allocationUs;
+                    }
+                }
+            }
+        }
+    }
+
+    boolean setChargingLocked(boolean charging) {
+        if (mCharging != charging) {
+            mCharging = charging;
+            if (charging) {
+                mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
+            } else {
+                mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG;
+            }
+            mHandler.sendEmptyMessage(MSG_REPORT_CHARGING);
+            return true;
+        }
+        return false;
+    }
+
+    void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery,
+            final int oldStatus, final int level, final int chargeUAh) {
+        boolean doWrite = false;
+        Message m = mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE);
+        m.arg1 = onBattery ? 1 : 0;
+        mHandler.sendMessage(m);
+
+        final long uptime = mSecUptime * 1000;
+        final long realtime = mSecRealtime * 1000;
+        final boolean screenOn = mScreenState == Display.STATE_ON;
+        if (onBattery) {
+            // We will reset our status if we are unplugging after the
+            // battery was last full, or the level is at 100, or
+            // we have gone through a significant charge (from a very low
+            // level to a now very high level).
+            boolean reset = false;
+            if (!mNoAutoReset && (oldStatus == BatteryManager.BATTERY_STATUS_FULL
+                    || level >= 90
+                    || (mDischargeCurrentLevel < 20 && level >= 80)
+                    || (getHighDischargeAmountSinceCharge() >= 200
+                            && mHistoryBuffer.dataSize() >= MAX_HISTORY_BUFFER))) {
+                Slog.i(TAG, "Resetting battery stats: level=" + level + " status=" + oldStatus
+                        + " dischargeLevel=" + mDischargeCurrentLevel
+                        + " lowAmount=" + getLowDischargeAmountSinceCharge()
+                        + " highAmount=" + getHighDischargeAmountSinceCharge());
+                // Before we write, collect a snapshot of the final aggregated
+                // stats to be reported in the next checkin.  Only do this if we have
+                // a sufficient amount of data to make it interesting.
+                if (getLowDischargeAmountSinceCharge() >= 20) {
+                    final Parcel parcel = Parcel.obtain();
+                    writeSummaryToParcel(parcel, true);
+                    BackgroundThread.getHandler().post(new Runnable() {
+                        @Override public void run() {
+                            synchronized (mCheckinFile) {
+                                FileOutputStream stream = null;
+                                try {
+                                    stream = mCheckinFile.startWrite();
+                                    stream.write(parcel.marshall());
+                                    stream.flush();
+                                    FileUtils.sync(stream);
+                                    stream.close();
+                                    mCheckinFile.finishWrite(stream);
+                                } catch (IOException e) {
+                                    Slog.w("BatteryStats",
+                                            "Error writing checkin battery statistics", e);
+                                    mCheckinFile.failWrite(stream);
+                                } finally {
+                                    parcel.recycle();
+                                }
+                            }
+                        }
+                    });
+                }
+                doWrite = true;
+                resetAllStatsLocked();
+                if (chargeUAh > 0 && level > 0) {
+                    // Only use the reported coulomb charge value if it is supported and reported.
+                    mEstimatedBatteryCapacity = (int) ((chargeUAh / 1000) / (level / 100.0));
+                }
+                mDischargeStartLevel = level;
+                reset = true;
+                mDischargeStepTracker.init();
+            }
+            if (mCharging) {
+                setChargingLocked(false);
+            }
+            mLastChargingStateLevel = level;
+            mOnBattery = mOnBatteryInternal = true;
+            mLastDischargeStepLevel = level;
+            mMinDischargeStepLevel = level;
+            mDischargeStepTracker.clearTime();
+            mDailyDischargeStepTracker.clearTime();
+            mInitStepMode = mCurStepMode;
+            mModStepMode = 0;
+            pullPendingStateUpdatesLocked();
+            mHistoryCur.batteryLevel = (byte)level;
+            mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            if (reset) {
+                mRecordingHistory = true;
+                startRecordingHistory(mSecRealtime, mSecUptime, reset);
+            }
+            addHistoryRecordLocked(mSecRealtime, mSecUptime);
+            mDischargeCurrentLevel = mDischargeUnplugLevel = level;
+            if (screenOn) {
+                mDischargeScreenOnUnplugLevel = level;
+                mDischargeScreenOffUnplugLevel = 0;
+            } else {
+                mDischargeScreenOnUnplugLevel = 0;
+                mDischargeScreenOffUnplugLevel = level;
+            }
+            mDischargeAmountScreenOn = 0;
+            mDischargeAmountScreenOff = 0;
+            updateTimeBasesLocked(true, !screenOn, uptime, realtime);
+        } else {
+            mLastChargingStateLevel = level;
+            mOnBattery = mOnBatteryInternal = false;
+            pullPendingStateUpdatesLocked();
+            mHistoryCur.batteryLevel = (byte)level;
+            mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(mSecRealtime, mSecUptime);
+            mDischargeCurrentLevel = mDischargePlugLevel = level;
+            if (level < mDischargeUnplugLevel) {
+                mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1;
+                mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level;
+            }
+            updateDischargeScreenLevelsLocked(screenOn, screenOn);
+            updateTimeBasesLocked(false, !screenOn, uptime, realtime);
+            mChargeStepTracker.init();
+            mLastChargeStepLevel = level;
+            mMaxChargeStepLevel = level;
+            mInitStepMode = mCurStepMode;
+            mModStepMode = 0;
+        }
+        if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) {
+            if (mFile != null) {
+                writeAsyncLocked();
+            }
+        }
+    }
+
+    private void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs,
+            boolean reset) {
+        mRecordingHistory = true;
+        mHistoryCur.currentTime = System.currentTimeMillis();
+        addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs,
+                reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME,
+                mHistoryCur);
+        mHistoryCur.currentTime = 0;
+        if (reset) {
+            initActiveHistoryEventsLocked(elapsedRealtimeMs, uptimeMs);
+        }
+    }
+
+    private void recordCurrentTimeChangeLocked(final long currentTime, final long elapsedRealtimeMs,
+            final long uptimeMs) {
+        if (mRecordingHistory) {
+            mHistoryCur.currentTime = currentTime;
+            addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_CURRENT_TIME,
+                    mHistoryCur);
+            mHistoryCur.currentTime = 0;
+        }
+    }
+
+    private void recordShutdownLocked(final long elapsedRealtimeMs, final long uptimeMs) {
+        if (mRecordingHistory) {
+            mHistoryCur.currentTime = System.currentTimeMillis();
+            addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_SHUTDOWN,
+                    mHistoryCur);
+            mHistoryCur.currentTime = 0;
+        }
+    }
+
+    private void scheduleSyncExternalStatsLocked(String reason, int updateFlags) {
+        if (mExternalSync != null) {
+            mExternalSync.scheduleSync(reason, updateFlags);
+        }
+    }
+
+    // This should probably be exposed in the API, though it's not critical
+    public static final int BATTERY_PLUGGED_NONE = 0;
+
+    public void setBatteryStateLocked(int status, int health, int plugType, int level,
+            int temp, int volt, int chargeUAh, int chargeFullUAh) {
+        // Temperature is encoded without the signed bit, so clamp any negative temperatures to 0.
+        temp = Math.max(0, temp);
+
+        final boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
+        final long uptime = mClocks.uptimeMillis();
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        if (!mHaveBatteryLevel) {
+            mHaveBatteryLevel = true;
+            // We start out assuming that the device is plugged in (not
+            // on battery).  If our first report is now that we are indeed
+            // plugged in, then twiddle our state to correctly reflect that
+            // since we won't be going through the full setOnBattery().
+            if (onBattery == mOnBattery) {
+                if (onBattery) {
+                    mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+                } else {
+                    mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+                }
+            }
+            // Always start out assuming charging, that will be updated later.
+            mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
+            mHistoryCur.batteryStatus = (byte)status;
+            mHistoryCur.batteryLevel = (byte)level;
+            mHistoryCur.batteryChargeUAh = chargeUAh;
+            mMaxChargeStepLevel = mMinDischargeStepLevel =
+                    mLastChargeStepLevel = mLastDischargeStepLevel = level;
+            mLastChargingStateLevel = level;
+        } else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) {
+            recordDailyStatsIfNeededLocked(level >= 100 && onBattery);
+        }
+        int oldStatus = mHistoryCur.batteryStatus;
+        if (onBattery) {
+            mDischargeCurrentLevel = level;
+            if (!mRecordingHistory) {
+                mRecordingHistory = true;
+                startRecordingHistory(elapsedRealtime, uptime, true);
+            }
+        } else if (level < 96) {
+            if (!mRecordingHistory) {
+                mRecordingHistory = true;
+                startRecordingHistory(elapsedRealtime, uptime, true);
+            }
+        }
+        mCurrentBatteryLevel = level;
+        if (mDischargePlugLevel < 0) {
+            mDischargePlugLevel = level;
+        }
+
+        if (onBattery != mOnBattery) {
+            mHistoryCur.batteryLevel = (byte)level;
+            mHistoryCur.batteryStatus = (byte)status;
+            mHistoryCur.batteryHealth = (byte)health;
+            mHistoryCur.batteryPlugType = (byte)plugType;
+            mHistoryCur.batteryTemperature = (short)temp;
+            mHistoryCur.batteryVoltage = (char)volt;
+            if (chargeUAh < mHistoryCur.batteryChargeUAh) {
+                // Only record discharges
+                final long chargeDiff = mHistoryCur.batteryChargeUAh - chargeUAh;
+                mDischargeCounter.addCountLocked(chargeDiff);
+                mDischargeScreenOffCounter.addCountLocked(chargeDiff);
+            }
+            mHistoryCur.batteryChargeUAh = chargeUAh;
+            setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);
+        } else {
+            boolean changed = false;
+            if (mHistoryCur.batteryLevel != level) {
+                mHistoryCur.batteryLevel = (byte)level;
+                changed = true;
+
+                // TODO(adamlesinski): Schedule the creation of a HistoryStepDetails record
+                // which will pull external stats.
+                scheduleSyncExternalStatsLocked("battery-level", ExternalStatsSync.UPDATE_ALL);
+            }
+            if (mHistoryCur.batteryStatus != status) {
+                mHistoryCur.batteryStatus = (byte)status;
+                changed = true;
+            }
+            if (mHistoryCur.batteryHealth != health) {
+                mHistoryCur.batteryHealth = (byte)health;
+                changed = true;
+            }
+            if (mHistoryCur.batteryPlugType != plugType) {
+                mHistoryCur.batteryPlugType = (byte)plugType;
+                changed = true;
+            }
+            if (temp >= (mHistoryCur.batteryTemperature+10)
+                    || temp <= (mHistoryCur.batteryTemperature-10)) {
+                mHistoryCur.batteryTemperature = (short)temp;
+                changed = true;
+            }
+            if (volt > (mHistoryCur.batteryVoltage+20)
+                    || volt < (mHistoryCur.batteryVoltage-20)) {
+                mHistoryCur.batteryVoltage = (char)volt;
+                changed = true;
+            }
+            if (chargeUAh >= (mHistoryCur.batteryChargeUAh+10)
+                    || chargeUAh <= (mHistoryCur.batteryChargeUAh-10)) {
+                if (chargeUAh < mHistoryCur.batteryChargeUAh) {
+                    // Only record discharges
+                    final long chargeDiff = mHistoryCur.batteryChargeUAh - chargeUAh;
+                    mDischargeCounter.addCountLocked(chargeDiff);
+                    mDischargeScreenOffCounter.addCountLocked(chargeDiff);
+                }
+                mHistoryCur.batteryChargeUAh = chargeUAh;
+                changed = true;
+            }
+            long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
+                    | (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
+                    | (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
+            if (onBattery) {
+                changed |= setChargingLocked(false);
+                if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) {
+                    mDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
+                            modeBits, elapsedRealtime);
+                    mDailyDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
+                            modeBits, elapsedRealtime);
+                    mLastDischargeStepLevel = level;
+                    mMinDischargeStepLevel = level;
+                    mInitStepMode = mCurStepMode;
+                    mModStepMode = 0;
+                }
+            } else {
+                if (level >= 90) {
+                    // If the battery level is at least 90%, always consider the device to be
+                    // charging even if it happens to go down a level.
+                    changed |= setChargingLocked(true);
+                    mLastChargeStepLevel = level;
+                } if (!mCharging) {
+                    if (mLastChargeStepLevel < level) {
+                        // We have not reporting that we are charging, but the level has now
+                        // gone up, so consider the state to be charging.
+                        changed |= setChargingLocked(true);
+                        mLastChargeStepLevel = level;
+                    }
+                } else {
+                    if (mLastChargeStepLevel > level) {
+                        // We had reported that the device was charging, but here we are with
+                        // power connected and the level going down.  Looks like the current
+                        // power supplied isn't enough, so consider the device to now be
+                        // discharging.
+                        changed |= setChargingLocked(false);
+                        mLastChargeStepLevel = level;
+                    }
+                }
+                if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) {
+                    mChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
+                            modeBits, elapsedRealtime);
+                    mDailyChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
+                            modeBits, elapsedRealtime);
+                    mLastChargeStepLevel = level;
+                    mMaxChargeStepLevel = level;
+                    mInitStepMode = mCurStepMode;
+                    mModStepMode = 0;
+                }
+            }
+            if (changed) {
+                addHistoryRecordLocked(elapsedRealtime, uptime);
+            }
+        }
+        if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) {
+            // We don't record history while we are plugged in and fully charged.
+            // The next time we are unplugged, history will be cleared.
+            mRecordingHistory = DEBUG;
+        }
+
+        if (mMinLearnedBatteryCapacity == -1) {
+            mMinLearnedBatteryCapacity = chargeFullUAh;
+        } else {
+            Math.min(mMinLearnedBatteryCapacity, chargeFullUAh);
+        }
+        mMaxLearnedBatteryCapacity = Math.max(mMaxLearnedBatteryCapacity, chargeFullUAh);
+    }
+
+    public long getAwakeTimeBattery() {
+        return computeBatteryUptime(getBatteryUptimeLocked(), STATS_CURRENT);
+    }
+
+    public long getAwakeTimePlugged() {
+        return (mClocks.uptimeMillis() * 1000) - getAwakeTimeBattery();
+    }
+
+    @Override
+    public long computeUptime(long curTime, int which) {
+        switch (which) {
+            case STATS_SINCE_CHARGED: return mUptime + (curTime-mUptimeStart);
+            case STATS_CURRENT: return (curTime-mUptimeStart);
+            case STATS_SINCE_UNPLUGGED: return (curTime-mOnBatteryTimeBase.getUptimeStart());
+        }
+        return 0;
+    }
+
+    @Override
+    public long computeRealtime(long curTime, int which) {
+        switch (which) {
+            case STATS_SINCE_CHARGED: return mRealtime + (curTime-mRealtimeStart);
+            case STATS_CURRENT: return (curTime-mRealtimeStart);
+            case STATS_SINCE_UNPLUGGED: return (curTime-mOnBatteryTimeBase.getRealtimeStart());
+        }
+        return 0;
+    }
+
+    @Override
+    public long computeBatteryUptime(long curTime, int which) {
+        return mOnBatteryTimeBase.computeUptime(curTime, which);
+    }
+
+    @Override
+    public long computeBatteryRealtime(long curTime, int which) {
+        return mOnBatteryTimeBase.computeRealtime(curTime, which);
+    }
+
+    @Override
+    public long computeBatteryScreenOffUptime(long curTime, int which) {
+        return mOnBatteryScreenOffTimeBase.computeUptime(curTime, which);
+    }
+
+    @Override
+    public long computeBatteryScreenOffRealtime(long curTime, int which) {
+        return mOnBatteryScreenOffTimeBase.computeRealtime(curTime, which);
+    }
+
+    private long computeTimePerLevel(long[] steps, int numSteps) {
+        // For now we'll do a simple average across all steps.
+        if (numSteps <= 0) {
+            return -1;
+        }
+        long total = 0;
+        for (int i=0; i<numSteps; i++) {
+            total += steps[i] & STEP_LEVEL_TIME_MASK;
+        }
+        return total / numSteps;
+        /*
+        long[] buckets = new long[numSteps];
+        int numBuckets = 0;
+        int numToAverage = 4;
+        int i = 0;
+        while (i < numSteps) {
+            long totalTime = 0;
+            int num = 0;
+            for (int j=0; j<numToAverage && (i+j)<numSteps; j++) {
+                totalTime += steps[i+j] & STEP_LEVEL_TIME_MASK;
+                num++;
+            }
+            buckets[numBuckets] = totalTime / num;
+            numBuckets++;
+            numToAverage *= 2;
+            i += num;
+        }
+        if (numBuckets < 1) {
+            return -1;
+        }
+        long averageTime = buckets[numBuckets-1];
+        for (i=numBuckets-2; i>=0; i--) {
+            averageTime = (averageTime + buckets[i]) / 2;
+        }
+        return averageTime;
+        */
+    }
+
+    @Override
+    public long computeBatteryTimeRemaining(long curTime) {
+        if (!mOnBattery) {
+            return -1;
+        }
+        /* Simple implementation just looks at the average discharge per level across the
+           entire sample period.
+        int discharge = (getLowDischargeAmountSinceCharge()+getHighDischargeAmountSinceCharge())/2;
+        if (discharge < 2) {
+            return -1;
+        }
+        long duration = computeBatteryRealtime(curTime, STATS_SINCE_CHARGED);
+        if (duration < 1000*1000) {
+            return -1;
+        }
+        long usPerLevel = duration/discharge;
+        return usPerLevel * mCurrentBatteryLevel;
+        */
+        if (mDischargeStepTracker.mNumStepDurations < 1) {
+            return -1;
+        }
+        long msPerLevel = mDischargeStepTracker.computeTimePerLevel();
+        if (msPerLevel <= 0) {
+            return -1;
+        }
+        return (msPerLevel * mCurrentBatteryLevel) * 1000;
+    }
+
+    @Override
+    public LevelStepTracker getDischargeLevelStepTracker() {
+        return mDischargeStepTracker;
+    }
+
+    @Override
+    public LevelStepTracker getDailyDischargeLevelStepTracker() {
+        return mDailyDischargeStepTracker;
+    }
+
+    @Override
+    public long computeChargeTimeRemaining(long curTime) {
+        if (mOnBattery) {
+            // Not yet working.
+            return -1;
+        }
+        /* Broken
+        int curLevel = mCurrentBatteryLevel;
+        int plugLevel = mDischargePlugLevel;
+        if (plugLevel < 0 || curLevel < (plugLevel+1)) {
+            return -1;
+        }
+        long duration = computeBatteryRealtime(curTime, STATS_SINCE_UNPLUGGED);
+        if (duration < 1000*1000) {
+            return -1;
+        }
+        long usPerLevel = duration/(curLevel-plugLevel);
+        return usPerLevel * (100-curLevel);
+        */
+        if (mChargeStepTracker.mNumStepDurations < 1) {
+            return -1;
+        }
+        long msPerLevel = mChargeStepTracker.computeTimePerLevel();
+        if (msPerLevel <= 0) {
+            return -1;
+        }
+        return (msPerLevel * (100-mCurrentBatteryLevel)) * 1000;
+    }
+
+    @Override
+    public LevelStepTracker getChargeLevelStepTracker() {
+        return mChargeStepTracker;
+    }
+
+    @Override
+    public LevelStepTracker getDailyChargeLevelStepTracker() {
+        return mDailyChargeStepTracker;
+    }
+
+    @Override
+    public ArrayList<PackageChange> getDailyPackageChanges() {
+        return mDailyPackageChanges;
+    }
+
+    protected long getBatteryUptimeLocked() {
+        return mOnBatteryTimeBase.getUptime(mClocks.uptimeMillis() * 1000);
+    }
+
+    @Override
+    public long getBatteryUptime(long curTime) {
+        return mOnBatteryTimeBase.getUptime(curTime);
+    }
+
+    @Override
+    public long getBatteryRealtime(long curTime) {
+        return mOnBatteryTimeBase.getRealtime(curTime);
+    }
+
+    @Override
+    public int getDischargeStartLevel() {
+        synchronized(this) {
+            return getDischargeStartLevelLocked();
+        }
+    }
+
+    public int getDischargeStartLevelLocked() {
+            return mDischargeUnplugLevel;
+    }
+
+    @Override
+    public int getDischargeCurrentLevel() {
+        synchronized(this) {
+            return getDischargeCurrentLevelLocked();
+        }
+    }
+
+    public int getDischargeCurrentLevelLocked() {
+        return mDischargeCurrentLevel;
+    }
+
+    @Override
+    public int getLowDischargeAmountSinceCharge() {
+        synchronized(this) {
+            int val = mLowDischargeAmountSinceCharge;
+            if (mOnBattery && mDischargeCurrentLevel < mDischargeUnplugLevel) {
+                val += mDischargeUnplugLevel-mDischargeCurrentLevel-1;
+            }
+            return val;
+        }
+    }
+
+    @Override
+    public int getHighDischargeAmountSinceCharge() {
+        synchronized(this) {
+            int val = mHighDischargeAmountSinceCharge;
+            if (mOnBattery && mDischargeCurrentLevel < mDischargeUnplugLevel) {
+                val += mDischargeUnplugLevel-mDischargeCurrentLevel;
+            }
+            return val;
+        }
+    }
+
+    @Override
+    public int getDischargeAmount(int which) {
+        int dischargeAmount = which == STATS_SINCE_CHARGED
+                ? getHighDischargeAmountSinceCharge()
+                : (getDischargeStartLevel() - getDischargeCurrentLevel());
+        if (dischargeAmount < 0) {
+            dischargeAmount = 0;
+        }
+        return dischargeAmount;
+    }
+
+    public int getDischargeAmountScreenOn() {
+        synchronized(this) {
+            int val = mDischargeAmountScreenOn;
+            if (mOnBattery && mScreenState == Display.STATE_ON
+                    && mDischargeCurrentLevel < mDischargeScreenOnUnplugLevel) {
+                val += mDischargeScreenOnUnplugLevel-mDischargeCurrentLevel;
+            }
+            return val;
+        }
+    }
+
+    public int getDischargeAmountScreenOnSinceCharge() {
+        synchronized(this) {
+            int val = mDischargeAmountScreenOnSinceCharge;
+            if (mOnBattery && mScreenState == Display.STATE_ON
+                    && mDischargeCurrentLevel < mDischargeScreenOnUnplugLevel) {
+                val += mDischargeScreenOnUnplugLevel-mDischargeCurrentLevel;
+            }
+            return val;
+        }
+    }
+
+    public int getDischargeAmountScreenOff() {
+        synchronized(this) {
+            int val = mDischargeAmountScreenOff;
+            if (mOnBattery && mScreenState != Display.STATE_ON
+                    && mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) {
+                val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel;
+            }
+            return val;
+        }
+    }
+
+    public int getDischargeAmountScreenOffSinceCharge() {
+        synchronized(this) {
+            int val = mDischargeAmountScreenOffSinceCharge;
+            if (mOnBattery && mScreenState != Display.STATE_ON
+                    && mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) {
+                val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel;
+            }
+            return val;
+        }
+    }
+
+    /**
+     * Retrieve the statistics object for a particular uid, creating if needed.
+     */
+    public Uid getUidStatsLocked(int uid) {
+        Uid u = mUidStats.get(uid);
+        if (u == null) {
+            u = new Uid(this, uid);
+            mUidStats.put(uid, u);
+        }
+        return u;
+    }
+
+    public void onCleanupUserLocked(int userId) {
+        final int firstUidForUser = UserHandle.getUid(userId, 0);
+        final int lastUidForUser = UserHandle.getUid(userId, UserHandle.PER_USER_RANGE - 1);
+        mKernelUidCpuFreqTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+        mKernelUidCpuTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+    }
+
+    public void onUserRemovedLocked(int userId) {
+        final int firstUidForUser = UserHandle.getUid(userId, 0);
+        final int lastUidForUser = UserHandle.getUid(userId, UserHandle.PER_USER_RANGE - 1);
+        mUidStats.put(firstUidForUser, null);
+        mUidStats.put(lastUidForUser, null);
+        final int firstIndex = mUidStats.indexOfKey(firstUidForUser);
+        final int lastIndex = mUidStats.indexOfKey(lastUidForUser);
+        mUidStats.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+    }
+
+    /**
+     * Remove the statistics object for a particular uid.
+     */
+    public void removeUidStatsLocked(int uid) {
+        mKernelUidCpuTimeReader.removeUid(uid);
+        mKernelUidCpuFreqTimeReader.removeUid(uid);
+        mUidStats.remove(uid);
+    }
+
+    /**
+     * Retrieve the statistics object for a particular process, creating
+     * if needed.
+     */
+    public Uid.Proc getProcessStatsLocked(int uid, String name) {
+        uid = mapUid(uid);
+        Uid u = getUidStatsLocked(uid);
+        return u.getProcessStatsLocked(name);
+    }
+
+    /**
+     * Retrieve the statistics object for a particular process, creating
+     * if needed.
+     */
+    public Uid.Pkg getPackageStatsLocked(int uid, String pkg) {
+        uid = mapUid(uid);
+        Uid u = getUidStatsLocked(uid);
+        return u.getPackageStatsLocked(pkg);
+    }
+
+    /**
+     * Retrieve the statistics object for a particular service, creating
+     * if needed.
+     */
+    public Uid.Pkg.Serv getServiceStatsLocked(int uid, String pkg, String name) {
+        uid = mapUid(uid);
+        Uid u = getUidStatsLocked(uid);
+        return u.getServiceStatsLocked(pkg, name);
+    }
+
+    public void shutdownLocked() {
+        recordShutdownLocked(mClocks.elapsedRealtime(), mClocks.uptimeMillis());
+        writeSyncLocked();
+        mShuttingDown = true;
+    }
+
+    Parcel mPendingWrite = null;
+    final ReentrantLock mWriteLock = new ReentrantLock();
+
+    public void writeAsyncLocked() {
+        writeLocked(false);
+    }
+
+    public void writeSyncLocked() {
+        writeLocked(true);
+    }
+
+    void writeLocked(boolean sync) {
+        if (mFile == null) {
+            Slog.w("BatteryStats", "writeLocked: no file associated with this instance");
+            return;
+        }
+
+        if (mShuttingDown) {
+            return;
+        }
+
+        Parcel out = Parcel.obtain();
+        writeSummaryToParcel(out, true);
+        mLastWriteTime = mClocks.elapsedRealtime();
+
+        if (mPendingWrite != null) {
+            mPendingWrite.recycle();
+        }
+        mPendingWrite = out;
+
+        if (sync) {
+            commitPendingDataToDisk();
+        } else {
+            BackgroundThread.getHandler().post(new Runnable() {
+                @Override public void run() {
+                    commitPendingDataToDisk();
+                }
+            });
+        }
+    }
+
+    public void commitPendingDataToDisk() {
+        final Parcel next;
+        synchronized (this) {
+            next = mPendingWrite;
+            mPendingWrite = null;
+            if (next == null) {
+                return;
+            }
+        }
+
+        mWriteLock.lock();
+        try {
+            FileOutputStream stream = new FileOutputStream(mFile.chooseForWrite());
+            stream.write(next.marshall());
+            stream.flush();
+            FileUtils.sync(stream);
+            stream.close();
+            mFile.commit();
+        } catch (IOException e) {
+            Slog.w("BatteryStats", "Error writing battery statistics", e);
+            mFile.rollback();
+        } finally {
+            next.recycle();
+            mWriteLock.unlock();
+        }
+    }
+
+    public void readLocked() {
+        if (mDailyFile != null) {
+            readDailyStatsLocked();
+        }
+
+        if (mFile == null) {
+            Slog.w("BatteryStats", "readLocked: no file associated with this instance");
+            return;
+        }
+
+        mUidStats.clear();
+
+        try {
+            File file = mFile.chooseForRead();
+            if (!file.exists()) {
+                return;
+            }
+            FileInputStream stream = new FileInputStream(file);
+
+            byte[] raw = BatteryStatsHelper.readFully(stream);
+            Parcel in = Parcel.obtain();
+            in.unmarshall(raw, 0, raw.length);
+            in.setDataPosition(0);
+            stream.close();
+
+            readSummaryFromParcel(in);
+        } catch(Exception e) {
+            Slog.e("BatteryStats", "Error reading battery statistics", e);
+            resetAllStatsLocked();
+        }
+
+        mEndPlatformVersion = Build.ID;
+
+        if (mHistoryBuffer.dataPosition() > 0) {
+            mRecordingHistory = true;
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+            if (USE_OLD_HISTORY) {
+                addHistoryRecordLocked(elapsedRealtime, uptime, HistoryItem.CMD_START, mHistoryCur);
+            }
+            addHistoryBufferLocked(elapsedRealtime, uptime, HistoryItem.CMD_START, mHistoryCur);
+            startRecordingHistory(elapsedRealtime, uptime, false);
+        }
+
+        recordDailyStatsIfNeededLocked(false);
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    void readHistory(Parcel in, boolean andOldHistory) throws ParcelFormatException {
+        final long historyBaseTime = in.readLong();
+
+        mHistoryBuffer.setDataSize(0);
+        mHistoryBuffer.setDataPosition(0);
+        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();
+            if (str == null) {
+                throw new ParcelFormatException("null history tag string");
+            }
+            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;
+        }
+
+        int bufSize = in.readInt();
+        int curPos = in.dataPosition();
+        if (bufSize >= (MAX_MAX_HISTORY_BUFFER*3)) {
+            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_HISTORY) Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
+                    + " bytes at " + curPos);
+            mHistoryBuffer.appendFrom(in, curPos, bufSize);
+            in.setDataPosition(curPos + bufSize);
+        }
+
+        if (andOldHistory) {
+            readOldHistory(in);
+        }
+
+        if (DEBUG_HISTORY) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("****************** OLD mHistoryBaseTime: ");
+            TimeUtils.formatDuration(mHistoryBaseTime, sb);
+            Slog.i(TAG, sb.toString());
+        }
+        mHistoryBaseTime = historyBaseTime;
+        if (DEBUG_HISTORY) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("****************** NEW mHistoryBaseTime: ");
+            TimeUtils.formatDuration(mHistoryBaseTime, sb);
+            Slog.i(TAG, sb.toString());
+        }
+
+        // We are just arbitrarily going to insert 1 minute from the sample of
+        // the last run until samples in this run.
+        if (mHistoryBaseTime > 0) {
+            long oldnow = mClocks.elapsedRealtime();
+            mHistoryBaseTime = mHistoryBaseTime - oldnow + 1;
+            if (DEBUG_HISTORY) {
+                StringBuilder sb = new StringBuilder(128);
+                sb.append("****************** ADJUSTED mHistoryBaseTime: ");
+                TimeUtils.formatDuration(mHistoryBaseTime, sb);
+                Slog.i(TAG, sb.toString());
+            }
+        }
+    }
+
+    void readOldHistory(Parcel in) {
+        if (!USE_OLD_HISTORY) {
+            return;
+        }
+        mHistory = mHistoryEnd = mHistoryCache = null;
+        long time;
+        while (in.dataAvail() > 0 && (time=in.readLong()) >= 0) {
+            HistoryItem rec = new HistoryItem(time, in);
+            addHistoryRecordLocked(rec);
+        }
+    }
+
+    void writeHistory(Parcel out, boolean inclData, boolean andOldHistory) {
+        if (DEBUG_HISTORY) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("****************** WRITING mHistoryBaseTime: ");
+            TimeUtils.formatDuration(mHistoryBaseTime, sb);
+            sb.append(" mLastHistoryElapsedRealtime: ");
+            TimeUtils.formatDuration(mLastHistoryElapsedRealtime, sb);
+            Slog.i(TAG, sb.toString());
+        }
+        out.writeLong(mHistoryBaseTime + mLastHistoryElapsedRealtime);
+        if (!inclData) {
+            out.writeInt(0);
+            out.writeInt(0);
+            return;
+        }
+        out.writeInt(mHistoryTagPool.size());
+        for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
+            HistoryTag tag = ent.getKey();
+            out.writeInt(ent.getValue());
+            out.writeString(tag.string);
+            out.writeInt(tag.uid);
+        }
+        out.writeInt(mHistoryBuffer.dataSize());
+        if (DEBUG_HISTORY) Slog.i(TAG, "***************** WRITING HISTORY: "
+                + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
+        out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+
+        if (andOldHistory) {
+            writeOldHistory(out);
+        }
+    }
+
+    void writeOldHistory(Parcel out) {
+        if (!USE_OLD_HISTORY) {
+            return;
+        }
+        HistoryItem rec = mHistory;
+        while (rec != null) {
+            if (rec.time >= 0) rec.writeToParcel(out, 0);
+            rec = rec.next;
+        }
+        out.writeLong(-1);
+    }
+
+    public void readSummaryFromParcel(Parcel in) throws ParcelFormatException {
+        final int version = in.readInt();
+        if (version != VERSION) {
+            Slog.w("BatteryStats", "readFromParcel: version got " + version
+                + ", expected " + VERSION + "; erasing old stats");
+            return;
+        }
+
+        readHistory(in, true);
+
+        mStartCount = in.readInt();
+        mUptime = in.readLong();
+        mRealtime = in.readLong();
+        mStartClockTime = in.readLong();
+        mStartPlatformVersion = in.readString();
+        mEndPlatformVersion = in.readString();
+        mOnBatteryTimeBase.readSummaryFromParcel(in);
+        mOnBatteryScreenOffTimeBase.readSummaryFromParcel(in);
+        mDischargeUnplugLevel = in.readInt();
+        mDischargePlugLevel = in.readInt();
+        mDischargeCurrentLevel = in.readInt();
+        mCurrentBatteryLevel = in.readInt();
+        mEstimatedBatteryCapacity = in.readInt();
+        mMinLearnedBatteryCapacity = in.readInt();
+        mMaxLearnedBatteryCapacity = in.readInt();
+        mLowDischargeAmountSinceCharge = in.readInt();
+        mHighDischargeAmountSinceCharge = in.readInt();
+        mDischargeAmountScreenOnSinceCharge = in.readInt();
+        mDischargeAmountScreenOffSinceCharge = in.readInt();
+        mDischargeStepTracker.readFromParcel(in);
+        mChargeStepTracker.readFromParcel(in);
+        mDailyDischargeStepTracker.readFromParcel(in);
+        mDailyChargeStepTracker.readFromParcel(in);
+        mDischargeCounter.readSummaryFromParcelLocked(in);
+        mDischargeScreenOffCounter.readSummaryFromParcelLocked(in);
+        int NPKG = in.readInt();
+        if (NPKG > 0) {
+            mDailyPackageChanges = new ArrayList<>(NPKG);
+            while (NPKG > 0) {
+                NPKG--;
+                PackageChange pc = new PackageChange();
+                pc.mPackageName = in.readString();
+                pc.mUpdate = in.readInt() != 0;
+                pc.mVersionCode = in.readInt();
+                mDailyPackageChanges.add(pc);
+            }
+        } else {
+            mDailyPackageChanges = null;
+        }
+        mDailyStartTime = in.readLong();
+        mNextMinDailyDeadline = in.readLong();
+        mNextMaxDailyDeadline = in.readLong();
+
+        mStartCount++;
+
+        mScreenState = Display.STATE_UNKNOWN;
+        mScreenOnTimer.readSummaryFromParcelLocked(in);
+        for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+            mScreenBrightnessTimer[i].readSummaryFromParcelLocked(in);
+        }
+        mInteractive = false;
+        mInteractiveTimer.readSummaryFromParcelLocked(in);
+        mPhoneOn = false;
+        mPowerSaveModeEnabledTimer.readSummaryFromParcelLocked(in);
+        mLongestLightIdleTime = in.readLong();
+        mLongestFullIdleTime = in.readLong();
+        mDeviceIdleModeLightTimer.readSummaryFromParcelLocked(in);
+        mDeviceIdleModeFullTimer.readSummaryFromParcelLocked(in);
+        mDeviceLightIdlingTimer.readSummaryFromParcelLocked(in);
+        mDeviceIdlingTimer.readSummaryFromParcelLocked(in);
+        mPhoneOnTimer.readSummaryFromParcelLocked(in);
+        for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
+            mPhoneSignalStrengthsTimer[i].readSummaryFromParcelLocked(in);
+        }
+        mPhoneSignalScanningTimer.readSummaryFromParcelLocked(in);
+        for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
+            mPhoneDataConnectionsTimer[i].readSummaryFromParcelLocked(in);
+        }
+        for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
+            mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in);
+            mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in);
+        }
+        mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+        mMobileRadioActiveTimer.readSummaryFromParcelLocked(in);
+        mMobileRadioActivePerAppTimer.readSummaryFromParcelLocked(in);
+        mMobileRadioActiveAdjustedTime.readSummaryFromParcelLocked(in);
+        mMobileRadioActiveUnknownTime.readSummaryFromParcelLocked(in);
+        mMobileRadioActiveUnknownCount.readSummaryFromParcelLocked(in);
+        mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+        mWifiOn = false;
+        mWifiOnTimer.readSummaryFromParcelLocked(in);
+        mGlobalWifiRunning = false;
+        mGlobalWifiRunningTimer.readSummaryFromParcelLocked(in);
+        for (int i=0; i<NUM_WIFI_STATES; i++) {
+            mWifiStateTimer[i].readSummaryFromParcelLocked(in);
+        }
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            mWifiSupplStateTimer[i].readSummaryFromParcelLocked(in);
+        }
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            mWifiSignalStrengthsTimer[i].readSummaryFromParcelLocked(in);
+        }
+        mWifiActivity.readSummaryFromParcel(in);
+        mBluetoothActivity.readSummaryFromParcel(in);
+        mModemActivity.readSummaryFromParcel(in);
+        mHasWifiReporting = in.readInt() != 0;
+        mHasBluetoothReporting = in.readInt() != 0;
+        mHasModemReporting = in.readInt() != 0;
+
+        mNumConnectivityChange = mLoadedNumConnectivityChange = in.readInt();
+        mFlashlightOnNesting = 0;
+        mFlashlightOnTimer.readSummaryFromParcelLocked(in);
+        mCameraOnNesting = 0;
+        mCameraOnTimer.readSummaryFromParcelLocked(in);
+        mBluetoothScanNesting = 0;
+        mBluetoothScanTimer.readSummaryFromParcelLocked(in);
+
+        int NKW = in.readInt();
+        if (NKW > 10000) {
+            throw new ParcelFormatException("File corrupt: too many kernel wake locks " + NKW);
+        }
+        for (int ikw = 0; ikw < NKW; ikw++) {
+            if (in.readInt() != 0) {
+                String kwltName = in.readString();
+                getKernelWakelockTimerLocked(kwltName).readSummaryFromParcelLocked(in);
+            }
+        }
+
+        int NWR = in.readInt();
+        if (NWR > 10000) {
+            throw new ParcelFormatException("File corrupt: too many wakeup reasons " + NWR);
+        }
+        for (int iwr = 0; iwr < NWR; iwr++) {
+            if (in.readInt() != 0) {
+                String reasonName = in.readString();
+                getWakeupReasonTimerLocked(reasonName).readSummaryFromParcelLocked(in);
+            }
+        }
+
+        int NMS = in.readInt();
+        for (int ims = 0; ims < NMS; ims++) {
+            if (in.readInt() != 0) {
+                long kmstName = in.readLong();
+                getKernelMemoryTimerLocked(kmstName).readSummaryFromParcelLocked(in);
+            }
+        }
+
+        final int NU = in.readInt();
+        if (NU > 10000) {
+            throw new ParcelFormatException("File corrupt: too many uids " + NU);
+        }
+        for (int iu = 0; iu < NU; iu++) {
+            int uid = in.readInt();
+            Uid u = new Uid(this, uid);
+            mUidStats.put(uid, u);
+
+            u.mOnBatteryBackgroundTimeBase.readSummaryFromParcel(in);
+            u.mOnBatteryScreenOffBackgroundTimeBase.readSummaryFromParcel(in);
+
+            u.mWifiRunning = false;
+            if (in.readInt() != 0) {
+                u.mWifiRunningTimer.readSummaryFromParcelLocked(in);
+            }
+            u.mFullWifiLockOut = false;
+            if (in.readInt() != 0) {
+                u.mFullWifiLockTimer.readSummaryFromParcelLocked(in);
+            }
+            u.mWifiScanStarted = false;
+            if (in.readInt() != 0) {
+                u.mWifiScanTimer.readSummaryFromParcelLocked(in);
+            }
+            u.mWifiBatchedScanBinStarted = Uid.NO_BATCHED_SCAN_STARTED;
+            for (int i = 0; i < Uid.NUM_WIFI_BATCHED_SCAN_BINS; i++) {
+                if (in.readInt() != 0) {
+                    u.makeWifiBatchedScanBin(i, null);
+                    u.mWifiBatchedScanTimer[i].readSummaryFromParcelLocked(in);
+                }
+            }
+            u.mWifiMulticastEnabled = false;
+            if (in.readInt() != 0) {
+                u.mWifiMulticastTimer.readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                u.createAudioTurnedOnTimerLocked().readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                u.createVideoTurnedOnTimerLocked().readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                u.createFlashlightTurnedOnTimerLocked().readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                u.createCameraTurnedOnTimerLocked().readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                u.createForegroundActivityTimerLocked().readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                u.createForegroundServiceTimerLocked().readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                u.createAggregatedPartialWakelockTimerLocked().readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                u.createBluetoothScanTimerLocked().readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                u.createBluetoothUnoptimizedScanTimerLocked().readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                u.createBluetoothScanResultCounterLocked().readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                u.createBluetoothScanResultBgCounterLocked().readSummaryFromParcelLocked(in);
+            }
+            u.mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+            for (int i = 0; i < Uid.NUM_PROCESS_STATE; i++) {
+                if (in.readInt() != 0) {
+                    u.makeProcessState(i, null);
+                    u.mProcessStateTimer[i].readSummaryFromParcelLocked(in);
+                }
+            }
+            if (in.readInt() != 0) {
+                u.createVibratorOnTimerLocked().readSummaryFromParcelLocked(in);
+            }
+
+            if (in.readInt() != 0) {
+                if (u.mUserActivityCounters == null) {
+                    u.initUserActivityLocked();
+                }
+                for (int i=0; i<Uid.NUM_USER_ACTIVITY_TYPES; i++) {
+                    u.mUserActivityCounters[i].readSummaryFromParcelLocked(in);
+                }
+            }
+
+            if (in.readInt() != 0) {
+                if (u.mNetworkByteActivityCounters == null) {
+                    u.initNetworkActivityLocked();
+                }
+                for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
+                    u.mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in);
+                    u.mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in);
+                }
+                u.mMobileRadioActiveTime.readSummaryFromParcelLocked(in);
+                u.mMobileRadioActiveCount.readSummaryFromParcelLocked(in);
+            }
+
+            u.mUserCpuTime.readSummaryFromParcelLocked(in);
+            u.mSystemCpuTime.readSummaryFromParcelLocked(in);
+
+            if (in.readInt() != 0) {
+                final int numClusters = in.readInt();
+                if (mPowerProfile != null && mPowerProfile.getNumCpuClusters() != numClusters) {
+                    throw new ParcelFormatException("Incompatible cpu cluster arrangement");
+                }
+
+                u.mCpuClusterSpeedTimesUs = new LongSamplingCounter[numClusters][];
+                for (int cluster = 0; cluster < numClusters; cluster++) {
+                    if (in.readInt() != 0) {
+                        final int NSB = in.readInt();
+                        if (mPowerProfile != null &&
+                                mPowerProfile.getNumSpeedStepsInCpuCluster(cluster) != NSB) {
+                            throw new ParcelFormatException("File corrupt: too many speed bins " +
+                                    NSB);
+                        }
+
+                        u.mCpuClusterSpeedTimesUs[cluster] = new LongSamplingCounter[NSB];
+                        for (int speed = 0; speed < NSB; speed++) {
+                            if (in.readInt() != 0) {
+                                u.mCpuClusterSpeedTimesUs[cluster][speed] = new LongSamplingCounter(
+                                        mOnBatteryTimeBase);
+                                u.mCpuClusterSpeedTimesUs[cluster][speed].readSummaryFromParcelLocked(in);
+                            }
+                        }
+                    } else {
+                        u.mCpuClusterSpeedTimesUs[cluster] = null;
+                    }
+                }
+            } else {
+                u.mCpuClusterSpeedTimesUs = null;
+            }
+
+            u.mCpuFreqTimeMs = LongSamplingCounterArray.readSummaryFromParcelLocked(
+                    in, mOnBatteryTimeBase);
+            u.mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readSummaryFromParcelLocked(
+                    in, mOnBatteryScreenOffTimeBase);
+
+            if (in.readInt() != 0) {
+                u.mMobileRadioApWakeupCount = new LongSamplingCounter(mOnBatteryTimeBase);
+                u.mMobileRadioApWakeupCount.readSummaryFromParcelLocked(in);
+            } else {
+                u.mMobileRadioApWakeupCount = null;
+            }
+
+            if (in.readInt() != 0) {
+                u.mWifiRadioApWakeupCount = new LongSamplingCounter(mOnBatteryTimeBase);
+                u.mWifiRadioApWakeupCount.readSummaryFromParcelLocked(in);
+            } else {
+                u.mWifiRadioApWakeupCount = null;
+            }
+
+            int NW = in.readInt();
+            if (NW > (MAX_WAKELOCKS_PER_UID+1)) {
+                throw new ParcelFormatException("File corrupt: too many wake locks " + NW);
+            }
+            for (int iw = 0; iw < NW; iw++) {
+                String wlName = in.readString();
+                u.readWakeSummaryFromParcelLocked(wlName, in);
+            }
+
+            int NS = in.readInt();
+            if (NS > (MAX_WAKELOCKS_PER_UID+1)) {
+                throw new ParcelFormatException("File corrupt: too many syncs " + NS);
+            }
+            for (int is = 0; is < NS; is++) {
+                String name = in.readString();
+                u.readSyncSummaryFromParcelLocked(name, in);
+            }
+
+            int NJ = in.readInt();
+            if (NJ > (MAX_WAKELOCKS_PER_UID+1)) {
+                throw new ParcelFormatException("File corrupt: too many job timers " + NJ);
+            }
+            for (int ij = 0; ij < NJ; ij++) {
+                String name = in.readString();
+                u.readJobSummaryFromParcelLocked(name, in);
+            }
+
+            u.readJobCompletionsFromParcelLocked(in);
+
+            int NP = in.readInt();
+            if (NP > 1000) {
+                throw new ParcelFormatException("File corrupt: too many sensors " + NP);
+            }
+            for (int is = 0; is < NP; is++) {
+                int seNumber = in.readInt();
+                if (in.readInt() != 0) {
+                    u.getSensorTimerLocked(seNumber, true).readSummaryFromParcelLocked(in);
+                }
+            }
+
+            NP = in.readInt();
+            if (NP > 1000) {
+                throw new ParcelFormatException("File corrupt: too many processes " + NP);
+            }
+            for (int ip = 0; ip < NP; ip++) {
+                String procName = in.readString();
+                Uid.Proc p = u.getProcessStatsLocked(procName);
+                p.mUserTime = p.mLoadedUserTime = in.readLong();
+                p.mSystemTime = p.mLoadedSystemTime = in.readLong();
+                p.mForegroundTime = p.mLoadedForegroundTime = in.readLong();
+                p.mStarts = p.mLoadedStarts = in.readInt();
+                p.mNumCrashes = p.mLoadedNumCrashes = in.readInt();
+                p.mNumAnrs = p.mLoadedNumAnrs = in.readInt();
+                p.readExcessivePowerFromParcelLocked(in);
+            }
+
+            NP = in.readInt();
+            if (NP > 10000) {
+                throw new ParcelFormatException("File corrupt: too many packages " + NP);
+            }
+            for (int ip = 0; ip < NP; ip++) {
+                String pkgName = in.readString();
+                Uid.Pkg p = u.getPackageStatsLocked(pkgName);
+                final int NWA = in.readInt();
+                if (NWA > 1000) {
+                    throw new ParcelFormatException("File corrupt: too many wakeup alarms " + NWA);
+                }
+                p.mWakeupAlarms.clear();
+                for (int iwa=0; iwa<NWA; iwa++) {
+                    String tag = in.readString();
+                    Counter c = new Counter(mOnBatteryScreenOffTimeBase);
+                    c.readSummaryFromParcelLocked(in);
+                    p.mWakeupAlarms.put(tag, c);
+                }
+                NS = in.readInt();
+                if (NS > 1000) {
+                    throw new ParcelFormatException("File corrupt: too many services " + NS);
+                }
+                for (int is = 0; is < NS; is++) {
+                    String servName = in.readString();
+                    Uid.Pkg.Serv s = u.getServiceStatsLocked(pkgName, servName);
+                    s.mStartTime = s.mLoadedStartTime = in.readLong();
+                    s.mStarts = s.mLoadedStarts = in.readInt();
+                    s.mLaunches = s.mLoadedLaunches = in.readInt();
+                }
+            }
+        }
+    }
+
+    /**
+     * Writes a summary of the statistics to a Parcel, in a format suitable to be written to
+     * disk.  This format does not allow a lossless round-trip.
+     *
+     * @param out the Parcel to be written to.
+     */
+    public void writeSummaryToParcel(Parcel out, boolean inclHistory) {
+        pullPendingStateUpdatesLocked();
+
+        // Pull the clock time.  This may update the time and make a new history entry
+        // if we had originally pulled a time before the RTC was set.
+        long startClockTime = getStartClockTime();
+
+        final long NOW_SYS = mClocks.uptimeMillis() * 1000;
+        final long NOWREAL_SYS = mClocks.elapsedRealtime() * 1000;
+
+        out.writeInt(VERSION);
+
+        writeHistory(out, inclHistory, true);
+
+        out.writeInt(mStartCount);
+        out.writeLong(computeUptime(NOW_SYS, STATS_SINCE_CHARGED));
+        out.writeLong(computeRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED));
+        out.writeLong(startClockTime);
+        out.writeString(mStartPlatformVersion);
+        out.writeString(mEndPlatformVersion);
+        mOnBatteryTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS);
+        mOnBatteryScreenOffTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS);
+        out.writeInt(mDischargeUnplugLevel);
+        out.writeInt(mDischargePlugLevel);
+        out.writeInt(mDischargeCurrentLevel);
+        out.writeInt(mCurrentBatteryLevel);
+        out.writeInt(mEstimatedBatteryCapacity);
+        out.writeInt(mMinLearnedBatteryCapacity);
+        out.writeInt(mMaxLearnedBatteryCapacity);
+        out.writeInt(getLowDischargeAmountSinceCharge());
+        out.writeInt(getHighDischargeAmountSinceCharge());
+        out.writeInt(getDischargeAmountScreenOnSinceCharge());
+        out.writeInt(getDischargeAmountScreenOffSinceCharge());
+        mDischargeStepTracker.writeToParcel(out);
+        mChargeStepTracker.writeToParcel(out);
+        mDailyDischargeStepTracker.writeToParcel(out);
+        mDailyChargeStepTracker.writeToParcel(out);
+        mDischargeCounter.writeSummaryFromParcelLocked(out);
+        mDischargeScreenOffCounter.writeSummaryFromParcelLocked(out);
+        if (mDailyPackageChanges != null) {
+            final int NPKG = mDailyPackageChanges.size();
+            out.writeInt(NPKG);
+            for (int i=0; i<NPKG; i++) {
+                PackageChange pc = mDailyPackageChanges.get(i);
+                out.writeString(pc.mPackageName);
+                out.writeInt(pc.mUpdate ? 1 : 0);
+                out.writeInt(pc.mVersionCode);
+            }
+        } else {
+            out.writeInt(0);
+        }
+        out.writeLong(mDailyStartTime);
+        out.writeLong(mNextMinDailyDeadline);
+        out.writeLong(mNextMaxDailyDeadline);
+
+        mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+            mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        }
+        mInteractiveTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mPowerSaveModeEnabledTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        out.writeLong(mLongestLightIdleTime);
+        out.writeLong(mLongestFullIdleTime);
+        mDeviceIdleModeLightTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mDeviceIdleModeFullTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mDeviceLightIdlingTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mDeviceIdlingTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
+            mPhoneSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        }
+        mPhoneSignalScanningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
+            mPhoneDataConnectionsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        }
+        for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
+            mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out);
+            mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out);
+        }
+        mMobileRadioActiveTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mMobileRadioActivePerAppTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mMobileRadioActiveAdjustedTime.writeSummaryFromParcelLocked(out);
+        mMobileRadioActiveUnknownTime.writeSummaryFromParcelLocked(out);
+        mMobileRadioActiveUnknownCount.writeSummaryFromParcelLocked(out);
+        mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        for (int i=0; i<NUM_WIFI_STATES; i++) {
+            mWifiStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        }
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            mWifiSupplStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        }
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            mWifiSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        }
+        mWifiActivity.writeSummaryToParcel(out);
+        mBluetoothActivity.writeSummaryToParcel(out);
+        mModemActivity.writeSummaryToParcel(out);
+        out.writeInt(mHasWifiReporting ? 1 : 0);
+        out.writeInt(mHasBluetoothReporting ? 1 : 0);
+        out.writeInt(mHasModemReporting ? 1 : 0);
+
+        out.writeInt(mNumConnectivityChange);
+        mFlashlightOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mCameraOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mBluetoothScanTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+
+        out.writeInt(mKernelWakelockStats.size());
+        for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
+            Timer kwlt = ent.getValue();
+            if (kwlt != null) {
+                out.writeInt(1);
+                out.writeString(ent.getKey());
+                kwlt.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+        }
+
+        out.writeInt(mWakeupReasonStats.size());
+        for (Map.Entry<String, SamplingTimer> ent : mWakeupReasonStats.entrySet()) {
+            SamplingTimer timer = ent.getValue();
+            if (timer != null) {
+                out.writeInt(1);
+                out.writeString(ent.getKey());
+                timer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+        }
+
+        out.writeInt(mKernelMemoryStats.size());
+        for (int i = 0; i < mKernelMemoryStats.size(); i++) {
+            Timer kmt = mKernelMemoryStats.valueAt(i);
+            if (kmt != null) {
+                out.writeInt(1);
+                out.writeLong(mKernelMemoryStats.keyAt(i));
+                kmt.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+        }
+
+        final int NU = mUidStats.size();
+        out.writeInt(NU);
+        for (int iu = 0; iu < NU; iu++) {
+            out.writeInt(mUidStats.keyAt(iu));
+            Uid u = mUidStats.valueAt(iu);
+
+            u.mOnBatteryBackgroundTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS);
+            u.mOnBatteryScreenOffBackgroundTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS);
+
+            if (u.mWifiRunningTimer != null) {
+                out.writeInt(1);
+                u.mWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mFullWifiLockTimer != null) {
+                out.writeInt(1);
+                u.mFullWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mWifiScanTimer != null) {
+                out.writeInt(1);
+                u.mWifiScanTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+            for (int i = 0; i < Uid.NUM_WIFI_BATCHED_SCAN_BINS; i++) {
+                if (u.mWifiBatchedScanTimer[i] != null) {
+                    out.writeInt(1);
+                    u.mWifiBatchedScanTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+                } else {
+                    out.writeInt(0);
+                }
+            }
+            if (u.mWifiMulticastTimer != null) {
+                out.writeInt(1);
+                u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mAudioTurnedOnTimer != null) {
+                out.writeInt(1);
+                u.mAudioTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mVideoTurnedOnTimer != null) {
+                out.writeInt(1);
+                u.mVideoTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mFlashlightTurnedOnTimer != null) {
+                out.writeInt(1);
+                u.mFlashlightTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mCameraTurnedOnTimer != null) {
+                out.writeInt(1);
+                u.mCameraTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mForegroundActivityTimer != null) {
+                out.writeInt(1);
+                u.mForegroundActivityTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mForegroundServiceTimer != null) {
+                out.writeInt(1);
+                u.mForegroundServiceTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mAggregatedPartialWakelockTimer != null) {
+                out.writeInt(1);
+                u.mAggregatedPartialWakelockTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mBluetoothScanTimer != null) {
+                out.writeInt(1);
+                u.mBluetoothScanTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mBluetoothUnoptimizedScanTimer != null) {
+                out.writeInt(1);
+                u.mBluetoothUnoptimizedScanTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mBluetoothScanResultCounter != null) {
+                out.writeInt(1);
+                u.mBluetoothScanResultCounter.writeSummaryFromParcelLocked(out);
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mBluetoothScanResultBgCounter != null) {
+                out.writeInt(1);
+                u.mBluetoothScanResultBgCounter.writeSummaryFromParcelLocked(out);
+            } else {
+                out.writeInt(0);
+            }
+            for (int i = 0; i < Uid.NUM_PROCESS_STATE; i++) {
+                if (u.mProcessStateTimer[i] != null) {
+                    out.writeInt(1);
+                    u.mProcessStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+                } else {
+                    out.writeInt(0);
+                }
+            }
+            if (u.mVibratorOnTimer != null) {
+                out.writeInt(1);
+                u.mVibratorOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+
+            if (u.mUserActivityCounters == null) {
+                out.writeInt(0);
+            } else {
+                out.writeInt(1);
+                for (int i=0; i<Uid.NUM_USER_ACTIVITY_TYPES; i++) {
+                    u.mUserActivityCounters[i].writeSummaryFromParcelLocked(out);
+                }
+            }
+
+            if (u.mNetworkByteActivityCounters == null) {
+                out.writeInt(0);
+            } else {
+                out.writeInt(1);
+                for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
+                    u.mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out);
+                    u.mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out);
+                }
+                u.mMobileRadioActiveTime.writeSummaryFromParcelLocked(out);
+                u.mMobileRadioActiveCount.writeSummaryFromParcelLocked(out);
+            }
+
+            u.mUserCpuTime.writeSummaryFromParcelLocked(out);
+            u.mSystemCpuTime.writeSummaryFromParcelLocked(out);
+
+            if (u.mCpuClusterSpeedTimesUs != null) {
+                out.writeInt(1);
+                out.writeInt(u.mCpuClusterSpeedTimesUs.length);
+                for (LongSamplingCounter[] cpuSpeeds : u.mCpuClusterSpeedTimesUs) {
+                    if (cpuSpeeds != null) {
+                        out.writeInt(1);
+                        out.writeInt(cpuSpeeds.length);
+                        for (LongSamplingCounter c : cpuSpeeds) {
+                            if (c != null) {
+                                out.writeInt(1);
+                                c.writeSummaryFromParcelLocked(out);
+                            } else {
+                                out.writeInt(0);
+                            }
+                        }
+                    } else {
+                        out.writeInt(0);
+                    }
+                }
+            } else {
+                out.writeInt(0);
+            }
+
+            LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mCpuFreqTimeMs);
+            LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mScreenOffCpuFreqTimeMs);
+
+            if (u.mMobileRadioApWakeupCount != null) {
+                out.writeInt(1);
+                u.mMobileRadioApWakeupCount.writeSummaryFromParcelLocked(out);
+            } else {
+                out.writeInt(0);
+            }
+
+            if (u.mWifiRadioApWakeupCount != null) {
+                out.writeInt(1);
+                u.mWifiRadioApWakeupCount.writeSummaryFromParcelLocked(out);
+            } else {
+                out.writeInt(0);
+            }
+
+            final ArrayMap<String, Uid.Wakelock> wakeStats = u.mWakelockStats.getMap();
+            int NW = wakeStats.size();
+            out.writeInt(NW);
+            for (int iw=0; iw<NW; iw++) {
+                out.writeString(wakeStats.keyAt(iw));
+                Uid.Wakelock wl = wakeStats.valueAt(iw);
+                if (wl.mTimerFull != null) {
+                    out.writeInt(1);
+                    wl.mTimerFull.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+                } else {
+                    out.writeInt(0);
+                }
+                if (wl.mTimerPartial != null) {
+                    out.writeInt(1);
+                    wl.mTimerPartial.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+                } else {
+                    out.writeInt(0);
+                }
+                if (wl.mTimerWindow != null) {
+                    out.writeInt(1);
+                    wl.mTimerWindow.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+                } else {
+                    out.writeInt(0);
+                }
+                if (wl.mTimerDraw != null) {
+                    out.writeInt(1);
+                    wl.mTimerDraw.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+                } else {
+                    out.writeInt(0);
+                }
+            }
+
+            final ArrayMap<String, DualTimer> syncStats = u.mSyncStats.getMap();
+            int NS = syncStats.size();
+            out.writeInt(NS);
+            for (int is=0; is<NS; is++) {
+                out.writeString(syncStats.keyAt(is));
+                syncStats.valueAt(is).writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            }
+
+            final ArrayMap<String, DualTimer> jobStats = u.mJobStats.getMap();
+            int NJ = jobStats.size();
+            out.writeInt(NJ);
+            for (int ij=0; ij<NJ; ij++) {
+                out.writeString(jobStats.keyAt(ij));
+                jobStats.valueAt(ij).writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            }
+
+            u.writeJobCompletionsToParcelLocked(out);
+
+            int NSE = u.mSensorStats.size();
+            out.writeInt(NSE);
+            for (int ise=0; ise<NSE; ise++) {
+                out.writeInt(u.mSensorStats.keyAt(ise));
+                Uid.Sensor se = u.mSensorStats.valueAt(ise);
+                if (se.mTimer != null) {
+                    out.writeInt(1);
+                    se.mTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+                } else {
+                    out.writeInt(0);
+                }
+            }
+
+            int NP = u.mProcessStats.size();
+            out.writeInt(NP);
+            for (int ip=0; ip<NP; ip++) {
+                out.writeString(u.mProcessStats.keyAt(ip));
+                Uid.Proc ps = u.mProcessStats.valueAt(ip);
+                out.writeLong(ps.mUserTime);
+                out.writeLong(ps.mSystemTime);
+                out.writeLong(ps.mForegroundTime);
+                out.writeInt(ps.mStarts);
+                out.writeInt(ps.mNumCrashes);
+                out.writeInt(ps.mNumAnrs);
+                ps.writeExcessivePowerToParcelLocked(out);
+            }
+
+            NP = u.mPackageStats.size();
+            out.writeInt(NP);
+            if (NP > 0) {
+                for (Map.Entry<String, BatteryStatsImpl.Uid.Pkg> ent
+                    : u.mPackageStats.entrySet()) {
+                    out.writeString(ent.getKey());
+                    Uid.Pkg ps = ent.getValue();
+                    final int NWA = ps.mWakeupAlarms.size();
+                    out.writeInt(NWA);
+                    for (int iwa=0; iwa<NWA; iwa++) {
+                        out.writeString(ps.mWakeupAlarms.keyAt(iwa));
+                        ps.mWakeupAlarms.valueAt(iwa).writeSummaryFromParcelLocked(out);
+                    }
+                    NS = ps.mServiceStats.size();
+                    out.writeInt(NS);
+                    for (int is=0; is<NS; is++) {
+                        out.writeString(ps.mServiceStats.keyAt(is));
+                        BatteryStatsImpl.Uid.Pkg.Serv ss = ps.mServiceStats.valueAt(is);
+                        long time = ss.getStartTimeToNowLocked(
+                                mOnBatteryTimeBase.getUptime(NOW_SYS));
+                        out.writeLong(time);
+                        out.writeInt(ss.mStarts);
+                        out.writeInt(ss.mLaunches);
+                    }
+                }
+            }
+        }
+    }
+
+    public void readFromParcel(Parcel in) {
+        readFromParcelLocked(in);
+    }
+
+    void readFromParcelLocked(Parcel in) {
+        int magic = in.readInt();
+        if (magic != MAGIC) {
+            throw new ParcelFormatException("Bad magic number: #" + Integer.toHexString(magic));
+        }
+
+        readHistory(in, false);
+
+        mStartCount = in.readInt();
+        mStartClockTime = in.readLong();
+        mStartPlatformVersion = in.readString();
+        mEndPlatformVersion = in.readString();
+        mUptime = in.readLong();
+        mUptimeStart = in.readLong();
+        mRealtime = in.readLong();
+        mRealtimeStart = in.readLong();
+        mOnBattery = in.readInt() != 0;
+        mEstimatedBatteryCapacity = in.readInt();
+        mMinLearnedBatteryCapacity = in.readInt();
+        mMaxLearnedBatteryCapacity = in.readInt();
+        mOnBatteryInternal = false; // we are no longer really running.
+        mOnBatteryTimeBase.readFromParcel(in);
+        mOnBatteryScreenOffTimeBase.readFromParcel(in);
+
+        mScreenState = Display.STATE_UNKNOWN;
+        mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase, in);
+        for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+            mScreenBrightnessTimer[i] = new StopwatchTimer(mClocks, null, -100-i, null,
+                    mOnBatteryTimeBase, in);
+        }
+        mInteractive = false;
+        mInteractiveTimer = new StopwatchTimer(mClocks, null, -10, null, mOnBatteryTimeBase, in);
+        mPhoneOn = false;
+        mPowerSaveModeEnabledTimer = new StopwatchTimer(mClocks, null, -2, null,
+                mOnBatteryTimeBase, in);
+        mLongestLightIdleTime = in.readLong();
+        mLongestFullIdleTime = in.readLong();
+        mDeviceIdleModeLightTimer = new StopwatchTimer(mClocks, null, -14, null,
+                mOnBatteryTimeBase, in);
+        mDeviceIdleModeFullTimer = new StopwatchTimer(mClocks, null, -11, null,
+                mOnBatteryTimeBase, in);
+        mDeviceLightIdlingTimer = new StopwatchTimer(mClocks, null, -15, null,
+                mOnBatteryTimeBase, in);
+        mDeviceIdlingTimer = new StopwatchTimer(mClocks, null, -12, null, mOnBatteryTimeBase, in);
+        mPhoneOnTimer = new StopwatchTimer(mClocks, null, -3, null, mOnBatteryTimeBase, in);
+        for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
+            mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(mClocks, null, -200-i,
+                    null, mOnBatteryTimeBase, in);
+        }
+        mPhoneSignalScanningTimer = new StopwatchTimer(mClocks, null, -200+1, null,
+                mOnBatteryTimeBase, in);
+        for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
+            mPhoneDataConnectionsTimer[i] = new StopwatchTimer(mClocks, null, -300-i,
+                    null, mOnBatteryTimeBase, in);
+        }
+        for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
+            mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+            mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+        }
+        mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+        mMobileRadioActiveTimer = new StopwatchTimer(mClocks, null, -400, null,
+                mOnBatteryTimeBase, in);
+        mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null, 
+                mOnBatteryTimeBase, in);
+        mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
+        mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
+        mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase, in);
+        mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+        mWifiOn = false;
+        mWifiOnTimer = new StopwatchTimer(mClocks, null, -4, null, mOnBatteryTimeBase, in);
+        mGlobalWifiRunning = false;
+        mGlobalWifiRunningTimer = new StopwatchTimer(mClocks, null, -5, null,
+                mOnBatteryTimeBase, in);
+        for (int i=0; i<NUM_WIFI_STATES; i++) {
+            mWifiStateTimer[i] = new StopwatchTimer(mClocks, null, -600-i,
+                    null, mOnBatteryTimeBase, in);
+        }
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            mWifiSupplStateTimer[i] = new StopwatchTimer(mClocks, null, -700-i,
+                    null, mOnBatteryTimeBase, in);
+        }
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            mWifiSignalStrengthsTimer[i] = new StopwatchTimer(mClocks, null, -800-i,
+                    null, mOnBatteryTimeBase, in);
+        }
+
+        mWifiActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
+                NUM_WIFI_TX_LEVELS, in);
+        mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
+                NUM_BT_TX_LEVELS, in);
+        mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
+                ModemActivityInfo.TX_POWER_LEVELS, in);
+        mHasWifiReporting = in.readInt() != 0;
+        mHasBluetoothReporting = in.readInt() != 0;
+        mHasModemReporting = in.readInt() != 0;
+
+        mNumConnectivityChange = in.readInt();
+        mLoadedNumConnectivityChange = in.readInt();
+        mUnpluggedNumConnectivityChange = in.readInt();
+        mAudioOnNesting = 0;
+        mAudioOnTimer = new StopwatchTimer(mClocks, null, -7, null, mOnBatteryTimeBase);
+        mVideoOnNesting = 0;
+        mVideoOnTimer = new StopwatchTimer(mClocks, null, -8, null, mOnBatteryTimeBase);
+        mFlashlightOnNesting = 0;
+        mFlashlightOnTimer = new StopwatchTimer(mClocks, null, -9, null, mOnBatteryTimeBase, in);
+        mCameraOnNesting = 0;
+        mCameraOnTimer = new StopwatchTimer(mClocks, null, -13, null, mOnBatteryTimeBase, in);
+        mBluetoothScanNesting = 0;
+        mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase, in);
+        mDischargeUnplugLevel = in.readInt();
+        mDischargePlugLevel = in.readInt();
+        mDischargeCurrentLevel = in.readInt();
+        mCurrentBatteryLevel = in.readInt();
+        mLowDischargeAmountSinceCharge = in.readInt();
+        mHighDischargeAmountSinceCharge = in.readInt();
+        mDischargeAmountScreenOn = in.readInt();
+        mDischargeAmountScreenOnSinceCharge = in.readInt();
+        mDischargeAmountScreenOff = in.readInt();
+        mDischargeAmountScreenOffSinceCharge = in.readInt();
+        mDischargeStepTracker.readFromParcel(in);
+        mChargeStepTracker.readFromParcel(in);
+        mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+        mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+        mLastWriteTime = in.readLong();
+
+        mKernelWakelockStats.clear();
+        int NKW = in.readInt();
+        for (int ikw = 0; ikw < NKW; ikw++) {
+            if (in.readInt() != 0) {
+                String wakelockName = in.readString();
+                SamplingTimer kwlt = new SamplingTimer(mClocks, mOnBatteryScreenOffTimeBase, in);
+                mKernelWakelockStats.put(wakelockName, kwlt);
+            }
+        }
+
+        mWakeupReasonStats.clear();
+        int NWR = in.readInt();
+        for (int iwr = 0; iwr < NWR; iwr++) {
+            if (in.readInt() != 0) {
+                String reasonName = in.readString();
+                SamplingTimer timer = new SamplingTimer(mClocks, mOnBatteryTimeBase, in);
+                mWakeupReasonStats.put(reasonName, timer);
+            }
+        }
+
+        mKernelMemoryStats.clear();
+        int nmt = in.readInt();
+        for (int imt = 0; imt < nmt; imt++) {
+            if (in.readInt() != 0) {
+                Long bucket = in.readLong();
+                SamplingTimer kmt = new SamplingTimer(mClocks, mOnBatteryTimeBase, in);
+                mKernelMemoryStats.put(bucket, kmt);
+            }
+        }
+
+        mPartialTimers.clear();
+        mFullTimers.clear();
+        mWindowTimers.clear();
+        mWifiRunningTimers.clear();
+        mFullWifiLockTimers.clear();
+        mWifiScanTimers.clear();
+        mWifiBatchedScanTimers.clear();
+        mWifiMulticastTimers.clear();
+        mAudioTurnedOnTimers.clear();
+        mVideoTurnedOnTimers.clear();
+        mFlashlightTurnedOnTimers.clear();
+        mCameraTurnedOnTimers.clear();
+
+        int numUids = in.readInt();
+        mUidStats.clear();
+        for (int i = 0; i < numUids; i++) {
+            int uid = in.readInt();
+            Uid u = new Uid(this, uid);
+            u.readFromParcelLocked(mOnBatteryTimeBase, mOnBatteryScreenOffTimeBase, in);
+            mUidStats.append(uid, u);
+        }
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        writeToParcelLocked(out, true, flags);
+    }
+
+    public void writeToParcelWithoutUids(Parcel out, int flags) {
+        writeToParcelLocked(out, false, flags);
+    }
+
+    @SuppressWarnings("unused")
+    void writeToParcelLocked(Parcel out, boolean inclUids, int flags) {
+        // Need to update with current kernel wake lock counts.
+        pullPendingStateUpdatesLocked();
+
+        // Pull the clock time.  This may update the time and make a new history entry
+        // if we had originally pulled a time before the RTC was set.
+        long startClockTime = getStartClockTime();
+
+        final long uSecUptime = mClocks.uptimeMillis() * 1000;
+        final long uSecRealtime = mClocks.elapsedRealtime() * 1000;
+        final long batteryRealtime = mOnBatteryTimeBase.getRealtime(uSecRealtime);
+        final long batteryScreenOffRealtime = mOnBatteryScreenOffTimeBase.getRealtime(uSecRealtime);
+
+        out.writeInt(MAGIC);
+
+        writeHistory(out, true, false);
+
+        out.writeInt(mStartCount);
+        out.writeLong(startClockTime);
+        out.writeString(mStartPlatformVersion);
+        out.writeString(mEndPlatformVersion);
+        out.writeLong(mUptime);
+        out.writeLong(mUptimeStart);
+        out.writeLong(mRealtime);
+        out.writeLong(mRealtimeStart);
+        out.writeInt(mOnBattery ? 1 : 0);
+        out.writeInt(mEstimatedBatteryCapacity);
+        out.writeInt(mMinLearnedBatteryCapacity);
+        out.writeInt(mMaxLearnedBatteryCapacity);
+        mOnBatteryTimeBase.writeToParcel(out, uSecUptime, uSecRealtime);
+        mOnBatteryScreenOffTimeBase.writeToParcel(out, uSecUptime, uSecRealtime);
+
+        mScreenOnTimer.writeToParcel(out, uSecRealtime);
+        for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+            mScreenBrightnessTimer[i].writeToParcel(out, uSecRealtime);
+        }
+        mInteractiveTimer.writeToParcel(out, uSecRealtime);
+        mPowerSaveModeEnabledTimer.writeToParcel(out, uSecRealtime);
+        out.writeLong(mLongestLightIdleTime);
+        out.writeLong(mLongestFullIdleTime);
+        mDeviceIdleModeLightTimer.writeToParcel(out, uSecRealtime);
+        mDeviceIdleModeFullTimer.writeToParcel(out, uSecRealtime);
+        mDeviceLightIdlingTimer.writeToParcel(out, uSecRealtime);
+        mDeviceIdlingTimer.writeToParcel(out, uSecRealtime);
+        mPhoneOnTimer.writeToParcel(out, uSecRealtime);
+        for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
+            mPhoneSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime);
+        }
+        mPhoneSignalScanningTimer.writeToParcel(out, uSecRealtime);
+        for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
+            mPhoneDataConnectionsTimer[i].writeToParcel(out, uSecRealtime);
+        }
+        for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
+            mNetworkByteActivityCounters[i].writeToParcel(out);
+            mNetworkPacketActivityCounters[i].writeToParcel(out);
+        }
+        mMobileRadioActiveTimer.writeToParcel(out, uSecRealtime);
+        mMobileRadioActivePerAppTimer.writeToParcel(out, uSecRealtime);
+        mMobileRadioActiveAdjustedTime.writeToParcel(out);
+        mMobileRadioActiveUnknownTime.writeToParcel(out);
+        mMobileRadioActiveUnknownCount.writeToParcel(out);
+        mWifiOnTimer.writeToParcel(out, uSecRealtime);
+        mGlobalWifiRunningTimer.writeToParcel(out, uSecRealtime);
+        for (int i=0; i<NUM_WIFI_STATES; i++) {
+            mWifiStateTimer[i].writeToParcel(out, uSecRealtime);
+        }
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            mWifiSupplStateTimer[i].writeToParcel(out, uSecRealtime);
+        }
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            mWifiSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime);
+        }
+        mWifiActivity.writeToParcel(out, 0);
+        mBluetoothActivity.writeToParcel(out, 0);
+        mModemActivity.writeToParcel(out, 0);
+        out.writeInt(mHasWifiReporting ? 1 : 0);
+        out.writeInt(mHasBluetoothReporting ? 1 : 0);
+        out.writeInt(mHasModemReporting ? 1 : 0);
+
+        out.writeInt(mNumConnectivityChange);
+        out.writeInt(mLoadedNumConnectivityChange);
+        out.writeInt(mUnpluggedNumConnectivityChange);
+        mFlashlightOnTimer.writeToParcel(out, uSecRealtime);
+        mCameraOnTimer.writeToParcel(out, uSecRealtime);
+        mBluetoothScanTimer.writeToParcel(out, uSecRealtime);
+        out.writeInt(mDischargeUnplugLevel);
+        out.writeInt(mDischargePlugLevel);
+        out.writeInt(mDischargeCurrentLevel);
+        out.writeInt(mCurrentBatteryLevel);
+        out.writeInt(mLowDischargeAmountSinceCharge);
+        out.writeInt(mHighDischargeAmountSinceCharge);
+        out.writeInt(mDischargeAmountScreenOn);
+        out.writeInt(mDischargeAmountScreenOnSinceCharge);
+        out.writeInt(mDischargeAmountScreenOff);
+        out.writeInt(mDischargeAmountScreenOffSinceCharge);
+        mDischargeStepTracker.writeToParcel(out);
+        mChargeStepTracker.writeToParcel(out);
+        mDischargeCounter.writeToParcel(out);
+        mDischargeScreenOffCounter.writeToParcel(out);
+        out.writeLong(mLastWriteTime);
+
+        if (inclUids) {
+            out.writeInt(mKernelWakelockStats.size());
+            for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
+                SamplingTimer kwlt = ent.getValue();
+                if (kwlt != null) {
+                    out.writeInt(1);
+                    out.writeString(ent.getKey());
+                    kwlt.writeToParcel(out, uSecRealtime);
+                } else {
+                    out.writeInt(0);
+                }
+            }
+            out.writeInt(mWakeupReasonStats.size());
+            for (Map.Entry<String, SamplingTimer> ent : mWakeupReasonStats.entrySet()) {
+                SamplingTimer timer = ent.getValue();
+                if (timer != null) {
+                    out.writeInt(1);
+                    out.writeString(ent.getKey());
+                    timer.writeToParcel(out, uSecRealtime);
+                } else {
+                    out.writeInt(0);
+                }
+            }
+        } else {
+            out.writeInt(0);
+        }
+
+        out.writeInt(mKernelMemoryStats.size());
+        for (int i = 0; i < mKernelMemoryStats.size(); i++) {
+            SamplingTimer kmt = mKernelMemoryStats.valueAt(i);
+            if (kmt != null) {
+                out.writeInt(1);
+                out.writeLong(mKernelMemoryStats.keyAt(i));
+                kmt.writeToParcel(out, uSecRealtime);
+            } else {
+                out.writeInt(0);
+            }
+        }
+
+        if (inclUids) {
+            int size = mUidStats.size();
+            out.writeInt(size);
+            for (int i = 0; i < size; i++) {
+                out.writeInt(mUidStats.keyAt(i));
+                Uid uid = mUidStats.valueAt(i);
+
+                uid.writeToParcelLocked(out, uSecUptime, uSecRealtime);
+            }
+        } else {
+            out.writeInt(0);
+        }
+    }
+
+    public static final Parcelable.Creator<BatteryStatsImpl> CREATOR =
+        new Parcelable.Creator<BatteryStatsImpl>() {
+        public BatteryStatsImpl createFromParcel(Parcel in) {
+            return new BatteryStatsImpl(in);
+        }
+
+        public BatteryStatsImpl[] newArray(int size) {
+            return new BatteryStatsImpl[size];
+        }
+    };
+
+    public void prepareForDumpLocked() {
+        // Need to retrieve current kernel wake lock stats before printing.
+        pullPendingStateUpdatesLocked();
+
+        // Pull the clock time.  This may update the time and make a new history entry
+        // if we had originally pulled a time before the RTC was set.
+        getStartClockTime();
+    }
+
+    public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) {
+        if (DEBUG) {
+            pw.println("mOnBatteryTimeBase:");
+            mOnBatteryTimeBase.dump(pw, "  ");
+            pw.println("mOnBatteryScreenOffTimeBase:");
+            mOnBatteryScreenOffTimeBase.dump(pw, "  ");
+            Printer pr = new PrintWriterPrinter(pw);
+            pr.println("*** Screen timer:");
+            mScreenOnTimer.logState(pr, "  ");
+            for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+                pr.println("*** Screen brightness #" + i + ":");
+                mScreenBrightnessTimer[i].logState(pr, "  ");
+            }
+            pr.println("*** Interactive timer:");
+            mInteractiveTimer.logState(pr, "  ");
+            pr.println("*** Power save mode timer:");
+            mPowerSaveModeEnabledTimer.logState(pr, "  ");
+            pr.println("*** Device idle mode light timer:");
+            mDeviceIdleModeLightTimer.logState(pr, "  ");
+            pr.println("*** Device idle mode full timer:");
+            mDeviceIdleModeFullTimer.logState(pr, "  ");
+            pr.println("*** Device light idling timer:");
+            mDeviceLightIdlingTimer.logState(pr, "  ");
+            pr.println("*** Device idling timer:");
+            mDeviceIdlingTimer.logState(pr, "  ");
+            pr.println("*** Phone timer:");
+            mPhoneOnTimer.logState(pr, "  ");
+            for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
+                pr.println("*** Phone signal strength #" + i + ":");
+                mPhoneSignalStrengthsTimer[i].logState(pr, "  ");
+            }
+            pr.println("*** Signal scanning :");
+            mPhoneSignalScanningTimer.logState(pr, "  ");
+            for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
+                pr.println("*** Data connection type #" + i + ":");
+                mPhoneDataConnectionsTimer[i].logState(pr, "  ");
+            }
+            pr.println("*** mMobileRadioPowerState=" + mMobileRadioPowerState);
+            pr.println("*** Mobile network active timer:");
+            mMobileRadioActiveTimer.logState(pr, "  ");
+            pr.println("*** Mobile network active adjusted timer:");
+            mMobileRadioActiveAdjustedTime.logState(pr, "  ");
+            pr.println("*** mWifiRadioPowerState=" + mWifiRadioPowerState);
+            pr.println("*** Wifi timer:");
+            mWifiOnTimer.logState(pr, "  ");
+            pr.println("*** WifiRunning timer:");
+            mGlobalWifiRunningTimer.logState(pr, "  ");
+            for (int i=0; i<NUM_WIFI_STATES; i++) {
+                pr.println("*** Wifi state #" + i + ":");
+                mWifiStateTimer[i].logState(pr, "  ");
+            }
+            for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+                pr.println("*** Wifi suppl state #" + i + ":");
+                mWifiSupplStateTimer[i].logState(pr, "  ");
+            }
+            for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+                pr.println("*** Wifi signal strength #" + i + ":");
+                mWifiSignalStrengthsTimer[i].logState(pr, "  ");
+            }
+            pr.println("*** Flashlight timer:");
+            mFlashlightOnTimer.logState(pr, "  ");
+            pr.println("*** Camera timer:");
+            mCameraOnTimer.logState(pr, "  ");
+        }
+        super.dumpLocked(context, pw, flags, reqUid, histStart);
+    }
+}
diff --git a/com/android/internal/os/BinderInternal.java b/com/android/internal/os/BinderInternal.java
new file mode 100644
index 0000000..ea4575a
--- /dev/null
+++ b/com/android/internal/os/BinderInternal.java
@@ -0,0 +1,109 @@
+/*
+ * 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.os.IBinder;
+import android.os.SystemClock;
+import android.util.EventLog;
+
+import dalvik.system.VMRuntime;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Private and debugging Binder APIs.
+ * 
+ * @see IBinder
+ */
+public class 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 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);
+        }
+    }
+
+    /**
+     * 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.
+     */
+    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);
+    
+    static native final void handleGc();
+    
+    public static void forceGc(String reason) {
+        EventLog.writeEvent(2741, reason);
+        VMRuntime.getRuntime().requestConcurrentGC();
+    }
+    
+    static void forceBinderGc() {
+        forceGc("Binder");
+    }
+}
diff --git a/com/android/internal/os/BluetoothPowerCalculator.java b/com/android/internal/os/BluetoothPowerCalculator.java
new file mode 100644
index 0000000..2f383ea
--- /dev/null
+++ b/com/android/internal/os/BluetoothPowerCalculator.java
@@ -0,0 +1,102 @@
+/*
+ * 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.os.BatteryStats;
+import android.util.Log;
+
+public class BluetoothPowerCalculator extends PowerCalculator {
+    private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+    private static final String TAG = "BluetoothPowerCalculator";
+    private final double mIdleMa;
+    private final double mRxMa;
+    private final double mTxMa;
+    private double mAppTotalPowerMah = 0;
+    private long mAppTotalTimeMs = 0;
+
+    public BluetoothPowerCalculator(PowerProfile profile) {
+        mIdleMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE);
+        mRxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX);
+        mTxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX);
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+
+        final BatteryStats.ControllerActivityCounter counter = u.getBluetoothControllerActivity();
+        if (counter == null) {
+            return;
+        }
+
+        final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(statsType);
+        final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(statsType);
+        final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(statsType);
+        final long totalTimeMs = idleTimeMs + txTimeMs + rxTimeMs;
+        double powerMah = counter.getPowerCounter().getCountLocked(statsType)
+                / (double)(1000*60*60);
+
+        if (powerMah == 0) {
+            powerMah = ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa))
+                    / (1000*60*60);
+        }
+
+        app.bluetoothPowerMah = powerMah;
+        app.bluetoothRunningTimeMs = totalTimeMs;
+        app.btRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_RX_DATA, statsType);
+        app.btTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_TX_DATA, statsType);
+
+        mAppTotalPowerMah += powerMah;
+        mAppTotalTimeMs += totalTimeMs;
+    }
+
+    @Override
+    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+                                   long rawUptimeUs, int statsType) {
+        final BatteryStats.ControllerActivityCounter counter =
+                stats.getBluetoothControllerActivity();
+
+        final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(statsType);
+        final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(statsType);
+        final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(statsType);
+        final long totalTimeMs = idleTimeMs + txTimeMs + rxTimeMs;
+        double powerMah = counter.getPowerCounter().getCountLocked(statsType)
+                 / (double)(1000*60*60);
+
+        if (powerMah == 0) {
+            // Some devices do not report the power, so calculate it.
+            powerMah = ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa))
+                    / (1000*60*60);
+        }
+
+        // Subtract what the apps used, but clamp to 0.
+        powerMah = Math.max(0, powerMah - mAppTotalPowerMah);
+
+        if (DEBUG && powerMah != 0) {
+            Log.d(TAG, "Bluetooth active: time=" + (totalTimeMs)
+                    + " power=" + BatteryStatsHelper.makemAh(powerMah));
+        }
+
+        app.bluetoothPowerMah = powerMah;
+        app.bluetoothRunningTimeMs = Math.max(0, totalTimeMs - mAppTotalTimeMs);
+    }
+
+    @Override
+    public void reset() {
+        mAppTotalPowerMah = 0;
+        mAppTotalTimeMs = 0;
+    }
+}
diff --git a/com/android/internal/os/CameraPowerCalculator.java b/com/android/internal/os/CameraPowerCalculator.java
new file mode 100644
index 0000000..3273080
--- /dev/null
+++ b/com/android/internal/os/CameraPowerCalculator.java
@@ -0,0 +1,48 @@
+/*
+ * 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.os.BatteryStats;
+
+/**
+ * Power calculator for the camera subsystem, excluding the flashlight.
+ *
+ * Note: Power draw for the flash unit should be included in the FlashlightPowerCalculator.
+ */
+public class CameraPowerCalculator extends PowerCalculator {
+    private final double mCameraPowerOnAvg;
+
+    public CameraPowerCalculator(PowerProfile profile) {
+        mCameraPowerOnAvg = profile.getAveragePower(PowerProfile.POWER_CAMERA);
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+
+        // Calculate camera power usage.  Right now, this is a (very) rough estimate based on the
+        // average power usage for a typical camera application.
+        final BatteryStats.Timer timer = u.getCameraTurnedOnTimer();
+        if (timer != null) {
+            final long totalTime = timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000;
+            app.cameraTimeMs = totalTime;
+            app.cameraPowerMah = (totalTime * mCameraPowerOnAvg) / (1000*60*60);
+        } else {
+            app.cameraTimeMs = 0;
+            app.cameraPowerMah = 0;
+        }
+    }
+}
diff --git a/com/android/internal/os/ClassLoaderFactory.java b/com/android/internal/os/ClassLoaderFactory.java
new file mode 100644
index 0000000..b2b769e
--- /dev/null
+++ b/com/android/internal/os/ClassLoaderFactory.java
@@ -0,0 +1,112 @@
+/*
+ * 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.Trace;
+
+import dalvik.system.DelegateLastClassLoader;
+import dalvik.system.DexClassLoader;
+import dalvik.system.PathClassLoader;
+
+/**
+ * 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 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) {
+        if (isPathClassLoaderName(classloaderName)) {
+            return new PathClassLoader(dexPath, librarySearchPath, parent);
+        } else if (isDelegateLastClassLoaderName(classloaderName)) {
+            return new DelegateLastClassLoader(dexPath, librarySearchPath, parent);
+        }
+
+        throw new AssertionError("Invalid classLoaderName: " + classloaderName);
+    }
+
+    /**
+     * 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) {
+
+        final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent,
+                classloaderName);
+
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "createClassloaderNamespace");
+        String errorMessage = createClassloaderNamespace(classLoader,
+                                                         targetSdkVersion,
+                                                         librarySearchPath,
+                                                         libraryPermittedPath,
+                                                         isNamespaceShared);
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        if (errorMessage != null) {
+            throw new UnsatisfiedLinkError("Unable to create namespace for the classloader " +
+                                           classLoader + ": " + errorMessage);
+        }
+
+        return classLoader;
+    }
+
+    private static native String createClassloaderNamespace(ClassLoader classLoader,
+                                                            int targetSdkVersion,
+                                                            String librarySearchPath,
+                                                            String libraryPermittedPath,
+                                                            boolean isNamespaceShared);
+}
diff --git a/com/android/internal/os/CpuPowerCalculator.java b/com/android/internal/os/CpuPowerCalculator.java
new file mode 100644
index 0000000..bb743c1
--- /dev/null
+++ b/com/android/internal/os/CpuPowerCalculator.java
@@ -0,0 +1,97 @@
+/*
+ * 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.os.BatteryStats;
+import android.util.ArrayMap;
+import android.util.Log;
+
+public class CpuPowerCalculator extends PowerCalculator {
+    private static final String TAG = "CpuPowerCalculator";
+    private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+    private static final long MICROSEC_IN_HR = (long) 60 * 60 * 1000 * 1000;
+    private final PowerProfile mProfile;
+
+    public CpuPowerCalculator(PowerProfile profile) {
+        mProfile = profile;
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+
+        app.cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
+        final int numClusters = mProfile.getNumCpuClusters();
+
+        double cpuPowerMaUs = 0;
+        for (int cluster = 0; cluster < numClusters; cluster++) {
+            final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
+            for (int speed = 0; speed < speedsForCluster; speed++) {
+                final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType);
+                final double cpuSpeedStepPower = timeUs *
+                        mProfile.getAveragePowerForCpu(cluster, speed);
+                if (DEBUG) {
+                    Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
+                            + speed + " timeUs=" + timeUs + " power="
+                            + BatteryStatsHelper.makemAh(cpuSpeedStepPower / MICROSEC_IN_HR));
+                }
+                cpuPowerMaUs += cpuSpeedStepPower;
+            }
+        }
+        app.cpuPowerMah = cpuPowerMaUs / MICROSEC_IN_HR;
+
+        if (DEBUG && (app.cpuTimeMs != 0 || app.cpuPowerMah != 0)) {
+            Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + app.cpuTimeMs + " ms power="
+                    + BatteryStatsHelper.makemAh(app.cpuPowerMah));
+        }
+
+        // Keep track of the package with highest drain.
+        double highestDrain = 0;
+
+        app.cpuFgTimeMs = 0;
+        final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
+        final int processStatsCount = processStats.size();
+        for (int i = 0; i < processStatsCount; i++) {
+            final BatteryStats.Uid.Proc ps = processStats.valueAt(i);
+            final String processName = processStats.keyAt(i);
+            app.cpuFgTimeMs += ps.getForegroundTime(statsType);
+
+            final long costValue = ps.getUserTime(statsType) + ps.getSystemTime(statsType)
+                    + ps.getForegroundTime(statsType);
+
+            // Each App can have multiple packages and with multiple running processes.
+            // Keep track of the package who's process has the highest drain.
+            if (app.packageWithHighestDrain == null ||
+                    app.packageWithHighestDrain.startsWith("*")) {
+                highestDrain = costValue;
+                app.packageWithHighestDrain = processName;
+            } else if (highestDrain < costValue && !processName.startsWith("*")) {
+                highestDrain = costValue;
+                app.packageWithHighestDrain = processName;
+            }
+        }
+
+        // Ensure that the CPU times make sense.
+        if (app.cpuFgTimeMs > app.cpuTimeMs) {
+            if (DEBUG && app.cpuFgTimeMs > app.cpuTimeMs + 10000) {
+                Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
+            }
+
+            // Statistics may not have been gathered yet.
+            app.cpuTimeMs = app.cpuFgTimeMs;
+        }
+    }
+}
diff --git a/com/android/internal/os/FlashlightPowerCalculator.java b/com/android/internal/os/FlashlightPowerCalculator.java
new file mode 100644
index 0000000..fef66ff
--- /dev/null
+++ b/com/android/internal/os/FlashlightPowerCalculator.java
@@ -0,0 +1,46 @@
+/*
+ * 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.os.BatteryStats;
+
+/**
+ * Power calculator for the flashlight.
+ */
+public class FlashlightPowerCalculator extends PowerCalculator {
+    private final double mFlashlightPowerOnAvg;
+
+    public FlashlightPowerCalculator(PowerProfile profile) {
+        mFlashlightPowerOnAvg = profile.getAveragePower(PowerProfile.POWER_FLASHLIGHT);
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+
+        // Calculate flashlight power usage.  Right now, this is based on the average power draw
+        // of the flash unit when kept on over a short period of time.
+        final BatteryStats.Timer timer = u.getFlashlightTurnedOnTimer();
+        if (timer != null) {
+            final long totalTime = timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000;
+            app.flashlightTimeMs = totalTime;
+            app.flashlightPowerMah = (totalTime * mFlashlightPowerOnAvg) / (1000*60*60);
+        } else {
+            app.flashlightTimeMs = 0;
+            app.flashlightPowerMah = 0;
+        }
+    }
+}
diff --git a/com/android/internal/os/FuseAppLoop.java b/com/android/internal/os/FuseAppLoop.java
new file mode 100644
index 0000000..088e726
--- /dev/null
+++ b/com/android/internal/os/FuseAppLoop.java
@@ -0,0 +1,385 @@
+/*
+ * 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.os.ProxyFileDescriptorCallback;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+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.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) {
+            Preconditions.checkNotNull(callback);
+            Preconditions.checkNotNull(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 = 256 * 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(entry.getThreadId());
+                        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")
+    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")
+    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(entry.getThreadId());
+                }
+            } 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;
+    }
+
+    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;
+    }
+
+    private void recycleLocked(Args args) {
+        if (mArgsPool.size() < ARGS_POOL_SIZE) {
+            mArgsPool.add(args);
+        }
+    }
+
+    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 = Preconditions.checkNotNull(callback);
+            this.handler = Preconditions.checkNotNull(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 Thread ID and byte buffer.
+     */
+    private static class BytesMap {
+        final Map<Long, BytesMapEntry> mEntries = new HashMap<>();
+
+        byte[] startUsing(long threadId) {
+            BytesMapEntry entry = mEntries.get(threadId);
+            if (entry == null) {
+                entry = new BytesMapEntry();
+                mEntries.put(threadId, entry);
+            }
+            entry.counter++;
+            return entry.bytes;
+        }
+
+        void stopUsing(long threadId) {
+            final BytesMapEntry entry = mEntries.get(threadId);
+            Preconditions.checkNotNull(entry);
+            entry.counter--;
+            if (entry.counter <= 0) {
+                mEntries.remove(threadId);
+            }
+        }
+
+        void clear() {
+            mEntries.clear();
+        }
+    }
+
+    private static class Args {
+        long unique;
+        long inode;
+        long offset;
+        int size;
+        byte[] data;
+        CallbackEntry entry;
+    }
+}
diff --git a/com/android/internal/os/FuseUnavailableMountException.java b/com/android/internal/os/FuseUnavailableMountException.java
new file mode 100644
index 0000000..ca3cfb9
--- /dev/null
+++ b/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/com/android/internal/os/HandlerCaller.java b/com/android/internal/os/HandlerCaller.java
new file mode 100644
index 0000000..ae7c5f2
--- /dev/null
+++ b/com/android/internal/os/HandlerCaller.java
@@ -0,0 +1,266 @@
+/*
+ * 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.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+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);
+    }
+    
+    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;
+    }
+
+    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);
+    }
+    
+    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);
+    }
+    
+    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);
+    }
+    
+    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);
+    }
+
+    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);
+    }
+    
+    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/com/android/internal/os/KernelCpuSpeedReader.java b/com/android/internal/os/KernelCpuSpeedReader.java
new file mode 100644
index 0000000..757a112
--- /dev/null
+++ b/com/android/internal/os/KernelCpuSpeedReader.java
@@ -0,0 +1,94 @@
+/*
+ * 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.text.TextUtils;
+import android.os.StrictMode;
+import android.system.OsConstants;
+import android.util.Slog;
+
+import libcore.io.Libcore;
+
+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 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);
+        mLastSpeedTimesMs = new long[numSpeedSteps];
+        mDeltaSpeedTimesMs = new long[numSpeedSteps];
+        long jiffyHz = Libcore.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;
+    }
+}
diff --git a/com/android/internal/os/KernelMemoryBandwidthStats.java b/com/android/internal/os/KernelMemoryBandwidthStats.java
new file mode 100644
index 0000000..15a5e3e
--- /dev/null
+++ b/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/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/com/android/internal/os/KernelUidCpuFreqTimeReader.java
new file mode 100644
index 0000000..8884d24
--- /dev/null
+++ b/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -0,0 +1,244 @@
+/*
+ * 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.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+
+/**
+ * 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 class KernelUidCpuFreqTimeReader {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "KernelUidCpuFreqTimeReader";
+    private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
+
+    public interface Callback {
+        void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
+    }
+
+    private long[] mCpuFreqs;
+    private int mCpuFreqsCount;
+    private long mLastTimeReadMs;
+    private long mNowTimeMs;
+
+    private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
+
+    // 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 TOTAL_READ_ERROR_COUNT = 5;
+    private int mReadErrorCounter;
+    private boolean mProcFileAvailable;
+    private boolean mPerClusterTimesAvailable;
+
+    public boolean perClusterTimesAvailable() {
+        return mPerClusterTimesAvailable;
+    }
+
+    public long[] readFreqs(@NonNull PowerProfile powerProfile) {
+        checkNotNull(powerProfile);
+
+        if (mCpuFreqs != null) {
+            // No need to read cpu freqs more than once.
+            return mCpuFreqs;
+        }
+        if (!mProcFileAvailable && mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+            return null;
+        }
+        try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
+            mProcFileAvailable = true;
+            return readFreqs(reader, powerProfile);
+        } catch (IOException e) {
+            mReadErrorCounter++;
+            Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
+            return null;
+        }
+    }
+
+    @VisibleForTesting
+    public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile)
+            throws IOException {
+        final String line = reader.readLine();
+        if (line == null) {
+            return null;
+        }
+        return readCpuFreqs(line, powerProfile);
+    }
+
+    public void readDelta(@Nullable Callback callback) {
+        if (!mProcFileAvailable) {
+            return;
+        }
+        try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
+            mNowTimeMs = SystemClock.elapsedRealtime();
+            readDelta(reader, callback);
+            mLastTimeReadMs = mNowTimeMs;
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
+        }
+    }
+
+    public void removeUid(int uid) {
+        mLastUidCpuFreqTimeMs.delete(uid);
+    }
+
+    public void removeUidsInRange(int startUid, int endUid) {
+        if (endUid < startUid) {
+            return;
+        }
+        mLastUidCpuFreqTimeMs.put(startUid, null);
+        mLastUidCpuFreqTimeMs.put(endUid, null);
+        final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
+        final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
+        mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+    }
+
+    @VisibleForTesting
+    public void readDelta(BufferedReader reader, @Nullable Callback callback) throws IOException {
+        String line = reader.readLine();
+        if (line == null) {
+            return;
+        }
+        while ((line = reader.readLine()) != null) {
+            final int index = line.indexOf(' ');
+            final int uid = Integer.parseInt(line.substring(0, index - 1), 10);
+            readTimesForUid(uid, line.substring(index + 1, line.length()), callback);
+        }
+    }
+
+    private void readTimesForUid(int uid, String line, Callback callback) {
+        long[] uidTimeMs = mLastUidCpuFreqTimeMs.get(uid);
+        if (uidTimeMs == null) {
+            uidTimeMs = new long[mCpuFreqsCount];
+            mLastUidCpuFreqTimeMs.put(uid, uidTimeMs);
+        }
+        final String[] timesStr = line.split(" ");
+        final int size = timesStr.length;
+        if (size != uidTimeMs.length) {
+            Slog.e(TAG, "No. of readings don't match cpu freqs, readings: " + size
+                    + " cpuFreqsCount: " + uidTimeMs.length);
+            return;
+        }
+        final long[] deltaUidTimeMs = new long[size];
+        final long[] curUidTimeMs = new long[size];
+        boolean notify = false;
+        for (int i = 0; i < size; ++i) {
+            // Times read will be in units of 10ms
+            final long totalTimeMs = Long.parseLong(timesStr[i], 10) * 10;
+            deltaUidTimeMs[i] = totalTimeMs - uidTimeMs[i];
+            // If there is malformed data for any uid, then we just log about it and ignore
+            // the data for that uid.
+            if (deltaUidTimeMs[i] < 0 || totalTimeMs < 0) {
+                if (DEBUG) {
+                    final StringBuilder sb = new StringBuilder("Malformed cpu freq data for UID=")
+                            .append(uid).append("\n");
+                    sb.append("data=").append("(").append(uidTimeMs[i]).append(",")
+                            .append(totalTimeMs).append(")").append("\n");
+                    sb.append("times=").append("(");
+                    TimeUtils.formatDuration(mLastTimeReadMs, sb);
+                    sb.append(",");
+                    TimeUtils.formatDuration(mNowTimeMs, sb);
+                    sb.append(")");
+                    Slog.e(TAG, sb.toString());
+                }
+                return;
+            }
+            curUidTimeMs[i] = totalTimeMs;
+            notify = notify || (deltaUidTimeMs[i] > 0);
+        }
+        if (notify) {
+            System.arraycopy(curUidTimeMs, 0, uidTimeMs, 0, size);
+            if (callback != null) {
+                callback.onUidCpuFreqTime(uid, deltaUidTimeMs);
+            }
+        }
+    }
+
+    private long[] readCpuFreqs(String line, PowerProfile powerProfile) {
+        final String[] freqStr = line.split(" ");
+        // First item would be "uid: " which needs to be ignored.
+        mCpuFreqsCount = freqStr.length - 1;
+        mCpuFreqs = new long[mCpuFreqsCount];
+        for (int i = 0; i < mCpuFreqsCount; ++i) {
+            mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
+        }
+
+        // Check if the freqs in the proc file correspond to per-cluster freqs.
+        final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
+        final int numClusters = powerProfile.getNumCpuClusters();
+        if (numClusterFreqs.size() == numClusters) {
+            mPerClusterTimesAvailable = true;
+            for (int i = 0; i < numClusters; ++i) {
+                if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
+                    mPerClusterTimesAvailable = false;
+                    break;
+                }
+            }
+        } else {
+            mPerClusterTimesAvailable = false;
+        }
+        Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
+
+        return mCpuFreqs;
+    }
+
+    /**
+     * 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 < mCpuFreqsCount; ++i) {
+            freqsFound++;
+            if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) {
+                numClusterFreqs.add(freqsFound);
+                freqsFound = 0;
+            }
+        }
+        return numClusterFreqs;
+    }
+}
diff --git a/com/android/internal/os/KernelUidCpuTimeReader.java b/com/android/internal/os/KernelUidCpuTimeReader.java
new file mode 100644
index 0000000..37d9d1d
--- /dev/null
+++ b/com/android/internal/os/KernelUidCpuTimeReader.java
@@ -0,0 +1,171 @@
+/*
+ * 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.annotation.Nullable;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseLongArray;
+import android.util.TimeUtils;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+
+/**
+ * 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.
+ */
+public class KernelUidCpuTimeReader {
+    private static final String TAG = "KernelUidCpuTimeReader";
+    private static final String sProcFile = "/proc/uid_cputime/show_uid_stat";
+    private static final String sRemoveUidProcFile = "/proc/uid_cputime/remove_uid_range";
+
+    /**
+     * Callback interface for processing each line of the proc file.
+     */
+    public interface Callback {
+        /**
+         * @param uid UID of the app
+         * @param userTimeUs time spent executing in user space in microseconds
+         * @param systemTimeUs time spent executing in kernel space in microseconds
+         */
+        void onUidCpuTime(int uid, long userTimeUs, long systemTimeUs);
+    }
+
+    private SparseLongArray mLastUserTimeUs = new SparseLongArray();
+    private SparseLongArray mLastSystemTimeUs = new SparseLongArray();
+    private long mLastTimeReadUs = 0;
+
+    /**
+     * Reads the proc file, calling into the callback with a delta of time for each UID.
+     * @param callback The callback to invoke for each line of the proc file. If null,
+     *                 the data is consumed and subsequent calls to readDelta will provide
+     *                 a fresh delta.
+     */
+    public void readDelta(@Nullable Callback callback) {
+        long nowUs = SystemClock.elapsedRealtime() * 1000;
+        try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
+            TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
+            String line;
+            while ((line = reader.readLine()) != null) {
+                splitter.setString(line);
+                final String uidStr = splitter.next();
+                final int uid = Integer.parseInt(uidStr.substring(0, uidStr.length() - 1), 10);
+                final long userTimeUs = Long.parseLong(splitter.next(), 10);
+                final long systemTimeUs = Long.parseLong(splitter.next(), 10);
+
+                // Only report if there is a callback and if this is not the first read.
+                if (callback != null && mLastTimeReadUs != 0) {
+                    long userTimeDeltaUs = userTimeUs;
+                    long systemTimeDeltaUs = systemTimeUs;
+                    int index = mLastUserTimeUs.indexOfKey(uid);
+                    if (index >= 0) {
+                        userTimeDeltaUs -= mLastUserTimeUs.valueAt(index);
+                        systemTimeDeltaUs -= mLastSystemTimeUs.valueAt(index);
+
+                        final long timeDiffUs = nowUs - mLastTimeReadUs;
+                        if (userTimeDeltaUs < 0 || systemTimeDeltaUs < 0) {
+                            StringBuilder sb = new StringBuilder("Malformed cpu data for UID=");
+                            sb.append(uid).append("!\n");
+                            sb.append("Time between reads: ");
+                            TimeUtils.formatDuration(timeDiffUs / 1000, sb);
+                            sb.append("\n");
+                            sb.append("Previous times: u=");
+                            TimeUtils.formatDuration(mLastUserTimeUs.valueAt(index) / 1000, sb);
+                            sb.append(" s=");
+                            TimeUtils.formatDuration(mLastSystemTimeUs.valueAt(index) / 1000, sb);
+
+                            sb.append("\nCurrent times: u=");
+                            TimeUtils.formatDuration(userTimeUs / 1000, sb);
+                            sb.append(" s=");
+                            TimeUtils.formatDuration(systemTimeUs / 1000, sb);
+                            sb.append("\nDelta: u=");
+                            TimeUtils.formatDuration(userTimeDeltaUs / 1000, sb);
+                            sb.append(" s=");
+                            TimeUtils.formatDuration(systemTimeDeltaUs / 1000, sb);
+                            Slog.e(TAG, sb.toString());
+
+                            userTimeDeltaUs = 0;
+                            systemTimeDeltaUs = 0;
+                        }
+                    }
+
+                    if (userTimeDeltaUs != 0 || systemTimeDeltaUs != 0) {
+                        callback.onUidCpuTime(uid, userTimeDeltaUs, systemTimeDeltaUs);
+                    }
+                }
+                mLastUserTimeUs.put(uid, userTimeUs);
+                mLastSystemTimeUs.put(uid, systemTimeUs);
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage());
+        }
+        mLastTimeReadUs = nowUs;
+    }
+
+    /**
+     * Removes the UID from the kernel module and from internal accounting data.
+     * @param uid The UID to remove.
+     */
+    public void removeUid(int uid) {
+        final int index = mLastSystemTimeUs.indexOfKey(uid);
+        if (index >= 0) {
+            mLastSystemTimeUs.removeAt(index);
+            mLastUserTimeUs.removeAt(index);
+        }
+        removeUidsFromKernelModule(uid, uid);
+    }
+
+    /**
+     * Removes UIDs in a given range from the kernel module and internal accounting data.
+     * @param startUid the first uid to remove
+     * @param endUid the last uid to remove
+     */
+    public void removeUidsInRange(int startUid, int endUid) {
+        if (endUid < startUid) {
+            return;
+        }
+        mLastSystemTimeUs.put(startUid, 0);
+        mLastUserTimeUs.put(startUid, 0);
+        mLastSystemTimeUs.put(endUid, 0);
+        mLastUserTimeUs.put(endUid, 0);
+        final int startIndex = mLastSystemTimeUs.indexOfKey(startUid);
+        final int endIndex = mLastSystemTimeUs.indexOfKey(endUid);
+        mLastSystemTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1);
+        mLastUserTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1);
+        removeUidsFromKernelModule(startUid, endUid);
+    }
+
+    private void removeUidsFromKernelModule(int startUid, int endUid) {
+        Slog.d(TAG, "Removing uids " + startUid + "-" + endUid);
+        try (FileWriter writer = new FileWriter(sRemoveUidProcFile)) {
+            writer.write(startUid + "-" + endUid);
+            writer.flush();
+        } catch (IOException e) {
+            Slog.e(TAG, "failed to remove uids " + startUid + " - " + endUid
+                    + " from uid_cputime module", e);
+        }
+    }
+}
diff --git a/com/android/internal/os/KernelWakelockReader.java b/com/android/internal/os/KernelWakelockReader.java
new file mode 100644
index 0000000..7178ec7
--- /dev/null
+++ b/com/android/internal/os/KernelWakelockReader.java
@@ -0,0 +1,206 @@
+/*
+ * 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.os.Process;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileInputStream;
+import java.util.Iterator;
+
+/**
+ * Reads and parses wakelock stats from the kernel (/proc/wakelocks).
+ */
+public class KernelWakelockReader {
+    private static final String TAG = "KernelWakelockReader";
+    private static int sKernelWakelockUpdateVersion = 0;
+    private static final String sWakelockFile = "/proc/wakelocks";
+    private static final String sWakeupSourceFile = "/d/wakeup_sources";
+
+    private static final int[] PROC_WAKELOCKS_FORMAT = new int[] {
+        Process.PROC_TAB_TERM|Process.PROC_OUT_STRING|                // 0: name
+                              Process.PROC_QUOTES,
+        Process.PROC_TAB_TERM|Process.PROC_OUT_LONG,                  // 1: count
+        Process.PROC_TAB_TERM,
+        Process.PROC_TAB_TERM,
+        Process.PROC_TAB_TERM,
+        Process.PROC_TAB_TERM|Process.PROC_OUT_LONG,                  // 5: totalTime
+    };
+
+    private static final int[] WAKEUP_SOURCES_FORMAT = new int[] {
+        Process.PROC_TAB_TERM|Process.PROC_OUT_STRING,                // 0: name
+        Process.PROC_TAB_TERM|Process.PROC_COMBINE|
+                              Process.PROC_OUT_LONG,                  // 1: count
+        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+        Process.PROC_TAB_TERM|Process.PROC_COMBINE
+                             |Process.PROC_OUT_LONG,                  // 6: totalTime
+    };
+
+    private final String[] mProcWakelocksName = new String[3];
+    private final long[] mProcWakelocksData = new long[3];
+
+    /**
+     * Reads kernel wakelock stats and updates the staleStats with the new information.
+     * @param staleStats Existing object to update.
+     * @return the updated data.
+     */
+    public final KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats) {
+        byte[] buffer = new byte[32*1024];
+        int len;
+        boolean wakeup_sources;
+        final long startTime = SystemClock.uptimeMillis();
+
+        try {
+            FileInputStream is;
+            try {
+                is = new FileInputStream(sWakelockFile);
+                wakeup_sources = false;
+            } catch (java.io.FileNotFoundException e) {
+                try {
+                    is = new FileInputStream(sWakeupSourceFile);
+                    wakeup_sources = true;
+                } catch (java.io.FileNotFoundException e2) {
+                    Slog.wtf(TAG, "neither " + sWakelockFile + " nor " +
+                            sWakeupSourceFile + " exists");
+                    return null;
+                }
+            }
+
+            len = is.read(buffer);
+            is.close();
+        } catch (java.io.IOException e) {
+            Slog.wtf(TAG, "failed to read kernel wakelocks", e);
+            return null;
+        }
+
+        final long readTime = SystemClock.uptimeMillis() - startTime;
+        if (readTime > 100) {
+            Slog.w(TAG, "Reading wakelock stats took " + readTime + "ms");
+        }
+
+        if (len > 0) {
+            if (len >= buffer.length) {
+                Slog.wtf(TAG, "Kernel wake locks exceeded buffer size " + buffer.length);
+            }
+            int i;
+            for (i=0; i<len; i++) {
+                if (buffer[i] == '\0') {
+                    len = i;
+                    break;
+                }
+            }
+        }
+        return parseProcWakelocks(buffer, len, wakeup_sources, staleStats);
+    }
+
+    /**
+     * Reads the wakelocks and updates the staleStats with the new information.
+     */
+    @VisibleForTesting
+    public KernelWakelockStats parseProcWakelocks(byte[] wlBuffer, int len, boolean wakeup_sources,
+                                                  final KernelWakelockStats staleStats) {
+        String name;
+        int count;
+        long totalTime;
+        int startIndex;
+        int endIndex;
+
+        // Advance past the first line.
+        int i;
+        for (i = 0; i < len && wlBuffer[i] != '\n' && wlBuffer[i] != '\0'; i++);
+        startIndex = endIndex = i + 1;
+
+        synchronized(this) {
+            sKernelWakelockUpdateVersion++;
+            while (endIndex < len) {
+                for (endIndex=startIndex;
+                        endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0';
+                        endIndex++);
+                // Don't go over the end of the buffer, Process.parseProcLine might
+                // write to wlBuffer[endIndex]
+                if (endIndex > (len - 1) ) {
+                    break;
+                }
+
+                String[] nameStringArray = mProcWakelocksName;
+                long[] wlData = mProcWakelocksData;
+                // Stomp out any bad characters since this is from a circular buffer
+                // A corruption is seen sometimes that results in the vm crashing
+                // This should prevent crashes and the line will probably fail to parse
+                for (int j = startIndex; j < endIndex; j++) {
+                    if ((wlBuffer[j] & 0x80) != 0) wlBuffer[j] = (byte) '?';
+                }
+                boolean parsed = Process.parseProcLine(wlBuffer, startIndex, endIndex,
+                        wakeup_sources ? WAKEUP_SOURCES_FORMAT :
+                                         PROC_WAKELOCKS_FORMAT,
+                        nameStringArray, wlData, null);
+
+                name = nameStringArray[0];
+                count = (int) wlData[1];
+
+                if (wakeup_sources) {
+                        // convert milliseconds to microseconds
+                        totalTime = wlData[2] * 1000;
+                } else {
+                        // convert nanoseconds to microseconds with rounding.
+                        totalTime = (wlData[2] + 500) / 1000;
+                }
+
+                if (parsed && name.length() > 0) {
+                    if (!staleStats.containsKey(name)) {
+                        staleStats.put(name, new KernelWakelockStats.Entry(count, totalTime,
+                                sKernelWakelockUpdateVersion));
+                    } else {
+                        KernelWakelockStats.Entry kwlStats = staleStats.get(name);
+                        if (kwlStats.mVersion == sKernelWakelockUpdateVersion) {
+                            kwlStats.mCount += count;
+                            kwlStats.mTotalTime += totalTime;
+                        } else {
+                            kwlStats.mCount = count;
+                            kwlStats.mTotalTime = totalTime;
+                            kwlStats.mVersion = sKernelWakelockUpdateVersion;
+                        }
+                    }
+                } else if (!parsed) {
+                    try {
+                        Slog.wtf(TAG, "Failed to parse proc line: " +
+                                new String(wlBuffer, startIndex, endIndex - startIndex));
+                    } catch (Exception e) {
+                        Slog.wtf(TAG, "Failed to parse proc line!");
+                    }
+                }
+                startIndex = endIndex + 1;
+            }
+
+            // Don't report old data.
+            Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator();
+            while (itr.hasNext()) {
+                if (itr.next().mVersion != sKernelWakelockUpdateVersion) {
+                    itr.remove();
+                }
+            }
+
+            staleStats.kernelWakelockVersion = sKernelWakelockUpdateVersion;
+            return staleStats;
+        }
+    }
+}
diff --git a/com/android/internal/os/KernelWakelockStats.java b/com/android/internal/os/KernelWakelockStats.java
new file mode 100644
index 0000000..144ea00
--- /dev/null
+++ b/com/android/internal/os/KernelWakelockStats.java
@@ -0,0 +1,37 @@
+/*
+ * 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 java.util.HashMap;
+
+/**
+ * Kernel wakelock stats object.
+ */
+public class KernelWakelockStats extends HashMap<String, KernelWakelockStats.Entry> {
+    public static class Entry {
+        public int mCount;
+        public long mTotalTime;
+        public int mVersion;
+
+        Entry(int count, long totalTime, int version) {
+            mCount = count;
+            mTotalTime = totalTime;
+            mVersion = version;
+        }
+    }
+
+    int kernelWakelockVersion;
+}
diff --git a/com/android/internal/os/LoggingPrintStream.java b/com/android/internal/os/LoggingPrintStream.java
new file mode 100644
index 0000000..f14394a
--- /dev/null
+++ b/com/android/internal/os/LoggingPrintStream.java
@@ -0,0 +1,345 @@
+/*
+ * 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;
+
+/**
+ * A print stream which logs output line by line.
+ *
+ * {@hide}
+ */
+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/com/android/internal/os/MemoryPowerCalculator.java b/com/android/internal/os/MemoryPowerCalculator.java
new file mode 100644
index 0000000..efd3ab5
--- /dev/null
+++ b/com/android/internal/os/MemoryPowerCalculator.java
@@ -0,0 +1,54 @@
+package com.android.internal.os;
+
+import android.os.BatteryStats;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+public class MemoryPowerCalculator extends PowerCalculator {
+
+    public static final String TAG = "MemoryPowerCalculator";
+    private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+    private final double[] powerAverages;
+
+    public MemoryPowerCalculator(PowerProfile profile) {
+        int numBuckets = profile.getNumElements(PowerProfile.POWER_MEMORY);
+        powerAverages = new double[numBuckets];
+        for (int i = 0; i < numBuckets; i++) {
+            powerAverages[i] = profile.getAveragePower(PowerProfile.POWER_MEMORY, i);
+            if (powerAverages[i] == 0 && DEBUG) {
+                Log.d(TAG, "Problem with PowerProfile. Received 0 value in MemoryPowerCalculator");
+            }
+        }
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+            long rawUptimeUs, int statsType) {}
+
+    @Override
+    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+            long rawUptimeUs, int statsType) {
+        double totalMah = 0;
+        long totalTimeMs = 0;
+        LongSparseArray<? extends BatteryStats.Timer> timers = stats.getKernelMemoryStats();
+        for (int i = 0; i < timers.size() && i < powerAverages.length; i++) {
+            double mAatRail = powerAverages[(int) timers.keyAt(i)];
+            long timeMs = timers.valueAt(i).getTotalTimeLocked(rawRealtimeUs, statsType);
+            double mAm = (mAatRail * timeMs) / (1000*60);
+            if(DEBUG) {
+                Log.d(TAG, "Calculating mAh for bucket " + timers.keyAt(i) + " while unplugged");
+                Log.d(TAG, "Converted power profile number from "
+                        + powerAverages[(int) timers.keyAt(i)] + " into " + mAatRail);
+                Log.d(TAG, "Calculated mAm " + mAm);
+            }
+            totalMah += mAm/60;
+            totalTimeMs += timeMs;
+        }
+        app.usagePowerMah = totalMah;
+        app.usageTimeMs = totalTimeMs;
+        if (DEBUG) {
+            Log.d(TAG, String.format("Calculated total mAh for memory %f while unplugged %d ",
+                    totalMah, totalTimeMs));
+        }
+    }
+}
diff --git a/com/android/internal/os/MobileRadioPowerCalculator.java b/com/android/internal/os/MobileRadioPowerCalculator.java
new file mode 100644
index 0000000..8586d76
--- /dev/null
+++ b/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -0,0 +1,149 @@
+/*
+ * 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.os.BatteryStats;
+import android.telephony.SignalStrength;
+import android.util.Log;
+
+public class MobileRadioPowerCalculator extends PowerCalculator {
+    private static final String TAG = "MobileRadioPowerController";
+    private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+    private final double mPowerRadioOn;
+    private final double[] mPowerBins = new double[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+    private final double mPowerScan;
+    private BatteryStats mStats;
+    private long mTotalAppMobileActiveMs = 0;
+
+    /**
+     * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio.
+     */
+    private double getMobilePowerPerPacket(long rawRealtimeUs, int statsType) {
+        final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
+        final double MOBILE_POWER = mPowerRadioOn / 3600;
+
+        final long mobileRx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
+                statsType);
+        final long mobileTx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
+                statsType);
+        final long mobileData = mobileRx + mobileTx;
+
+        final long radioDataUptimeMs =
+                mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
+        final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0)
+                ? (mobileData / (double)radioDataUptimeMs)
+                : (((double)MOBILE_BPS) / 8 / 2048);
+        return (MOBILE_POWER / mobilePps) / (60*60);
+    }
+
+    public MobileRadioPowerCalculator(PowerProfile profile, BatteryStats stats) {
+        mPowerRadioOn = profile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE);
+        for (int i = 0; i < mPowerBins.length; i++) {
+            mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i);
+        }
+        mPowerScan = profile.getAveragePower(PowerProfile.POWER_RADIO_SCANNING);
+        mStats = stats;
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+        // Add cost of mobile traffic.
+        app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
+                statsType);
+        app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
+                statsType);
+        app.mobileActive = u.getMobileRadioActiveTime(statsType) / 1000;
+        app.mobileActiveCount = u.getMobileRadioActiveCount(statsType);
+        app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA,
+                statsType);
+        app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA,
+                statsType);
+
+        if (app.mobileActive > 0) {
+            // We are tracking when the radio is up, so can use the active time to
+            // determine power use.
+            mTotalAppMobileActiveMs += app.mobileActive;
+            app.mobileRadioPowerMah = (app.mobileActive * mPowerRadioOn) / (1000*60*60);
+        } else {
+            // We are not tracking when the radio is up, so must approximate power use
+            // based on the number of packets.
+            app.mobileRadioPowerMah = (app.mobileRxPackets + app.mobileTxPackets)
+                    * getMobilePowerPerPacket(rawRealtimeUs, statsType);
+        }
+        if (DEBUG && app.mobileRadioPowerMah != 0) {
+            Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
+                    + (app.mobileRxPackets + app.mobileTxPackets)
+                    + " active time " + app.mobileActive
+                    + " power=" + BatteryStatsHelper.makemAh(app.mobileRadioPowerMah));
+        }
+    }
+
+    @Override
+    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+                                   long rawUptimeUs, int statsType) {
+        double power = 0;
+        long signalTimeMs = 0;
+        long noCoverageTimeMs = 0;
+        for (int i = 0; i < mPowerBins.length; i++) {
+            long strengthTimeMs = stats.getPhoneSignalStrengthTime(i, rawRealtimeUs, statsType)
+                    / 1000;
+            final double p = (strengthTimeMs * mPowerBins[i]) / (60*60*1000);
+            if (DEBUG && p != 0) {
+                Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
+                        + BatteryStatsHelper.makemAh(p));
+            }
+            power += p;
+            signalTimeMs += strengthTimeMs;
+            if (i == 0) {
+                noCoverageTimeMs = strengthTimeMs;
+            }
+        }
+
+        final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType)
+                / 1000;
+        final double p = (scanningTimeMs * mPowerScan) / (60*60*1000);
+        if (DEBUG && p != 0) {
+            Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs
+                    + " power=" + BatteryStatsHelper.makemAh(p));
+        }
+        power += p;
+        long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
+        long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs;
+        if (remainingActiveTimeMs > 0) {
+            power += (mPowerRadioOn * remainingActiveTimeMs) / (1000*60*60);
+        }
+
+        if (power != 0) {
+            if (signalTimeMs != 0) {
+                app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
+            }
+            app.mobileActive = remainingActiveTimeMs;
+            app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType);
+            app.mobileRadioPowerMah = power;
+        }
+    }
+
+    @Override
+    public void reset() {
+        mTotalAppMobileActiveMs = 0;
+    }
+
+    public void reset(BatteryStats stats) {
+        reset();
+        mStats = stats;
+    }
+}
diff --git a/com/android/internal/os/PowerCalculator.java b/com/android/internal/os/PowerCalculator.java
new file mode 100644
index 0000000..cd69d68
--- /dev/null
+++ b/com/android/internal/os/PowerCalculator.java
@@ -0,0 +1,56 @@
+/*
+ * 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.os.BatteryStats;
+
+/**
+ * Calculates power use of a device subsystem for an app.
+ */
+public abstract class PowerCalculator {
+    /**
+     * Calculate the amount of power an app used for this subsystem.
+     * @param app The BatterySipper that represents the power use of an app.
+     * @param u The recorded stats for the app.
+     * @param rawRealtimeUs The raw system realtime in microseconds.
+     * @param rawUptimeUs The raw system uptime in microseconds.
+     * @param statsType The type of stats. Can be {@link BatteryStats#STATS_CURRENT},
+     *                  {@link BatteryStats#STATS_SINCE_CHARGED}, or
+     *                  {@link BatteryStats#STATS_SINCE_UNPLUGGED}.
+     */
+    public abstract void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                                      long rawUptimeUs, int statsType);
+
+    /**
+     * Calculate the remaining power that can not be attributed to an app.
+     * @param app The BatterySipper that will represent this remaining power.
+     * @param stats The BatteryStats object from which to retrieve data.
+     * @param rawRealtimeUs The raw system realtime in microseconds.
+     * @param rawUptimeUs The raw system uptime in microseconds.
+     * @param statsType The type of stats. Can be {@link BatteryStats#STATS_CURRENT},
+     *                  {@link BatteryStats#STATS_SINCE_CHARGED}, or
+     *                  {@link BatteryStats#STATS_SINCE_UNPLUGGED}.
+     */
+    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+                                   long rawUptimeUs, int statsType) {
+    }
+
+    /**
+     * Reset any state maintained in this calculator.
+     */
+    public void reset() {
+    }
+}
diff --git a/com/android/internal/os/PowerProfile.java b/com/android/internal/os/PowerProfile.java
new file mode 100644
index 0000000..872b465
--- /dev/null
+++ b/com/android/internal/os/PowerProfile.java
@@ -0,0 +1,459 @@
+/*
+ * 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.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+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]
+ */
+public class PowerProfile {
+
+    /**
+     * No power consumption, or accounted for elsewhere.
+     */
+    public static final String POWER_NONE = "none";
+
+    /**
+     * Power consumption when CPU is in power collapse mode.
+     */
+    public static final String POWER_CPU_IDLE = "cpu.idle";
+
+    /**
+     * Power consumption when CPU is awake (when a wake lock is held).  This
+     * should be 0 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_IDLE due to a wake lock being held but with no
+     * CPU activity.
+     */
+    public static final String POWER_CPU_AWAKE = "cpu.awake";
+
+    /**
+     * Power consumption when CPU is in power collapse mode.
+     */
+    @Deprecated
+    public static final String POWER_CPU_ACTIVE = "cpu.active";
+
+    /**
+     * Power consumption when WiFi driver is scanning for networks.
+     */
+    public static final String POWER_WIFI_SCAN = "wifi.scan";
+
+    /**
+     * Power consumption when WiFi driver is on.
+     */
+    public static final String POWER_WIFI_ON = "wifi.on";
+
+    /**
+     * Power consumption when WiFi driver is transmitting/receiving.
+     */
+    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_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.
+     */
+    public static final String POWER_GPS_ON = "gps.on";
+
+    /**
+     * Power consumption when Bluetooth driver is on.
+     * @deprecated
+     */
+    @Deprecated
+    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
+    public static final String POWER_BLUETOOTH_AT_CMD = "bluetooth.at";
+
+
+    /**
+     * Power consumption when screen is on, not including the backlight power.
+     */
+    public static final String POWER_SCREEN_ON = "screen.on";
+
+    /**
+     * Power consumption when cell radio is on but not on a call.
+     */
+    public static final String POWER_RADIO_ON = "radio.on";
+
+    /**
+     * Power consumption when cell radio is hunting for a signal.
+     */
+    public static final String POWER_RADIO_SCANNING = "radio.scanning";
+
+    /**
+     * Power consumption when talking on the phone.
+     */
+    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
+     */
+    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 = "dsp.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 = "dsp.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";
+
+    @Deprecated
+    public static final String POWER_CPU_SPEEDS = "cpu.speeds";
+
+    /**
+     * 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";
+
+    static final HashMap<String, Object> sPowerMap = new HashMap<>();
+
+    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 Object sLock = new Object();
+
+    public PowerProfile(Context context) {
+        // Read the XML file for the given profile (normally only one per
+        // device)
+        synchronized (sLock) {
+            if (sPowerMap.size() == 0) {
+                readPowerValuesFromXml(context);
+            }
+            initCpuClusters();
+        }
+    }
+
+    private void readPowerValuesFromXml(Context context) {
+        int id = com.android.internal.R.xml.power_profile;
+        final Resources resources = context.getResources();
+        XmlResourceParser parser = resources.getXml(id);
+        boolean parsingArray = false;
+        ArrayList<Double> array = new ArrayList<Double>();
+        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
+                    sPowerMap.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)) {
+                            sPowerMap.put(name, value);
+                        } else if (parsingArray) {
+                            array.add(value);
+                        }
+                    }
+                }
+            }
+            if (parsingArray) {
+                sPowerMap.put(arrayName, array.toArray(new Double[array.size()]));
+            }
+        } catch (XmlPullParserException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            parser.close();
+        }
+
+        // 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,
+                com.android.internal.R.integer.config_wifi_idle_receive_cur_ma,
+                com.android.internal.R.integer.config_wifi_active_rx_cur_ma,
+                com.android.internal.R.integer.config_wifi_tx_cur_ma,
+                com.android.internal.R.integer.config_wifi_operating_voltage_mv,
+        };
+
+        String[] configResIdKeys = new String[]{
+                POWER_BLUETOOTH_CONTROLLER_IDLE,
+                POWER_BLUETOOTH_CONTROLLER_RX,
+                POWER_BLUETOOTH_CONTROLLER_TX,
+                POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE,
+                POWER_WIFI_CONTROLLER_IDLE,
+                POWER_WIFI_CONTROLLER_RX,
+                POWER_WIFI_CONTROLLER_TX,
+                POWER_WIFI_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 ((sPowerMap.containsKey(key) && (Double) sPowerMap.get(key) > 0)) {
+                continue;
+            }
+            int value = resources.getInteger(configResIds[i]);
+            if (value > 0) {
+                sPowerMap.put(key, (double) value);
+            }
+        }
+    }
+
+    private CpuClusterKey[] mCpuClusters;
+
+    private static final String POWER_CPU_CLUSTER_CORE_COUNT = "cpu.clusters.cores";
+    private static final String POWER_CPU_CLUSTER_SPEED_PREFIX = "cpu.speeds.cluster";
+    private static final String POWER_CPU_CLUSTER_ACTIVE_PREFIX = "cpu.active.cluster";
+
+    @SuppressWarnings("deprecation")
+    private void initCpuClusters() {
+        // Figure out how many CPU clusters we're dealing with
+        final Object obj = sPowerMap.get(POWER_CPU_CLUSTER_CORE_COUNT);
+        if (obj == null || !(obj instanceof Double[])) {
+            // Default to single.
+            mCpuClusters = new CpuClusterKey[1];
+            mCpuClusters[0] = new CpuClusterKey(POWER_CPU_SPEEDS, POWER_CPU_ACTIVE, 1);
+
+        } else {
+            final Double[] array = (Double[]) obj;
+            mCpuClusters = new CpuClusterKey[array.length];
+            for (int cluster = 0; cluster < array.length; cluster++) {
+                int numCpusInCluster = (int) Math.round(array[cluster]);
+                mCpuClusters[cluster] = new CpuClusterKey(
+                        POWER_CPU_CLUSTER_SPEED_PREFIX + cluster,
+                        POWER_CPU_CLUSTER_ACTIVE_PREFIX + cluster,
+                        numCpusInCluster);
+            }
+        }
+    }
+
+    public static class CpuClusterKey {
+        private final String timeKey;
+        private final String powerKey;
+        private final int numCpus;
+
+        private CpuClusterKey(String timeKey, String powerKey, int numCpus) {
+            this.timeKey = timeKey;
+            this.powerKey = powerKey;
+            this.numCpus = numCpus;
+        }
+    }
+
+    public int getNumCpuClusters() {
+        return mCpuClusters.length;
+    }
+
+    public int getNumCoresInCpuCluster(int index) {
+        return mCpuClusters[index].numCpus;
+    }
+
+    public int getNumSpeedStepsInCpuCluster(int index) {
+        Object value = sPowerMap.get(mCpuClusters[index].timeKey);
+        if (value != null && value instanceof Double[]) {
+            return ((Double[])value).length;
+        }
+        return 1; // Only one speed
+    }
+
+    public double getAveragePowerForCpu(int cluster, int step) {
+        if (cluster >= 0 && cluster < mCpuClusters.length) {
+            return getAveragePower(mCpuClusters[cluster].powerKey, step);
+        }
+        return 0;
+    }
+
+    /**
+     * 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 (sPowerMap.containsKey(key)) {
+            Object data = sPowerMap.get(key);
+            if (data instanceof Double[]) {
+                final Double[] values = (Double[]) data;
+                return values.length;
+            } else {
+                return 1;
+            }
+        }
+        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 (sPowerMap.containsKey(type)) {
+            Object data = sPowerMap.get(type);
+            if (data instanceof Double[]) {
+                return ((Double[])data)[0];
+            } else {
+                return (Double) sPowerMap.get(type);
+            }
+        } else {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the average current in mA consumed by the subsystem
+     * @param type the subsystem type
+     * @return the average current in milliAmps.
+     */
+    public double getAveragePower(String type) {
+        return getAveragePowerOrDefault(type, 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.
+     */
+    public double getAveragePower(String type, int level) {
+        if (sPowerMap.containsKey(type)) {
+            Object data = sPowerMap.get(type);
+            if (data instanceof Double[]) {
+                final Double[] values = (Double[]) data;
+                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 (Double) data;
+            }
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Returns the battery capacity, if available, in milli Amp Hours. If not available,
+     * it returns zero.
+     * @return the battery capacity in mAh
+     */
+    public double getBatteryCapacity() {
+        return getAveragePower(POWER_BATTERY_CAPACITY);
+    }
+}
diff --git a/com/android/internal/os/ProcessCpuTracker.java b/com/android/internal/os/ProcessCpuTracker.java
new file mode 100644
index 0000000..e46dfc4
--- /dev/null
+++ b/com/android/internal/os/ProcessCpuTracker.java
@@ -0,0 +1,916 @@
+/*
+ * 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.*;
+
+import android.os.FileUtils;
+import android.os.Process;
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.system.OsConstants;
+import android.util.Slog;
+
+import com.android.internal.util.FastPrintWriter;
+
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
+import java.io.File;
+import java.io.FileInputStream;
+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;
+import java.util.StringTokenizer;
+
+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];
+
+    /** Stores user time and system time in jiffies.  Used for
+     * public API to retrieve CPU use for a process.  Must lock while in use. */
+    private final long[] mSinglePidStatsData = 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[] 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;
+
+    private byte[] mBuffer = new byte[4096];
+
+    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 BatteryStatsImpl.Uid.Proc batteryStats;
+
+        public boolean interesting;
+
+        public String baseName;
+        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.
+         */
+        public long rel_uptime;
+
+        /**
+         * Time in milliseconds.
+         */
+        public long base_utime;
+
+        /**
+         * Time in milliseconds.
+         */
+        public long base_stime;
+
+        /**
+         * Time in milliseconds.
+         */
+        public int rel_utime;
+
+        /**
+         * Time in milliseconds.
+         */
+        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));
+                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));
+                statFile = new File(taskDir, "stat").toString();
+                cmdlineFile = null;
+                threadsDir = null;
+                threadStats = null;
+                workingThreads = null;
+            }
+            uid = FileUtils.getUid(statFile.toString());
+        }
+    }
+
+    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;
+        }
+    };
+
+
+    public ProcessCpuTracker(boolean includeThreads) {
+        mIncludeThreads = includeThreads;
+        long jiffyHz = Libcore.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();
+    }
+
+    public void update() {
+        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) spent executing in
+     * both user and system code.  Safe to call without lock held.
+     */
+    public long getCpuTimeForPid(int pid) {
+        synchronized (mSinglePidStatsData) {
+            final String statFile = "/proc/" + pid + "/stat";
+            final long[] statsData = mSinglePidStatsData;
+            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;
+        }
+    }
+
+    /**
+     * @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;
+    }
+
+    final public int countWorkingStats() {
+        buildWorkingProcs();
+        return mWorkingProcs.size();
+    }
+
+    final public Stats getWorkingStats(int index) {
+        return mWorkingProcs.get(index);
+    }
+
+    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();
+    }
+
+    final public String printCurrentState(long now) {
+        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 N = mWorkingProcs.size();
+        for (int i=0; i<N; 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 String readFile(String file, char endChar) {
+        // Permit disk reads here, as /proc/meminfo isn't really "on
+        // disk" and should be fast.  TODO: make BlockGuard ignore
+        // /proc/ and /sys/ files perhaps?
+        StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
+        FileInputStream is = null;
+        try {
+            is = new FileInputStream(file);
+            int len = is.read(mBuffer);
+            is.close();
+
+            if (len > 0) {
+                int i;
+                for (i=0; i<len; i++) {
+                    if (mBuffer[i] == endChar) {
+                        break;
+                    }
+                }
+                return new String(mBuffer, 0, i);
+            }
+        } catch (java.io.FileNotFoundException e) {
+        } catch (java.io.IOException e) {
+        } finally {
+            IoUtils.closeQuietly(is);
+            StrictMode.setThreadPolicy(savedPolicy);
+        }
+        return null;
+    }
+
+    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>")) {
+            String cmdName = readFile(cmdlineFile, '\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/com/android/internal/os/RoSystemProperties.java b/com/android/internal/os/RoSystemProperties.java
new file mode 100644
index 0000000..89a4e17
--- /dev/null
+++ b/com/android/internal/os/RoSystemProperties.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+/**
+ * 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");
+
+    // ------ ro.config.* -------- //
+    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);
+
+    // ------ ro.fw.* ------------ //
+    public static final boolean FW_SYSTEM_USER_SPLIT =
+            SystemProperties.getBoolean("ro.fw.system_user_split", false);
+
+    // ------ ro.crypto.* -------- //
+    public static final String CRYPTO_STATE = SystemProperties.get("ro.crypto.state");
+    public static final String CRYPTO_TYPE = SystemProperties.get("ro.crypto.type");
+    // These are pseudo-properties
+    public static final boolean CRYPTO_ENCRYPTABLE =
+            !CRYPTO_STATE.isEmpty() && !"unsupported".equals(CRYPTO_STATE);
+    public static final boolean CRYPTO_ENCRYPTED =
+            "encrypted".equalsIgnoreCase(CRYPTO_STATE);
+    public static final boolean CRYPTO_FILE_ENCRYPTED =
+            "file".equalsIgnoreCase(CRYPTO_TYPE);
+    public static final boolean CRYPTO_BLOCK_ENCRYPTED =
+            "block".equalsIgnoreCase(CRYPTO_TYPE);
+
+    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/com/android/internal/os/RuntimeInit.java b/com/android/internal/os/RuntimeInit.java
new file mode 100644
index 0000000..66475e4
--- /dev/null
+++ b/com/android/internal/os/RuntimeInit.java
@@ -0,0 +1,452 @@
+/*
+ * 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.os.Build;
+import android.os.DeadObjectException;
+import android.os.Debug;
+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 com.android.server.NetworkManagementSocketTagger;
+import dalvik.system.VMRuntime;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.TimeZone;
+import java.util.logging.LogManager;
+import org.apache.harmony.luni.internal.util.TimezoneGetter;
+
+/**
+ * Main entry point for runtime initialization.  Not for
+ * public consumption.
+ * @hide
+ */
+public class RuntimeInit {
+    final static String TAG = "AndroidRuntime";
+    final static boolean DEBUG = false;
+
+    /** true if commonInit() has been called */
+    private static boolean initialized;
+
+    private static IBinder mApplicationObject;
+
+    private static volatile boolean mCrashing = false;
+
+    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);
+    }
+
+    /**
+     * 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 {
+        @Override
+        public void uncaughtException(Thread t, Throwable e) {
+            // Don't re-enter if KillApplicationHandler has already run
+            if (mCrashing) return;
+            if (mApplicationObject == null) {
+                // The "FATAL EXCEPTION" string is still used on Android even though
+                // apps can set a custom UncaughtExceptionHandler that renders uncaught
+                // exceptions non-fatal.
+                Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
+            } else {
+                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(t.getName()).append("\n");
+                final String processName = ActivityThread.currentProcessName();
+                if (processName != null) {
+                    message.append("Process: ").append(processName).append(", ");
+                }
+                message.append("PID: ").append(Process.myPid());
+                Clog_e(TAG, message.toString(), 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,
+     * {@link LoggingHandler} will already have logged details.
+     */
+    private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
+        public void uncaughtException(Thread t, Throwable e) {
+            try {
+                // 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);
+            }
+        }
+    }
+
+    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.
+         */
+        Thread.setUncaughtExceptionPreHandler(new LoggingHandler());
+        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler());
+
+        /*
+         * Install a TimezoneGetter subclass for ZoneInfo.db
+         */
+        TimezoneGetter.setInstance(new TimezoneGetter() {
+            @Override
+            public String getId() {
+                return SystemProperties.get("persist.sys.timezone");
+            }
+        });
+        TimeZone.setDefault(null);
+
+        /*
+         * 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.
+         */
+        NetworkManagementSocketTagger.install();
+
+        /*
+         * If we're running in an emulator launched with "-trace", put the
+         * VM into emulator trace profiling mode so that the user can hit
+         * F9/F10 at any time to capture traces.  This has performance
+         * consequences, so it's not something you want to do always.
+         */
+        String trace = SystemProperties.get("ro.kernel.android.tracing");
+        if (trace.equals("1")) {
+            Slog.i(TAG, "NOTE: emulator trace profiling enabled");
+            Debug.enableEmulatorTraceOutput();
+        }
+
+        initialized = true;
+    }
+
+    /**
+     * Returns an HTTP user agent of the form
+     * "Dalvik/1.1.0 (Linux; U; Android Eclair Build/MASTER)".
+     */
+    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; // "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; // "MASTER" 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
+     */
+    private 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);
+    }
+
+    public static final void main(String[] argv) {
+        enableDdms();
+        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, 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);
+
+        // We want to be fairly aggressive about heap utilization, to avoid
+        // holding on to a lot of memory that isn't needed.
+        VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
+        VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
+
+        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.
+     */
+    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"));
+    }
+
+    /**
+     * 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
+     */
+    public static void wtf(String tag, Throwable t, boolean system) {
+        try {
+            if (ActivityManager.getService().handleApplicationWtf(
+                    mApplicationObject, tag, system,
+                    new ApplicationErrorReport.ParcelableCrashInfo(t))) {
+                // 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);
+            }
+        }
+    }
+
+    /**
+     * Set the object identifying this application/process, for reporting VM
+     * errors.
+     */
+    public static final void setApplicationObject(IBinder app) {
+        mApplicationObject = app;
+    }
+
+    public static final IBinder getApplicationObject() {
+        return mApplicationObject;
+    }
+
+    /**
+     * Enable DDMS.
+     */
+    static final 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/com/android/internal/os/SensorPowerCalculator.java b/com/android/internal/os/SensorPowerCalculator.java
new file mode 100644
index 0000000..c98639b
--- /dev/null
+++ b/com/android/internal/os/SensorPowerCalculator.java
@@ -0,0 +1,63 @@
+/*
+ * 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.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.os.BatteryStats;
+import android.util.SparseArray;
+
+import java.util.List;
+
+public class SensorPowerCalculator extends PowerCalculator {
+    private final List<Sensor> mSensors;
+    private final double mGpsPowerOn;
+
+    public SensorPowerCalculator(PowerProfile profile, SensorManager sensorManager) {
+        mSensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
+        mGpsPowerOn = profile.getAveragePower(PowerProfile.POWER_GPS_ON);
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+        // Process Sensor usage
+        final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
+        final int NSE = sensorStats.size();
+        for (int ise = 0; ise < NSE; ise++) {
+            final BatteryStats.Uid.Sensor sensor = sensorStats.valueAt(ise);
+            final int sensorHandle = sensorStats.keyAt(ise);
+            final BatteryStats.Timer timer = sensor.getSensorTime();
+            final long sensorTime = timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000;
+            switch (sensorHandle) {
+                case BatteryStats.Uid.Sensor.GPS:
+                    app.gpsTimeMs = sensorTime;
+                    app.gpsPowerMah = (app.gpsTimeMs * mGpsPowerOn) / (1000*60*60);
+                    break;
+                default:
+                    final int sensorsCount = mSensors.size();
+                    for (int i = 0; i < sensorsCount; i++) {
+                        final Sensor s = mSensors.get(i);
+                        if (s.getHandle() == sensorHandle) {
+                            app.sensorPowerMah += (sensorTime * s.getPower()) / (1000*60*60);
+                            break;
+                        }
+                    }
+                    break;
+            }
+        }
+    }
+}
diff --git a/com/android/internal/os/SomeArgs.java b/com/android/internal/os/SomeArgs.java
new file mode 100644
index 0000000..8fb56d4
--- /dev/null
+++ b/com/android/internal/os/SomeArgs.java
@@ -0,0 +1,120 @@
+/*
+ * 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;
+
+/**
+ * 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.
+ */
+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;
+
+    public Object arg1;
+    public Object arg2;
+    public Object arg3;
+    public Object arg4;
+    public Object arg5;
+    public Object arg6;
+    public Object arg7;
+    public Object arg8;
+    public int argi1;
+    public int argi2;
+    public int argi3;
+    public int argi4;
+    public int argi5;
+    public int argi6;
+
+    private SomeArgs() {
+        /* do nothing - reduce visibility */
+    }
+
+    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();
+        }
+    }
+
+    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;
+    }
+}
diff --git a/com/android/internal/os/TransferPipe.java b/com/android/internal/os/TransferPipe.java
new file mode 100644
index 0000000..738ecc0
--- /dev/null
+++ b/com/android/internal/os/TransferPipe.java
@@ -0,0 +1,308 @@
+/*
+ * 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;
+
+/**
+ * Helper for transferring data through a pipe from a client app.
+ */
+public final 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 {
+        mThread = new Thread(this, "TransferPipe");
+        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);
+        }
+    }
+
+    @Override
+    public void run() {
+        final byte[] buffer = new byte[1024];
+        final FileInputStream fis;
+        final FileOutputStream fos;
+
+        synchronized (this) {
+            ParcelFileDescriptor readFd = getReadFd();
+            if (readFd == null) {
+                Slog.w(TAG, "Pipe has been closed...");
+                return;
+            }
+            fis = new FileInputStream(readFd.getFileDescriptor());
+            fos = new FileOutputStream(mOutFd);
+        }
+
+        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/com/android/internal/os/WakelockPowerCalculator.java b/com/android/internal/os/WakelockPowerCalculator.java
new file mode 100644
index 0000000..c7897b2
--- /dev/null
+++ b/com/android/internal/os/WakelockPowerCalculator.java
@@ -0,0 +1,81 @@
+/*
+ * 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.os.BatteryStats;
+import android.util.ArrayMap;
+import android.util.Log;
+
+public class WakelockPowerCalculator extends PowerCalculator {
+    private static final String TAG = "WakelockPowerCalculator";
+    private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+    private final double mPowerWakelock;
+    private long mTotalAppWakelockTimeMs = 0;
+
+    public WakelockPowerCalculator(PowerProfile profile) {
+        mPowerWakelock = profile.getAveragePower(PowerProfile.POWER_CPU_AWAKE);
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+        long wakeLockTimeUs = 0;
+        final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats =
+                u.getWakelockStats();
+        final int wakelockStatsCount = wakelockStats.size();
+        for (int i = 0; i < wakelockStatsCount; i++) {
+            final BatteryStats.Uid.Wakelock wakelock = wakelockStats.valueAt(i);
+
+            // Only care about partial wake locks since full wake locks
+            // are canceled when the user turns the screen off.
+            BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
+            if (timer != null) {
+                wakeLockTimeUs += timer.getTotalTimeLocked(rawRealtimeUs, statsType);
+            }
+        }
+        app.wakeLockTimeMs = wakeLockTimeUs / 1000; // convert to millis
+        mTotalAppWakelockTimeMs += app.wakeLockTimeMs;
+
+        // Add cost of holding a wake lock.
+        app.wakeLockPowerMah = (app.wakeLockTimeMs * mPowerWakelock) / (1000*60*60);
+        if (DEBUG && app.wakeLockPowerMah != 0) {
+            Log.d(TAG, "UID " + u.getUid() + ": wake " + app.wakeLockTimeMs
+                    + " power=" + BatteryStatsHelper.makemAh(app.wakeLockPowerMah));
+        }
+    }
+
+    @Override
+    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+                                   long rawUptimeUs, int statsType) {
+        long wakeTimeMillis = stats.getBatteryUptime(rawUptimeUs) / 1000;
+        wakeTimeMillis -= mTotalAppWakelockTimeMs
+                + (stats.getScreenOnTime(rawRealtimeUs, statsType) / 1000);
+        if (wakeTimeMillis > 0) {
+            final double power = (wakeTimeMillis * mPowerWakelock) / (1000*60*60);
+            if (DEBUG) {
+                Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power "
+                        + BatteryStatsHelper.makemAh(power));
+            }
+            app.wakeLockTimeMs += wakeTimeMillis;
+            app.wakeLockPowerMah += power;
+        }
+    }
+
+    @Override
+    public void reset() {
+        mTotalAppWakelockTimeMs = 0;
+    }
+}
diff --git a/com/android/internal/os/WebViewZygoteInit.java b/com/android/internal/os/WebViewZygoteInit.java
new file mode 100644
index 0000000..cadb66a
--- /dev/null
+++ b/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.net.LocalSocket;
+import android.os.Build;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.TextUtils;
+import android.util.Log;
+import android.webkit.WebViewFactory;
+import android.webkit.WebViewFactoryProvider;
+
+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 ZygoteServer sServer;
+
+    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 void handlePreloadPackage(String packagePath, String libsPath, 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);
+            }
+
+            // 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);
+            }
+
+            Log.i(TAG, "Package preload done");
+        }
+    }
+
+    public static void main(String argv[]) {
+        sServer = new WebViewZygoteServer();
+
+        // Zygote goes into its own process group.
+        try {
+            Os.setpgid(0, 0);
+        } catch (ErrnoException ex) {
+            throw new RuntimeException("Failed to setpgid(0,0)", ex);
+        }
+
+        final Runnable caller;
+        try {
+            sServer.registerServerSocket("webview_zygote");
+            // The select loop returns early in the child process after a fork and
+            // loops forever in the zygote.
+            caller = sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Fatal exception:", e);
+            throw e;
+        } finally {
+            sServer.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/com/android/internal/os/WifiPowerCalculator.java b/com/android/internal/os/WifiPowerCalculator.java
new file mode 100644
index 0000000..b447039
--- /dev/null
+++ b/com/android/internal/os/WifiPowerCalculator.java
@@ -0,0 +1,105 @@
+/*
+ * 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.os.BatteryStats;
+import android.util.Log;
+
+/**
+ * WiFi power calculator for when BatteryStats supports energy reporting
+ * from the WiFi controller.
+ */
+public class WifiPowerCalculator extends PowerCalculator {
+    private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+    private static final String TAG = "WifiPowerCalculator";
+    private final double mIdleCurrentMa;
+    private final double mTxCurrentMa;
+    private final double mRxCurrentMa;
+    private double mTotalAppPowerDrain = 0;
+    private long mTotalAppRunningTime = 0;
+
+    public WifiPowerCalculator(PowerProfile profile) {
+        mIdleCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE);
+        mTxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX);
+        mRxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX);
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+        final BatteryStats.ControllerActivityCounter counter = u.getWifiControllerActivity();
+        if (counter == null) {
+            return;
+        }
+
+        final long idleTime = counter.getIdleTimeCounter().getCountLocked(statsType);
+        final long txTime = counter.getTxTimeCounters()[0].getCountLocked(statsType);
+        final long rxTime = counter.getRxTimeCounter().getCountLocked(statsType);
+        app.wifiRunningTimeMs = idleTime + rxTime + txTime;
+        mTotalAppRunningTime += app.wifiRunningTimeMs;
+
+        app.wifiPowerMah =
+                ((idleTime * mIdleCurrentMa) + (txTime * mTxCurrentMa) + (rxTime * mRxCurrentMa))
+                / (1000*60*60);
+        mTotalAppPowerDrain += app.wifiPowerMah;
+
+        app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA,
+                statsType);
+        app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA,
+                statsType);
+        app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA,
+                statsType);
+        app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA,
+                statsType);
+
+        if (DEBUG && app.wifiPowerMah != 0) {
+            Log.d(TAG, "UID " + u.getUid() + ": idle=" + idleTime + "ms rx=" + rxTime + "ms tx=" +
+                    txTime + "ms power=" + BatteryStatsHelper.makemAh(app.wifiPowerMah));
+        }
+    }
+
+    @Override
+    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+                                   long rawUptimeUs, int statsType) {
+        final BatteryStats.ControllerActivityCounter counter = stats.getWifiControllerActivity();
+
+        final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(statsType);
+        final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(statsType);
+        final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(statsType);
+
+        app.wifiRunningTimeMs = Math.max(0,
+                (idleTimeMs + rxTimeMs + txTimeMs) - mTotalAppRunningTime);
+
+        double powerDrainMah = counter.getPowerCounter().getCountLocked(statsType)
+                / (double)(1000*60*60);
+        if (powerDrainMah == 0) {
+            // Some controllers do not report power drain, so we can calculate it here.
+            powerDrainMah = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa)
+                    + (rxTimeMs * mRxCurrentMa)) / (1000*60*60);
+        }
+        app.wifiPowerMah = Math.max(0, powerDrainMah - mTotalAppPowerDrain);
+
+        if (DEBUG) {
+            Log.d(TAG, "left over WiFi power: " + BatteryStatsHelper.makemAh(app.wifiPowerMah));
+        }
+    }
+
+    @Override
+    public void reset() {
+        mTotalAppPowerDrain = 0;
+        mTotalAppRunningTime = 0;
+    }
+}
diff --git a/com/android/internal/os/WifiPowerEstimator.java b/com/android/internal/os/WifiPowerEstimator.java
new file mode 100644
index 0000000..d175202
--- /dev/null
+++ b/com/android/internal/os/WifiPowerEstimator.java
@@ -0,0 +1,102 @@
+/*
+ * 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.os.BatteryStats;
+import android.util.Log;
+
+/**
+ * Estimates WiFi power usage based on timers in BatteryStats.
+ */
+public class WifiPowerEstimator extends PowerCalculator {
+    private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+    private static final String TAG = "WifiPowerEstimator";
+    private final double mWifiPowerPerPacket;
+    private final double mWifiPowerOn;
+    private final double mWifiPowerScan;
+    private final double mWifiPowerBatchScan;
+    private long mTotalAppWifiRunningTimeMs = 0;
+
+    public WifiPowerEstimator(PowerProfile profile) {
+        mWifiPowerPerPacket = getWifiPowerPerPacket(profile);
+        mWifiPowerOn = profile.getAveragePower(PowerProfile.POWER_WIFI_ON);
+        mWifiPowerScan = profile.getAveragePower(PowerProfile.POWER_WIFI_SCAN);
+        mWifiPowerBatchScan = profile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN);
+    }
+
+    /**
+     * Return estimated power per Wi-Fi packet in mAh/packet where 1 packet = 2 KB.
+     */
+    private static double getWifiPowerPerPacket(PowerProfile profile) {
+        final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
+        final double WIFI_POWER = profile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
+                / 3600;
+        return WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048);
+    }
+
+    @Override
+    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+                             long rawUptimeUs, int statsType) {
+        app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA,
+                statsType);
+        app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA,
+                statsType);
+        app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA,
+                statsType);
+        app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA,
+                statsType);
+
+        final double wifiPacketPower = (app.wifiRxPackets + app.wifiTxPackets)
+                * mWifiPowerPerPacket;
+
+        app.wifiRunningTimeMs = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000;
+        mTotalAppWifiRunningTimeMs += app.wifiRunningTimeMs;
+        final double wifiLockPower = (app.wifiRunningTimeMs * mWifiPowerOn) / (1000*60*60);
+
+        final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType) / 1000;
+        final double wifiScanPower = (wifiScanTimeMs * mWifiPowerScan) / (1000*60*60);
+
+        double wifiBatchScanPower = 0;
+        for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
+            final long batchScanTimeMs =
+                    u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000;
+            final double batchScanPower = (batchScanTimeMs * mWifiPowerBatchScan) / (1000*60*60);
+            wifiBatchScanPower += batchScanPower;
+        }
+
+        app.wifiPowerMah = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower;
+        if (DEBUG && app.wifiPowerMah != 0) {
+            Log.d(TAG, "UID " + u.getUid() + ": power=" +
+                    BatteryStatsHelper.makemAh(app.wifiPowerMah));
+        }
+    }
+
+    @Override
+    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
+                                   long rawUptimeUs, int statsType) {
+        final long totalRunningTimeMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType)
+                / 1000;
+        final double powerDrain = ((totalRunningTimeMs - mTotalAppWifiRunningTimeMs) * mWifiPowerOn)
+                / (1000*60*60);
+        app.wifiRunningTimeMs = totalRunningTimeMs;
+        app.wifiPowerMah = Math.max(0, powerDrain);
+    }
+
+    @Override
+    public void reset() {
+        mTotalAppWifiRunningTimeMs = 0;
+    }
+}
diff --git a/com/android/internal/os/WrapperInit.java b/com/android/internal/os/WrapperInit.java
new file mode 100644
index 0000000..4901080
--- /dev/null
+++ b/com/android/internal/os/WrapperInit.java
@@ -0,0 +1,219 @@
+/*
+ * 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.TimingsTraceLog;
+import android.util.Slog;
+import dalvik.system.VMRuntime;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import libcore.io.IoUtils;
+
+/**
+ * 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) {
+            try {
+                FileDescriptor fd = new FileDescriptor();
+                fd.setInt$(fdNum);
+                DataOutputStream os = new DataOutputStream(new FileOutputStream(fd));
+                os.writeInt(Process.myPid());
+                os.close();
+                IoUtils.closeQuietly(fd);
+            } catch (IOException ex) {
+                Slog.d(TAG, "Could not write pid of wrapped process to Zygote pipe.", ex);
+            }
+        }
+
+        // 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);
+
+        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, 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/com/android/internal/os/Zygote.java b/com/android/internal/os/Zygote.java
new file mode 100644
index 0000000..4e4b5b8
--- /dev/null
+++ b/com/android/internal/os/Zygote.java
@@ -0,0 +1,222 @@
+/*
+ * 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 android.os.IVold;
+import android.os.Trace;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import dalvik.system.ZygoteHooks;
+
+/** @hide */
+public final class Zygote {
+    /*
+    * Bit values for "debugFlags" 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;
+
+    /** 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;
+    /** Read-only external storage should be mounted. */
+    public static final int MOUNT_EXTERNAL_READ = IVold.REMOUNT_MODE_READ;
+    /** Read-write external storage should be mounted. */
+    public static final int MOUNT_EXTERNAL_WRITE = IVold.REMOUNT_MODE_WRITE;
+
+    private static final ZygoteHooks VM_HOOKS = new ZygoteHooks();
+
+    private Zygote() {}
+
+    /**
+     * 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 debugFlags bit flags that enable debugging 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 instructionSet null-ok the instruction set to use.
+     * @param appDataDir null-ok the data directory of the app.
+     *
+     * @return 0 if this is the child, pid of the child
+     * if this is the parent, or -1 on error.
+     */
+    public static int forkAndSpecialize(int uid, int gid, int[] gids, int debugFlags,
+          int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
+          int[] fdsToIgnore, String instructionSet, String appDataDir) {
+        VM_HOOKS.preFork();
+        // Resets nice priority for zygote process.
+        resetNicePriority();
+        int pid = nativeForkAndSpecialize(
+                  uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
+                  fdsToIgnore, instructionSet, appDataDir);
+        // Enable tracing as soon as possible for the child process.
+        if (pid == 0) {
+            Trace.setTracingEnabled(true, debugFlags);
+
+            // Note that this event ends at the end of handleChildProc,
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
+        }
+        VM_HOOKS.postForkCommon();
+        return pid;
+    }
+
+    native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int debugFlags,
+          int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
+          int[] fdsToIgnore, String instructionSet, String appDataDir);
+
+    /**
+     * Called to do any initialization before starting an application.
+     */
+    native static 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 debugFlags bit flags that enable debugging 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.
+     */
+    public static int forkSystemServer(int uid, int gid, int[] gids, int debugFlags,
+            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
+        VM_HOOKS.preFork();
+        // Resets nice priority for zygote process.
+        resetNicePriority();
+        int pid = nativeForkSystemServer(
+                uid, gid, gids, debugFlags, rlimits, permittedCapabilities, effectiveCapabilities);
+        // Enable tracing as soon as we enter the system_server.
+        if (pid == 0) {
+            Trace.setTracingEnabled(true, debugFlags);
+        }
+        VM_HOOKS.postForkCommon();
+        return pid;
+    }
+
+    native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int debugFlags,
+            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
+
+    /**
+     * Lets children of the zygote inherit open file descriptors to this path.
+     */
+    native protected static void nativeAllowFileAcrossFork(String path);
+
+    /**
+     * Zygote unmount storage space on initializing.
+     * This method is called once.
+     */
+    native protected static void nativeUnmountStorageOnInit();
+
+    private static void callPostForkChildHooks(int debugFlags, boolean isSystemServer,
+            String instructionSet) {
+        VM_HOOKS.postForkChild(debugFlags, isSystemServer, instructionSet);
+    }
+
+    /**
+     * Resets the calling thread priority to the default value (Thread.NORM_PRIORITY
+     * or nice value 0). This updates both the priority value in java.lang.Thread and
+     * the nice value (setpriority).
+     */
+    static void resetNicePriority() {
+        Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
+    }
+
+    /**
+     * 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.
+     */
+    public 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)
+     */
+    public static void appendQuotedShellArgs(StringBuilder command, String[] args) {
+        for (String arg : args) {
+            command.append(" '").append(arg.replace("'", "'\\''")).append("'");
+        }
+    }
+}
diff --git a/com/android/internal/os/ZygoteConnection.java b/com/android/internal/os/ZygoteConnection.java
new file mode 100644
index 0000000..9fa3239
--- /dev/null
+++ b/com/android/internal/os/ZygoteConnection.java
@@ -0,0 +1,921 @@
+/*
+ * 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 android.system.OsConstants.STDERR_FILENO;
+import static android.system.OsConstants.STDIN_FILENO;
+import static android.system.OsConstants.STDOUT_FILENO;
+import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEOUT_MILLIS;
+import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC;
+import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
+
+import android.net.Credentials;
+import android.net.LocalSocket;
+import android.os.FactoryTest;
+import android.os.Process;
+import android.os.SystemProperties;
+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 java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import libcore.io.IoUtils;
+
+/**
+ * A connection that can make spawn requests.
+ */
+class ZygoteConnection {
+    private static final String TAG = "Zygote";
+
+    /** a prototype instance for a future List.toArray() */
+    private static final int[][] intArray2d = new int[0][0];
+
+    /**
+     * 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 DataOutputStream mSocketOutStream;
+    private final BufferedReader mSocketReader;
+    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
+     */
+    ZygoteConnection(LocalSocket socket, String abiList) throws IOException {
+        mSocket = socket;
+        this.abiList = abiList;
+
+        mSocketOutStream
+                = new DataOutputStream(socket.getOutputStream());
+
+        mSocketReader = new BufferedReader(
+                new InputStreamReader(socket.getInputStream()), 256);
+
+        mSocket.setSoTimeout(CONNECTION_TIMEOUT_MILLIS);
+
+        try {
+            peer = mSocket.getPeerCredentials();
+        } catch (IOException ex) {
+            Log.e(TAG, "Cannot read peer credentials", ex);
+            throw ex;
+        }
+
+        isEof = false;
+    }
+
+    /**
+     * Returns the file descriptor of the associated socket.
+     *
+     * @return null-ok; file descriptor
+     */
+    FileDescriptor getFileDesciptor() {
+        return mSocket.getFileDescriptor();
+    }
+
+    /**
+     * Reads one start command from the command socket. If successful, a child is forked and 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 the client closes the socket, an {@code EOF} condition is set, which callers can test
+     * for by calling {@code ZygoteConnection.isClosedByPeer}.
+     */
+    Runnable processOneCommand(ZygoteServer zygoteServer) {
+        String args[];
+        Arguments parsedArgs = null;
+        FileDescriptor[] descriptors;
+
+        try {
+            args = readArgumentList();
+            descriptors = mSocket.getAncillaryFileDescriptors();
+        } catch (IOException ex) {
+            throw new IllegalStateException("IOException on command socket", ex);
+        }
+
+        // readArgumentList returns null only when it has reached EOF with no available
+        // data to read. This will only happen when the remote socket has disconnected.
+        if (args == null) {
+            isEof = true;
+            return null;
+        }
+
+        int pid = -1;
+        FileDescriptor childPipeFd = null;
+        FileDescriptor serverPipeFd = null;
+
+        parsedArgs = new Arguments(args);
+
+        if (parsedArgs.abiListQuery) {
+            handleAbiListQuery();
+            return null;
+        }
+
+        if (parsedArgs.preloadDefault) {
+            handlePreload();
+            return null;
+        }
+
+        if (parsedArgs.preloadPackage != null) {
+            handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs,
+                    parsedArgs.preloadPackageCacheKey);
+            return null;
+        }
+
+        if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
+            throw new ZygoteSecurityException("Client may not specify capabilities: " +
+                    "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
+                    ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
+        }
+
+        applyUidSecurityPolicy(parsedArgs, peer);
+        applyInvokeWithSecurityPolicy(parsedArgs, peer);
+
+        applyDebuggerSystemProperty(parsedArgs);
+        applyInvokeWithSystemProperty(parsedArgs);
+
+        int[][] rlimits = null;
+
+        if (parsedArgs.rlimits != null) {
+            rlimits = parsedArgs.rlimits.toArray(intArray2d);
+        }
+
+        int[] fdsToIgnore = null;
+
+        if (parsedArgs.invokeWith != 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$();
+        }
+
+        fd = zygoteServer.getServerSocketFileDescriptor();
+
+        if (fd != null) {
+            fdsToClose[1] = fd.getInt$();
+        }
+
+        fd = null;
+
+        pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
+                parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
+                parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
+                parsedArgs.appDataDir);
+
+        try {
+            if (pid == 0) {
+                // in child
+                zygoteServer.setForkChild();
+
+                zygoteServer.closeServerSocket();
+                IoUtils.closeQuietly(serverPipeFd);
+                serverPipeFd = null;
+
+                return handleChildProc(parsedArgs, descriptors, childPipeFd);
+            } else {
+                // In the parent. A pid < 0 indicates a failure and will be handled in
+                // handleParentProc.
+                IoUtils.closeQuietly(childPipeFd);
+                childPipeFd = null;
+                handleParentProc(pid, descriptors, serverPipeFd);
+                return null;
+            }
+        } finally {
+            IoUtils.closeQuietly(childPipeFd);
+            IoUtils.closeQuietly(serverPipeFd);
+        }
+    }
+
+    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);
+        }
+    }
+
+    /**
+     * 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);
+        }
+    }
+
+    protected void preload() {
+        ZygoteInit.lazyPreload();
+    }
+
+    protected boolean isPreloadComplete() {
+        return ZygoteInit.isPreloadComplete();
+    }
+
+    protected DataOutputStream getSocketOutputStream() {
+        return mSocketOutStream;
+    }
+
+    protected void handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
+        throw new RuntimeException("Zyogte does not support package preloading");
+    }
+
+    /**
+     * Closes socket associated with this connection.
+     */
+    void closeSocket() {
+        try {
+            mSocket.close();
+        } catch (IOException ex) {
+            Log.e(TAG, "Exception while closing command "
+                    + "socket in parent", ex);
+        }
+    }
+
+    boolean isClosedByPeer() {
+        return isEof;
+    }
+
+    /**
+     * 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> --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>
+     */
+    static class Arguments {
+        /** from --setuid */
+        int uid = 0;
+        boolean uidSpecified;
+
+        /** from --setgid */
+        int gid = 0;
+        boolean gidSpecified;
+
+        /** from --setgroups */
+        int[] gids;
+
+        /**
+         * From --enable-jdwp, --enable-checkjni, --enable-assert,
+         * --enable-safemode, --generate-debug-info, --enable-jni-logging,
+         * --java-debuggable, and --native-debuggable.
+         */
+        int debugFlags;
+
+        /** From --mount-external */
+        int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
+
+        /** from --target-sdk-version. */
+        int targetSdkVersion;
+        boolean targetSdkVersionSpecified;
+
+        /** from --nice-name */
+        String niceName;
+
+        /** from --capabilities */
+        boolean capabilitiesSpecified;
+        long permittedCapabilities;
+        long effectiveCapabilities;
+
+        /** from --seinfo */
+        boolean seInfoSpecified;
+        String seInfo;
+
+        /** from all --rlimit=r,c,m */
+        ArrayList<int[]> rlimits;
+
+        /** from --invoke-with */
+        String invokeWith;
+
+        /**
+         * Any args after and including the first non-option arg
+         * (or after a '--')
+         */
+        String remainingArgs[];
+
+        /**
+         * Whether the current arguments constitute an ABI list query.
+         */
+        boolean abiListQuery;
+
+        /**
+         * The instruction set to use, or null when not important.
+         */
+        String instructionSet;
+
+        /**
+         * 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 appDataDir;
+
+        /**
+         * Whether to preload a package, with the package path in the remainingArgs.
+         */
+        String preloadPackage;
+        String preloadPackageLibs;
+        String preloadPackageCacheKey;
+
+        /**
+         * 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 preloadDefault;
+
+        /**
+         * Constructs instance and parses args
+         * @param args zygote command-line args
+         * @throws IllegalArgumentException
+         */
+        Arguments(String args[]) throws IllegalArgumentException {
+            parseArgs(args);
+        }
+
+        /**
+         * Parses the commandline arguments intended for the Zygote spawner
+         * (such as "--setuid=" and "--setgid=") and creates an array
+         * containing the remaining args.
+         *
+         * Per security review bug #1112214, duplicate args are disallowed in
+         * critical cases to make injection harder.
+         */
+        private void parseArgs(String args[])
+                throws IllegalArgumentException {
+            int curArg = 0;
+
+            boolean seenRuntimeArgs = false;
+
+            for ( /* curArg */ ; curArg < args.length; curArg++) {
+                String arg = args[curArg];
+
+                if (arg.equals("--")) {
+                    curArg++;
+                    break;
+                } else if (arg.startsWith("--setuid=")) {
+                    if (uidSpecified) {
+                        throw new IllegalArgumentException(
+                                "Duplicate arg specified");
+                    }
+                    uidSpecified = true;
+                    uid = Integer.parseInt(
+                            arg.substring(arg.indexOf('=') + 1));
+                } else if (arg.startsWith("--setgid=")) {
+                    if (gidSpecified) {
+                        throw new IllegalArgumentException(
+                                "Duplicate arg specified");
+                    }
+                    gidSpecified = true;
+                    gid = Integer.parseInt(
+                            arg.substring(arg.indexOf('=') + 1));
+                } else if (arg.startsWith("--target-sdk-version=")) {
+                    if (targetSdkVersionSpecified) {
+                        throw new IllegalArgumentException(
+                                "Duplicate target-sdk-version specified");
+                    }
+                    targetSdkVersionSpecified = true;
+                    targetSdkVersion = Integer.parseInt(
+                            arg.substring(arg.indexOf('=') + 1));
+                } else if (arg.equals("--enable-jdwp")) {
+                    debugFlags |= Zygote.DEBUG_ENABLE_JDWP;
+                } else if (arg.equals("--enable-safemode")) {
+                    debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
+                } else if (arg.equals("--enable-checkjni")) {
+                    debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
+                } else if (arg.equals("--generate-debug-info")) {
+                    debugFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO;
+                } else if (arg.equals("--always-jit")) {
+                    debugFlags |= Zygote.DEBUG_ALWAYS_JIT;
+                } else if (arg.equals("--native-debuggable")) {
+                    debugFlags |= Zygote.DEBUG_NATIVE_DEBUGGABLE;
+                } else if (arg.equals("--java-debuggable")) {
+                    debugFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;
+                } else if (arg.equals("--enable-jni-logging")) {
+                    debugFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING;
+                } else if (arg.equals("--enable-assert")) {
+                    debugFlags |= Zygote.DEBUG_ENABLE_ASSERT;
+                } else if (arg.equals("--runtime-args")) {
+                    seenRuntimeArgs = true;
+                } else if (arg.startsWith("--seinfo=")) {
+                    if (seInfoSpecified) {
+                        throw new IllegalArgumentException(
+                                "Duplicate arg specified");
+                    }
+                    seInfoSpecified = true;
+                    seInfo = arg.substring(arg.indexOf('=') + 1);
+                } else if (arg.startsWith("--capabilities=")) {
+                    if (capabilitiesSpecified) {
+                        throw new IllegalArgumentException(
+                                "Duplicate arg specified");
+                    }
+                    capabilitiesSpecified = true;
+                    String capString = arg.substring(arg.indexOf('=')+1);
+
+                    String[] capStrings = capString.split(",", 2);
+
+                    if (capStrings.length == 1) {
+                        effectiveCapabilities = Long.decode(capStrings[0]);
+                        permittedCapabilities = effectiveCapabilities;
+                    } else {
+                        permittedCapabilities = Long.decode(capStrings[0]);
+                        effectiveCapabilities = Long.decode(capStrings[1]);
+                    }
+                } else if (arg.startsWith("--rlimit=")) {
+                    // Duplicate --rlimit arguments are specifically allowed.
+                    String[] limitStrings
+                            = arg.substring(arg.indexOf('=')+1).split(",");
+
+                    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 (rlimits == null) {
+                        rlimits = new ArrayList();
+                    }
+
+                    rlimits.add(rlimitTuple);
+                } else if (arg.startsWith("--setgroups=")) {
+                    if (gids != null) {
+                        throw new IllegalArgumentException(
+                                "Duplicate arg specified");
+                    }
+
+                    String[] params
+                            = arg.substring(arg.indexOf('=') + 1).split(",");
+
+                    gids = new int[params.length];
+
+                    for (int i = params.length - 1; i >= 0 ; i--) {
+                        gids[i] = Integer.parseInt(params[i]);
+                    }
+                } else if (arg.equals("--invoke-with")) {
+                    if (invokeWith != null) {
+                        throw new IllegalArgumentException(
+                                "Duplicate arg specified");
+                    }
+                    try {
+                        invokeWith = args[++curArg];
+                    } catch (IndexOutOfBoundsException ex) {
+                        throw new IllegalArgumentException(
+                                "--invoke-with requires argument");
+                    }
+                } else if (arg.startsWith("--nice-name=")) {
+                    if (niceName != null) {
+                        throw new IllegalArgumentException(
+                                "Duplicate arg specified");
+                    }
+                    niceName = arg.substring(arg.indexOf('=') + 1);
+                } else if (arg.equals("--mount-external-default")) {
+                    mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
+                } else if (arg.equals("--mount-external-read")) {
+                    mountExternal = Zygote.MOUNT_EXTERNAL_READ;
+                } else if (arg.equals("--mount-external-write")) {
+                    mountExternal = Zygote.MOUNT_EXTERNAL_WRITE;
+                } else if (arg.equals("--query-abi-list")) {
+                    abiListQuery = true;
+                } else if (arg.startsWith("--instruction-set=")) {
+                    instructionSet = arg.substring(arg.indexOf('=') + 1);
+                } else if (arg.startsWith("--app-data-dir=")) {
+                    appDataDir = arg.substring(arg.indexOf('=') + 1);
+                } else if (arg.equals("--preload-package")) {
+                    preloadPackage = args[++curArg];
+                    preloadPackageLibs = args[++curArg];
+                    preloadPackageCacheKey = args[++curArg];
+                } else if (arg.equals("--preload-default")) {
+                    preloadDefault = true;
+                } else {
+                    break;
+                }
+            }
+
+            if (abiListQuery) {
+                if (args.length - curArg > 0) {
+                    throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
+                }
+            } else if (preloadPackage != null) {
+                if (args.length - curArg > 0) {
+                    throw new IllegalArgumentException(
+                            "Unexpected arguments after --preload-package.");
+                }
+            } else if (!preloadDefault) {
+                if (!seenRuntimeArgs) {
+                    throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
+                }
+
+                remainingArgs = new String[args.length - curArg];
+                System.arraycopy(args, curArg, remainingArgs, 0, remainingArgs.length);
+            }
+        }
+    }
+
+    /**
+     * Reads an argument list from the command socket/
+     * @return Argument list or null if EOF is reached
+     * @throws IOException passed straight through
+     */
+    private String[] readArgumentList()
+            throws IOException {
+
+        /**
+         * See android.os.Process.zygoteSendArgsAndGetPid()
+         * 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.
+         */
+
+        int argc;
+
+        try {
+            String s = mSocketReader.readLine();
+
+            if (s == null) {
+                // EOF reached.
+                return null;
+            }
+            argc = Integer.parseInt(s);
+        } catch (NumberFormatException ex) {
+            Log.e(TAG, "invalid Zygote wire format: non-int at argc");
+            throw new IOException("invalid wire format");
+        }
+
+        // See bug 1092107: large argc can be used for a DOS attack
+        if (argc > MAX_ZYGOTE_ARGC) {
+            throw new IOException("max arg count exceeded");
+        }
+
+        String[] result = new String[argc];
+        for (int i = 0; i < argc; i++) {
+            result[i] = mSocketReader.readLine();
+            if (result[i] == null) {
+                // We got an unexpected EOF.
+                throw new IOException("truncated request");
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * uid 1000 (Process.SYSTEM_UID) may specify any uid &gt; 1000 in normal
+     * operation. It may also specify any gid and setgroups() list it chooses.
+     * In factory test mode, it may specify any UID.
+     *
+     * @param args non-null; zygote spawner arguments
+     * @param peer non-null; peer credentials
+     * @throws ZygoteSecurityException
+     */
+    private static void applyUidSecurityPolicy(Arguments args, Credentials peer)
+            throws ZygoteSecurityException {
+
+        if (peer.getUid() == Process.SYSTEM_UID) {
+            /* In normal operation, SYSTEM_UID can only specify a restricted
+             * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
+             */
+            boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF;
+
+            if (uidRestricted && args.uidSpecified && (args.uid < Process.SYSTEM_UID)) {
+                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.uidSpecified) {
+            args.uid = peer.getUid();
+            args.uidSpecified = true;
+        }
+        if (!args.gidSpecified) {
+            args.gid = peer.getGid();
+            args.gidSpecified = true;
+        }
+    }
+
+    /**
+     * Applies debugger system properties to the zygote arguments.
+     *
+     * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
+     * the debugger state is specified via the "--enable-jdwp" flag
+     * in the spawn request.
+     *
+     * @param args non-null; zygote spawner args
+     */
+    public static void applyDebuggerSystemProperty(Arguments args) {
+        if (RoSystemProperties.DEBUGGABLE) {
+            args.debugFlags |= Zygote.DEBUG_ENABLE_JDWP;
+        }
+    }
+
+    /**
+     * 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
+     */
+    private static void applyInvokeWithSecurityPolicy(Arguments args, Credentials peer)
+            throws ZygoteSecurityException {
+        int peerUid = peer.getUid();
+
+        if (args.invokeWith != null && peerUid != 0 &&
+            (args.debugFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
+            throw new ZygoteSecurityException("Peer is permitted to specify an"
+                    + "explicit invoke-with wrapper command only for debuggable"
+                    + "applications.");
+        }
+    }
+
+    /**
+     * Applies invoke-with system properties to the zygote arguments.
+     *
+     * @param args non-null; zygote args
+     */
+    public static void applyInvokeWithSystemProperty(Arguments args) {
+        if (args.invokeWith == null && args.niceName != null) {
+            String property = "wrap." + args.niceName;
+            args.invokeWith = SystemProperties.get(property);
+            if (args.invokeWith != null && args.invokeWith.length() == 0) {
+                args.invokeWith = null;
+            }
+        }
+    }
+
+    /**
+     * 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 descriptors null-ok; new file descriptors for stdio if available.
+     * @param pipeFd null-ok; pipe for communication back to Zygote.
+     */
+    private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
+            FileDescriptor pipeFd) {
+        /**
+         * 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();
+        if (descriptors != null) {
+            try {
+                Os.dup2(descriptors[0], STDIN_FILENO);
+                Os.dup2(descriptors[1], STDOUT_FILENO);
+                Os.dup2(descriptors[2], STDERR_FILENO);
+
+                for (FileDescriptor fd: descriptors) {
+                    IoUtils.closeQuietly(fd);
+                }
+            } catch (ErrnoException ex) {
+                Log.e(TAG, "Error reopening stdio", ex);
+            }
+        }
+
+        if (parsedArgs.niceName != null) {
+            Process.setArgV0(parsedArgs.niceName);
+        }
+
+        // End of the postFork event.
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        if (parsedArgs.invokeWith != null) {
+            WrapperInit.execApplication(parsedArgs.invokeWith,
+                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
+                    VMRuntime.getCurrentInstructionSet(),
+                    pipeFd, parsedArgs.remainingArgs);
+
+            // Should not get here.
+            throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
+        } else {
+            return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
+                    null /* 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 descriptors null-ok; file descriptors for child's new stdio if
+     * specified.
+     * @param pipeFd null-ok; pipe for communication with child.
+     */
+    private void handleParentProc(int pid, FileDescriptor[] descriptors, FileDescriptor pipeFd) {
+        if (pid > 0) {
+            setChildPgid(pid);
+        }
+
+        if (descriptors != null) {
+            for (FileDescriptor fd: descriptors) {
+                IoUtils.closeQuietly(fd);
+            }
+        }
+
+        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)((endTime - startTime) / 1000000l);
+                    remainingSleepTime = WRAPPED_PID_TIMEOUT_MILLIS - elapsedTimeMs;
+
+                    if (res > 0) {
+                        if ((fds[0].revents & POLLIN) != 0) {
+                            // Only read one byte, so as not to block.
+                            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/com/android/internal/os/ZygoteConnectionConstants.java b/com/android/internal/os/ZygoteConnectionConstants.java
new file mode 100644
index 0000000..506e39f
--- /dev/null
+++ b/com/android/internal/os/ZygoteConnectionConstants.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+    /** max number of arguments that a connection can specify */
+    public static final int MAX_ZYGOTE_ARGC = 1024;
+
+    /**
+     * 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 = 30000;
+}
diff --git a/com/android/internal/os/ZygoteInit.java b/com/android/internal/os/ZygoteInit.java
new file mode 100644
index 0000000..7058193
--- /dev/null
+++ b/com/android/internal/os/ZygoteInit.java
@@ -0,0 +1,874 @@
+/*
+ * 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 android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.icu.impl.CacheValue;
+import android.icu.text.DecimalFormatSymbols;
+import android.icu.util.ULocale;
+import android.opengl.EGL14;
+import android.os.Build;
+import android.os.IInstalld;
+import android.os.Environment;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.Seccomp;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.ZygoteProcess;
+import android.os.storage.StorageManager;
+import android.security.keystore.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.util.TimingsTraceLog;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+import android.webkit.WebViewFactory;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+
+import com.android.internal.util.Preconditions;
+import dalvik.system.DexFile;
+import dalvik.system.VMRuntime;
+import dalvik.system.ZygoteHooks;
+
+import libcore.io.IoUtils;
+
+import java.io.BufferedReader;
+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.Security;
+import java.security.Provider;
+
+/**
+ * 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 ZygoteConnection.Arguments} for documentation on the
+ * client protocol.
+ *
+ * @hide
+ */
+public class ZygoteInit {
+    private static final String TAG = "Zygote";
+
+    private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload";
+    private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0";
+
+    private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020;
+    private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030;
+
+    /** when preloading, GC after allocating this many bytes */
+    private static final int PRELOAD_GC_THRESHOLD = 50000;
+
+    private static final String ABI_LIST_ARG = "--abi-list=";
+
+    private static final String SOCKET_NAME_ARG = "--socket-name=";
+
+    /**
+     * Used to pre-load resources.
+     */
+    private static Resources mResources;
+
+    /**
+     * The path of a file that contains classes to preload.
+     */
+    private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";
+
+    /** Controls whether we should preload resources during zygote init. */
+    public static final boolean PRELOAD_RESOURCES = true;
+
+    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;
+
+    static void preload(TimingsTraceLog bootTimingsTraceLog) {
+        Log.d(TAG, "begin preload");
+        bootTimingsTraceLog.traceBegin("BeginIcuCachePinning");
+        beginIcuCachePinning();
+        bootTimingsTraceLog.traceEnd(); // BeginIcuCachePinning
+        bootTimingsTraceLog.traceBegin("PreloadClasses");
+        preloadClasses();
+        bootTimingsTraceLog.traceEnd(); // PreloadClasses
+        bootTimingsTraceLog.traceBegin("PreloadResources");
+        preloadResources();
+        bootTimingsTraceLog.traceEnd(); // PreloadResources
+        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadAppProcessHALs");
+        nativePreloadAppProcessHALs();
+        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");
+        preloadOpenGL();
+        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();
+        endIcuCachePinning();
+        warmUpJcaProviders();
+        Log.d(TAG, "end preload");
+
+        sPreloadComplete = true;
+    }
+
+    public static void lazyPreload() {
+        Preconditions.checkState(!sPreloadComplete);
+        Log.i(TAG, "Lazily preloading resources.");
+
+        preload(new TimingsTraceLog("ZygoteInitTiming_lazy", Trace.TRACE_TAG_DALVIK));
+    }
+
+    private static void beginIcuCachePinning() {
+        // Pin ICU data in memory from this point that would normally be held by soft references.
+        // Without this, any references created immediately below or during class preloading
+        // would be collected when the Zygote GC runs in gcAndFinalize().
+        Log.i(TAG, "Installing ICU cache reference pinning...");
+
+        CacheValue.setStrength(CacheValue.Strength.STRONG);
+
+        Log.i(TAG, "Preloading ICU data...");
+        // Explicitly exercise code to cache data apps are likely to need.
+        ULocale[] localesToPin = { ULocale.ROOT, ULocale.US, ULocale.getDefault() };
+        for (ULocale uLocale : localesToPin) {
+            new DecimalFormatSymbols(uLocale);
+        }
+    }
+
+    private static void endIcuCachePinning() {
+        // All cache references created by ICU from this point will be soft.
+        CacheValue.setStrength(CacheValue.Strength.SOFT);
+
+        Log.i(TAG, "Uninstalled ICU cache reference pinning...");
+    }
+
+    private static void preloadSharedLibraries() {
+        Log.i(TAG, "Preloading shared libraries...");
+        System.loadLibrary("android");
+        System.loadLibrary("compiler_rt");
+        System.loadLibrary("jnigraphics");
+    }
+
+    native private static void nativePreloadAppProcessHALs();
+
+    private static void preloadOpenGL() {
+        String driverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER);
+        if (!SystemProperties.getBoolean(PROPERTY_DISABLE_OPENGL_PRELOADING, false) &&
+                (driverPackageName == null || driverPackageName.isEmpty())) {
+            EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+        }
+    }
+
+    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() manipulates the list of JCA providers to insert
+        // preferred providers. Note this is not done via security.properties as the JCA providers
+        // are not on the classpath in the case of, for example, raw dalvikvm runtimes.
+        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);
+    }
+
+    /**
+     * 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;
+        }
+
+        // Alter the target heap utilization.  With explicit GCs this
+        // is not likely to have any effect.
+        float defaultUtilization = runtime.getTargetHeapUtilization();
+        runtime.setTargetHeapUtilization(0.8f);
+
+        try {
+            BufferedReader br
+                = new BufferedReader(new InputStreamReader(is), 256);
+
+            int count = 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 {
+                    if (false) {
+                        Log.v(TAG, "Preloading " + line + "...");
+                    }
+                    // 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) {
+                    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;
+                    }
+                    if (t instanceof RuntimeException) {
+                        throw (RuntimeException) t;
+                    }
+                    throw new RuntimeException(t);
+                }
+                Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+            }
+
+            Log.i(TAG, "...preloaded " + count + " classes in "
+                    + (SystemClock.uptimeMillis()-startTime) + "ms.");
+        } catch (IOException e) {
+            Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
+        } finally {
+            IoUtils.closeQuietly(is);
+            // Restore default.
+            runtime.setTargetHeapUtilization(defaultUtilization);
+
+            // 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);
+
+            // 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 commonly used resources, so they can be shared across
+     * processes.
+     *
+     * These tend to be a few Kbytes, but are frequently in the 20-40K
+     * range, and occasionally even larger.
+     */
+    private static void preloadResources() {
+        final VMRuntime runtime = VMRuntime.getRuntime();
+
+        try {
+            mResources = Resources.getSystem();
+            mResources.startPreloading();
+            if (PRELOAD_RESOURCES) {
+                Log.i(TAG, "Preloading resources...");
+
+                long startTime = SystemClock.uptimeMillis();
+                TypedArray ar = mResources.obtainTypedArray(
+                        com.android.internal.R.array.preloaded_drawables);
+                int N = preloadDrawables(ar);
+                ar.recycle();
+                Log.i(TAG, "...preloaded " + N + " resources in "
+                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
+
+                startTime = SystemClock.uptimeMillis();
+                ar = mResources.obtainTypedArray(
+                        com.android.internal.R.array.preloaded_color_state_lists);
+                N = preloadColorStateLists(ar);
+                ar.recycle();
+                Log.i(TAG, "...preloaded " + N + " resources in "
+                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
+
+                if (mResources.getBoolean(
+                        com.android.internal.R.bool.config_freeformWindowManagement)) {
+                    startTime = SystemClock.uptimeMillis();
+                    ar = mResources.obtainTypedArray(
+                            com.android.internal.R.array.preloaded_freeform_multi_window_drawables);
+                    N = preloadDrawables(ar);
+                    ar.recycle();
+                    Log.i(TAG, "...preloaded " + N + " resource in "
+                            + (SystemClock.uptimeMillis() - startTime) + "ms.");
+                }
+            }
+            mResources.finishPreloading();
+        } catch (RuntimeException e) {
+            Log.w(TAG, "Failure preloading resources", e);
+        }
+    }
+
+    private static int preloadColorStateLists(TypedArray ar) {
+        int N = ar.length();
+        for (int i=0; i<N; i++) {
+            int id = ar.getResourceId(i, 0);
+            if (false) {
+                Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
+            }
+            if (id != 0) {
+                if (mResources.getColorStateList(id, null) == null) {
+                    throw new IllegalArgumentException(
+                            "Unable to find preloaded color resource #0x"
+                            + Integer.toHexString(id)
+                            + " (" + ar.getString(i) + ")");
+                }
+            }
+        }
+        return N;
+    }
+
+
+    private static int preloadDrawables(TypedArray ar) {
+        int N = ar.length();
+        for (int i=0; i<N; i++) {
+            int id = ar.getResourceId(i, 0);
+            if (false) {
+                Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
+            }
+            if (id != 0) {
+                if (mResources.getDrawable(id, null) == null) {
+                    throw new IllegalArgumentException(
+                            "Unable to find preloaded drawable resource #0x"
+                            + Integer.toHexString(id)
+                            + " (" + ar.getString(i) + ")");
+                }
+            }
+        }
+        return N;
+    }
+
+    /**
+     * 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().
+     */
+    /*package*/ static void gcAndFinalize() {
+        final VMRuntime runtime = VMRuntime.getRuntime();
+
+        /* runFinalizationSync() lets finalizers be called in Zygote,
+         * which doesn't have a HeapWorker thread.
+         */
+        System.gc();
+        runtime.runFinalizationSync();
+        System.gc();
+    }
+
+    /**
+     * Finish remaining work for the newly forked system server process.
+     */
+    private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
+        // set umask to 0077 so new files and directories will default to owner-only permissions.
+        Os.umask(S_IRWXG | S_IRWXO);
+
+        if (parsedArgs.niceName != null) {
+            Process.setArgV0(parsedArgs.niceName);
+        }
+
+        final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
+        if (systemServerClasspath != null) {
+            performSystemServerDexOpt(systemServerClasspath);
+            // Capturing profiles is only supported for debug or eng builds since selinux normally
+            // prevents it.
+            boolean profileSystemServer = SystemProperties.getBoolean(
+                    "dalvik.vm.profilesystemserver", false);
+            if (profileSystemServer && (Build.IS_USERDEBUG || Build.IS_ENG)) {
+                try {
+                    File profileDir = Environment.getDataProfilesDePackageDirectory(
+                            Process.SYSTEM_UID, "system_server");
+                    File profile = new File(profileDir, "primary.prof");
+                    profile.getParentFile().mkdirs();
+                    profile.createNewFile();
+                    String[] codePaths = systemServerClasspath.split(":");
+                    VMRuntime.registerAppInfo(profile.getPath(), codePaths);
+                } catch (Exception e) {
+                    Log.wtf(TAG, "Failed to set up system server profile", e);
+                }
+            }
+        }
+
+        if (parsedArgs.invokeWith != null) {
+            String[] args = parsedArgs.remainingArgs;
+            // 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.invokeWith,
+                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
+                    VMRuntime.getCurrentInstructionSet(), null, args);
+
+            throw new IllegalStateException("Unexpected return from WrapperInit.execApplication");
+        } else {
+            ClassLoader cl = null;
+            if (systemServerClasspath != null) {
+                cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
+
+                Thread.currentThread().setContextClassLoader(cl);
+            }
+
+            /*
+             * Pass the remaining arguments to SystemServer.
+             */
+            return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
+        }
+
+        /* should never reach here */
+    }
+
+    /**
+     * 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 use java.library.path as the native library path.
+     */
+    static ClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
+        String libraryPath = System.getProperty("java.library.path");
+
+        return ClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath,
+                ClassLoader.getSystemClassLoader(), targetSdkVersion, true /* isNamespaceShared */,
+                null /* classLoaderName */);
+    }
+
+    /**
+     * Performs dex-opt on the elements of {@code classPath}, if needed. We
+     * choose the instruction set of the current runtime.
+     */
+    private static void performSystemServerDexOpt(String classPath) {
+        final String[] classPathElements = classPath.split(":");
+        final IInstalld installd = IInstalld.Stub
+                .asInterface(ServiceManager.getService("installd"));
+        final String instructionSet = VMRuntime.getRuntime().vmInstructionSet();
+
+        String classPathForElement = "";
+        for (String classPathElement : classPathElements) {
+            // System server is fully AOTed and never profiled
+            // for profile guided compilation.
+            String systemServerFilter = SystemProperties.get(
+                    "dalvik.vm.systemservercompilerfilter", "speed");
+
+            int dexoptNeeded;
+            try {
+                dexoptNeeded = DexFile.getDexOptNeeded(
+                    classPathElement, instructionSet, systemServerFilter,
+                    false /* newProfile */, false /* downgrade */);
+            } catch (FileNotFoundException ignored) {
+                // Do not add to the classpath.
+                Log.w(TAG, "Missing classpath element for system server: " + classPathElement);
+                continue;
+            } catch (IOException e) {
+                // Not fully clear what to do here as we don't know the cause of the
+                // IO exception. Add to the classpath to be conservative, but don't
+                // attempt to compile it.
+                Log.w(TAG, "Error checking classpath element for system server: "
+                        + classPathElement, e);
+                dexoptNeeded = DexFile.NO_DEXOPT_NEEDED;
+            }
+
+            if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+                final String packageName = "*";
+                final String outputPath = null;
+                final int dexFlags = 0;
+                final String compilerFilter = systemServerFilter;
+                final String uuid = StorageManager.UUID_PRIVATE_INTERNAL;
+                final String seInfo = null;
+                final String classLoaderContext =
+                        getSystemServerClassLoaderContext(classPathForElement);
+                try {
+                    installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName,
+                            instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter,
+                            uuid, classLoaderContext, seInfo, false /* downgrade */);
+                } catch (RemoteException | ServiceSpecificException e) {
+                    // Ignore (but log), we need this on the classpath for fallback mode.
+                    Log.w(TAG, "Failed compiling classpath element for system server: "
+                            + classPathElement, e);
+                }
+            }
+
+            classPathForElement = encodeSystemServerClassPath(
+                    classPathForElement, classPathElement);
+        }
+    }
+
+    /**
+     * Encodes the system server class loader context in a format that is accepted by dexopt.
+     * This assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}.
+     *
+     * Note that ideally we would use the {@code DexoptUtils} to compute this. However we have no
+     * dependency here on the server so we hard code the logic again.
+     */
+    private static String getSystemServerClassLoaderContext(String classPath) {
+        return classPath == null ? "PCL[]" : "PCL[" + classPath + "]";
+    }
+
+    /**
+     * Encodes the class path in a format accepted by dexopt.
+     * @param classPath the old class path (may be empty).
+     * @param newElement the new class path elements
+     * @return the class path encoding resulted from appending {@code newElement} to
+     * {@code classPath}.
+     */
+    private static String encodeSystemServerClassPath(String classPath, String newElement) {
+        return (classPath == null || classPath.isEmpty())
+                ? newElement
+                : classPath + ":" + newElement;
+    }
+
+    /**
+     * Prepare the arguments and forks for the system server process.
+     *
+     * Returns an {@code Runnable} that provides an entrypoint into system_server code in the
+     * child process, and {@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 &= ((long) data[0].effective) | (((long) 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,1032,3001,3002,3003,3006,3007,3009,3010",
+            "--capabilities=" + capabilities + "," + capabilities,
+            "--nice-name=system_server",
+            "--runtime-args",
+            "com.android.server.SystemServer",
+        };
+        ZygoteConnection.Arguments parsedArgs = null;
+
+        int pid;
+
+        try {
+            parsedArgs = new ZygoteConnection.Arguments(args);
+            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
+            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
+
+            /* Request to fork the system server process */
+            pid = Zygote.forkSystemServer(
+                    parsedArgs.uid, parsedArgs.gid,
+                    parsedArgs.gids,
+                    parsedArgs.debugFlags,
+                    null,
+                    parsedArgs.permittedCapabilities,
+                    parsedArgs.effectiveCapabilities);
+        } 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;
+    }
+
+    public static void main(String argv[]) {
+        ZygoteServer zygoteServer = new ZygoteServer();
+
+        // 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);
+        }
+
+        final Runnable caller;
+        try {
+            // Report Zygote start time to tron unless it is a runtime restart
+            if (!"1".equals(SystemProperties.get("sys.boot_completed"))) {
+                MetricsLogger.histogram(null, "boot_zygote_init",
+                        (int) SystemClock.elapsedRealtime());
+            }
+
+            String bootTimeTag = Process.is64Bit() ? "Zygote64Timing" : "Zygote32Timing";
+            TimingsTraceLog bootTimingsTraceLog = new TimingsTraceLog(bootTimeTag,
+                    Trace.TRACE_TAG_DALVIK);
+            bootTimingsTraceLog.traceBegin("ZygoteInit");
+            RuntimeInit.enableDdms();
+
+            boolean startSystemServer = false;
+            String socketName = "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)) {
+                    socketName = argv[i].substring(SOCKET_NAME_ARG.length());
+                } else {
+                    throw new RuntimeException("Unknown command line argument: " + argv[i]);
+                }
+            }
+
+            if (abiList == null) {
+                throw new RuntimeException("No ABI list supplied.");
+            }
+
+            zygoteServer.registerServerSocket(socketName);
+            // 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
+            } else {
+                Zygote.resetNicePriority();
+            }
+
+            // Do an initial gc to clean up after startup
+            bootTimingsTraceLog.traceBegin("PostZygoteInitGC");
+            gcAndFinalize();
+            bootTimingsTraceLog.traceEnd(); // PostZygoteInitGC
+
+            bootTimingsTraceLog.traceEnd(); // ZygoteInit
+            // Disable tracing so that forked processes do not inherit stale tracing tags from
+            // Zygote.
+            Trace.setTracingEnabled(false, 0);
+
+            // Zygote process unmounts root storage spaces.
+            Zygote.nativeUnmountStorageOnInit();
+
+            // Set seccomp policy
+            Seccomp.setPolicy();
+
+            ZygoteHooks.stopZygoteNoThreadCreation();
+
+            if (startSystemServer) {
+                Runnable r = forkSystemServer(abiList, socketName, 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 exception", ex);
+            throw ex;
+        } finally {
+            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 = Process.ZYGOTE_SOCKET.equals(socketName) ?
+                Process.SECONDARY_ZYGOTE_SOCKET : Process.ZYGOTE_SOCKET;
+        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 argv arg strings
+     */
+    public static final Runnable zygoteInit(int targetSdkVersion, 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, argv, classLoader);
+    }
+
+    private static final native void nativeZygoteInit();
+}
diff --git a/com/android/internal/os/ZygoteSecurityException.java b/com/android/internal/os/ZygoteSecurityException.java
new file mode 100644
index 0000000..13b4759
--- /dev/null
+++ b/com/android/internal/os/ZygoteSecurityException.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ * Exception thrown when a security policy is violated.
+ */
+class ZygoteSecurityException extends RuntimeException {
+    ZygoteSecurityException(String message) {
+        super(message);
+    }
+}
diff --git a/com/android/internal/os/ZygoteServer.java b/com/android/internal/os/ZygoteServer.java
new file mode 100644
index 0000000..8baa15a
--- /dev/null
+++ b/com/android/internal/os/ZygoteServer.java
@@ -0,0 +1,227 @@
+/*
+ * 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.system.Os;
+import android.system.ErrnoException;
+import android.system.StructPollfd;
+import android.util.Log;
+
+import android.util.Slog;
+import java.io.IOException;
+import java.io.FileDescriptor;
+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 ZygoteConnection.Arguments} for documentation on the
+ * client protocol.
+ */
+class ZygoteServer {
+    public static final String TAG = "ZygoteServer";
+
+    private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
+
+    private LocalServerSocket mServerSocket;
+
+    /**
+     * Set by the child process, immediately after a call to {@code Zygote.forkAndSpecialize}.
+     */
+    private boolean mIsForkChild;
+
+    ZygoteServer() {
+    }
+
+    void setForkChild() {
+        mIsForkChild = true;
+    }
+
+    /**
+     * Registers a server socket for zygote command connections
+     *
+     * @throws RuntimeException when open fails
+     */
+    void registerServerSocket(String socketName) {
+        if (mServerSocket == null) {
+            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(fullSocketName + " unset or invalid", ex);
+            }
+
+            try {
+                FileDescriptor fd = new FileDescriptor();
+                fd.setInt$(fileDesc);
+                mServerSocket = new LocalServerSocket(fd);
+            } catch (IOException ex) {
+                throw new RuntimeException(
+                        "Error binding to local socket '" + fileDesc + "'", ex);
+            }
+        }
+    }
+
+    /**
+     * Waits for and accepts a single command connection. Throws
+     * RuntimeException on failure.
+     */
+    private ZygoteConnection acceptCommandPeer(String abiList) {
+        try {
+            return createNewConnection(mServerSocket.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 (mServerSocket != null) {
+                FileDescriptor fd = mServerSocket.getFileDescriptor();
+                mServerSocket.close();
+                if (fd != null) {
+                    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);
+        }
+
+        mServerSocket = 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 getServerSocketFileDescriptor() {
+        return mServerSocket.getFileDescriptor();
+    }
+
+    /**
+     * 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.
+     */
+    Runnable runSelectLoop(String abiList) {
+        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
+        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
+
+        fds.add(mServerSocket.getFileDescriptor());
+        peers.add(null);
+
+        while (true) {
+            StructPollfd[] pollFds = new StructPollfd[fds.size()];
+            for (int i = 0; i < pollFds.length; ++i) {
+                pollFds[i] = new StructPollfd();
+                pollFds[i].fd = fds.get(i);
+                pollFds[i].events = (short) POLLIN;
+            }
+            try {
+                Os.poll(pollFds, -1);
+            } catch (ErrnoException ex) {
+                throw new RuntimeException("poll failed", ex);
+            }
+            for (int i = pollFds.length - 1; i >= 0; --i) {
+                if ((pollFds[i].revents & POLLIN) == 0) {
+                    continue;
+                }
+
+                if (i == 0) {
+                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
+                    peers.add(newPeer);
+                    fds.add(newPeer.getFileDesciptor());
+                } else {
+                    try {
+                        ZygoteConnection connection = peers.get(i);
+                        final Runnable command = connection.processOneCommand(this);
+
+                        if (mIsForkChild) {
+                            // We're in the child. We should always have a command to run at this
+                            // stage if processOneCommand 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 processOneCommand. This shows up as
+                            // a regular POLLIN event in our regular processing loop.
+                            if (connection.isClosedByPeer()) {
+                                connection.closeSocket();
+                                peers.remove(i);
+                                fds.remove(i);
+                            }
+                        }
+                    } 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(i);
+                            conn.closeSocket();
+
+                            fds.remove(i);
+                        } 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;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}