From 010bf374d8145528f8733dfa6ef101b15de23a8d Mon Sep 17 00:00:00 2001 From: Adam Lesinski Date: Mon, 11 Apr 2016 12:18:18 -0700 Subject: [PATCH] BatteryStats: Introduce Async external stats requests Instead of calling out to external processes with a blocking IPC, pass along a Binder on which the external process can pass back the response. The calling process can then wait for the reply with a timeout. This eliminates watchdog restarts of the system_server when an external process like telephony or bluetooth hangs. Bug:26842468 Change-Id: I1b242e4ed22a63f1a4a0be8c78de8ac4d7bf56c5 --- .../BluetoothActivityEnergyInfo.java | 6 +- .../android/bluetooth/BluetoothAdapter.java | 53 ++- core/java/android/bluetooth/IBluetooth.aidl | 10 + core/java/android/os/BatteryStats.java | 2 + .../android/os/SynchronousResultReceiver.java | 80 ++++ .../android/internal/app/IBatteryStats.aidl | 7 + .../server/am/BatteryStatsService.java | 430 ++++++++++-------- .../android/telephony/TelephonyManager.java | 22 +- .../internal/telephony/ITelephony.aidl | 9 +- wifi/java/android/net/wifi/IWifiManager.aidl | 10 +- .../net/wifi/WifiActivityEnergyInfo.java | 6 +- 11 files changed, 427 insertions(+), 208 deletions(-) create mode 100644 core/java/android/os/SynchronousResultReceiver.java diff --git a/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java b/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java index e32a47056fef8..84f6060d3aed6 100644 --- a/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java +++ b/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java @@ -156,8 +156,8 @@ public final class BluetoothActivityEnergyInfo implements Parcelable { * @return if the record is valid */ public boolean isValid() { - return ((mControllerTxTimeMs !=0) || - (mControllerRxTimeMs !=0) || - (mControllerIdleTimeMs !=0)); + return ((mControllerTxTimeMs >=0) && + (mControllerRxTimeMs >=0) && + (mControllerIdleTimeMs >=0)); } } diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 2a7eff883095c..e74847711bdcc 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -31,11 +31,14 @@ import android.bluetooth.le.ScanRecord; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.content.Context; +import android.os.BatteryStats; import android.os.Binder; import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.ServiceManager; +import android.os.SynchronousResultReceiver; import android.os.SystemProperties; import android.util.Log; import android.util.Pair; @@ -53,6 +56,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.concurrent.TimeoutException; /** * Represents the local device Bluetooth adapter. The {@link BluetoothAdapter} @@ -1369,33 +1373,62 @@ public final class BluetoothAdapter { * * @return a record with {@link BluetoothActivityEnergyInfo} or null if * report is unavailable or unsupported + * @deprecated use the asynchronous + * {@link #requestControllerActivityEnergyInfo(int, ResultReceiver)} instead. * @hide */ + @Deprecated public BluetoothActivityEnergyInfo getControllerActivityEnergyInfo(int updateType) { - if (getState() != STATE_ON) return null; + SynchronousResultReceiver receiver = new SynchronousResultReceiver(); + requestControllerActivityEnergyInfo(updateType, receiver); + try { + SynchronousResultReceiver.Result result = receiver.awaitResult(1000); + if (result.bundle != null) { + return result.bundle.getParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY); + } + } catch (TimeoutException e) { + Log.e(TAG, "getControllerActivityEnergyInfo timed out"); + } + return null; + } + + /** + * Request the record of {@link BluetoothActivityEnergyInfo} object that + * has the activity and energy info. This can be used to ascertain what + * the controller has been up to, since the last sample. + * + * A null value for the activity info object may be sent if the bluetooth service is + * unreachable or the device does not support reporting such information. + * + * @param updateType Type of info, cached vs refreshed. + * @param result The callback to which to send the activity info. + * @hide + */ + public void requestControllerActivityEnergyInfo(int updateType, ResultReceiver result) { + if (getState() != STATE_ON) { + result.send(0, null); + return; + } + try { - BluetoothActivityEnergyInfo record; if (!mService.isActivityAndEnergyReportingSupported()) { - return null; + result.send(0, null); + return; } synchronized(this) { if (updateType == ACTIVITY_ENERGY_INFO_REFRESHED) { mService.getActivityEnergyInfoFromController(); wait(CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS); } - record = mService.reportActivityInfo(); - if (record.isValid()) { - return record; - } else { - return null; - } + mService.requestActivityInfo(result); } } catch (InterruptedException e) { Log.e(TAG, "getControllerActivityEnergyInfoCallback wait interrupted: " + e); + result.send(0, null); } catch (RemoteException e) { Log.e(TAG, "getControllerActivityEnergyInfoCallback: " + e); + result.send(0, null); } - return null; } /** diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index 9cd7d0585af52..45d864150d745 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -23,6 +23,7 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.OobData; import android.os.ParcelUuid; import android.os.ParcelFileDescriptor; +import android.os.ResultReceiver; /** * System private API for talking with the Bluetooth service. @@ -104,6 +105,15 @@ interface IBluetooth void getActivityEnergyInfoFromController(); BluetoothActivityEnergyInfo reportActivityInfo(); + /** + * Requests the controller activity info asynchronously. + * The implementor is expected to reply with the + * {@link android.bluetooth.BluetoothActivityEnergyInfo} object placed into the Bundle with the + * key {@link android.os.BatteryStats#RESULT_RECEIVER_CONTROLLER_KEY}. + * The result code is ignored. + */ + oneway void requestActivityInfo(in ResultReceiver result); + void onLeServiceUp(); void onBrEdrDown(); } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 773e7dd601c85..7f94d0e60fc53 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -242,6 +242,8 @@ public abstract class BatteryStats implements Parcelable { private static final String VIDEO_DATA = "vid"; private static final String AUDIO_DATA = "aud"; + public static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity"; + private final StringBuilder mFormatBuilder = new StringBuilder(32); private final Formatter mFormatter = new Formatter(mFormatBuilder); diff --git a/core/java/android/os/SynchronousResultReceiver.java b/core/java/android/os/SynchronousResultReceiver.java new file mode 100644 index 0000000000000..d1b6288e1b13c --- /dev/null +++ b/core/java/android/os/SynchronousResultReceiver.java @@ -0,0 +1,80 @@ +/* + * 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 android.os; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Extends ResultReceiver to allow the server end of the ResultReceiver to synchronously wait + * on the response from the client. This enables an RPC like system but with the ability to + * timeout and discard late results. + * + * NOTE: Can only be used for one response. Subsequent responses on the same instance are ignored. + * {@hide} + */ +public class SynchronousResultReceiver extends ResultReceiver { + public static class Result { + public int resultCode; + @Nullable public Bundle bundle; + + public Result(int resultCode, @Nullable Bundle bundle) { + this.resultCode = resultCode; + this.bundle = bundle; + } + } + + private final CompletableFuture mFuture = new CompletableFuture<>(); + + public SynchronousResultReceiver() { + super((Handler) null); + } + + @Override + final protected void onReceiveResult(int resultCode, Bundle resultData) { + super.onReceiveResult(resultCode, resultData); + mFuture.complete(new Result(resultCode, resultData)); + } + + /** + * Blocks waiting for the result from the remote client. + * + * @return the Result + * @throws TimeoutException if the timeout in milliseconds expired. + */ + public @NonNull Result awaitResult(long timeoutMillis) throws TimeoutException { + final long deadline = System.currentTimeMillis() + timeoutMillis; + while (timeoutMillis >= 0) { + try { + return mFuture.get(timeoutMillis, TimeUnit.MILLISECONDS); + } catch (ExecutionException e) { + // This will NEVER happen. + throw new AssertionError("Error receiving response", e); + } catch (InterruptedException e) { + // The thread was interrupted, try and get the value again, this time + // with the remaining time until the deadline. + timeoutMillis -= deadline - System.currentTimeMillis(); + } + } + throw new TimeoutException(); + } + +} diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 8e38c5a415677..a2a135b59a732 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -18,10 +18,13 @@ package com.android.internal.app; import com.android.internal.os.BatteryStatsImpl; +import android.bluetooth.BluetoothActivityEnergyInfo; +import android.net.wifi.WifiActivityEnergyInfo; import android.os.ParcelFileDescriptor; import android.os.WorkSource; import android.os.health.HealthStatsParceler; import android.telephony.DataConnectionRealTimeInfo; +import android.telephony.ModemActivityInfo; import android.telephony.SignalStrength; interface IBatteryStats { @@ -129,4 +132,8 @@ interface IBatteryStats { HealthStatsParceler takeUidSnapshot(int uid); HealthStatsParceler[] takeUidSnapshots(in int[] uid); + + oneway void noteBluetoothControllerActivity(in BluetoothActivityEnergyInfo info); + oneway void noteModemControllerActivity(in ModemActivityInfo info); + oneway void noteWifiControllerActivity(in WifiActivityEnergyInfo info); } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 3d420471ea826..6cd6ff43b5cee 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -16,6 +16,7 @@ package com.android.server.am; +import android.annotation.Nullable; import android.bluetooth.BluetoothActivityEnergyInfo; import android.bluetooth.BluetoothAdapter; import android.content.Context; @@ -32,11 +33,12 @@ import android.os.Message; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.ParcelFormatException; -import android.os.PowerManager; +import android.os.Parcelable; import android.os.PowerManagerInternal; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SynchronousResultReceiver; import android.os.SystemClock; import android.os.UserHandle; import android.os.WorkSource; @@ -56,7 +58,6 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.os.BatteryStatsHelper; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.PowerProfile; -import com.android.internal.telephony.ITelephony; import com.android.server.FgThread; import com.android.server.LocalServices; @@ -71,7 +72,7 @@ import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; -import java.util.Map; +import java.util.concurrent.TimeoutException; /** * All information we are collecting about things that can happen that impact @@ -81,15 +82,32 @@ public final class BatteryStatsService extends IBatteryStats.Stub implements PowerManagerInternal.LowPowerModeListener { static final String TAG = "BatteryStatsService"; - static IBatteryStats sService; + /** + * How long to wait on an individual subsystem to return its stats. + */ + private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000; + + private static IBatteryStats sService; + final BatteryStatsImpl mStats; - final BatteryStatsHandler mHandler; - Context mContext; - PowerManagerInternal mPowerManagerInternal; + private final BatteryStatsHandler mHandler; + private Context mContext; + private IWifiManager mWifiManager; + private TelephonyManager mTelephony; + + // Lock acquired when extracting data from external sources. + private final Object mExternalStatsLock = new Object(); + + // WiFi keeps an accumulated total of stats, unlike Bluetooth. + // Keep the last WiFi stats so we can compute a delta. + @GuardedBy("mExternalStatsLock") + private WifiActivityEnergyInfo mLastInfo = + new WifiActivityEnergyInfo(0, 0, 0, new long[]{0}, 0, 0, 0); class BatteryStatsHandler extends Handler implements BatteryStatsImpl.ExternalStatsSync { public static final int MSG_SYNC_EXTERNAL_STATS = 1; public static final int MSG_WRITE_TO_DISK = 2; + private int mUpdateFlags = 0; private IntArray mUidsToRemove = new IntArray(); @@ -107,7 +125,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub updateFlags = mUpdateFlags; mUpdateFlags = 0; } - updateExternalStats((String)msg.obj, updateFlags); + updateExternalStatsSync((String)msg.obj, updateFlags); // other parts of the system could be calling into us // from mStats in order to report of changes. We must grab the mStats @@ -124,7 +142,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub break; case MSG_WRITE_TO_DISK: - updateExternalStats("write", UPDATE_ALL); + updateExternalStatsSync("write", UPDATE_ALL); synchronized (mStats) { mStats.writeAsyncLocked(); } @@ -178,16 +196,16 @@ public final class BatteryStatsService extends IBatteryStats.Stub * initialized. So we initialize the low power observer later. */ public void initPowerManagement() { - mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); - mPowerManagerInternal.registerLowPowerModeObserver(this); - mStats.notePowerSaveMode(mPowerManagerInternal.getLowPowerModeEnabled()); + final PowerManagerInternal powerMgr = LocalServices.getService(PowerManagerInternal.class); + powerMgr.registerLowPowerModeObserver(this); + mStats.notePowerSaveMode(powerMgr.getLowPowerModeEnabled()); (new WakeupReasonThread()).start(); } public void shutdown() { Slog.w("BatteryStats", "Writing battery stats before shutdown..."); - updateExternalStats("shutdown", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); + updateExternalStatsSync("shutdown", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); synchronized (mStats) { mStats.shutdownLocked(); } @@ -287,7 +305,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub //Slog.i("foo", "SENDING BATTERY INFO:"); //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM)); Parcel out = Parcel.obtain(); - updateExternalStats("get-stats", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); + updateExternalStatsSync("get-stats", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); synchronized (mStats) { mStats.writeToParcel(out, 0); } @@ -302,7 +320,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub //Slog.i("foo", "SENDING BATTERY INFO:"); //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM)); Parcel out = Parcel.obtain(); - updateExternalStats("get-stats", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); + updateExternalStatsSync("get-stats", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); synchronized (mStats) { mStats.writeToParcel(out, 0); } @@ -875,6 +893,47 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } + @Override + public void noteWifiControllerActivity(WifiActivityEnergyInfo info) { + enforceCallingPermission(); + + if (info == null || !info.isValid()) { + Slog.e(TAG, "invalid wifi data given: " + info); + return; + } + + synchronized (mStats) { + mStats.updateWifiStateLocked(info); + } + } + + @Override + public void noteBluetoothControllerActivity(BluetoothActivityEnergyInfo info) { + enforceCallingPermission(); + if (info == null || !info.isValid()) { + Slog.e(TAG, "invalid bluetooth data given: " + info); + return; + } + + synchronized (mStats) { + mStats.updateBluetoothStateLocked(info); + } + } + + @Override + public void noteModemControllerActivity(ModemActivityInfo info) { + enforceCallingPermission(); + + if (info == null || !info.isValid()) { + Slog.e(TAG, "invalid modem data given: " + info); + return; + } + + synchronized (mStats) { + mStats.updateMobileRadioStateLocked(SystemClock.elapsedRealtime(), info); + } + } + public boolean isOnBattery() { return mStats.isOnBattery(); } @@ -901,7 +960,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub // Sync external stats first as the battery has changed states. If we don't sync // immediately here, we may not collect the relevant data later. - updateExternalStats("battery-state", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); + updateExternalStatsSync("battery-state", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); synchronized (mStats) { mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt); } @@ -1088,9 +1147,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub pw.println("Battery stats reset."); noOutput = true; } - updateExternalStats("dump", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); + updateExternalStatsSync("dump", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); } else if ("--write".equals(arg)) { - updateExternalStats("dump", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); + updateExternalStatsSync("dump", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); synchronized (mStats) { mStats.writeSyncLocked(); pw.println("Battery stats written."); @@ -1154,7 +1213,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY; } // Fetch data from external sources and update the BatteryStatsImpl object with them. - updateExternalStats("dump", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); + updateExternalStatsSync("dump", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); } finally { Binder.restoreCallingIdentity(ident); } @@ -1215,145 +1274,96 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } - // Objects for extracting data from external sources. - private final Object mExternalStatsLock = new Object(); + private WifiActivityEnergyInfo extractDelta(WifiActivityEnergyInfo latest) { + final long timePeriodMs = latest.mTimestamp - mLastInfo.mTimestamp; + final long lastIdleMs = mLastInfo.mControllerIdleTimeMs; + final long lastTxMs = mLastInfo.mControllerTxTimeMs; + final long lastRxMs = mLastInfo.mControllerRxTimeMs; + final long lastEnergy = mLastInfo.mControllerEnergyUsed; - @GuardedBy("mExternalStatsLock") - private IWifiManager mWifiManager; + // We will modify the last info object to be the delta, and store the new + // WifiActivityEnergyInfo object as our last one. + final WifiActivityEnergyInfo delta = mLastInfo; + delta.mTimestamp = latest.getTimeStamp(); + delta.mStackState = latest.getStackState(); - // WiFi keeps an accumulated total of stats, unlike Bluetooth. - // Keep the last WiFi stats so we can compute a delta. - @GuardedBy("mExternalStatsLock") - private WifiActivityEnergyInfo mLastInfo = - new WifiActivityEnergyInfo(0, 0, 0, new long[]{0}, 0, 0, 0); + // These times seem to be the most reliable. + delta.mControllerTxTimeMs = latest.mControllerTxTimeMs - lastTxMs; + delta.mControllerRxTimeMs = latest.mControllerRxTimeMs - lastRxMs; - @GuardedBy("mExternalStatsLock") - private WifiActivityEnergyInfo pullWifiEnergyInfoLocked() { - if (mWifiManager == null) { - mWifiManager = IWifiManager.Stub.asInterface( - ServiceManager.getService(Context.WIFI_SERVICE)); - if (mWifiManager == null) { - return null; - } + // WiFi calculates the idle time as a difference from the on time and the various + // Rx + Tx times. There seems to be some missing time there because this sometimes + // becomes negative. Just cap it at 0 and move on. + // b/21613534 + delta.mControllerIdleTimeMs = Math.max(0, latest.mControllerIdleTimeMs - lastIdleMs); + delta.mControllerEnergyUsed = Math.max(0, latest.mControllerEnergyUsed - lastEnergy); + + if (delta.mControllerTxTimeMs < 0 || delta.mControllerRxTimeMs < 0) { + // The stats were reset by the WiFi system (which is why our delta is negative). + // Returns the unaltered stats. + delta.mControllerEnergyUsed = latest.mControllerEnergyUsed; + delta.mControllerRxTimeMs = latest.mControllerRxTimeMs; + delta.mControllerTxTimeMs = latest.mControllerTxTimeMs; + delta.mControllerIdleTimeMs = latest.mControllerIdleTimeMs; + + Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + delta); } - try { - // We read the data even if we are not on battery. This is so that we keep the - // correct delta from when we should start reading (aka when we are on battery). - WifiActivityEnergyInfo info = mWifiManager.reportActivityInfo(); - if (info != null && info.isValid()) { - if (info.mControllerEnergyUsed < 0 || info.mControllerIdleTimeMs < 0 || - info.mControllerRxTimeMs < 0 || info.mControllerTxTimeMs < 0) { - Slog.wtf(TAG, "Reported WiFi energy data is invalid: " + info); - return null; - } - - final long timePeriodMs = info.mTimestamp - mLastInfo.mTimestamp; - final long lastIdleMs = mLastInfo.mControllerIdleTimeMs; - final long lastTxMs = mLastInfo.mControllerTxTimeMs; - final long lastRxMs = mLastInfo.mControllerRxTimeMs; - final long lastEnergy = mLastInfo.mControllerEnergyUsed; - - // We will modify the last info object to be the delta, and store the new - // WifiActivityEnergyInfo object as our last one. - final WifiActivityEnergyInfo result = mLastInfo; - result.mTimestamp = info.getTimeStamp(); - result.mStackState = info.getStackState(); - - // These times seem to be the most reliable. - result.mControllerTxTimeMs = info.mControllerTxTimeMs - lastTxMs; - result.mControllerRxTimeMs = info.mControllerRxTimeMs - lastRxMs; - - // WiFi calculates the idle time as a difference from the on time and the various - // Rx + Tx times. There seems to be some missing time there because this sometimes - // becomes negative. Just cap it at 0 and move on. - // b/21613534 - result.mControllerIdleTimeMs = Math.max(0, info.mControllerIdleTimeMs - lastIdleMs); - result.mControllerEnergyUsed = - Math.max(0, info.mControllerEnergyUsed - lastEnergy); - - if (result.mControllerTxTimeMs < 0 || - result.mControllerRxTimeMs < 0) { - // The stats were reset by the WiFi system (which is why our delta is negative). - // Returns the unaltered stats. - result.mControllerEnergyUsed = info.mControllerEnergyUsed; - result.mControllerRxTimeMs = info.mControllerRxTimeMs; - result.mControllerTxTimeMs = info.mControllerTxTimeMs; - result.mControllerIdleTimeMs = info.mControllerIdleTimeMs; - - Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + result); - } - - // There is some accuracy error in reports so allow some slop in the results. - final long SAMPLE_ERROR_MILLIS = 750; - final long totalTimeMs = result.mControllerIdleTimeMs + result.mControllerRxTimeMs + - result.mControllerTxTimeMs; - if (totalTimeMs > timePeriodMs + SAMPLE_ERROR_MILLIS) { - StringBuilder sb = new StringBuilder(); - sb.append("Total time "); - TimeUtils.formatDuration(totalTimeMs, sb); - sb.append(" is longer than sample period "); - TimeUtils.formatDuration(timePeriodMs, sb); - sb.append(".\n"); - sb.append("Previous WiFi snapshot: ").append("idle="); - TimeUtils.formatDuration(lastIdleMs, sb); - sb.append(" rx="); - TimeUtils.formatDuration(lastRxMs, sb); - sb.append(" tx="); - TimeUtils.formatDuration(lastTxMs, sb); - sb.append(" e=").append(lastEnergy); - sb.append("\n"); - sb.append("Current WiFi snapshot: ").append("idle="); - TimeUtils.formatDuration(info.mControllerIdleTimeMs, sb); - sb.append(" rx="); - TimeUtils.formatDuration(info.mControllerRxTimeMs, sb); - sb.append(" tx="); - TimeUtils.formatDuration(info.mControllerTxTimeMs, sb); - sb.append(" e=").append(info.mControllerEnergyUsed); - Slog.wtf(TAG, sb.toString()); - } - - mLastInfo = info; - return result; - } - } catch (RemoteException e) { - // Nothing to report, WiFi is dead. + // There is some accuracy error in reports so allow some slop in the results. + final long SAMPLE_ERROR_MILLIS = 750; + final long totalTimeMs = delta.mControllerIdleTimeMs + delta.mControllerRxTimeMs + + delta.mControllerTxTimeMs; + if (totalTimeMs > timePeriodMs + SAMPLE_ERROR_MILLIS) { + StringBuilder sb = new StringBuilder(); + sb.append("Total time "); + TimeUtils.formatDuration(totalTimeMs, sb); + sb.append(" is longer than sample period "); + TimeUtils.formatDuration(timePeriodMs, sb); + sb.append(".\n"); + sb.append("Previous WiFi snapshot: ").append("idle="); + TimeUtils.formatDuration(lastIdleMs, sb); + sb.append(" rx="); + TimeUtils.formatDuration(lastRxMs, sb); + sb.append(" tx="); + TimeUtils.formatDuration(lastTxMs, sb); + sb.append(" e=").append(lastEnergy); + sb.append("\n"); + sb.append("Current WiFi snapshot: ").append("idle="); + TimeUtils.formatDuration(latest.mControllerIdleTimeMs, sb); + sb.append(" rx="); + TimeUtils.formatDuration(latest.mControllerRxTimeMs, sb); + sb.append(" tx="); + TimeUtils.formatDuration(latest.mControllerTxTimeMs, sb); + sb.append(" e=").append(latest.mControllerEnergyUsed); + Slog.wtf(TAG, sb.toString()); } - return null; + + mLastInfo = latest; + return delta; } - @GuardedBy("mExternalStatsLock") - private BluetoothActivityEnergyInfo pullBluetoothEnergyInfoLocked() { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - BluetoothActivityEnergyInfo info = adapter.getControllerActivityEnergyInfo( - BluetoothAdapter.ACTIVITY_ENERGY_INFO_REFRESHED); - if (info != null && info.isValid()) { - if (info.getControllerEnergyUsed() < 0 || info.getControllerIdleTimeMillis() < 0 || - info.getControllerRxTimeMillis() < 0 || info.getControllerTxTimeMillis() < 0) { - Slog.wtf(TAG, "Bluetooth energy data is invalid: " + info); - } - return info; - } + /** + * Helper method to extract the Parcelable controller info from a + * SynchronousResultReceiver. + */ + private static T awaitControllerInfo( + @Nullable SynchronousResultReceiver receiver) throws TimeoutException { + if (receiver == null) { + return null; } - return null; - } - @GuardedBy("mExternalStatsLock") - private ModemActivityInfo pullModemActivityInfoLocked() { - ITelephony tm = ITelephony.Stub.asInterface(ServiceManager.getService( - Context.TELEPHONY_SERVICE)); - try { - if (tm != null) { - ModemActivityInfo info = tm.getModemActivityInfo(); - if (info == null || info.isValid()) { - return info; - } - Slog.wtf(TAG, "Modem activity info is invalid: " + info); + final SynchronousResultReceiver.Result result = + receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS); + if (result.bundle != null) { + // This is the final destination for the Bundle. + result.bundle.setDefusable(true); + + final T data = result.bundle.getParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY); + if (data != null) { + return data; } - } catch (RemoteException e) { - // Nothing to do. } + Slog.e(TAG, "no controller energy info supplied"); return null; } @@ -1371,58 +1381,108 @@ public final class BatteryStatsService extends IBatteryStats.Stub * {@link BatteryStatsImpl.ExternalStatsSync#UPDATE_WIFI}, * and {@link BatteryStatsImpl.ExternalStatsSync#UPDATE_BT}. */ - void updateExternalStats(final String reason, final int updateFlags) { + void updateExternalStatsSync(final String reason, int updateFlags) { + SynchronousResultReceiver wifiReceiver = null; + SynchronousResultReceiver bluetoothReceiver = null; + SynchronousResultReceiver modemReceiver = null; + synchronized (mExternalStatsLock) { if (mContext == null) { - // We haven't started yet (which means the BatteryStatsImpl object has - // no power profile. Don't consume data we can't compute yet. + // Don't do any work yet. return; } - if (BatteryStatsImpl.DEBUG_ENERGY) { - Slog.d(TAG, "Updating external stats: reason=" + reason); - } - - WifiActivityEnergyInfo wifiEnergyInfo = null; if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) { - wifiEnergyInfo = pullWifiEnergyInfoLocked(); + if (mWifiManager == null) { + mWifiManager = IWifiManager.Stub.asInterface( + ServiceManager.getService(Context.WIFI_SERVICE)); + } + + if (mWifiManager != null) { + try { + wifiReceiver = new SynchronousResultReceiver(); + mWifiManager.requestActivityInfo(wifiReceiver); + } catch (RemoteException e) { + // Oh well. + } + } } - ModemActivityInfo modemActivityInfo = null; - if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) { - modemActivityInfo = pullModemActivityInfoLocked(); - } - - BluetoothActivityEnergyInfo bluetoothEnergyInfo = null; if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_BT) != 0) { - // We only pull bluetooth stats when we have to, as we are not distributing its - // use amongst apps and the sampling frequency does not matter. - bluetoothEnergyInfo = pullBluetoothEnergyInfoLocked(); + final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + bluetoothReceiver = new SynchronousResultReceiver(); + adapter.requestControllerActivityEnergyInfo( + BluetoothAdapter.ACTIVITY_ENERGY_INFO_REFRESHED, + bluetoothReceiver); + } + } + + if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) { + if (mTelephony == null) { + mTelephony = TelephonyManager.from(mContext); + } + + if (mTelephony != null) { + modemReceiver = new SynchronousResultReceiver(); + mTelephony.requestModemActivityInfo(modemReceiver); + } + } + + WifiActivityEnergyInfo wifiInfo = null; + BluetoothActivityEnergyInfo bluetoothInfo = null; + ModemActivityInfo modemInfo = null; + try { + wifiInfo = awaitControllerInfo(wifiReceiver); + } catch (TimeoutException e) { + Slog.w(TAG, "Timeout reading wifi stats"); + } + + try { + bluetoothInfo = awaitControllerInfo(bluetoothReceiver); + } catch (TimeoutException e) { + Slog.w(TAG, "Timeout reading bt stats"); + } + + try { + modemInfo = awaitControllerInfo(modemReceiver); + } catch (TimeoutException e) { + Slog.w(TAG, "Timeout reading modem stats"); } synchronized (mStats) { - final long elapsedRealtime = SystemClock.elapsedRealtime(); - final long uptime = SystemClock.uptimeMillis(); - if (mStats.mRecordAllHistory) { - mStats.addHistoryEventLocked(elapsedRealtime, uptime, - BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS, reason, 0); + mStats.addHistoryEventLocked( + SystemClock.elapsedRealtime(), + SystemClock.uptimeMillis(), + BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS, + reason, 0); + + mStats.updateCpuTimeLocked(); + mStats.updateKernelWakelocksLocked(); + + if (wifiInfo != null) { + if (wifiInfo.isValid()) { + mStats.updateWifiStateLocked(extractDelta(wifiInfo)); + } else { + Slog.e(TAG, "wifi info is invalid: " + wifiInfo); + } } - if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU) != 0) { - mStats.updateCpuTimeLocked(); - mStats.updateKernelWakelocksLocked(); + if (bluetoothInfo != null) { + if (bluetoothInfo.isValid()) { + mStats.updateBluetoothStateLocked(bluetoothInfo); + } else { + Slog.e(TAG, "bluetooth info is invalid: " + bluetoothInfo); + } } - if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) { - mStats.updateMobileRadioStateLocked(elapsedRealtime, modemActivityInfo); - } - - if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) { - mStats.updateWifiStateLocked(wifiEnergyInfo); - } - - if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_BT) != 0) { - mStats.updateBluetoothStateLocked(bluetoothEnergyInfo); + if (modemInfo != null) { + if (modemInfo.isValid()) { + mStats.updateMobileRadioStateLocked(SystemClock.elapsedRealtime(), + modemInfo); + } else { + Slog.e(TAG, "modem info is invalid: " + modemInfo); + } } } } @@ -1439,7 +1499,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub } long ident = Binder.clearCallingIdentity(); try { - updateExternalStats("get-health-stats-for-uid", + updateExternalStatsSync("get-health-stats-for-uid", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); synchronized (mStats) { return getHealthStatsForUidLocked(requestUid); @@ -1464,7 +1524,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub long ident = Binder.clearCallingIdentity(); int i=-1; try { - updateExternalStats("get-health-stats-for-uids", + updateExternalStatsSync("get-health-stats-for-uids", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL); synchronized (mStats) { final int N = requestUids.length; @@ -1499,7 +1559,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub /** * Gets a HealthStatsParceler for the given uid. You should probably call - * updateExternalStats first. + * updateExternalStatsSync first. */ HealthStatsParceler getHealthStatsForUidLocked(int requestUid) { final HealthStatsBatteryStatsWriter writer = new HealthStatsBatteryStatsWriter(); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 0bca62828be46..394fc27bf9b2b 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -25,6 +25,8 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.BatteryStats; +import android.os.ResultReceiver; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.os.Bundle; @@ -73,6 +75,14 @@ import java.util.regex.Pattern; public class TelephonyManager { private static final String TAG = "TelephonyManager"; + /** + * The key to use when placing the result of {@link #requestModemActivityInfo(ResultReceiver)} + * into the ResultReceiver Bundle. + * @hide + */ + public static final String MODEM_ACTIVITY_RESULT_KEY = + BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY; + private static ITelephonyRegistry sRegistry; /** @@ -5051,19 +5061,23 @@ public class TelephonyManager { } /** - * Returns the modem activity info. + * Requests the modem activity info. The recipient will place the result + * in `result`. + * @param result The object on which the recipient will send the resulting + * {@link android.telephony.ModemActivityInfo} object. * @hide */ - public ModemActivityInfo getModemActivityInfo() { + public void requestModemActivityInfo(ResultReceiver result) { try { ITelephony service = getITelephony(); if (service != null) { - return service.getModemActivityInfo(); + service.requestModemActivityInfo(result); + return; } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelephony#getModemActivityInfo", e); } - return null; + result.send(0, null); } /** diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index b41d361f6a16f..bb8aaad5db599 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -18,6 +18,7 @@ package com.android.internal.telephony; import android.content.Intent; import android.os.Bundle; +import android.os.ResultReceiver; import android.net.Uri; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; @@ -1027,9 +1028,13 @@ interface ITelephony { String getLocaleFromDefaultSim(); /** - * Return the modem activity info. + * Requests the modem activity info asynchronously. + * The implementor is expected to reply with the + * {@link android.telephony.ModemActivityInfo} object placed into the Bundle with the key + * {@link android.telephony.TelephonyManager#MODEM_ACTIVITY_RESULT_KEY}. + * The result code is ignored. */ - ModemActivityInfo getModemActivityInfo(); + oneway void requestModemActivityInfo(in ResultReceiver result); /** * Get the service state on specified subscription diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index be5b8fccafefb..9268a2b7cbe21 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -27,8 +27,8 @@ import android.net.Network; import android.net.DhcpInfo; - import android.os.Messenger; +import android.os.ResultReceiver; import android.os.WorkSource; /** @@ -42,6 +42,14 @@ interface IWifiManager WifiActivityEnergyInfo reportActivityInfo(); + /** + * Requests the controller activity info asynchronously. + * The implementor is expected to reply with the + * {@link android.net.wifi.WifiActivityEnergyInfo} object placed into the Bundle with the key + * {@link android.os.BatteryStats#RESULT_RECEIVER_CONTROLLER_KEY}. The result code is ignored. + */ + oneway void requestActivityInfo(in ResultReceiver result); + List getConfiguredNetworks(); List getPrivilegedConfiguredNetworks(); diff --git a/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java b/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java index 4c38c9bbe2cde..29bf02cac8fe9 100644 --- a/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java +++ b/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java @@ -181,8 +181,8 @@ public final class WifiActivityEnergyInfo implements Parcelable { * @return if the record is valid */ public boolean isValid() { - return ((mControllerTxTimeMs !=0) || - (mControllerRxTimeMs !=0) || - (mControllerIdleTimeMs !=0)); + return ((mControllerTxTimeMs >=0) && + (mControllerRxTimeMs >=0) && + (mControllerIdleTimeMs >=0)); } }