/* * 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 android.telephony; import android.annotation.DurationMillisLong; import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.telephony.ServiceState.FrequencyRange; import android.util.Range; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Objects; /** * Contains information about the modem's activity. May be useful for power stats reporting. * @hide */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @SystemApi public final class ModemActivityInfo implements Parcelable { private static final int TX_POWER_LEVELS = 5; /** * Corresponds to transmit power of less than 0dBm. */ public static final int TX_POWER_LEVEL_0 = 0; /** * Corresponds to transmit power between 0dBm and 5dBm. */ public static final int TX_POWER_LEVEL_1 = 1; /** * Corresponds to transmit power between 5dBm and 15dBm. */ public static final int TX_POWER_LEVEL_2 = 2; /** * Corresponds to transmit power between 15dBm and 20dBm. */ public static final int TX_POWER_LEVEL_3 = 3; /** * Corresponds to transmit power above 20dBm. */ public static final int TX_POWER_LEVEL_4 = 4; /** * The number of transmit power levels. Fixed by HAL definition. */ public static int getNumTxPowerLevels() { return TX_POWER_LEVELS; } /** @hide */ @IntDef(prefix = {"TX_POWER_LEVEL_"}, value = { TX_POWER_LEVEL_0, TX_POWER_LEVEL_1, TX_POWER_LEVEL_2, TX_POWER_LEVEL_3, TX_POWER_LEVEL_4, }) @Retention(RetentionPolicy.SOURCE) public @interface TxPowerLevel {} private static final Range[] TX_POWER_RANGES = new Range[] { new Range<>(Integer.MIN_VALUE, 0), new Range<>(0, 5), new Range<>(5, 15), new Range<>(15, 20), new Range<>(20, Integer.MAX_VALUE) }; private long mTimestamp; private int mSleepTimeMs; private int mIdleTimeMs; private int[] mTotalTxTimeMs; private int mTotalRxTimeMs; private int mSizeOfSpecificInfo; private ActivityStatsTechSpecificInfo[] mActivityStatsTechSpecificInfo; /** * @hide */ @TestApi public ModemActivityInfo(long timestamp, int sleepTimeMs, int idleTimeMs, @NonNull int[] txTimeMs, int rxTimeMs) { Objects.requireNonNull(txTimeMs); if (txTimeMs.length != TX_POWER_LEVELS) { throw new IllegalArgumentException("txTimeMs must have length == TX_POWER_LEVELS"); } mTimestamp = timestamp; mSleepTimeMs = sleepTimeMs; mIdleTimeMs = idleTimeMs; mTotalTxTimeMs = txTimeMs; mTotalRxTimeMs = rxTimeMs; mActivityStatsTechSpecificInfo = new ActivityStatsTechSpecificInfo[1]; mSizeOfSpecificInfo = mActivityStatsTechSpecificInfo.length; mActivityStatsTechSpecificInfo[0] = new ActivityStatsTechSpecificInfo( AccessNetworkConstants.AccessNetworkType.UNKNOWN, ServiceState.FREQUENCY_RANGE_UNKNOWN, txTimeMs, rxTimeMs); } /** * Provided for convenience in manipulation since the API exposes long values but internal * representations are ints. * @hide */ public ModemActivityInfo(long timestamp, long sleepTimeMs, long idleTimeMs, @NonNull int[] txTimeMs, long rxTimeMs) { this(timestamp, (int) sleepTimeMs, (int) idleTimeMs, txTimeMs, (int) rxTimeMs); } /** @hide */ public ModemActivityInfo(long timestamp, int sleepTimeMs, int idleTimeMs, @NonNull ActivityStatsTechSpecificInfo[] activityStatsTechSpecificInfo) { mTimestamp = timestamp; mSleepTimeMs = sleepTimeMs; mIdleTimeMs = idleTimeMs; mActivityStatsTechSpecificInfo = activityStatsTechSpecificInfo; mSizeOfSpecificInfo = mActivityStatsTechSpecificInfo.length; mTotalTxTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS]; for (int i = 0; i < getNumTxPowerLevels(); i++) { for (int j = 0; j < getSpecificInfoLength(); j++) { mTotalTxTimeMs[i] = mTotalTxTimeMs[i] + (int) mActivityStatsTechSpecificInfo[j].getTransmitTimeMillis(i); } } mTotalRxTimeMs = 0; for (int i = 0; i < getSpecificInfoLength(); i++) { mTotalRxTimeMs = mTotalRxTimeMs + (int) mActivityStatsTechSpecificInfo[i].getReceiveTimeMillis(); } } /** * Provided for convenience in manipulation since the API exposes long values but internal * representations are ints. * @hide */ public ModemActivityInfo(long timestamp, long sleepTimeMs, long idleTimeMs, @NonNull ActivityStatsTechSpecificInfo[] activityStatsTechSpecificInfo) { this(timestamp, (int) sleepTimeMs, (int) idleTimeMs, activityStatsTechSpecificInfo); } @Override public String toString() { return "ModemActivityInfo{" + " mTimestamp=" + mTimestamp + " mSleepTimeMs=" + mSleepTimeMs + " mIdleTimeMs=" + mIdleTimeMs + " mActivityStatsTechSpecificInfo=" + Arrays.toString(mActivityStatsTechSpecificInfo) + "}"; } public int describeContents() { return 0; } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { public ModemActivityInfo createFromParcel(@NonNull Parcel in) { long timestamp = in.readLong(); int sleepTimeMs = in.readInt(); int idleTimeMs = in.readInt(); Parcelable[] tempSpecifiers = in.createTypedArray(ActivityStatsTechSpecificInfo.CREATOR); ActivityStatsTechSpecificInfo[] activityStatsTechSpecificInfo; activityStatsTechSpecificInfo = new ActivityStatsTechSpecificInfo[tempSpecifiers.length]; for (int i = 0; i < tempSpecifiers.length; i++) { activityStatsTechSpecificInfo[i] = (ActivityStatsTechSpecificInfo) tempSpecifiers[i]; } return new ModemActivityInfo( timestamp, sleepTimeMs, idleTimeMs, activityStatsTechSpecificInfo); } public ModemActivityInfo[] newArray(int size) { return new ModemActivityInfo[size]; } }; /** * @param dest The Parcel in which the object should be written. * @param flags Additional flags about how the object should be written. */ public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeLong(mTimestamp); dest.writeInt(mSleepTimeMs); dest.writeInt(mIdleTimeMs); dest.writeTypedArray(mActivityStatsTechSpecificInfo, flags); } /** * Gets the timestamp at which this modem activity info was recorded. * * @return The timestamp, as returned by {@link SystemClock#elapsedRealtime()}, when this {@link * ModemActivityInfo} was recorded. */ public @ElapsedRealtimeLong long getTimestampMillis() { return mTimestamp; } /** @hide */ public void setTimestamp(long timestamp) { mTimestamp = timestamp; } /** * Gets the amount of time the modem spent transmitting at a certain power level. * * @param powerLevel The power level to query. * @return The amount of time, in milliseconds, that the modem spent transmitting at the given * power level. */ public @DurationMillisLong long getTransmitDurationMillisAtPowerLevel( @TxPowerLevel int powerLevel) { long txTimeMsAtPowerLevel = 0; for (int i = 0; i < getSpecificInfoLength(); i++) { txTimeMsAtPowerLevel += mActivityStatsTechSpecificInfo[i].getTransmitTimeMillis(powerLevel); } return txTimeMsAtPowerLevel; } /** @hide */ public @DurationMillisLong long getTransmitDurationMillisAtPowerLevel( @TxPowerLevel int powerLevel, int rat) { for (int i = 0; i < getSpecificInfoLength(); i++) { if (mActivityStatsTechSpecificInfo[i].getRat() == rat) { return mActivityStatsTechSpecificInfo[i].getTransmitTimeMillis(powerLevel); } } return 0; } /** @hide */ public @DurationMillisLong long getTransmitDurationMillisAtPowerLevel( @TxPowerLevel int powerLevel, int rat, @FrequencyRange int freq) { for (int i = 0; i < getSpecificInfoLength(); i++) { if (mActivityStatsTechSpecificInfo[i].getRat() == rat && mActivityStatsTechSpecificInfo[i].getFrequencyRange() == freq) { return mActivityStatsTechSpecificInfo[i].getTransmitTimeMillis(powerLevel); } } return 0; } /** * Gets the range of transmit powers corresponding to a certain power level. * * @param powerLevel The power level to query * @return A {@link Range} object representing the range of intensities (in dBm) to which this * power level corresponds. */ public @NonNull Range getTransmitPowerRange(@TxPowerLevel int powerLevel) { return TX_POWER_RANGES[powerLevel]; } /** @hide */ public int getSpecificInfoRat(int index) { return mActivityStatsTechSpecificInfo[index].getRat(); } /** @hide */ public int getSpecificInfoFrequencyRange(int index) { return mActivityStatsTechSpecificInfo[index].getFrequencyRange(); } /** @hide */ public void setTransmitTimeMillis(int[] txTimeMs) { mTotalTxTimeMs = Arrays.copyOf(txTimeMs, TX_POWER_LEVELS); } /** @hide */ public void setTransmitTimeMillis(int rat, int[] txTimeMs) { for (int i = 0; i < getSpecificInfoLength(); i++) { if (mActivityStatsTechSpecificInfo[i].getRat() == rat) { mActivityStatsTechSpecificInfo[i].setTransmitTimeMillis(txTimeMs); } } } /** @hide */ public void setTransmitTimeMillis(int rat, int freq, int[] txTimeMs) { for (int i = 0; i < getSpecificInfoLength(); i++) { if (mActivityStatsTechSpecificInfo[i].getRat() == rat && mActivityStatsTechSpecificInfo[i].getFrequencyRange() == freq) { mActivityStatsTechSpecificInfo[i].setTransmitTimeMillis(txTimeMs); } } } /** * @return The raw array of transmit power durations * @hide */ @NonNull public int[] getTransmitTimeMillis() { return mTotalTxTimeMs; } /** @hide */ public int[] getTransmitTimeMillis(@AccessNetworkConstants.RadioAccessNetworkType int rat) { for (int i = 0; i < mActivityStatsTechSpecificInfo.length; i++) { if (mActivityStatsTechSpecificInfo[i].getRat() == rat) { return mActivityStatsTechSpecificInfo[i].getTransmitTimeMillis(); } } return new int[5]; } /** @hide */ public int[] getTransmitTimeMillis( @AccessNetworkConstants.RadioAccessNetworkType int rat, @FrequencyRange int freq) { for (int i = 0; i < mActivityStatsTechSpecificInfo.length; i++) { if (mActivityStatsTechSpecificInfo[i].getRat() == rat && mActivityStatsTechSpecificInfo[i].getFrequencyRange() == freq) { return mActivityStatsTechSpecificInfo[i].getTransmitTimeMillis(); } } return new int[5]; } /** * Gets the amount of time (in milliseconds) when the modem is in a low power or sleep state. * * @return Time in milliseconds. */ public @DurationMillisLong long getSleepTimeMillis() { return mSleepTimeMs; } /** @hide */ public void setSleepTimeMillis(int sleepTimeMillis) { mSleepTimeMs = sleepTimeMillis; } /** * Provided for convenience, since the API surface needs to return longs but internal * representations are ints. * * @hide */ public void setSleepTimeMillis(long sleepTimeMillis) { mSleepTimeMs = (int) sleepTimeMillis; } /** * Computes the difference between this instance of {@link ModemActivityInfo} and another * instance. * * This method should be used to compute the amount of activity that has happened between two * samples of modem activity taken at separate times. The sample passed in as an argument to * this method should be the one that's taken later in time (and therefore has more activity). * @param other The other instance of {@link ModemActivityInfo} to diff against. * @return An instance of {@link ModemActivityInfo} representing the difference in modem * activity. */ public @NonNull ModemActivityInfo getDelta(@NonNull ModemActivityInfo other) { ActivityStatsTechSpecificInfo[] mDeltaSpecificInfo; mDeltaSpecificInfo = new ActivityStatsTechSpecificInfo[other.getSpecificInfoLength()]; boolean matched; for (int i = 0; i < other.getSpecificInfoLength(); i++) { matched = false; for (int j = 0; j < getSpecificInfoLength(); j++) { int rat = mActivityStatsTechSpecificInfo[j].getRat(); if (rat == other.mActivityStatsTechSpecificInfo[i].getRat() && !matched) { if (mActivityStatsTechSpecificInfo[j].getRat() == AccessNetworkConstants.AccessNetworkType.NGRAN) { if (other.mActivityStatsTechSpecificInfo[i].getFrequencyRange() == mActivityStatsTechSpecificInfo[j].getFrequencyRange()) { int freq = mActivityStatsTechSpecificInfo[j].getFrequencyRange(); int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS]; for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) { txTimeMs[lvl] = (int) (other.getTransmitDurationMillisAtPowerLevel( lvl, rat, freq) - getTransmitDurationMillisAtPowerLevel( lvl, rat, freq)); } matched = true; mDeltaSpecificInfo[i] = new ActivityStatsTechSpecificInfo( rat, freq, txTimeMs, (int) (other.getReceiveTimeMillis(rat, freq) - getReceiveTimeMillis(rat, freq))); } } else { int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS]; for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) { txTimeMs[lvl] = (int) (other.getTransmitDurationMillisAtPowerLevel(lvl, rat) - getTransmitDurationMillisAtPowerLevel(lvl, rat)); } matched = true; mDeltaSpecificInfo[i] = new ActivityStatsTechSpecificInfo( rat, ServiceState.FREQUENCY_RANGE_UNKNOWN, txTimeMs, (int) (other.getReceiveTimeMillis(rat) - getReceiveTimeMillis(rat))); } } } if (!matched) { mDeltaSpecificInfo[i] = other.mActivityStatsTechSpecificInfo[i]; } } return new ModemActivityInfo( other.getTimestampMillis(), other.getSleepTimeMillis() - getSleepTimeMillis(), other.getIdleTimeMillis() - getIdleTimeMillis(), mDeltaSpecificInfo); } /** * Gets the amount of time (in milliseconds) when the modem is awake but neither transmitting * nor receiving. * * @return Time in milliseconds. */ public @DurationMillisLong long getIdleTimeMillis() { return mIdleTimeMs; } /** @hide */ public void setIdleTimeMillis(int idleTimeMillis) { mIdleTimeMs = idleTimeMillis; } /** * Provided for convenience, since the API surface needs to return longs but internal * representations are ints. * * @hide */ public void setIdleTimeMillis(long idleTimeMillis) { mIdleTimeMs = (int) idleTimeMillis; } /** * Gets the amount of time (in milliseconds) when the modem is awake and receiving data. * * @return Time in milliseconds. */ public @DurationMillisLong long getReceiveTimeMillis() { return mTotalRxTimeMs; } /** @hide */ public @DurationMillisLong long getReceiveTimeMillis(int rat) { for (int i = 0; i < mActivityStatsTechSpecificInfo.length; i++) { if (mActivityStatsTechSpecificInfo[i].getRat() == rat) { return mActivityStatsTechSpecificInfo[i].getReceiveTimeMillis(); } } return 0; } /** @hide */ public @DurationMillisLong long getReceiveTimeMillis(int rat, int freq) { for (int i = 0; i < mActivityStatsTechSpecificInfo.length; i++) { if (mActivityStatsTechSpecificInfo[i].getRat() == rat && mActivityStatsTechSpecificInfo[i].getFrequencyRange() == freq) { return mActivityStatsTechSpecificInfo[i].getReceiveTimeMillis(); } } return 0; } /** @hide */ public void setReceiveTimeMillis(int rxTimeMillis) { mTotalRxTimeMs = rxTimeMillis; } /** * Provided for convenience, since the API surface needs to return longs but internal * representations are ints. * * @hide */ public void setReceiveTimeMillis(long receiveTimeMillis) { mTotalRxTimeMs = (int) receiveTimeMillis; } /** @hide */ public void setReceiveTimeMillis(int rat, long receiveTimeMillis) { for (int i = 0; i < mActivityStatsTechSpecificInfo.length; i++) { if (mActivityStatsTechSpecificInfo[i].getRat() == rat) { mActivityStatsTechSpecificInfo[i].setReceiveTimeMillis(receiveTimeMillis); } } } /** @hide */ public void setReceiveTimeMillis(int rat, int freq, long receiveTimeMillis) { for (int i = 0; i < mActivityStatsTechSpecificInfo.length; i++) { if (mActivityStatsTechSpecificInfo[i].getRat() == rat && mActivityStatsTechSpecificInfo[i].getFrequencyRange() == freq) { mActivityStatsTechSpecificInfo[i].setReceiveTimeMillis(receiveTimeMillis); } } } /** @hide */ public int getSpecificInfoLength() { return mSizeOfSpecificInfo; } /** * Indicates if the modem has reported valid {@link ModemActivityInfo}. * * @return {@code true} if this {@link ModemActivityInfo} record is valid, * {@code false} otherwise. * @hide */ @TestApi public boolean isValid() { if (mActivityStatsTechSpecificInfo == null) { return false; } else { boolean isTxPowerValid = true; boolean isRxPowerValid = true; for (int i = 0; i < getSpecificInfoLength(); i++) { if (!mActivityStatsTechSpecificInfo[i].isTxPowerValid()) { isTxPowerValid = false; } if (!mActivityStatsTechSpecificInfo[i].isRxPowerValid()) { isRxPowerValid = false; } } return isTxPowerValid && isRxPowerValid && ((getIdleTimeMillis() >= 0) && (getSleepTimeMillis() >= 0) && !isEmpty()); } } /** @hide */ @TestApi public boolean isEmpty() { boolean isTxPowerEmpty = true; boolean isRxPowerEmpty = true; for (int i = 0; i < getSpecificInfoLength(); i++) { if (!mActivityStatsTechSpecificInfo[i].isTxPowerEmpty()) { isTxPowerEmpty = false; } if (!mActivityStatsTechSpecificInfo[i].isRxPowerEmpty()) { isRxPowerEmpty = false; } } return isTxPowerEmpty && ((getIdleTimeMillis() == 0) && (getSleepTimeMillis() == 0) && isRxPowerEmpty); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ModemActivityInfo that = (ModemActivityInfo) o; return mTimestamp == that.mTimestamp && mSleepTimeMs == that.mSleepTimeMs && mIdleTimeMs == that.mIdleTimeMs && mSizeOfSpecificInfo == that.mSizeOfSpecificInfo && Arrays.equals( mActivityStatsTechSpecificInfo, that.mActivityStatsTechSpecificInfo); } @Override public int hashCode() { int result = Objects.hash(mTimestamp, mSleepTimeMs, mIdleTimeMs, mTotalRxTimeMs); result = 31 * result + Arrays.hashCode(mTotalTxTimeMs); return result; } }