diff --git a/res/values/strings.xml b/res/values/strings.xml
index 94d6384e732..694700c3aff 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -12607,6 +12607,12 @@
satellite messaging
Use of data is included with your account
+
+ Supported apps on your phone
+
+ see all apps
+
+ Supported apps on your phone
Access Point Names
diff --git a/res/xml/satellite_setting.xml b/res/xml/satellite_setting.xml
index 60fe5bfb335..21743f3f9d5 100644
--- a/res/xml/satellite_setting.xml
+++ b/res/xml/satellite_setting.xml
@@ -62,6 +62,23 @@
android:icon="@drawable/ic_android_satellite_24px"/>
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/android/settings/network/telephony/SatelliteAppListCategoryController.java b/src/com/android/settings/network/telephony/SatelliteAppListCategoryController.java
new file mode 100644
index 00000000000..4afa7f245ff
--- /dev/null
+++ b/src/com/android/settings/network/telephony/SatelliteAppListCategoryController.java
@@ -0,0 +1,98 @@
+/*
+ * 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.settings.network.telephony;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.telephony.flags.Flags;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.network.SatelliteRepository;
+import com.android.settingslib.Utils;
+
+import java.util.List;
+
+/** A controller to show some of apps info which supported on Satellite service. */
+public class SatelliteAppListCategoryController extends BasePreferenceController {
+ private static final String TAG = "SatelliteAppListCategoryController";
+ @VisibleForTesting
+ static final int MAXIMUM_OF_PREFERENCE_AMOUNT = 3;
+
+ private List mPackageNameList;
+
+ public SatelliteAppListCategoryController(
+ @NonNull Context context,
+ @NonNull String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ /** Initialize the necessary applications' data*/
+ public void init() {
+ SatelliteRepository satelliteRepository = new SatelliteRepository(mContext);
+ init(satelliteRepository);
+ }
+
+ @VisibleForTesting
+ void init(@NonNull SatelliteRepository satelliteRepository) {
+ mPackageNameList = satelliteRepository.getSatelliteDataOptimizedApps();
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ PreferenceCategory preferenceCategory = screen.findPreference(getPreferenceKey());
+ for (int i = 0; i < mPackageNameList.size() && i < MAXIMUM_OF_PREFERENCE_AMOUNT; i++) {
+ String packageName = mPackageNameList.get(i);
+ ApplicationInfo appInfo = getApplicationInfo(mContext, packageName);
+ if (appInfo != null) {
+ Drawable icon = Utils.getBadgedIcon(mContext, appInfo);
+ CharSequence name = appInfo.loadLabel(mContext.getPackageManager());
+ Preference pref = new Preference(mContext);
+ pref.setIcon(icon);
+ pref.setTitle(name);
+ preferenceCategory.addPreference(pref);
+ }
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (!Flags.satellite25q4Apis()) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+ return mPackageNameList.isEmpty()
+ ? CONDITIONALLY_UNAVAILABLE
+ : AVAILABLE;
+ }
+
+ static ApplicationInfo getApplicationInfo(Context context, String packageName) {
+ try {
+ PackageManager pm = context.getPackageManager();
+ return pm.getApplicationInfoAsUser(packageName, /* flags= */ 0, context.getUserId());
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/settings/network/telephony/SatelliteAppListFragment.java b/src/com/android/settings/network/telephony/SatelliteAppListFragment.java
new file mode 100644
index 00000000000..97f70bb2652
--- /dev/null
+++ b/src/com/android/settings/network/telephony/SatelliteAppListFragment.java
@@ -0,0 +1,126 @@
+/*
+ * 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.settings.network.telephony;
+
+import static com.android.settings.network.telephony.SatelliteAppListCategoryController.getApplicationInfo;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.graphics.drawable.Drawable;
+import android.os.UserManager;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.dashboard.RestrictedDashboardFragment;
+import com.android.settings.network.SatelliteRepository;
+import com.android.settingslib.Utils;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Shows all applications which support satellite service. */
+public class SatelliteAppListFragment extends RestrictedDashboardFragment {
+ public SatelliteAppListFragment() {
+ super(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ use(SatelliteAppListPreferenceController.class).init();
+ }
+
+ @Override
+ protected List createPreferenceControllers(Context context) {
+ SatelliteAppListPreferenceController satelliteAppListPreferenceController =
+ new SatelliteAppListPreferenceController(getContext());
+ return List.of(satelliteAppListPreferenceController);
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.satellite_settings_apps_list;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return "SatelliteAppListFragment";
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.SATELLITE_APPS_LIST;
+ }
+
+ @VisibleForTesting
+ static class SatelliteAppListPreferenceController extends BasePreferenceController {
+ private static final String TAG = "SatelliteAppListPreferenceController";
+ private static final String KEY = "key_satellite_app_list";
+
+ private List mApplicationInfoList = List.of();
+
+ SatelliteAppListPreferenceController(@NonNull Context context) {
+ super(context, KEY);
+ }
+
+ public void init() {
+ SatelliteRepository satelliteRepository = new SatelliteRepository(mContext);
+ init(satelliteRepository);
+ }
+
+ void init(@NonNull SatelliteRepository satelliteRepository) {
+ mApplicationInfoList =
+ satelliteRepository.getSatelliteDataOptimizedApps()
+ .stream()
+ .map(name -> getApplicationInfo(mContext, name))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ if (mApplicationInfoList.isEmpty()) {
+ return;
+ }
+ mApplicationInfoList.forEach(appInfo -> {
+ if (appInfo != null) {
+ Log.i(TAG, "Add preference to UI : " + appInfo.packageName);
+ Drawable icon = Utils.getBadgedIcon(mContext, appInfo);
+ CharSequence name = appInfo.loadLabel(mContext.getPackageManager());
+ Preference pref = new Preference(mContext);
+ pref.setIcon(icon);
+ pref.setTitle(name);
+ screen.addPreference(pref);
+ }
+ });
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+ }
+}
diff --git a/src/com/android/settings/network/telephony/SatelliteSetting.java b/src/com/android/settings/network/telephony/SatelliteSetting.java
index d4bd212d153..bc10abb212d 100644
--- a/src/com/android/settings/network/telephony/SatelliteSetting.java
+++ b/src/com/android/settings/network/telephony/SatelliteSetting.java
@@ -26,6 +26,7 @@ import static android.telephony.CarrierConfigManager.KEY_SATELLITE_INFORMATION_R
import android.app.Activity;
import android.app.settings.SettingsEnums;
+import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
@@ -92,6 +93,12 @@ public class SatelliteSetting extends RestrictedDashboardFragment {
return SettingsEnums.SATELLITE_SETTING;
}
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ use(SatelliteAppListCategoryController.class).init();
+ }
+
@Override
public void onCreate(@NonNull Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
diff --git a/tests/unit/src/com/android/settings/network/telephony/SatelliteAppListCategoryControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/SatelliteAppListCategoryControllerTest.java
new file mode 100644
index 00000000000..74797ae69d3
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/telephony/SatelliteAppListCategoryControllerTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.settings.network.telephony;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+import static com.android.settings.network.telephony.SatelliteAppListCategoryController.MAXIMUM_OF_PREFERENCE_AMOUNT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Looper;
+import android.platform.test.annotations.EnableFlags;
+
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.telephony.flags.Flags;
+import com.android.settings.network.SatelliteRepository;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Collections;
+import java.util.List;
+
+public class SatelliteAppListCategoryControllerTest {
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ private static final List PACKAGE_NAMES = List.of("com.android.settings",
+ "com.android.apps.messaging", "com.android.dialer", "com.android.systemui");
+ private static final String KEY = "SatelliteAppListCategoryControllerTest";
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private SatelliteRepository mRepository;
+
+ private Context mContext;
+ private SatelliteAppListCategoryController mController;
+
+
+ @Before
+ public void setUp() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SATELLITE_25Q4_APIS)
+ public void displayPreference_has4SatSupportedApps_showMaxPreference() throws Exception {
+ when(mRepository.getSatelliteDataOptimizedApps()).thenReturn(PACKAGE_NAMES);
+ when(mPackageManager.getApplicationInfoAsUser(any(), anyInt(), anyInt())).thenReturn(
+ new ApplicationInfo());
+ mController = new SatelliteAppListCategoryController(mContext, KEY);
+ mController.init(mRepository);
+ PreferenceManager preferenceManager = new PreferenceManager(mContext);
+ PreferenceScreen preferenceScreen = preferenceManager.createPreferenceScreen(mContext);
+ PreferenceCategory category = new PreferenceCategory(mContext);
+ category.setKey(mController.getPreferenceKey());
+ preferenceScreen.addPreference(category);
+
+ mController.displayPreference(preferenceScreen);
+
+ assertThat(category.getPreferenceCount() == MAXIMUM_OF_PREFERENCE_AMOUNT).isTrue();
+ }
+
+
+ @Test
+ @EnableFlags(Flags.FLAG_SATELLITE_25Q4_APIS)
+ public void getAvailabilityStatus_hasSatSupportedApps_returnAvailable() {
+ when(mRepository.getSatelliteDataOptimizedApps()).thenReturn(PACKAGE_NAMES);
+ mController = new SatelliteAppListCategoryController(mContext, KEY);
+ mController.init(mRepository);
+
+ int result = mController.getAvailabilityStatus();
+
+ assertThat(result).isEqualTo(AVAILABLE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SATELLITE_25Q4_APIS)
+ public void getAvailabilityStatus_noSatSupportedApps_returnUnavailable() {
+ List packageNames = Collections.emptyList();
+ when(mRepository.getSatelliteDataOptimizedApps()).thenReturn(packageNames);
+ mController = new SatelliteAppListCategoryController(mContext, KEY);
+ mController.init(mRepository);
+
+ int result = mController.getAvailabilityStatus();
+
+ assertThat(result).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
+}
diff --git a/tests/unit/src/com/android/settings/network/telephony/SatelliteAppListFragmentTest.java b/tests/unit/src/com/android/settings/network/telephony/SatelliteAppListFragmentTest.java
new file mode 100644
index 00000000000..ba91d179f99
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/telephony/SatelliteAppListFragmentTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.settings.network.telephony;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Looper;
+import android.platform.test.annotations.EnableFlags;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.telephony.flags.Flags;
+import com.android.settings.network.SatelliteRepository;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+public class SatelliteAppListFragmentTest {
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ private static final List PACKAGE_NAMES = List.of(
+ "com.android.settings",
+ "com.android.apps.messaging",
+ "com.android.dialer",
+ "com.android.systemui"
+ );
+ private static final String KEY = "SatelliteAppListPreferenceController";
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private SatelliteRepository mRepository;
+
+ private Context mContext;
+ private SatelliteAppListFragment.SatelliteAppListPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SATELLITE_25Q4_APIS)
+ public void displayPreference_has4SatSupportedApps_showMaxPreference() throws Exception {
+ when(mRepository.getSatelliteDataOptimizedApps()).thenReturn(PACKAGE_NAMES);
+ when(mPackageManager.getApplicationInfoAsUser(any(), anyInt(), anyInt())).thenReturn(
+ new ApplicationInfo());
+ mController = new SatelliteAppListFragment.SatelliteAppListPreferenceController(mContext);
+ mController.init(mRepository);
+ PreferenceManager preferenceManager = new PreferenceManager(mContext);
+ PreferenceScreen preferenceScreen = preferenceManager.createPreferenceScreen(mContext);
+
+ mController.displayPreference(preferenceScreen);
+
+ assertThat(preferenceScreen.getPreferenceCount() == PACKAGE_NAMES.size()).isTrue();
+ }
+}