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> [--] <start class name> <args>
+ * </ul>
+ */
+ static class Arguments {
+ /** first non-option argument */
+ String startClass;
+
+ /** all following arguments */
+ String[] startArgs;
+
+ /**
+ * Constructs instance and parses args
+ * @param args runtime command-line args
+ * @throws IllegalArgumentException
+ */
+ Arguments(String args[]) throws IllegalArgumentException {
+ parseArgs(args);
+ }
+
+ /**
+ * Parses the commandline arguments intended for the Runtime.
+ */
+ private void parseArgs(String args[])
+ throws IllegalArgumentException {
+ int curArg = 0;
+ for (; curArg < args.length; curArg++) {
+ String arg = args[curArg];
+
+ if (arg.equals("--")) {
+ curArg++;
+ break;
+ } else if (!arg.startsWith("--")) {
+ break;
+ }
+ }
+
+ if (curArg == args.length) {
+ throw new IllegalArgumentException("Missing classname argument to RuntimeInit!");
+ }
+
+ startClass = args[curArg++];
+ startArgs = new String[args.length - curArg];
+ System.arraycopy(args, curArg, startArgs, 0, startArgs.length);
+ }
+ }
+
+ /**
+ * Helper class which holds a method and arguments and can call them. This is used as part of
+ * a trampoline to get rid of the initial process setup stack frames.
+ */
+ static class MethodAndArgsCaller implements Runnable {
+ /** method to call */
+ private final Method mMethod;
+
+ /** argument array */
+ private final String[] mArgs;
+
+ public MethodAndArgsCaller(Method method, String[] args) {
+ mMethod = method;
+ mArgs = args;
+ }
+
+ public void run() {
+ try {
+ mMethod.invoke(null, new Object[] { mArgs });
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException(ex);
+ } catch (InvocationTargetException ex) {
+ Throwable cause = ex.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else if (cause instanceof Error) {
+ throw (Error) cause;
+ }
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+}
diff --git a/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 <command>" using the exec() system call.
+ * This method throws a runtime exception if exec() failed, otherwise, this
+ * method never returns.
+ *
+ * @param command The shell command to execute.
+ */
+ 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> [--] <args for RuntimeInit >
+ * </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 > 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 > 0 or indication of failed fork
+ * if < 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> [--] <start class name> <args>
+ * </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;
+ }
+ }
+ }
+ }
+ }
+ }
+}