[Satellite][WIP] Bind PSS with SatelliteTestApp
Earlier this test app is used to test SatelliteService. With this new
activity, PixelSatelliteService (Child class) can also be tested.
CL includes:
- SatelliteTestApp binds PixelSatelliteService.
- APIs of PSS can be triggered from UI.
- Multiple iterations can be triggered.
- Calculate time taken by the request.
Bug: 395782973
Doc: go/satellite-test-app
Test: manual
Flag: TEST_ONLY
Change-Id: I51395a53be5c10391c5c8452739414bb9bde397b
diff --git a/testapps/TestSatelliteApp/Android.bp b/testapps/TestSatelliteApp/Android.bp
index 78d125d..fa95de6 100644
--- a/testapps/TestSatelliteApp/Android.bp
+++ b/testapps/TestSatelliteApp/Android.bp
@@ -13,6 +13,7 @@
],
static_libs: [
"SatelliteClient",
+ "google-satellite",
],
owner: "google",
privileged: true,
diff --git a/testapps/TestSatelliteApp/AndroidManifest.xml b/testapps/TestSatelliteApp/AndroidManifest.xml
index de455f2..814e958 100644
--- a/testapps/TestSatelliteApp/AndroidManifest.xml
+++ b/testapps/TestSatelliteApp/AndroidManifest.xml
@@ -38,6 +38,7 @@
<activity android:name=".SendReceive" />
<activity android:name=".NbIotSatellite" />
<activity android:name=".TestSatelliteWrapper" />
+ <activity android:name=".PssActivity" />
<receiver
android:name=".SatelliteTestAppReceiver"
diff --git a/testapps/TestSatelliteApp/res/layout/activity_Pss.xml b/testapps/TestSatelliteApp/res/layout/activity_Pss.xml
new file mode 100644
index 0000000..7f8ac44
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_Pss.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2025 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
+ -->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:padding="20dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="@android:color/holo_blue_light"
+ android:text="@string/SelectCarrier"
+ android:padding="16dp"/>
+
+ <Spinner
+ android:id="@+id/spinner"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp" />
+
+ <Switch
+ android:id="@+id/DemoModeSwitch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:padding="16dp"
+ android:text="@string/DemoMode" />
+
+ <Switch
+ android:id="@+id/EnableSwitch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:padding="16dp"
+ android:text="@string/Enable" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="@android:color/holo_blue_light"
+ android:text="@string/SelectIterations"
+ android:padding="16dp"/>
+
+ <NumberPicker
+ android:id="@+id/numberPicker"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center" />
+
+ <Button
+ android:id="@+id/requestSatelliteEnable"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:text="@string/requestSatelliteEnable"/>
+
+ <TextView
+ android:id="@+id/logView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="@android:color/holo_blue_light"
+ android:padding="16dp"/>
+
+ </LinearLayout>
+</ScrollView>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml b/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml
index 43cce9b..c85594d 100644
--- a/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml
+++ b/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml
@@ -17,6 +17,7 @@
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
@@ -88,5 +89,12 @@
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:text="@string/TestSatelliteConstrainConnection"/>
+ <Button
+ android:id="@+id/PssActivity"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
+ android:text="@string/PssActivity"/>
</LinearLayout>
</ScrollView>
diff --git a/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml b/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml
index f48c022..ae76a56 100644
--- a/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml
+++ b/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml
@@ -113,4 +113,14 @@
<string name="unregisterForModemStateChanged">unregisterForModemStateChanged</string>
<string name="requestSatelliteAccessConfigurationForCurrentLocation">requestSatelliteAccessConfigurationForCurrentLocation</string>
+
+ <string name="requestSatelliteEnable">requestSatelliteEnable</string>
+ <string name="requestSatelliteModemState">requestSatelliteModemState</string>
+ <string name="startSendingNtnSignalStrength">startSendingSignalStrength</string>
+ <string name="stopSendingNtnSignalStrength">stopSendingSignalStrength</string>
+ <string name="PssActivity">Pss Activity</string>
+ <string name="DemoMode">Demo Mode</string>
+ <string name="Enable">Enable</string>
+ <string name="SelectIterations">Select number of iterations</string>
+ <string name="SelectCarrier">Select a carrier</string>
</resources>
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/PssActivity.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/PssActivity.java
new file mode 100644
index 0000000..84b49bd
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/PssActivity.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2025 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.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.telephony.IIntegerConsumer;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.satellite.stub.ISatellite;
+import android.telephony.satellite.stub.ISatelliteListener;
+import android.telephony.satellite.stub.NtnSignalStrength;
+import android.telephony.satellite.stub.PointingInfo;
+import android.telephony.satellite.stub.SatelliteCapabilities;
+import android.telephony.satellite.stub.SatelliteDatagram;
+import android.telephony.satellite.stub.SatelliteModemEnableRequestAttributes;
+import android.telephony.satellite.stub.SatelliteSubscriptionInfo;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.NumberPicker;
+import android.widget.Spinner;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Activity to bind PSS. */
+public class PssActivity extends Activity implements AdapterView.OnItemSelectedListener {
+
+ private static final String TAG = "PssActivity";
+ private static final String SATELLITE_ACTION = "android.telephony.satellite.SatelliteService";
+ private static final String PSS_PACKAGE = "com.google.android.satellite";
+
+ private ISatellite mSatelliteService;
+ private SubscriptionManager mSubscriptionManager;
+ private boolean mIsDemoModeSwitchEnabled = false;
+ private boolean mIsEnabledSwitchEnabled = false;
+ private int mIterations = 1;
+ private int mSuccessCount = 0;
+ private int mCurrentIteration = 0;
+ private long mTotalTime = 0;
+ private long mStartTime;
+ private String mIccId = "";
+ private Runnable mTask;
+ private TextView mLogTextView;
+ private final ArrayDeque<String> mLogQueue = new ArrayDeque();
+ private static final int QUEUE_SIZE = 10;
+
+ private List<Carrier> mSubList;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_Pss);
+ setTitle(R.string.PssActivity);
+
+ initViews();
+ getSubscriptionInfo();
+ bindPss();
+ }
+
+ private void getSubscriptionInfo() {
+ if (mSubscriptionManager == null) {
+ mSubscriptionManager =
+ (SubscriptionManager)
+ getBaseContext()
+ .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ }
+ mSubList = new ArrayList();
+
+ for (SubscriptionInfo subInfo : mSubscriptionManager.getAllSubscriptionInfoList()) {
+ logi("iccId: " + subInfo.getIccId() + " carrier name " + subInfo.getCarrierName());
+ boolean isNtn = subInfo.isOnlyNonTerrestrialNetwork();
+ mSubList.add(
+ new Carrier(subInfo.getIccId(), subInfo.getCarrierName().toString(), isNtn));
+
+ if (isNtn) {
+ mIccId = subInfo.getIccId();
+ logi("NTN iccId: " + mIccId);
+ }
+ }
+ Spinner mSpinner = findViewById(R.id.spinner);
+ ArrayAdapter<Carrier> adapter =
+ new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, mSubList);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mSpinner.setAdapter(adapter);
+ mSpinner.setOnItemSelectedListener(this);
+ }
+
+ private void initViews() {
+ findViewById(R.id.requestSatelliteEnable).setOnClickListener(this::requestSatelliteEnable);
+
+ mLogTextView = findViewById(R.id.logView);
+ Switch demoSwitch = findViewById(R.id.DemoModeSwitch);
+ demoSwitch.setOnCheckedChangeListener(
+ (buttonView, isChecked) -> mIsDemoModeSwitchEnabled = isChecked);
+
+ Switch enableSwitch = findViewById(R.id.EnableSwitch);
+ enableSwitch.setOnCheckedChangeListener(
+ (buttonView, isChecked) -> mIsEnabledSwitchEnabled = isChecked);
+
+ NumberPicker numberPicker = findViewById(R.id.numberPicker);
+ numberPicker.setMinValue(1);
+ numberPicker.setMaxValue(100);
+ numberPicker.setValue(1);
+ numberPicker.setOnValueChangedListener((picker, oldVal, newVal) -> mIterations = newVal);
+ }
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ Carrier selectedItem = mSubList.get(position);
+ mIccId = selectedItem.getIccId();
+ Toast.makeText(this, "Selected: " + selectedItem.getName(), Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Optional: Handle the case where nothing is selected
+ }
+
+ private void requestSatelliteEnable(View v) {
+ mCurrentIteration = 1;
+ mTotalTime = 0;
+ mSuccessCount = 0;
+ mStartTime = System.currentTimeMillis();
+ boolean isSwitchEnabled = mIsEnabledSwitchEnabled;
+ mTask =
+ () -> {
+ IIntegerConsumer callback =
+ new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ long duration = System.currentTimeMillis() - mStartTime;
+ mStartTime = System.currentTimeMillis();
+ logi(
+ "Request type: "
+ + (mIsEnabledSwitchEnabled
+ ? "Enable"
+ : "Disable")
+ + " mCurrentIteration: "
+ + mCurrentIteration
+ + " requestSatelliteEnable error: "
+ + result
+ + " duration: "
+ + duration);
+ mIsEnabledSwitchEnabled = !mIsEnabledSwitchEnabled;
+ if (result == 0) {
+ mTotalTime += duration;
+ mSuccessCount += 1;
+ }
+ if (mCurrentIteration < mIterations) {
+ mCurrentIteration++;
+ mTask.run();
+ } else {
+ logi(
+ "Pass: "
+ + mSuccessCount
+ + "/"
+ + mIterations
+ + " Average duration in ms : "
+ + mTotalTime / mSuccessCount);
+ mIsEnabledSwitchEnabled = isSwitchEnabled;
+ }
+ }
+ };
+ try {
+ mSatelliteService.requestSatelliteEnabled(
+ createModemEnableRequest(), callback);
+ } catch (Exception e) {
+ logi("requestSatelliteEnable: " + e);
+ }
+ };
+ mTask.run();
+ }
+
+ private SatelliteModemEnableRequestAttributes createModemEnableRequest() {
+ String apn = "pixel.ntn";
+ SatelliteModemEnableRequestAttributes attributes =
+ new SatelliteModemEnableRequestAttributes();
+ attributes.isEnabled = mIsEnabledSwitchEnabled;
+ attributes.isDemoMode = mIsDemoModeSwitchEnabled;
+ SatelliteSubscriptionInfo info = new SatelliteSubscriptionInfo();
+ info.iccId = mIccId;
+ info.niddApn = apn;
+ attributes.satelliteSubscriptionInfo = info;
+ return attributes;
+ }
+
+ private final class PssConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ logi("onServiceConnected Service: " + name);
+ mSatelliteService = ISatellite.Stub.asInterface(service);
+ try {
+ mSatelliteService.setSatelliteListener(mSatelliteListener);
+ } catch (Exception e) {
+ Log.e(TAG, "onServiceConnected: setListener error: ", e);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ logi("onServiceDisconnected Service: " + name);
+ }
+ }
+
+ private void bindPss() {
+ logi("Binding PSS...");
+ Intent intent = new Intent(SATELLITE_ACTION);
+ intent.setPackage(PSS_PACKAGE);
+ PssConnection pssConnection = new PssConnection();
+
+ try {
+ getBaseContext()
+ .bindService(
+ intent,
+ pssConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
+ } catch (SecurityException exception) {
+ logi("BindService failed " + exception);
+ }
+ }
+
+ private final ISatelliteListener mSatelliteListener =
+ new ISatelliteListener.Stub() {
+ @Override
+ public void onSatelliteDatagramReceived(
+ SatelliteDatagram datagram, int pendingCount) {
+ logi("onSatelliteDatagramReceived: " + Arrays.toString(datagram.data));
+ }
+
+ @Override
+ public void onPendingDatagrams() {
+ logi("onPendingDatagrams: ");
+ }
+
+ @Override
+ public void onSatellitePositionChanged(PointingInfo pointingInfo) {
+ logi(
+ "onSatellitePositionChanged: Azimuth= "
+ + pointingInfo.satelliteAzimuth
+ + " Elevation "
+ + pointingInfo.satelliteElevation);
+ }
+
+ @Override
+ public void onSatelliteModemStateChanged(int state) {
+ logi("onSatelliteModemStateChanged: " + state);
+ }
+
+ @Override
+ public void onNtnSignalStrengthChanged(NtnSignalStrength ntnSignalStrength) {
+ logi("onNtnSignalStrengthChanged: " + ntnSignalStrength.signalStrengthLevel);
+ }
+
+ @Override
+ public void onSatelliteCapabilitiesChanged(SatelliteCapabilities capabilities) {
+ logi(
+ "onSatelliteCapabilitiesChanged: "
+ + Arrays.toString(capabilities.supportedRadioTechnologies));
+ }
+
+ @Override
+ public void onSatelliteSupportedStateChanged(boolean supported) {
+ logi("onSatelliteSupportedStateChanged: " + supported);
+ }
+
+ @Override
+ public void onRegistrationFailure(int causeCode) {
+ logi("onRegistrationFailure: " + causeCode);
+ }
+
+ @Override
+ public void onTerrestrialNetworkAvailableChanged(boolean isAvailable) {
+ logi("onTerrestrialNetworkAvailableChanged: " + isAvailable);
+ }
+ };
+
+ private void logi(String str) {
+ Log.i(TAG, str);
+ if (mLogQueue.size() >= QUEUE_SIZE) {
+ mLogQueue.pollLast();
+ }
+ mLogQueue.offerFirst(str);
+ updateTextView();
+ }
+
+ private void updateTextView() {
+ StringBuilder sb = new StringBuilder();
+ for (String s : mLogQueue) {
+ sb.append(s).append("\n");
+ }
+ runOnUiThread(() -> mLogTextView.setText(sb.toString()));
+ }
+
+ private static class Carrier {
+ private final String mIccId;
+ private final String mName;
+ private final boolean mIsNtn;
+
+ Carrier(String iccId, String name, boolean isNtn) {
+ this.mIccId = iccId;
+ this.mName = name;
+ this.mIsNtn = isNtn;
+ }
+
+ public boolean isNtn() {
+ return mIsNtn;
+ }
+
+ public String getIccId() {
+ return mIccId;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public String toString() {
+ return mName + (isNtn() ? " (NTN)" : "");
+ }
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteTestApp.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteTestApp.java
index 911e179..e205f6e 100644
--- a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteTestApp.java
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteTestApp.java
@@ -139,6 +139,13 @@
}
});
});
+ findViewById(R.id.PssActivity).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SatelliteTestApp.this, PssActivity.class);
+ startActivity(intent);
+ }
+ });
}
@Override