Expose requestModemActivityInfo

Expose requestModemActivityInfo in TelephonyManager using the Executor +
Consumer pattern, and modify clients to use it.

Test: atest TelephonyManagerTest#testRequestModemActivityInfo
Fixes: 170427831
Change-Id: I7e8134c8058017b888c324a9f85d473fc3cdd8f5
This commit is contained in:
Hall Liu
2020-10-13 14:37:19 -07:00
parent c7393c8ed0
commit 5311ad3f07
12 changed files with 281 additions and 26 deletions

View File

@@ -36671,6 +36671,11 @@ package android.os {
ctor public OperationCanceledException(String);
}
public interface OutcomeReceiver<R, E extends java.lang.Throwable> {
method public default void onError(@NonNull E);
method public void onResult(@NonNull R);
}
public final class Parcel {
method public void appendFrom(android.os.Parcel, int, int);
method @Nullable public android.os.IBinder[] createBinderArray();

View File

@@ -11802,6 +11802,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestModemActivityInfo(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.ModemActivityInfo,android.telephony.TelephonyManager.ModemActivityInfoException>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestNumberVerification(@NonNull android.telephony.PhoneNumberRange, long, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.NumberVerificationCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetAllCarrierActions();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetCarrierKeysForImsiEncryption();
@@ -11920,6 +11921,14 @@ package android.telephony {
field public static final int RESULT_SUCCESS = 0; // 0x0
}
public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception {
method public int getErrorCode();
field public static final int ERROR_INVALID_INFO_RECEIVED = 2; // 0x2
field public static final int ERROR_MODEM_RESPONSE_ERROR = 3; // 0x3
field public static final int ERROR_PHONE_NOT_AVAILABLE = 1; // 0x1
field public static final int ERROR_UNKNOWN = 0; // 0x0
}
public final class UiccAccessRule implements android.os.Parcelable {
ctor public UiccAccessRule(byte[], @Nullable String, long);
method public int describeContents();

View File

@@ -1735,6 +1735,7 @@ package android.telephony {
public final class ModemActivityInfo implements android.os.Parcelable {
ctor public ModemActivityInfo(long, int, int, @NonNull int[], int);
method public boolean isEmpty();
method public boolean isValid();
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.annotation.NonNull;
/**
* Callback interface intended for use when an asynchronous operation may result in a failure.
*
* This interface may be used in cases where an asynchronous API may complete either with a value
* or with a {@link Throwable} that indicates an error.
* @param <R> The type of the result that's being sent.
* @param <E> The type of the {@link Throwable} that contains more information about the error.
*/
public interface OutcomeReceiver<R, E extends Throwable> {
/**
* Called when the asynchronous operation succeeds and delivers a result value.
* @param result The value delivered by the asynchronous operation.
*/
void onResult(@NonNull R result);
/**
* Called when the asynchronous operation fails. The mode of failure is indicated by the
* {@link Throwable} passed as an argument to this method.
* @param error A subclass of {@link Throwable} with more details about the error that occurred.
*/
default void onError(@NonNull E error) {}
}

View File

@@ -35200,6 +35200,11 @@ package android.os {
ctor public OperationCanceledException(String);
}
public interface OutcomeReceiver<R, E extends java.lang.Throwable> {
method public default void onError(@NonNull E);
method public void onResult(@NonNull R);
}
public final class Parcel {
method public void appendFrom(android.os.Parcel, int, int);
method @Nullable public android.os.IBinder[] createBinderArray();

View File

@@ -10657,6 +10657,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestModemActivityInfo(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.ModemActivityInfo,android.telephony.TelephonyManager.ModemActivityInfoException>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestNumberVerification(@NonNull android.telephony.PhoneNumberRange, long, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.NumberVerificationCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetAllCarrierActions();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetCarrierKeysForImsiEncryption();
@@ -10775,6 +10776,14 @@ package android.telephony {
field public static final int RESULT_SUCCESS = 0; // 0x0
}
public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception {
method public int getErrorCode();
field public static final int ERROR_INVALID_INFO_RECEIVED = 2; // 0x2
field public static final int ERROR_MODEM_RESPONSE_ERROR = 3; // 0x3
field public static final int ERROR_PHONE_NOT_AVAILABLE = 1; // 0x1
field public static final int ERROR_UNKNOWN = 0; // 0x0
}
public final class UiccAccessRule implements android.os.Parcelable {
ctor public UiccAccessRule(byte[], @Nullable String, long);
method public int describeContents();

View File

@@ -22,6 +22,7 @@ import android.content.Context;
import android.net.wifi.WifiManager;
import android.os.BatteryStats;
import android.os.Bundle;
import android.os.OutcomeReceiver;
import android.os.Parcelable;
import android.os.Process;
import android.os.ServiceManager;
@@ -39,6 +40,7 @@ import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.function.pooled.PooledLambda;
import java.util.concurrent.ExecutionException;
import libcore.util.EmptyArray;
import java.util.concurrent.CompletableFuture;
@@ -405,7 +407,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
// We will request data from external processes asynchronously, and wait on a timeout.
SynchronousResultReceiver wifiReceiver = null;
SynchronousResultReceiver bluetoothReceiver = null;
SynchronousResultReceiver modemReceiver = null;
CompletableFuture<ModemActivityInfo> modemFuture = CompletableFuture.completedFuture(null);
boolean railUpdated = false;
if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) {
@@ -460,8 +462,22 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
}
if (mTelephony != null) {
modemReceiver = new SynchronousResultReceiver("telephony");
mTelephony.requestModemActivityInfo(modemReceiver);
CompletableFuture<ModemActivityInfo> temp = new CompletableFuture<>();
mTelephony.requestModemActivityInfo(Runnable::run,
new OutcomeReceiver<ModemActivityInfo,
TelephonyManager.ModemActivityInfoException>() {
@Override
public void onResult(ModemActivityInfo result) {
temp.complete(result);
}
@Override
public void onError(TelephonyManager.ModemActivityInfoException e) {
Slog.w(TAG, "error reading modem stats:" + e);
temp.complete(null);
}
});
modemFuture = temp;
}
if (!railUpdated) {
synchronized (mStats) {
@@ -472,7 +488,15 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
final BluetoothActivityEnergyInfo bluetoothInfo = awaitControllerInfo(bluetoothReceiver);
final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
ModemActivityInfo modemInfo = null;
try {
modemInfo = modemFuture.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS,
TimeUnit.MILLISECONDS);
} catch (TimeoutException | InterruptedException e) {
Slog.w(TAG, "timeout or interrupt reading modem stats: " + e);
} catch (ExecutionException e) {
Slog.w(TAG, "exception reading modem stats: " + e.getCause());
}
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
final long elapsedRealtimeUs = elapsedRealtime * 1000;
@@ -523,11 +547,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
}
if (modemInfo != null) {
if (modemInfo.isValid()) {
mStats.updateMobileRadioState(modemInfo, elapsedRealtime, uptime);
} else {
Slog.w(TAG, "modem info is invalid: " + modemInfo);
}
mStats.updateMobileRadioState(modemInfo, elapsedRealtime, uptime);
}
}

View File

@@ -1732,7 +1732,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
public void noteModemControllerActivity(final ModemActivityInfo info) {
enforceCallingPermission();
if (info == null || !info.isValid()) {
if (info == null) {
Slog.e(TAG, "invalid modem data given: " + info);
return;
}

View File

@@ -97,6 +97,7 @@ import android.os.Environment;
import android.os.IStoraged;
import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.RemoteException;
@@ -167,6 +168,7 @@ import com.android.server.stats.pull.netstats.SubInfo;
import com.android.server.storage.DiskStatsFileLogger;
import com.android.server.storage.DiskStatsLoggingService;
import java.util.concurrent.ExecutionException;
import libcore.io.IoUtils;
import org.json.JSONArray;
@@ -1624,9 +1626,34 @@ public class StatsPullAtomService extends SystemService {
int pullModemActivityInfoLocked(int atomTag, List<StatsEvent> pulledData) {
final long token = Binder.clearCallingIdentity();
try {
SynchronousResultReceiver modemReceiver = new SynchronousResultReceiver("telephony");
mTelephony.requestModemActivityInfo(modemReceiver);
final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
CompletableFuture<ModemActivityInfo> modemFuture = new CompletableFuture<>();
mTelephony.requestModemActivityInfo(Runnable::run,
new OutcomeReceiver<ModemActivityInfo,
TelephonyManager.ModemActivityInfoException>() {
@Override
public void onResult(ModemActivityInfo result) {
modemFuture.complete(result);
}
@Override
public void onError(TelephonyManager.ModemActivityInfoException e) {
Slog.w(TAG, "error reading modem stats:" + e);
modemFuture.complete(null);
}
});
ModemActivityInfo modemInfo;
try {
modemInfo = modemFuture.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS,
TimeUnit.MILLISECONDS);
} catch (TimeoutException | InterruptedException e) {
Slog.w(TAG, "timeout or interrupt reading modem stats: " + e);
return StatsManager.PULL_SKIP;
} catch (ExecutionException e) {
Slog.w(TAG, "exception reading modem stats: " + e.getCause());
return StatsManager.PULL_SKIP;
}
if (modemInfo == null) {
return StatsManager.PULL_SKIP;
}

View File

@@ -764,6 +764,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestModemActivityInfo(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.ModemActivityInfo,android.telephony.TelephonyManager.ModemActivityInfoException>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestNumberVerification(@NonNull android.telephony.PhoneNumberRange, long, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.NumberVerificationCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetAllCarrierActions();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetCarrierKeysForImsiEncryption();
@@ -882,6 +883,14 @@ package android.telephony {
field public static final int RESULT_SUCCESS = 0; // 0x0
}
public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception {
method public int getErrorCode();
field public static final int ERROR_INVALID_INFO_RECEIVED = 2; // 0x2
field public static final int ERROR_MODEM_RESPONSE_ERROR = 3; // 0x3
field public static final int ERROR_PHONE_NOT_AVAILABLE = 1; // 0x1
field public static final int ERROR_UNKNOWN = 0; // 0x0
}
public final class UiccAccessRule implements android.os.Parcelable {
ctor public UiccAccessRule(byte[], @Nullable String, long);
method public int describeContents();

View File

@@ -130,7 +130,7 @@ public final class ModemActivityInfo implements Parcelable {
+ " mTimestamp=" + mTimestamp
+ " mSleepTimeMs=" + mSleepTimeMs
+ " mIdleTimeMs=" + mIdleTimeMs
+ " mTxTimeMs[]=" + mTxTimeMs
+ " mTxTimeMs[]=" + Arrays.toString(mTxTimeMs)
+ " mRxTimeMs=" + mRxTimeMs
+ "}";
}
@@ -319,8 +319,6 @@ public final class ModemActivityInfo implements Parcelable {
*
* @return {@code true} if this {@link ModemActivityInfo} record is valid,
* {@code false} otherwise.
* TODO: remove usages of this outside Telephony by always returning a valid (or null) result
* from telephony.
* @hide
*/
@TestApi
@@ -331,7 +329,9 @@ public final class ModemActivityInfo implements Parcelable {
&& (getReceiveTimeMillis() >= 0) && !isEmpty());
}
private boolean isEmpty() {
/** @hide */
@TestApi
public boolean isEmpty() {
boolean isTxPowerEmpty = mTxTimeMs == null || mTxTimeMs.length == 0
|| Arrays.stream(mTxTimeMs).allMatch((i) -> i == 0);

View File

@@ -55,7 +55,9 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
@@ -167,6 +169,9 @@ public class TelephonyManager {
*/
public static final String MODEM_ACTIVITY_RESULT_KEY = "controller_activity";
/** @hide */
public static final String EXCEPTION_RESULT_KEY = "exception";
/**
* The process name of the Phone app as well as many other apps that use this process name, such
* as settings and vendor components.
@@ -10364,26 +10369,149 @@ public class TelephonyManager {
return null;
}
/**
* 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 with key of
* {@link #MODEM_ACTIVITY_RESULT_KEY}.
* Exception that may be supplied to the callback provided in {@link #requestModemActivityInfo}.
* @hide
*/
public void requestModemActivityInfo(@NonNull ResultReceiver result) {
@SystemApi
public static class ModemActivityInfoException extends Exception {
/** Indicates that an unknown error occurred */
public static final int ERROR_UNKNOWN = 0;
/**
* Indicates that the modem or phone processes are not available (such as when the device
* is in airplane mode).
*/
public static final int ERROR_PHONE_NOT_AVAILABLE = 1;
/**
* Indicates that the modem supplied an invalid instance of {@link ModemActivityInfo}
*/
public static final int ERROR_INVALID_INFO_RECEIVED = 2;
/**
* Indicates that the modem encountered an internal failure when processing the request
* for activity info.
*/
public static final int ERROR_MODEM_RESPONSE_ERROR = 3;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"ERROR_"},
value = {
ERROR_UNKNOWN,
ERROR_PHONE_NOT_AVAILABLE,
ERROR_INVALID_INFO_RECEIVED,
ERROR_MODEM_RESPONSE_ERROR,
})
public @interface ModemActivityInfoError {}
private final int mErrorCode;
/** @hide */
public ModemActivityInfoException(@ModemActivityInfoError int errorCode) {
mErrorCode = errorCode;
}
public @ModemActivityInfoError int getErrorCode() {
return mErrorCode;
}
@Override
public String toString() {
switch (mErrorCode) {
case ERROR_UNKNOWN: return "ERROR_UNKNOWN";
case ERROR_PHONE_NOT_AVAILABLE: return "ERROR_PHONE_NOT_AVAILABLE";
case ERROR_INVALID_INFO_RECEIVED: return "ERROR_INVALID_INFO_RECEIVED";
case ERROR_MODEM_RESPONSE_ERROR: return "ERROR_MODEM_RESPONSE_ERROR";
default: return "UNDEFINED";
}
}
}
/**
* Requests the current modem activity info.
*
* The provided instance of {@link ModemActivityInfo} represents the cumulative activity since
* the last restart of the phone process.
*
* @param callback A callback object to which the result will be delivered. If there was an
* error processing the request, {@link OutcomeReceiver#onError} will be called
* with more details about the error.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public void requestModemActivityInfo(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<ModemActivityInfo, ModemActivityInfoException> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
// Pass no handler into the receiver, since we're going to be trampolining the call to the
// listener onto the provided executor.
ResultReceiver wrapperResultReceiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle data) {
if (data == null) {
Log.w(TAG, "requestModemActivityInfo: received null bundle");
sendErrorToListener(ModemActivityInfoException.ERROR_UNKNOWN);
return;
}
data.setDefusable(true);
if (data.containsKey(EXCEPTION_RESULT_KEY)) {
int receivedErrorCode = data.getInt(EXCEPTION_RESULT_KEY);
sendErrorToListener(receivedErrorCode);
return;
}
if (!data.containsKey(MODEM_ACTIVITY_RESULT_KEY)) {
Log.w(TAG, "requestModemActivityInfo: Bundle did not contain expected key");
sendErrorToListener(ModemActivityInfoException.ERROR_UNKNOWN);
return;
}
Parcelable receivedResult = data.getParcelable(MODEM_ACTIVITY_RESULT_KEY);
if (!(receivedResult instanceof ModemActivityInfo)) {
Log.w(TAG, "requestModemActivityInfo: Bundle contained something that wasn't "
+ "a ModemActivityInfo.");
sendErrorToListener(ModemActivityInfoException.ERROR_UNKNOWN);
return;
}
ModemActivityInfo modemActivityInfo = (ModemActivityInfo) receivedResult;
if (!modemActivityInfo.isValid()) {
Log.w(TAG, "requestModemActivityInfo: Received an invalid ModemActivityInfo");
sendErrorToListener(ModemActivityInfoException.ERROR_INVALID_INFO_RECEIVED);
return;
}
Log.d(TAG, "requestModemActivityInfo: Sending result to app: " + modemActivityInfo);
sendResultToListener(modemActivityInfo);
}
private void sendResultToListener(ModemActivityInfo info) {
Binder.withCleanCallingIdentity(() ->
executor.execute(() ->
callback.onResult(info)));
}
private void sendErrorToListener(int code) {
ModemActivityInfoException e = new ModemActivityInfoException(code);
Binder.withCleanCallingIdentity(() ->
executor.execute(() ->
callback.onError(e)));
}
};
try {
ITelephony service = getITelephony();
if (service != null) {
service.requestModemActivityInfo(result);
service.requestModemActivityInfo(wrapperResultReceiver);
return;
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#getModemActivityInfo", e);
}
result.send(0, null);
executor.execute(() -> callback.onError(
new ModemActivityInfoException(
ModemActivityInfoException.ERROR_PHONE_NOT_AVAILABLE)));
}
/**