Add SatelliteAccessController class to support messaging on Satellite Internet

- create SatelliteAccessController class to identify role sms and
  satellite communication enabled uid's, to keep track of role changes
  through OnRoleHoldersChangedListener and to request to create
  multi layer request to ConnectivityService.
- unit test coverage

Bug: 320514105
Test: m and atest FrameworksNetTests
Change-Id: I95086f7a4fd6fe1261f432e605d792204ff5310b
diff --git a/service/Android.bp b/service/Android.bp
index 1af9070..403ba7d 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -179,6 +179,8 @@
         "unsupportedappusage",
         "ServiceConnectivityResources",
         "framework-statsd",
+        "framework-permission",
+        "framework-permission-s",
     ],
     static_libs: [
         // Do not add libs here if they are already included
@@ -265,6 +267,8 @@
         "framework-tethering.impl",
         "framework-wifi",
         "libprotobuf-java-nano",
+        "framework-permission",
+        "framework-permission-s",
     ],
     jarjar_rules: ":connectivity-jarjar-rules",
     apex_available: [
diff --git a/service/src/com/android/server/connectivity/SatelliteAccessController.java b/service/src/com/android/server/connectivity/SatelliteAccessController.java
new file mode 100644
index 0000000..0968aff
--- /dev/null
+++ b/service/src/com/android/server/connectivity/SatelliteAccessController.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2024 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.server.connectivity;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Tracks the uid of all the default messaging application which are role_sms role and
+ * satellite_communication permission complaint and requests ConnectivityService to create multi
+ * layer request with satellite internet access support for the default message application.
+ * @hide
+ */
+public class SatelliteAccessController {
+    private static final String TAG = SatelliteAccessController.class.getSimpleName();
+    private final PackageManager mPackageManager;
+    private final Dependencies mDeps;
+    private final DefaultMessageRoleListener mDefaultMessageRoleListener;
+    private final Consumer<Set<Integer>> mCallback;
+    private final Set<Integer> mSatelliteNetworkPreferredUidCache = new ArraySet<>();
+    private final Handler mConnectivityServiceHandler;
+
+    /**
+     *  Monitor {@link android.app.role.OnRoleHoldersChangedListener#onRoleHoldersChanged(String,
+     *  UserHandle)},
+     *
+     */
+    private final class DefaultMessageRoleListener
+            implements OnRoleHoldersChangedListener {
+        @Override
+        public void onRoleHoldersChanged(String role, UserHandle user) {
+            if (RoleManager.ROLE_SMS.equals(role)) {
+                Log.i(TAG, "ROLE_SMS Change detected ");
+                onRoleSmsChanged();
+            }
+        }
+
+        public void register() {
+            try {
+                mDeps.addOnRoleHoldersChangedListenerAsUser(
+                        mConnectivityServiceHandler::post, this, UserHandle.ALL);
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Could not register satellite controller listener due to " + e);
+            }
+        }
+    }
+
+    public SatelliteAccessController(@NonNull final Context c,
+            Consumer<Set<Integer>> callback,
+            @NonNull final Handler connectivityServiceInternalHandler) {
+        this(c, new Dependencies(c), callback, connectivityServiceInternalHandler);
+    }
+
+    public static class Dependencies {
+        private final RoleManager mRoleManager;
+
+        private Dependencies(Context context) {
+            mRoleManager = context.getSystemService(RoleManager.class);
+        }
+
+        /** See {@link RoleManager#getRoleHolders(String)} */
+        public List<String> getRoleHolders(String roleName) {
+            return mRoleManager.getRoleHolders(roleName);
+        }
+
+        /** See {@link RoleManager#addOnRoleHoldersChangedListenerAsUser} */
+        public void addOnRoleHoldersChangedListenerAsUser(@NonNull Executor executor,
+                @NonNull OnRoleHoldersChangedListener listener, UserHandle user) {
+            mRoleManager.addOnRoleHoldersChangedListenerAsUser(executor, listener, user);
+        }
+    }
+
+    @VisibleForTesting
+    SatelliteAccessController(@NonNull final Context c, @NonNull final Dependencies deps,
+            Consumer<Set<Integer>> callback,
+            @NonNull final Handler connectivityServiceInternalHandler) {
+        mDeps = deps;
+        mPackageManager = c.getPackageManager();
+        mDefaultMessageRoleListener = new DefaultMessageRoleListener();
+        mCallback = callback;
+        mConnectivityServiceHandler = connectivityServiceInternalHandler;
+    }
+
+    private void updateSatelliteNetworkPreferredUidListCache(List<String> packageNames) {
+        for (String packageName : packageNames) {
+            // Check if SATELLITE_COMMUNICATION permission is enabled for default sms application
+            // package before adding it part of satellite network preferred uid cache list.
+            if (isSatellitePermissionEnabled(packageName)) {
+                mSatelliteNetworkPreferredUidCache.add(getUidForPackage(packageName));
+            }
+        }
+    }
+
+    //Check if satellite communication is enabled for the package
+    private boolean isSatellitePermissionEnabled(String packageName) {
+        if (mPackageManager != null) {
+            return mPackageManager.checkPermission(
+                    Manifest.permission.SATELLITE_COMMUNICATION, packageName)
+                    == PackageManager.PERMISSION_GRANTED;
+        }
+        return false;
+    }
+
+    private int getUidForPackage(String pkgName) {
+        if (pkgName == null) {
+            return Process.INVALID_UID;
+        }
+        try {
+            if (mPackageManager != null) {
+                ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pkgName, 0);
+                if (applicationInfo != null) {
+                    return applicationInfo.uid;
+                }
+            }
+        } catch (PackageManager.NameNotFoundException exception) {
+            Log.e(TAG, "Unable to find uid for package: " + pkgName);
+        }
+        return Process.INVALID_UID;
+    }
+
+    //on Role sms change triggered by OnRoleHoldersChangedListener()
+    private void onRoleSmsChanged() {
+        final List<String> packageNames = getRoleSmsChangedPackageName();
+
+        // Create a new Set
+        Set<Integer> previousSatellitePreferredUid = new ArraySet<>(
+                mSatelliteNetworkPreferredUidCache);
+
+        mSatelliteNetworkPreferredUidCache.clear();
+
+        if (packageNames != null) {
+            Log.i(TAG, "role_sms_packages: " + packageNames);
+            // On Role change listener, update the satellite network preferred uid cache list
+            updateSatelliteNetworkPreferredUidListCache(packageNames);
+            Log.i(TAG, "satellite_preferred_uid: " + mSatelliteNetworkPreferredUidCache);
+        } else {
+            Log.wtf(TAG, "package name was found null");
+        }
+
+        // on Role change, update the multilayer request at ConnectivityService with updated
+        // satellite network preferred uid cache list if changed or to revoke for previous default
+        // sms app
+        if (!mSatelliteNetworkPreferredUidCache.equals(previousSatellitePreferredUid)) {
+            Log.i(TAG, "update multi layer request");
+            mCallback.accept(mSatelliteNetworkPreferredUidCache);
+        }
+    }
+
+    private List<String> getRoleSmsChangedPackageName() {
+        try {
+            return mDeps.getRoleHolders(RoleManager.ROLE_SMS);
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Could not get package name at role sms change update due to: " + e);
+            return null;
+        }
+    }
+
+    /** Register OnRoleHoldersChangedListener */
+    public void start() {
+        mConnectivityServiceHandler.post(this::onRoleSmsChanged);
+        mDefaultMessageRoleListener.register();
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt b/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt
new file mode 100644
index 0000000..64a515a
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/SatelliteAccessControllerTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2024 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.server.connectivity
+
+import android.Manifest
+import android.app.role.OnRoleHoldersChangedListener
+import android.app.role.RoleManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Handler
+import android.os.UserHandle
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+private const val DEFAULT_MESSAGING_APP1 = "default_messaging_app_1"
+private const val DEFAULT_MESSAGING_APP2 = "default_messaging_app_2"
+private const val DEFAULT_MESSAGING_APP1_UID = 1234
+private const val DEFAULT_MESSAGING_APP2_UID = 5678
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class SatelliteAccessControllerTest {
+    private val context = mock(Context::class.java)
+    private val mPackageManager = mock(PackageManager::class.java)
+    private val mHandler = mock(Handler::class.java)
+    private val mRoleManager =
+        mock(SatelliteAccessController.Dependencies::class.java)
+    private val mCallback = mock(Consumer::class.java) as Consumer<Set<Int>>
+    private val mSatelliteAccessController by lazy {
+        SatelliteAccessController(context, mRoleManager, mCallback, mHandler)}
+    private var mRoleHolderChangedListener: OnRoleHoldersChangedListener? = null
+    @Before
+    @Throws(PackageManager.NameNotFoundException::class)
+    fun setup() {
+        doReturn(mPackageManager).`when`(context).packageManager
+        doReturn(PackageManager.PERMISSION_GRANTED)
+            .`when`(mPackageManager)
+            .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, DEFAULT_MESSAGING_APP1)
+        doReturn(PackageManager.PERMISSION_GRANTED)
+            .`when`(mPackageManager)
+            .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, DEFAULT_MESSAGING_APP2)
+
+        // Initialise default message application package1
+        val applicationInfo1 = ApplicationInfo()
+        applicationInfo1.uid = DEFAULT_MESSAGING_APP1_UID
+        doReturn(applicationInfo1)
+            .`when`(mPackageManager)
+            .getApplicationInfo(eq(DEFAULT_MESSAGING_APP1), anyInt())
+
+        // Initialise default message application package2
+        val applicationInfo2 = ApplicationInfo()
+        applicationInfo2.uid = DEFAULT_MESSAGING_APP2_UID
+        doReturn(applicationInfo2)
+            .`when`(mPackageManager)
+            .getApplicationInfo(eq(DEFAULT_MESSAGING_APP2), anyInt())
+
+        // Get registered listener using captor
+        val listenerCaptor = ArgumentCaptor.forClass(
+            OnRoleHoldersChangedListener::class.java
+        )
+        mSatelliteAccessController.start()
+        verify(mRoleManager).addOnRoleHoldersChangedListenerAsUser(
+            any(Executor::class.java), listenerCaptor.capture(), any(UserHandle::class.java))
+        mRoleHolderChangedListener = listenerCaptor.value
+    }
+
+    @Test
+    fun test_onRoleHoldersChanged_SatellitePreferredUid_Changed() {
+        doReturn(listOf<String>()).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+        val satelliteNetworkPreferredSet =
+            ArgumentCaptor.forClass(Set::class.java) as ArgumentCaptor<Set<Int>>
+        mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+        verify(mCallback, never()).accept(satelliteNetworkPreferredSet.capture())
+
+        // check DEFAULT_MESSAGING_APP1 is available as satellite network preferred uid
+        doReturn(listOf(DEFAULT_MESSAGING_APP1))
+            .`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+        mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+        verify(mCallback).accept(satelliteNetworkPreferredSet.capture())
+        var satelliteNetworkPreferredUids = satelliteNetworkPreferredSet.value
+        assertEquals(1, satelliteNetworkPreferredUids.size)
+        assertTrue(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP1_UID))
+        assertFalse(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP2_UID))
+
+        // check DEFAULT_MESSAGING_APP1 and DEFAULT_MESSAGING_APP2 is available
+        // as satellite network preferred uid
+        val dmas: MutableList<String> = ArrayList()
+        dmas.add(DEFAULT_MESSAGING_APP1)
+        dmas.add(DEFAULT_MESSAGING_APP2)
+        doReturn(dmas).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+        mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+        verify(mCallback, times(2))
+            .accept(satelliteNetworkPreferredSet.capture())
+        satelliteNetworkPreferredUids = satelliteNetworkPreferredSet.value
+        assertEquals(2, satelliteNetworkPreferredUids.size)
+        assertTrue(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP1_UID))
+        assertTrue(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP2_UID))
+
+        // check no uid is available as satellite network preferred uid
+        doReturn(listOf<String>()).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+        mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+        verify(mCallback, times(3))
+            .accept(satelliteNetworkPreferredSet.capture())
+        satelliteNetworkPreferredUids = satelliteNetworkPreferredSet.value
+        assertEquals(0, satelliteNetworkPreferredUids.size)
+        assertFalse(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP1_UID))
+        assertFalse(satelliteNetworkPreferredUids.contains(DEFAULT_MESSAGING_APP2_UID))
+
+        // No Change received at OnRoleSmsChanged, check callback not triggered
+        doReturn(listOf<String>()).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+        mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+        verify(mCallback, times(3))
+            .accept(satelliteNetworkPreferredSet.capture())
+    }
+
+    @Test
+    fun test_onRoleHoldersChanged_NoSatelliteCommunicationPermission() {
+        doReturn(listOf<Any>()).`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+        val satelliteNetworkPreferredSet =
+            ArgumentCaptor.forClass(Set::class.java) as ArgumentCaptor<Set<Int>>
+        mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+        verify(mCallback, never()).accept(satelliteNetworkPreferredSet.capture())
+
+        // check DEFAULT_MESSAGING_APP1 is not available as satellite network preferred uid
+        // since satellite communication permission not available.
+        doReturn(PackageManager.PERMISSION_DENIED)
+            .`when`(mPackageManager)
+            .checkPermission(Manifest.permission.SATELLITE_COMMUNICATION, DEFAULT_MESSAGING_APP1)
+        doReturn(listOf(DEFAULT_MESSAGING_APP1))
+            .`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+        mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.ALL)
+        verify(mCallback, never()).accept(satelliteNetworkPreferredSet.capture())
+    }
+
+    @Test
+    fun test_onRoleHoldersChanged_RoleSms_NotAvailable() {
+        doReturn(listOf(DEFAULT_MESSAGING_APP1))
+            .`when`(mRoleManager).getRoleHolders(RoleManager.ROLE_SMS)
+        val satelliteNetworkPreferredSet =
+            ArgumentCaptor.forClass(Set::class.java) as ArgumentCaptor<Set<Int>>
+        mRoleHolderChangedListener?.onRoleHoldersChanged(RoleManager.ROLE_BROWSER, UserHandle.ALL)
+        verify(mCallback, never()).accept(satelliteNetworkPreferredSet.capture())
+    }
+}