blob: a4c0a8386cab1dc971547a5da65e002aeeabe79a [file] [log] [blame]
/*
* Copyright (C) 2023 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.phone.testapps.satellitetestapp;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.telephony.satellite.AntennaDirection;
import android.telephony.satellite.AntennaPosition;
import android.telephony.satellite.SatelliteManager;
import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
import android.telephony.satellite.stub.ISatelliteListener;
import android.telephony.satellite.stub.NTRadioTechnology;
import android.telephony.satellite.stub.PointingInfo;
import android.telephony.satellite.stub.SatelliteCapabilities;
import android.telephony.satellite.stub.SatelliteDatagram;
import android.telephony.satellite.stub.SatelliteError;
import android.telephony.satellite.stub.SatelliteImplBase;
import android.telephony.satellite.stub.SatelliteModemState;
import android.telephony.satellite.stub.SatelliteService;
import android.util.Log;
import com.android.internal.telephony.IBooleanConsumer;
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.util.FunctionalUtils;
import com.android.telephony.Rlog;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* Test service for Satellite to verify end to end flow via testapp.
*/
public class TestSatelliteService extends SatelliteImplBase {
private static final String TAG = "TestSatelliteService";
// Hardcoded values below
private static final int SATELLITE_ALWAYS_VISIBLE = 0;
/** SatelliteCapabilities constant indicating that the radio technology is proprietary. */
private static final int[] SUPPORTED_RADIO_TECHNOLOGIES =
new int[]{NTRadioTechnology.PROPRIETARY};
/** SatelliteCapabilities constant indicating that pointing to satellite is required. */
private static final boolean POINTING_TO_SATELLITE_REQUIRED = true;
/** SatelliteCapabilities constant indicating the maximum number of characters per datagram. */
private static final int MAX_BYTES_PER_DATAGRAM = 339;
/** SatelliteCapabilities constant keys which are used to fill mAntennaPositionMap. */
private static final int[] ANTENNA_POSITION_KEYS = new int[]{
SatelliteManager.DISPLAY_MODE_OPENED, SatelliteManager.DISPLAY_MODE_CLOSED};
/** SatelliteCapabilities constant values which are used to fill mAntennaPositionMap. */
private static final AntennaPosition[] ANTENNA_POSITION_VALUES = new AntennaPosition[] {
new AntennaPosition(new AntennaDirection(1, 1, 1),
SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT),
new AntennaPosition(new AntennaDirection(2, 2, 2),
SatelliteManager.DEVICE_HOLD_POSITION_LANDSCAPE_LEFT)
};
@NonNull
private final Map<IBinder, ISatelliteListener> mRemoteListeners = new HashMap<>();
@Nullable private ILocalSatelliteListener mLocalListener;
private final LocalBinder mBinder = new LocalBinder();
@SatelliteError
private int mErrorCode = SatelliteError.ERROR_NONE;
// For local access of this Service.
class LocalBinder extends Binder {
TestSatelliteService getService() {
return TestSatelliteService.this;
}
}
private boolean mIsCommunicationAllowedInLocation;
private boolean mIsEnabled;
private boolean mIsProvisioned;
private boolean mIsSupported;
private int mModemState;
private boolean mIsCellularModemEnabledMode;
/**
* Create TestSatelliteService using the Executor specified for methods being called from
* the framework.
*
* @param executor The executor for the framework to use when executing satellite methods.
*/
public TestSatelliteService(@NonNull Executor executor) {
super(executor);
mIsCommunicationAllowedInLocation = true;
mIsEnabled = false;
mIsProvisioned = false;
mIsSupported = true;
mModemState = SatelliteModemState.SATELLITE_MODEM_STATE_OFF;
mIsCellularModemEnabledMode = false;
}
/**
* Zero-argument constructor to prevent service binding exception.
*/
public TestSatelliteService() {
this(Runnable::run);
}
@Override
public IBinder onBind(Intent intent) {
if (SatelliteService.SERVICE_INTERFACE.equals(intent.getAction())) {
logd("Remote service bound");
return getBinder();
}
logd("Local service bound");
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
logd("onCreate");
}
@Override
public void onDestroy() {
super.onDestroy();
logd("onDestroy");
}
@Override
public void setSatelliteListener(@NonNull ISatelliteListener listener) {
logd("setSatelliteListener");
mRemoteListeners.put(listener.asBinder(), listener);
notifyRemoteServiceConnected();
}
@Override
public void requestSatelliteListeningEnabled(boolean enabled, int timeout,
@NonNull IIntegerConsumer errorCallback) {
logd("requestSatelliteListeningEnabled: mErrorCode=" + mErrorCode);
if (mLocalListener != null) {
runWithExecutor(() -> mLocalListener.onSatelliteListeningEnabled(enabled));
} else {
loge("requestSatelliteListeningEnabled: mLocalListener is null");
}
if (!verifySatelliteModemState(errorCallback)) {
return;
}
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
return;
}
if (enabled) {
updateSatelliteModemState(SatelliteModemState.SATELLITE_MODEM_STATE_LISTENING);
} else {
updateSatelliteModemState(SatelliteModemState.SATELLITE_MODEM_STATE_IDLE);
}
runWithExecutor(() -> errorCallback.accept(SatelliteError.ERROR_NONE));
}
@Override
public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
@NonNull IIntegerConsumer errorCallback) {
logd("requestSatelliteEnabled: mErrorCode=" + mErrorCode + " enable = " + enableSatellite);
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
return;
}
if (enableSatellite) {
enableSatellite(errorCallback);
} else {
disableSatellite(errorCallback);
}
}
private void enableSatellite(@NonNull IIntegerConsumer errorCallback) {
mIsEnabled = true;
updateSatelliteModemState(SatelliteModemState.SATELLITE_MODEM_STATE_IDLE);
runWithExecutor(() -> errorCallback.accept(SatelliteError.ERROR_NONE));
}
private void disableSatellite(@NonNull IIntegerConsumer errorCallback) {
mIsEnabled = false;
updateSatelliteModemState(SatelliteModemState.SATELLITE_MODEM_STATE_OFF);
runWithExecutor(() -> errorCallback.accept(SatelliteError.ERROR_NONE));
}
@Override
public void requestIsSatelliteEnabled(@NonNull IIntegerConsumer errorCallback,
@NonNull IBooleanConsumer callback) {
logd("requestIsSatelliteEnabled: mErrorCode=" + mErrorCode);
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
return;
}
runWithExecutor(() -> callback.accept(mIsEnabled));
}
@Override
public void requestIsSatelliteSupported(@NonNull IIntegerConsumer errorCallback,
@NonNull IBooleanConsumer callback) {
logd("requestIsSatelliteSupported");
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
return;
}
runWithExecutor(() -> callback.accept(mIsSupported));
}
@Override
public void requestSatelliteCapabilities(@NonNull IIntegerConsumer errorCallback,
@NonNull ISatelliteCapabilitiesConsumer callback) {
logd("requestSatelliteCapabilities: mErrorCode=" + mErrorCode);
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
return;
}
SatelliteCapabilities capabilities = new SatelliteCapabilities();
capabilities.supportedRadioTechnologies = SUPPORTED_RADIO_TECHNOLOGIES;
capabilities.isPointingRequired = POINTING_TO_SATELLITE_REQUIRED;
capabilities.maxBytesPerOutgoingDatagram = MAX_BYTES_PER_DATAGRAM;
capabilities.antennaPositionKeys = ANTENNA_POSITION_KEYS;
capabilities.antennaPositionValues = ANTENNA_POSITION_VALUES;
runWithExecutor(() -> callback.accept(capabilities));
}
@Override
public void startSendingSatellitePointingInfo(@NonNull IIntegerConsumer errorCallback) {
logd("startSendingSatellitePointingInfo: mErrorCode=" + mErrorCode);
if (!verifySatelliteModemState(errorCallback)) {
if (mLocalListener != null) {
runWithExecutor(() -> mLocalListener.onStartSendingSatellitePointingInfo());
} else {
loge("startSendingSatellitePointingInfo: mLocalListener is null");
}
return;
}
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
} else {
runWithExecutor(() -> errorCallback.accept(SatelliteError.ERROR_NONE));
}
if (mLocalListener != null) {
runWithExecutor(() -> mLocalListener.onStartSendingSatellitePointingInfo());
} else {
loge("startSendingSatellitePointingInfo: mLocalListener is null");
}
}
@Override
public void stopSendingSatellitePointingInfo(@NonNull IIntegerConsumer errorCallback) {
logd("stopSendingSatellitePointingInfo: mErrorCode=" + mErrorCode);
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
} else {
runWithExecutor(() -> errorCallback.accept(SatelliteError.ERROR_NONE));
}
if (mLocalListener != null) {
runWithExecutor(() -> mLocalListener.onStopSendingSatellitePointingInfo());
} else {
loge("stopSendingSatellitePointingInfo: mLocalListener is null");
}
}
@Override
public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
@NonNull IIntegerConsumer errorCallback) {
logd("provisionSatelliteService: mErrorCode=" + mErrorCode);
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
return;
}
runWithExecutor(() -> errorCallback.accept(SatelliteError.ERROR_NONE));
updateSatelliteProvisionState(true);
}
@Override
public void deprovisionSatelliteService(@NonNull String token,
@NonNull IIntegerConsumer errorCallback) {
logd("deprovisionSatelliteService: mErrorCode=" + mErrorCode);
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
return;
}
runWithExecutor(() -> errorCallback.accept(SatelliteError.ERROR_NONE));
updateSatelliteProvisionState(false);
}
@Override
public void requestIsSatelliteProvisioned(@NonNull IIntegerConsumer errorCallback,
@NonNull IBooleanConsumer callback) {
logd("requestIsSatelliteProvisioned: mErrorCode=" + mErrorCode);
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
return;
}
runWithExecutor(() -> callback.accept(mIsProvisioned));
}
@Override
public void pollPendingSatelliteDatagrams(@NonNull IIntegerConsumer errorCallback) {
logd("pollPendingSatelliteDatagrams: mErrorCode=" + mErrorCode);
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
} else {
runWithExecutor(() -> errorCallback.accept(SatelliteError.ERROR_NONE));
}
if (mLocalListener != null) {
runWithExecutor(() -> mLocalListener.onPollPendingSatelliteDatagrams());
} else {
loge("pollPendingSatelliteDatagrams: mLocalListener is null");
}
}
@Override
public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency,
@NonNull IIntegerConsumer errorCallback) {
logd("sendSatelliteDatagram: mErrorCode=" + mErrorCode);
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
} else {
runWithExecutor(() -> errorCallback.accept(SatelliteError.ERROR_NONE));
}
if (mLocalListener != null) {
runWithExecutor(() -> mLocalListener.onSendSatelliteDatagram(datagram, isEmergency));
} else {
loge("sendSatelliteDatagram: mLocalListener is null");
}
}
@Override
public void requestSatelliteModemState(@NonNull IIntegerConsumer errorCallback,
@NonNull IIntegerConsumer callback) {
logd("requestSatelliteModemState: mErrorCode=" + mErrorCode);
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
return;
}
runWithExecutor(() -> callback.accept(mModemState));
}
@Override
public void requestIsSatelliteCommunicationAllowedForCurrentLocation(
@NonNull IIntegerConsumer errorCallback, @NonNull IBooleanConsumer callback) {
logd("requestIsSatelliteCommunicationAllowedForCurrentLocation: mErrorCode=" + mErrorCode);
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
return;
}
if (mIsCommunicationAllowedInLocation) {
runWithExecutor(() -> callback.accept(true));
} else {
runWithExecutor(() -> callback.accept(false));
}
}
@Override
public void requestTimeForNextSatelliteVisibility(@NonNull IIntegerConsumer errorCallback,
@NonNull IIntegerConsumer callback) {
logd("requestTimeForNextSatelliteVisibility: mErrorCode=" + mErrorCode);
if (mErrorCode != SatelliteError.ERROR_NONE) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
return;
}
runWithExecutor(() -> callback.accept(SATELLITE_ALWAYS_VISIBLE));
}
public void setLocalSatelliteListener(@NonNull ILocalSatelliteListener listener) {
logd("setLocalSatelliteListener: listener=" + listener);
mLocalListener = listener;
}
public void setErrorCode(@SatelliteError int errorCode) {
logd("setErrorCode: errorCode=" + errorCode);
mErrorCode = errorCode;
}
public void setSatelliteSupport(boolean supported) {
logd("setSatelliteSupport: supported=" + supported);
mIsSupported = supported;
}
public void sendOnSatelliteDatagramReceived(SatelliteDatagram datagram, int pendingCount) {
logd("sendOnSatelliteDatagramReceived");
mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
listener.onSatelliteDatagramReceived(datagram, pendingCount)));
}
public void sendOnPendingDatagrams() {
logd("sendOnPendingDatagrams");
mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
listener.onPendingDatagrams()));
}
public void sendOnSatellitePositionChanged(PointingInfo pointingInfo) {
logd("sendOnSatellitePositionChanged");
mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
listener.onSatellitePositionChanged(pointingInfo)));
}
/**
* Helper method to verify that the satellite modem is properly configured to receive
* requests.
*
* @param errorCallback The callback to notify of any errors preventing satellite requests.
* @return {@code true} if the satellite modem is configured to receive requests and
* {@code false} if it is not.
*/
private boolean verifySatelliteModemState(@NonNull IIntegerConsumer errorCallback) {
if (!mIsSupported) {
runWithExecutor(() -> errorCallback.accept(SatelliteError.REQUEST_NOT_SUPPORTED));
return false;
}
if (!mIsProvisioned) {
runWithExecutor(() -> errorCallback.accept(SatelliteError.SERVICE_NOT_PROVISIONED));
return false;
}
if (!mIsEnabled) {
runWithExecutor(() -> errorCallback.accept(SatelliteError.INVALID_MODEM_STATE));
return false;
}
return true;
}
/**
* Update the satellite modem state and notify listeners if it changed.
*
* @param modemState The {@link SatelliteModemState} to update.
*/
private void updateSatelliteModemState(int modemState) {
if (modemState == mModemState) {
return;
}
if (mIsCellularModemEnabledMode
&& modemState == SatelliteModemState.SATELLITE_MODEM_STATE_OFF) {
logd("Not updating the Modem state to Off as it is in CellularModemEnabledMode");
return;
}
mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
listener.onSatelliteModemStateChanged(modemState)));
mModemState = modemState;
}
/**
* Update the satellite provision state and notify listeners if it changed.
*
* @param isProvisioned {@code true} if the satellite is currently provisioned and
* {@code false} if it is not.
*/
private void updateSatelliteProvisionState(boolean isProvisioned) {
logd("updateSatelliteProvisionState: isProvisioned=" + isProvisioned
+ ", mIsProvisioned=" + mIsProvisioned);
if (isProvisioned == mIsProvisioned) {
return;
}
mIsProvisioned = isProvisioned;
logd("updateSatelliteProvisionState: mRemoteListeners.size=" + mRemoteListeners.size());
mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
listener.onSatelliteProvisionStateChanged(mIsProvisioned)));
}
/**
* Execute the given runnable using the executor that this service was created with.
*
* @param r A runnable that can throw an exception.
*/
private void runWithExecutor(@NonNull FunctionalUtils.ThrowingRunnable r) {
mExecutor.execute(() -> Binder.withCleanCallingIdentity(r));
}
private void notifyRemoteServiceConnected() {
logd("notifyRemoteServiceConnected");
runWithExecutor(() -> mLocalListener.onRemoteServiceConnected());
}
/**
* Log the message to the radio buffer with {@code DEBUG} priority.
*
* @param log The message to log.
*/
private static void logd(@NonNull String log) {
Rlog.d(TAG, log);
}
/**
* Log with error attribute
*
* @param s is string log
*/
protected void loge(@NonNull String s) {
Log.e(TAG, s);
}
}