1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.connectivity;
18 
19 import android.Manifest;
20 import android.annotation.NonNull;
21 import android.app.role.OnRoleHoldersChangedListener;
22 import android.app.role.RoleManager;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageManager;
29 import android.os.Handler;
30 import android.os.Process;
31 import android.os.UserHandle;
32 import android.os.UserManager;
33 import android.util.ArraySet;
34 import android.util.Log;
35 import android.util.SparseArray;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 
39 import java.util.List;
40 import java.util.Set;
41 import java.util.concurrent.Executor;
42 import java.util.function.Consumer;
43 
44 /**
45  * Tracks the uid of all the default messaging application which are role_sms role and
46  * satellite_communication permission complaint and requests ConnectivityService to create multi
47  * layer request with satellite internet access support for the default message application.
48  * @hide
49  */
50 public class SatelliteAccessController {
51     private static final String TAG = SatelliteAccessController.class.getSimpleName();
52     private final Context mContext;
53     private final Dependencies mDeps;
54     private final DefaultMessageRoleListener mDefaultMessageRoleListener;
55     private final Consumer<Set<Integer>> mCallback;
56     private final Handler mConnectivityServiceHandler;
57 
58     // At this sparseArray, Key is userId and values are uids of SMS apps that are allowed
59     // to use satellite network as fallback.
60     private final SparseArray<Set<Integer>> mAllUsersSatelliteNetworkFallbackUidCache =
61             new SparseArray<>();
62 
63     /**
64      *  Monitor {@link android.app.role.OnRoleHoldersChangedListener#onRoleHoldersChanged(String,
65      *  UserHandle)},
66      *
67      */
68     private final class DefaultMessageRoleListener
69             implements OnRoleHoldersChangedListener {
70         @Override
onRoleHoldersChanged(String role, UserHandle userHandle)71         public void onRoleHoldersChanged(String role, UserHandle userHandle) {
72             if (RoleManager.ROLE_SMS.equals(role)) {
73                 Log.i(TAG, "ROLE_SMS Change detected ");
74                 onRoleSmsChanged(userHandle);
75             }
76         }
77 
register()78         public void register() {
79             try {
80                 mDeps.addOnRoleHoldersChangedListenerAsUser(
81                         mConnectivityServiceHandler::post, this, UserHandle.ALL);
82             } catch (RuntimeException e) {
83                 Log.wtf(TAG, "Could not register satellite controller listener due to " + e);
84             }
85         }
86     }
87 
SatelliteAccessController(@onNull final Context c, Consumer<Set<Integer>> callback, @NonNull final Handler connectivityServiceInternalHandler)88     public SatelliteAccessController(@NonNull final Context c,
89             Consumer<Set<Integer>> callback,
90             @NonNull final Handler connectivityServiceInternalHandler) {
91         this(c, new Dependencies(c), callback, connectivityServiceInternalHandler);
92     }
93 
94     public static class Dependencies {
95         private final RoleManager mRoleManager;
96 
Dependencies(Context context)97         private Dependencies(Context context) {
98             mRoleManager = context.getSystemService(RoleManager.class);
99         }
100 
101         /** See {@link RoleManager#getRoleHoldersAsUser(String, UserHandle)} */
getRoleHoldersAsUser(String roleName, UserHandle userHandle)102         public List<String> getRoleHoldersAsUser(String roleName, UserHandle userHandle) {
103             return mRoleManager.getRoleHoldersAsUser(roleName, userHandle);
104         }
105 
106         /** See {@link RoleManager#addOnRoleHoldersChangedListenerAsUser} */
addOnRoleHoldersChangedListenerAsUser(@onNull Executor executor, @NonNull OnRoleHoldersChangedListener listener, UserHandle user)107         public void addOnRoleHoldersChangedListenerAsUser(@NonNull Executor executor,
108                 @NonNull OnRoleHoldersChangedListener listener, UserHandle user) {
109             mRoleManager.addOnRoleHoldersChangedListenerAsUser(executor, listener, user);
110         }
111     }
112 
113     @VisibleForTesting
SatelliteAccessController(@onNull final Context c, @NonNull final Dependencies deps, Consumer<Set<Integer>> callback, @NonNull final Handler connectivityServiceInternalHandler)114     SatelliteAccessController(@NonNull final Context c, @NonNull final Dependencies deps,
115             Consumer<Set<Integer>> callback,
116             @NonNull final Handler connectivityServiceInternalHandler) {
117         mContext = c;
118         mDeps = deps;
119         mDefaultMessageRoleListener = new DefaultMessageRoleListener();
120         mCallback = callback;
121         mConnectivityServiceHandler = connectivityServiceInternalHandler;
122     }
123 
updateSatelliteNetworkFallbackUidListCache(List<String> packageNames, @NonNull UserHandle userHandle)124     private Set<Integer> updateSatelliteNetworkFallbackUidListCache(List<String> packageNames,
125             @NonNull UserHandle userHandle) {
126         Set<Integer> fallbackUids = new ArraySet<>();
127         PackageManager pm =
128                 mContext.createContextAsUser(userHandle, 0).getPackageManager();
129         if (pm != null) {
130             for (String packageName : packageNames) {
131                 // Check if SATELLITE_COMMUNICATION permission is enabled for default sms
132                 // application package before adding it part of satellite network fallback uid
133                 // cache list.
134                 if (isSatellitePermissionEnabled(pm, packageName)) {
135                     int uid = getUidForPackage(pm, packageName);
136                     if (uid != Process.INVALID_UID) {
137                         fallbackUids.add(uid);
138                     }
139                 }
140             }
141         } else {
142             Log.wtf(TAG, "package manager found null");
143         }
144         return fallbackUids;
145     }
146 
147     //Check if satellite communication is enabled for the package
isSatellitePermissionEnabled(PackageManager packageManager, String packageName)148     private boolean isSatellitePermissionEnabled(PackageManager packageManager,
149             String packageName) {
150         return packageManager.checkPermission(
151                 Manifest.permission.SATELLITE_COMMUNICATION, packageName)
152                 == PackageManager.PERMISSION_GRANTED;
153     }
154 
getUidForPackage(PackageManager packageManager, String pkgName)155     private int getUidForPackage(PackageManager packageManager, String pkgName) {
156         if (pkgName == null) {
157             return Process.INVALID_UID;
158         }
159         try {
160             ApplicationInfo applicationInfo = packageManager.getApplicationInfo(pkgName, 0);
161             return applicationInfo.uid;
162         } catch (PackageManager.NameNotFoundException exception) {
163             Log.e(TAG, "Unable to find uid for package: " + pkgName);
164         }
165         return Process.INVALID_UID;
166     }
167 
168     // on Role sms change triggered by OnRoleHoldersChangedListener()
onRoleSmsChanged(@onNull UserHandle userHandle)169     private void onRoleSmsChanged(@NonNull UserHandle userHandle) {
170         int userId = userHandle.getIdentifier();
171         if (userId == Process.INVALID_UID) {
172             Log.wtf(TAG, "Invalid User Id");
173             return;
174         }
175 
176         //Returns empty list if no package exists
177         final List<String> packageNames =
178                 mDeps.getRoleHoldersAsUser(RoleManager.ROLE_SMS, userHandle);
179 
180         // Store previous satellite fallback uid available
181         final Set<Integer> prevUidsForUser =
182                 mAllUsersSatelliteNetworkFallbackUidCache.get(userId, new ArraySet<>());
183 
184         Log.i(TAG, "currentUser : role_sms_packages: " + userId + " : " + packageNames);
185         final Set<Integer> newUidsForUser =
186                 updateSatelliteNetworkFallbackUidListCache(packageNames, userHandle);
187         Log.i(TAG, "satellite_fallback_uid: " + newUidsForUser);
188 
189         // on Role change, update the multilayer request at ConnectivityService with updated
190         // satellite network fallback uid cache list of multiple users as applicable
191         if (newUidsForUser.equals(prevUidsForUser)) {
192             return;
193         }
194 
195         mAllUsersSatelliteNetworkFallbackUidCache.put(userId, newUidsForUser);
196 
197         // Update all users fallback cache for user, send cs fallback to update ML request
198         reportSatelliteNetworkFallbackUids();
199     }
200 
reportSatelliteNetworkFallbackUids()201     private void reportSatelliteNetworkFallbackUids() {
202         // Merge all uids of multiple users available
203         Set<Integer> mergedSatelliteNetworkFallbackUidCache = new ArraySet<>();
204         for (int i = 0; i < mAllUsersSatelliteNetworkFallbackUidCache.size(); i++) {
205             mergedSatelliteNetworkFallbackUidCache.addAll(
206                     mAllUsersSatelliteNetworkFallbackUidCache.valueAt(i));
207         }
208         Log.i(TAG, "merged uid list for multi layer request : "
209                 + mergedSatelliteNetworkFallbackUidCache);
210 
211         // trigger multiple layer request for satellite network fallback of multi user uids
212         mCallback.accept(mergedSatelliteNetworkFallbackUidCache);
213     }
214 
start()215     public void start() {
216         mConnectivityServiceHandler.post(this::updateAllUserRoleSmsUids);
217 
218         // register sms OnRoleHoldersChangedListener
219         mDefaultMessageRoleListener.register();
220 
221         // Monitor for User removal intent, to update satellite fallback uids.
222         IntentFilter userRemovedFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
223         mContext.registerReceiver(new BroadcastReceiver() {
224             @Override
225             public void onReceive(Context context, Intent intent) {
226                 final String action = intent.getAction();
227                 if (Intent.ACTION_USER_REMOVED.equals(action)) {
228                     final UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
229                     if (userHandle == null) return;
230                     updateSatelliteFallbackUidListOnUserRemoval(userHandle.getIdentifier());
231                 } else {
232                     Log.wtf(TAG, "received unexpected intent: " + action);
233                 }
234             }
235         }, userRemovedFilter, null, mConnectivityServiceHandler);
236 
237     }
238 
updateAllUserRoleSmsUids()239     private void updateAllUserRoleSmsUids() {
240         UserManager userManager = mContext.getSystemService(UserManager.class);
241         // get existing user handles of available users
242         List<UserHandle> existingUsers = userManager.getUserHandles(true /*excludeDying*/);
243 
244         // Iterate through the user handles and obtain their uids with role sms and satellite
245         // communication permission
246         Log.i(TAG, "existing users: " + existingUsers);
247         for (UserHandle userHandle : existingUsers) {
248             onRoleSmsChanged(userHandle);
249         }
250     }
251 
updateSatelliteFallbackUidListOnUserRemoval(int userIdRemoved)252     private void updateSatelliteFallbackUidListOnUserRemoval(int userIdRemoved) {
253         Log.i(TAG, "user id removed:" + userIdRemoved);
254         if (mAllUsersSatelliteNetworkFallbackUidCache.contains(userIdRemoved)) {
255             mAllUsersSatelliteNetworkFallbackUidCache.remove(userIdRemoved);
256             reportSatelliteNetworkFallbackUids();
257         }
258     }
259 }
260