• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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;
18 
19 import static android.Manifest.permission.DUMP;
20 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
21 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
22 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
23 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
24 import static android.net.vcn.VcnGatewayConnectionConfig.ALLOWED_CAPABILITIES;
25 import static android.net.vcn.VcnManager.VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY;
26 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
27 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE;
28 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED;
29 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
30 import static android.telephony.SubscriptionManager.isValidSubscriptionId;
31 
32 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
33 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
34 
35 import static java.util.Objects.requireNonNull;
36 
37 import android.annotation.NonNull;
38 import android.annotation.Nullable;
39 import android.annotation.SuppressLint;
40 import android.annotation.TargetApi;
41 import android.app.AppOpsManager;
42 import android.content.BroadcastReceiver;
43 import android.content.Context;
44 import android.content.Intent;
45 import android.content.IntentFilter;
46 import android.content.pm.PackageManager;
47 import android.net.ConnectivityManager;
48 import android.net.LinkProperties;
49 import android.net.Network;
50 import android.net.NetworkCapabilities;
51 import android.net.NetworkRequest;
52 import android.net.vcn.IVcnManagementService;
53 import android.net.vcn.IVcnStatusCallback;
54 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
55 import android.net.vcn.VcnConfig;
56 import android.net.vcn.VcnManager.VcnErrorCode;
57 import android.net.vcn.VcnManager.VcnStatusCode;
58 import android.net.vcn.VcnUnderlyingNetworkPolicy;
59 import android.net.vcn.util.PersistableBundleUtils;
60 import android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
61 import android.net.wifi.WifiInfo;
62 import android.os.Binder;
63 import android.os.Build;
64 import android.os.Environment;
65 import android.os.Handler;
66 import android.os.HandlerThread;
67 import android.os.IBinder;
68 import android.os.Looper;
69 import android.os.ParcelUuid;
70 import android.os.PersistableBundle;
71 import android.os.Process;
72 import android.os.RemoteException;
73 import android.os.ServiceSpecificException;
74 import android.os.UserHandle;
75 import android.os.UserManager;
76 import android.telephony.SubscriptionInfo;
77 import android.telephony.SubscriptionManager;
78 import android.telephony.TelephonyManager;
79 import android.util.ArrayMap;
80 import android.util.ArraySet;
81 import android.util.IndentingPrintWriter;
82 import android.util.LocalLog;
83 import android.util.Log;
84 import android.util.Slog;
85 
86 import com.android.internal.annotations.GuardedBy;
87 import com.android.internal.annotations.VisibleForTesting;
88 import com.android.internal.annotations.VisibleForTesting.Visibility;
89 import com.android.net.module.util.BinderUtils;
90 import com.android.net.module.util.HandlerUtils;
91 import com.android.net.module.util.LocationPermissionChecker;
92 import com.android.net.module.util.PermissionUtils;
93 import com.android.server.vcn.TelephonySubscriptionTracker;
94 import com.android.server.vcn.Vcn;
95 import com.android.server.vcn.VcnContext;
96 import com.android.server.vcn.VcnNetworkProvider;
97 
98 import java.io.File;
99 import java.io.FileDescriptor;
100 import java.io.IOException;
101 import java.io.PrintWriter;
102 import java.util.ArrayList;
103 import java.util.Collections;
104 import java.util.Iterator;
105 import java.util.List;
106 import java.util.Map;
107 import java.util.Map.Entry;
108 import java.util.Objects;
109 import java.util.Set;
110 import java.util.concurrent.TimeUnit;
111 
112 /**
113  * VcnManagementService manages Virtual Carrier Network profiles and lifecycles.
114  *
115  * <pre>The internal structure of the VCN Management subsystem is as follows:
116  *
117  * +-------------------------+ 1:1                                +--------------------------------+
118  * |  VcnManagementService   | ------------ Creates ------------> |  TelephonySubscriptionManager  |
119  * |                         |                                    |                                |
120  * |   Manages configs and   |                                    | Tracks subscriptions, carrier  |
121  * | Vcn instance lifecycles | <--- Notifies of subscription & -- | privilege changes, caches maps |
122  * +-------------------------+      carrier privilege changes     +--------------------------------+
123  *      | 1:N          ^
124  *      |              |
125  *      |              +-------------------------------+
126  *      +---------------+                              |
127  *                      |                              |
128  *         Creates when config present,                |
129  *        subscription group active, and               |
130  *      providing app is carrier privileged     Notifies of safe
131  *                      |                      mode state changes
132  *                      v                              |
133  * +-----------------------------------------------------------------------+
134  * |                                  Vcn                                  |
135  * |                                                                       |
136  * |       Manages GatewayConnection lifecycles based on fulfillable       |
137  * |                NetworkRequest(s) and overall safe-mode                |
138  * +-----------------------------------------------------------------------+
139  *                      | 1:N                          ^
140  *              Creates to fulfill                     |
141  *           NetworkRequest(s), tears   Notifies of VcnGatewayConnection
142  *          down when no longer needed   teardown (e.g. Network reaped)
143  *                      |                 and safe-mode timer changes
144  *                      v                              |
145  * +-----------------------------------------------------------------------+
146  * |                          VcnGatewayConnection                         |
147  * |                                                                       |
148  * |       Manages a single (IKEv2) tunnel session and NetworkAgent,       |
149  * |  handles mobility events, (IPsec) Tunnel setup and safe-mode timers   |
150  * +-----------------------------------------------------------------------+
151  *                      | 1:1                          ^
152  *                      |                              |
153  *          Creates upon instantiation      Notifies of changes in
154  *                      |                 selected underlying network
155  *                      |                     or its properties
156  *                      v                              |
157  * +-----------------------------------------------------------------------+
158  * |                       UnderlyingNetworkController                     |
159  * |                                                                       |
160  * | Manages lifecycle of underlying physical networks, filing requests to |
161  * | bring them up, and releasing them as they become no longer necessary  |
162  * +-----------------------------------------------------------------------+
163  * </pre>
164  *
165  * @hide
166  */
167 // TODO(b/180451994): ensure all incoming + outgoing calls have a cleared calling identity
168 @TargetApi(Build.VERSION_CODES.BAKLAVA)
169 public class VcnManagementService extends IVcnManagementService.Stub {
170     @NonNull private static final String TAG = VcnManagementService.class.getSimpleName();
171     @NonNull private static final String CONTEXT_ATTRIBUTION_TAG = "VCN";
172 
173     private static final long DUMP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(5);
174     private static final int LOCAL_LOG_LINE_COUNT = 512;
175 
176     private static final Set<Integer> RESTRICTED_TRANSPORTS_DEFAULT =
177             Collections.singleton(TRANSPORT_WIFI);
178 
179     // Public for use in all other VCN classes
180     @NonNull public static final LocalLog LOCAL_LOG = new LocalLog(LOCAL_LOG_LINE_COUNT);
181 
182     public static final boolean VDBG = false; // STOPSHIP: if true
183 
184     // The system path is copied from Environment.getDataSystemDirectory
185     @VisibleForTesting(visibility = Visibility.PRIVATE)
186     static final String VCN_CONFIG_FILE =
187             new File(Environment.getDataDirectory(), "system/vcn/configs.xml").getPath();
188 
189     // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS
190     @VisibleForTesting(visibility = Visibility.PRIVATE)
191     static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
192 
193     /* Binder context for this service */
194     @NonNull private final Context mContext;
195     @NonNull private final Dependencies mDeps;
196 
197     @NonNull private final Looper mLooper;
198     @NonNull private final Handler mHandler;
199     @NonNull private final VcnNetworkProvider mNetworkProvider;
200     @NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb;
201     @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
202     @NonNull private final BroadcastReceiver mVcnBroadcastReceiver;
203 
204     @NonNull
205     private final TrackingNetworkCallback mTrackingNetworkCallback = new TrackingNetworkCallback();
206 
207     @GuardedBy("mLock")
208     @NonNull
209     private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>();
210 
211     @GuardedBy("mLock")
212     @NonNull
213     private final Map<ParcelUuid, Vcn> mVcns = new ArrayMap<>();
214 
215     @GuardedBy("mLock")
216     @NonNull
217     private TelephonySubscriptionSnapshot mLastSnapshot =
218             TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT;
219 
220     @NonNull private final Object mLock = new Object();
221 
222     @NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper;
223 
224     @GuardedBy("mLock")
225     @NonNull
226     private final Map<IBinder, PolicyListenerBinderDeath> mRegisteredPolicyListeners =
227             new ArrayMap<>();
228 
229     @GuardedBy("mLock")
230     @NonNull
231     private final Map<IBinder, VcnStatusCallbackInfo> mRegisteredStatusCallbacks = new ArrayMap<>();
232 
233     @VisibleForTesting(visibility = Visibility.PRIVATE)
VcnManagementService(@onNull Context context, @NonNull Dependencies deps)234     VcnManagementService(@NonNull Context context, @NonNull Dependencies deps) {
235         mContext =
236                 requireNonNull(context, "Missing context")
237                         .createAttributionContext(CONTEXT_ATTRIBUTION_TAG);
238         mDeps = requireNonNull(deps, "Missing dependencies");
239 
240         mLooper = mDeps.getLooper();
241         mHandler = new Handler(mLooper);
242         mNetworkProvider = new VcnNetworkProvider(mContext, mLooper);
243         mTelephonySubscriptionTrackerCb = new VcnSubscriptionTrackerCallback();
244         mTelephonySubscriptionTracker = mDeps.newTelephonySubscriptionTracker(
245                 mContext, mLooper, mTelephonySubscriptionTrackerCb);
246 
247         mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE);
248 
249         mVcnBroadcastReceiver = new VcnBroadcastReceiver();
250 
251         final IntentFilter intentFilter = new IntentFilter();
252         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
253         intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
254         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
255         intentFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
256         intentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
257         intentFilter.addDataScheme("package");
258         mContext.registerReceiver(
259                 mVcnBroadcastReceiver, intentFilter, null /* broadcastPermission */, mHandler);
260 
261         // Run on handler to ensure I/O does not block system server startup
262         mHandler.post(() -> {
263             PersistableBundle configBundle = null;
264             try {
265                 configBundle = mConfigDiskRwHelper.readFromDisk();
266             } catch (IOException e1) {
267                 logErr("Failed to read configs from disk; retrying", e1);
268 
269                 // Retry immediately. The IOException may have been transient.
270                 try {
271                     configBundle = mConfigDiskRwHelper.readFromDisk();
272                 } catch (IOException e2) {
273                     logWtf("Failed to read configs from disk", e2);
274                     return;
275                 }
276             }
277 
278             if (configBundle != null) {
279                 final Map<ParcelUuid, VcnConfig> configs =
280                         PersistableBundleUtils.toMap(
281                                 configBundle,
282                                 PersistableBundleUtils::toParcelUuid,
283                                 VcnConfig::new);
284 
285                 synchronized (mLock) {
286                     for (Entry<ParcelUuid, VcnConfig> entry : configs.entrySet()) {
287                         // Ensure no new configs are overwritten; a carrier app may have added a new
288                         // config.
289                         if (!mConfigs.containsKey(entry.getKey())) {
290                             mConfigs.put(entry.getKey(), entry.getValue());
291                         }
292                     }
293 
294                     // Re-evaluate subscriptions, and start/stop VCNs. This starts with an empty
295                     // snapshot, and therefore safe even before telephony subscriptions are loaded.
296                     mTelephonySubscriptionTrackerCb.onNewSnapshot(mLastSnapshot);
297                 }
298             }
299         });
300     }
301 
302     /** Called by ConnectivityServiceInitializerB to create instances. */
303     // VcnManagementService will be jarjared but ConnectivityServiceInitializerB will not. Thus this
304     // method needs to be public for ConnectivityServiceInitializerB to access
create(@onNull Context context)305     public static VcnManagementService create(@NonNull Context context) {
306         return new VcnManagementService(context, new Dependencies());
307     }
308 
309     /** External dependencies used by VcnManagementService, for injection in tests */
310     @VisibleForTesting(visibility = Visibility.PRIVATE)
311     public static class Dependencies {
312         private HandlerThread mHandlerThread;
313 
314         /** Retrieves a looper for the VcnManagementService */
getLooper()315         public Looper getLooper() {
316             if (mHandlerThread == null) {
317                 synchronized (this) {
318                     if (mHandlerThread == null) {
319                         mHandlerThread = new HandlerThread(TAG);
320                         mHandlerThread.start();
321                     }
322                 }
323             }
324             return mHandlerThread.getLooper();
325         }
326 
327         /** Creates a new VcnInstance using the provided configuration */
newTelephonySubscriptionTracker( @onNull Context context, @NonNull Looper looper, @NonNull TelephonySubscriptionTrackerCallback callback)328         public TelephonySubscriptionTracker newTelephonySubscriptionTracker(
329                 @NonNull Context context,
330                 @NonNull Looper looper,
331                 @NonNull TelephonySubscriptionTrackerCallback callback) {
332             return new TelephonySubscriptionTracker(context, new Handler(looper), callback);
333         }
334 
335         /**
336          * Retrieves the caller's UID
337          *
338          * <p>This call MUST be made before calling {@link Binder#clearCallingIdentity}, otherwise
339          * this will not work properly.
340          *
341          * @return
342          */
getBinderCallingUid()343         public int getBinderCallingUid() {
344             return Binder.getCallingUid();
345         }
346 
347         /**
348          * Creates and returns a new {@link PersistableBundle.LockingReadWriteHelper}
349          *
350          * @param path the file path to read/write from/to.
351          * @return the {@link PersistableBundleUtils.LockingReadWriteHelper} instance
352          */
353         public PersistableBundleUtils.LockingReadWriteHelper
newPersistableBundleLockingReadWriteHelper(@onNull String path)354                 newPersistableBundleLockingReadWriteHelper(@NonNull String path) {
355             return new PersistableBundleUtils.LockingReadWriteHelper(path);
356         }
357 
358         /** Creates a new VcnContext */
newVcnContext( @onNull Context context, @NonNull Looper looper, @NonNull VcnNetworkProvider vcnNetworkProvider, boolean isInTestMode)359         public VcnContext newVcnContext(
360                 @NonNull Context context,
361                 @NonNull Looper looper,
362                 @NonNull VcnNetworkProvider vcnNetworkProvider,
363                 boolean isInTestMode) {
364             return new VcnContext(context, looper, vcnNetworkProvider, isInTestMode);
365         }
366 
367         /** Creates a new Vcn instance using the provided configuration */
newVcn( @onNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull VcnCallback vcnCallback)368         public Vcn newVcn(
369                 @NonNull VcnContext vcnContext,
370                 @NonNull ParcelUuid subscriptionGroup,
371                 @NonNull VcnConfig config,
372                 @NonNull TelephonySubscriptionSnapshot snapshot,
373                 @NonNull VcnCallback vcnCallback) {
374             return new Vcn(vcnContext, subscriptionGroup, config, snapshot, vcnCallback);
375         }
376 
377         /** Gets the subId indicated by the given {@link WifiInfo}. */
getSubIdForWifiInfo(@onNull WifiInfo wifiInfo)378         public int getSubIdForWifiInfo(@NonNull WifiInfo wifiInfo) {
379             return wifiInfo.getSubscriptionId();
380         }
381 
382         /** Creates a new LocationPermissionChecker for the provided Context. */
newLocationPermissionChecker(@onNull Context context)383         public LocationPermissionChecker newLocationPermissionChecker(@NonNull Context context) {
384             return new LocationPermissionChecker(context);
385         }
386 
387         /** Gets transports that need to be marked as restricted by the VCN from CarrierConfig */
388         // TODO: b/262269892 This method was created to perform experiments before the relevant API
389         // was exposed. Now it is obsolete and should be removed.
390         @VisibleForTesting(visibility = Visibility.PRIVATE)
getRestrictedTransportsFromCarrierConfig( ParcelUuid subGrp, TelephonySubscriptionSnapshot lastSnapshot)391         public Set<Integer> getRestrictedTransportsFromCarrierConfig(
392                 ParcelUuid subGrp, TelephonySubscriptionSnapshot lastSnapshot) {
393             if (!Build.isDebuggable()) {
394                 return RESTRICTED_TRANSPORTS_DEFAULT;
395             }
396 
397             final PersistableBundleWrapper carrierConfig =
398                     lastSnapshot.getCarrierConfigForSubGrp(subGrp);
399             if (carrierConfig == null) {
400                 return RESTRICTED_TRANSPORTS_DEFAULT;
401             }
402 
403             final int[] defaultValue =
404                     RESTRICTED_TRANSPORTS_DEFAULT.stream().mapToInt(i -> i).toArray();
405             final int[] restrictedTransportsArray =
406                     carrierConfig.getIntArray(
407                             VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
408                             defaultValue);
409 
410             // Convert to a boxed set
411             final Set<Integer> restrictedTransports = new ArraySet<>();
412             for (int transport : restrictedTransportsArray) {
413                 restrictedTransports.add(transport);
414             }
415             return restrictedTransports;
416         }
417 
418         /** Gets the transports that need to be marked as restricted by the VCN */
getRestrictedTransports( ParcelUuid subGrp, TelephonySubscriptionSnapshot lastSnapshot, VcnConfig vcnConfig)419         public Set<Integer> getRestrictedTransports(
420                 ParcelUuid subGrp,
421                 TelephonySubscriptionSnapshot lastSnapshot,
422                 VcnConfig vcnConfig) {
423             final Set<Integer> restrictedTransports = new ArraySet<>();
424             restrictedTransports.addAll(vcnConfig.getRestrictedUnderlyingNetworkTransports());
425 
426             // TODO: b/262269892 Remove the ability to configure restricted transports
427             // via CarrierConfig
428             restrictedTransports.addAll(
429                     getRestrictedTransportsFromCarrierConfig(subGrp, lastSnapshot));
430 
431             return restrictedTransports;
432         }
433     }
434 
435     /** Notifies the VcnManagementService that external dependencies can be set up. */
systemReady()436     public void systemReady() {
437         mNetworkProvider.register();
438         mContext.getSystemService(ConnectivityManager.class)
439                 .registerNetworkCallback(
440                         new NetworkRequest.Builder().clearCapabilities().build(),
441                         mTrackingNetworkCallback);
442         mTelephonySubscriptionTracker.register();
443     }
444 
445     // The system server automatically has the required permissions for #getMainUser()
446     @SuppressLint("AndroidFrameworkRequiresPermission")
enforcePrimaryUser()447     private void enforcePrimaryUser() {
448         final int uid = mDeps.getBinderCallingUid();
449         if (uid == Process.SYSTEM_UID) {
450             throw new IllegalStateException(
451                     "Calling identity was System Server. Was Binder calling identity cleared?");
452         }
453 
454         final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
455         final UserManager userManager = mContext.getSystemService(UserManager.class);
456 
457         BinderUtils.withCleanCallingIdentity(
458                 () -> {
459                     if (!Objects.equals(userManager.getMainUser(), userHandle)) {
460                         throw new SecurityException(
461                                 "VcnManagementService can only be used by callers running as"
462                                         + " the main user");
463                     }
464                 });
465     }
466 
enforceCallingUserAndCarrierPrivilege( ParcelUuid subscriptionGroup, String pkgName)467     private void enforceCallingUserAndCarrierPrivilege(
468             ParcelUuid subscriptionGroup, String pkgName) {
469         // Only apps running in the primary (system) user are allowed to configure the VCN. This is
470         // in line with Telephony's behavior with regards to binding to a Carrier App provided
471         // CarrierConfigService.
472         enforcePrimaryUser();
473 
474         // TODO (b/172619301): Check based on events propagated from CarrierPrivilegesTracker
475         final SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class);
476         final List<SubscriptionInfo> subscriptionInfos = new ArrayList<>();
477         BinderUtils.withCleanCallingIdentity(
478                 () -> {
479                     List<SubscriptionInfo> subsInGroup =
480                             subMgr.getSubscriptionsInGroup(subscriptionGroup);
481                     if (subsInGroup == null) {
482                         logWtf("Received null from getSubscriptionsInGroup");
483                         subsInGroup = Collections.emptyList();
484                     }
485                     subscriptionInfos.addAll(subsInGroup);
486                 });
487 
488         for (SubscriptionInfo info : subscriptionInfos) {
489             final TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class)
490                     .createForSubscriptionId(info.getSubscriptionId());
491 
492             // Check subscription is active first; much cheaper/faster check, and an app (currently)
493             // cannot be carrier privileged for inactive subscriptions.
494             final int simSlotIndex = info.getSimSlotIndex();
495             final boolean isValidSlotIndex =
496                     simSlotIndex >= 0 && simSlotIndex < telMgr.getActiveModemCount();
497             if (isValidSlotIndex
498                     && telMgr.checkCarrierPrivilegesForPackage(pkgName)
499                             == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
500                 // TODO (b/173717728): Allow configuration for inactive, but manageable
501                 // subscriptions.
502                 // TODO (b/173718661): Check for whole subscription groups at a time.
503                 return;
504             }
505         }
506 
507         throw new SecurityException(
508                 "Carrier privilege required for subscription group to set VCN Config");
509     }
510 
511     private void enforceManageTestNetworksForTestMode(@NonNull VcnConfig vcnConfig) {
512         if (vcnConfig.isTestModeProfile()) {
513             mContext.enforceCallingPermission(
514                     android.Manifest.permission.MANAGE_TEST_NETWORKS,
515                     "Test-mode require the MANAGE_TEST_NETWORKS permission");
516         }
517     }
518 
519     private boolean isActiveSubGroup(
520             @NonNull ParcelUuid subGrp, @NonNull TelephonySubscriptionSnapshot snapshot) {
521         if (subGrp == null || snapshot == null) {
522             return false;
523         }
524 
525         return Objects.equals(subGrp, snapshot.getActiveDataSubscriptionGroup());
526     }
527 
528     private class VcnBroadcastReceiver extends BroadcastReceiver {
529         @Override
530         public void onReceive(Context context, Intent intent) {
531             final String action = intent.getAction();
532             if (action == null) {
533                 return;
534             }
535 
536             switch (action) {
537                 case Intent.ACTION_PACKAGE_ADDED: // Fallthrough
538                 case Intent.ACTION_PACKAGE_REPLACED: // Fallthrough
539                 case Intent.ACTION_PACKAGE_REMOVED:
540                     // Reevaluate subscriptions
541                     mTelephonySubscriptionTracker.handleSubscriptionsChanged();
542 
543                     break;
544                 case Intent.ACTION_PACKAGE_FULLY_REMOVED:
545                 case Intent.ACTION_PACKAGE_DATA_CLEARED:
546                     final String pkgName = intent.getData().getSchemeSpecificPart();
547 
548                     if (pkgName == null || pkgName.isEmpty()) {
549                         logWtf("Package name was empty or null for intent with action" + action);
550                         return;
551                     }
552 
553                     // Clear configs for the packages that had data cleared, or removed.
554                     synchronized (mLock) {
555                         final List<ParcelUuid> toRemove = new ArrayList<>();
556                         for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
557                             if (pkgName.equals(entry.getValue().getProvisioningPackageName())) {
558                                 toRemove.add(entry.getKey());
559                             }
560                         }
561 
562                         for (ParcelUuid subGrp : toRemove) {
563                             stopAndClearVcnConfigInternalLocked(subGrp);
564                         }
565 
566                         if (!toRemove.isEmpty()) {
567                             writeConfigsToDiskLocked();
568                         }
569                     }
570 
571                     break;
572                 default:
573                     Slog.wtf(TAG, "received unexpected intent: " + action);
574             }
575         }
576     }
577 
578     private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback {
579         /**
580          * Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker}
581          *
582          * <p>Start any unstarted VCN instances
583          *
584          * @hide
585          */
586         public void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
587             // Startup VCN instances
588             synchronized (mLock) {
589                 final TelephonySubscriptionSnapshot oldSnapshot = mLastSnapshot;
590                 mLastSnapshot = snapshot;
591                 logInfo("new snapshot: " + mLastSnapshot);
592 
593                 // Start any VCN instances as necessary
594                 for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
595                     final ParcelUuid subGrp = entry.getKey();
596 
597                     // TODO(b/193687515): Support multiple VCNs active at the same time
598                     if (snapshot.packageHasPermissionsForSubscriptionGroup(
599                                     subGrp, entry.getValue().getProvisioningPackageName())
600                             && isActiveSubGroup(subGrp, snapshot)) {
601                         if (!mVcns.containsKey(subGrp)) {
602                             startVcnLocked(subGrp, entry.getValue());
603                         }
604 
605                         // Cancel any scheduled teardowns for active subscriptions
606                         mHandler.removeCallbacksAndMessages(mVcns.get(subGrp));
607                     }
608                 }
609 
610                 boolean needNotifyAllPolicyListeners = false;
611                 // Schedule teardown of any VCN instances that have lost carrier privileges (after a
612                 // delay)
613                 for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
614                     final ParcelUuid subGrp = entry.getKey();
615                     final VcnConfig config = mConfigs.get(subGrp);
616 
617                     final boolean isActiveSubGrp = isActiveSubGroup(subGrp, snapshot);
618                     final boolean isValidActiveDataSubIdNotInVcnSubGrp =
619                             isValidSubscriptionId(snapshot.getActiveDataSubscriptionId())
620                                     && !isActiveSubGroup(subGrp, snapshot);
621 
622                     // TODO(b/193687515): Support multiple VCNs active at the same time
623                     if (config == null
624                             || !snapshot.packageHasPermissionsForSubscriptionGroup(
625                                     subGrp, config.getProvisioningPackageName())
626                             || !isActiveSubGrp) {
627                         final ParcelUuid uuidToTeardown = subGrp;
628                         final Vcn instanceToTeardown = entry.getValue();
629 
630                         // TODO(b/193687515): Support multiple VCNs active at the same time
631                         // If directly switching to a subscription not in the current group,
632                         // teardown immediately to prevent other subscription's network from being
633                         // outscored by the VCN. Otherwise, teardown after a delay to ensure that
634                         // SIM profile switches do not trigger the VCN to cycle.
635                         final long teardownDelayMs =
636                                 isValidActiveDataSubIdNotInVcnSubGrp
637                                         ? 0
638                                         : CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS;
639                         mHandler.postDelayed(() -> {
640                             synchronized (mLock) {
641                                 // Guard against case where this is run after a old instance was
642                                 // torn down, and a new instance was started. Verify to ensure
643                                 // correct instance is torn down. This could happen as a result of a
644                                 // Carrier App manually removing/adding a VcnConfig.
645                                 if (mVcns.get(uuidToTeardown) == instanceToTeardown) {
646                                     stopVcnLocked(uuidToTeardown);
647 
648                                     // TODO(b/181789060): invoke asynchronously after Vcn notifies
649                                     // through VcnCallback
650                                     notifyAllPermissionedStatusCallbacksLocked(
651                                             uuidToTeardown, VCN_STATUS_CODE_INACTIVE);
652                                 }
653                             }
654                         }, instanceToTeardown, teardownDelayMs);
655                     } else {
656                         // If this VCN's status has not changed, update it with the new snapshot
657                         entry.getValue().updateSubscriptionSnapshot(mLastSnapshot);
658                         needNotifyAllPolicyListeners |=
659                                 !Objects.equals(
660                                         oldSnapshot.getCarrierConfigForSubGrp(subGrp),
661                                         mLastSnapshot.getCarrierConfigForSubGrp(subGrp));
662                     }
663                 }
664 
665                 final Map<ParcelUuid, Set<Integer>> oldSubGrpMappings =
666                         getSubGroupToSubIdMappings(oldSnapshot);
667                 final Map<ParcelUuid, Set<Integer>> currSubGrpMappings =
668                         getSubGroupToSubIdMappings(mLastSnapshot);
669                 if (!currSubGrpMappings.equals(oldSubGrpMappings)) {
garbageCollectAndWriteVcnConfigsLocked()670                     garbageCollectAndWriteVcnConfigsLocked();
671                     needNotifyAllPolicyListeners = true;
672                 }
673 
674                 if (needNotifyAllPolicyListeners) {
notifyAllPolicyListenersLocked()675                     notifyAllPolicyListenersLocked();
676                 }
677             }
678         }
679     }
680 
681     @GuardedBy("mLock")
682     private Map<ParcelUuid, Set<Integer>> getSubGroupToSubIdMappings(
683             @NonNull TelephonySubscriptionSnapshot snapshot) {
684         final Map<ParcelUuid, Set<Integer>> subGrpMappings = new ArrayMap<>();
685         for (ParcelUuid subGrp : mVcns.keySet()) {
686             subGrpMappings.put(subGrp, snapshot.getAllSubIdsInGroup(subGrp));
687         }
688         return subGrpMappings;
689     }
690 
691     @GuardedBy("mLock")
692     private void stopVcnLocked(@NonNull ParcelUuid uuidToTeardown) {
693         logInfo("Stopping VCN config for subGrp: " + uuidToTeardown);
694 
695         // Remove in 2 steps. Make sure teardownAsync is triggered before removing from the map.
696         final Vcn vcnToTeardown = mVcns.get(uuidToTeardown);
697         if (vcnToTeardown == null) {
698             return;
699         }
700 
701         vcnToTeardown.teardownAsynchronously();
702         mVcns.remove(uuidToTeardown);
703 
704         // Now that the VCN is removed, notify all registered listeners to refresh their
705         // UnderlyingNetworkPolicy.
706         notifyAllPolicyListenersLocked();
707     }
708 
709     @GuardedBy("mLock")
710     private void notifyAllPolicyListenersLocked() {
711         for (final PolicyListenerBinderDeath policyListener : mRegisteredPolicyListeners.values()) {
712             BinderUtils.withCleanCallingIdentity(() -> {
713                 try {
714                     policyListener.mListener.onPolicyChanged();
715                 } catch (RemoteException e) {
716                     logDbg("VcnStatusCallback threw on VCN status change", e);
717                 }
718             });
719         }
720     }
721 
722     @GuardedBy("mLock")
723     private void notifyAllPermissionedStatusCallbacksLocked(
724             @NonNull ParcelUuid subGroup, @VcnStatusCode int statusCode) {
725         for (final VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
726             if (isCallbackPermissioned(cbInfo, subGroup)) {
727                 BinderUtils.withCleanCallingIdentity(() -> {
728                     try {
729                         cbInfo.mCallback.onVcnStatusChanged(statusCode);
730                     } catch (RemoteException e) {
731                         logDbg("VcnStatusCallback threw on VCN status change", e);
732                     }
733                 });
734             }
735         }
736     }
737 
738     @GuardedBy("mLock")
739     private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
740         logInfo("Starting VCN config for subGrp: " + subscriptionGroup);
741 
742         // TODO(b/193687515): Support multiple VCNs active at the same time
743         if (!mVcns.isEmpty()) {
744             // Only one VCN supported at a time; teardown all others before starting new one
745             for (ParcelUuid uuidToTeardown : mVcns.keySet()) {
746                 stopVcnLocked(uuidToTeardown);
747             }
748         }
749 
750         final VcnCallbackImpl vcnCallback = new VcnCallbackImpl(subscriptionGroup);
751 
752         final VcnContext vcnContext =
753                 mDeps.newVcnContext(
754                         mContext, mLooper, mNetworkProvider, config.isTestModeProfile());
755         final Vcn newInstance =
756                 mDeps.newVcn(vcnContext, subscriptionGroup, config, mLastSnapshot, vcnCallback);
757         mVcns.put(subscriptionGroup, newInstance);
758 
759         // Now that a new VCN has started, notify all registered listeners to refresh their
760         // UnderlyingNetworkPolicy.
761         notifyAllPolicyListenersLocked();
762 
763         // TODO(b/181789060): invoke asynchronously after Vcn notifies through VcnCallback
764         notifyAllPermissionedStatusCallbacksLocked(subscriptionGroup, VCN_STATUS_CODE_ACTIVE);
765     }
766 
767     @GuardedBy("mLock")
768     private void startOrUpdateVcnLocked(
769             @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
770         logDbg("Starting or updating VCN config for subGrp: " + subscriptionGroup);
771 
772         if (mVcns.containsKey(subscriptionGroup)) {
773             final Vcn vcn = mVcns.get(subscriptionGroup);
774             vcn.updateConfig(config);
775             notifyAllPolicyListenersLocked();
776         } else {
777             // TODO(b/193687515): Support multiple VCNs active at the same time
778             if (isActiveSubGroup(subscriptionGroup, mLastSnapshot)) {
779                 startVcnLocked(subscriptionGroup, config);
780             }
781         }
782     }
783 
784     /**
785      * Sets a VCN config for a given subscription group.
786      *
787      * <p>Implements the IVcnManagementService Binder interface.
788      */
789     @Override
790     public void setVcnConfig(
791             @NonNull ParcelUuid subscriptionGroup,
792             @NonNull VcnConfig config,
793             @NonNull String opPkgName) {
794         requireNonNull(subscriptionGroup, "subscriptionGroup was null");
795         requireNonNull(config, "config was null");
796         requireNonNull(opPkgName, "opPkgName was null");
797         if (!config.getProvisioningPackageName().equals(opPkgName)) {
798             throw new IllegalArgumentException("Mismatched caller and VcnConfig creator");
799         }
800         logInfo("VCN config updated for subGrp: " + subscriptionGroup);
801 
802         mContext.getSystemService(AppOpsManager.class)
803                 .checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName());
804         enforceManageTestNetworksForTestMode(config);
805         enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName);
806 
807         BinderUtils.withCleanCallingIdentity(() -> {
808             synchronized (mLock) {
809                 mConfigs.put(subscriptionGroup, config);
810                 startOrUpdateVcnLocked(subscriptionGroup, config);
811 
812                 writeConfigsToDiskLocked();
813             }
814         });
815     }
816 
817     private void enforceCarrierPrivilegeOrProvisioningPackage(
818             @NonNull ParcelUuid subscriptionGroup, @NonNull String pkg) {
819         // Only apps running in the primary (system) user are allowed to configure the VCN. This is
820         // in line with Telephony's behavior with regards to binding to a Carrier App provided
821         // CarrierConfigService.
822         enforcePrimaryUser();
823 
824         if (isProvisioningPackageForConfig(subscriptionGroup, pkg)) {
825             return;
826         }
827 
828         // Must NOT be called from cleared binder identity, since this checks user calling identity
829         enforceCallingUserAndCarrierPrivilege(subscriptionGroup, pkg);
830     }
831 
832     private boolean isProvisioningPackageForConfig(
833             @NonNull ParcelUuid subscriptionGroup, @NonNull String pkg) {
834         // Try-finally to return early if matching owned subscription found.
835         final long identity = Binder.clearCallingIdentity();
836         try {
837             synchronized (mLock) {
838                 final VcnConfig config = mConfigs.get(subscriptionGroup);
839                 if (config != null && pkg.equals(config.getProvisioningPackageName())) {
840                     return true;
841                 }
842             }
843         } finally {
844             Binder.restoreCallingIdentity(identity);
845         }
846 
847         return false;
848     }
849 
850     /**
851      * Clears the VcnManagementService for a given subscription group.
852      *
853      * <p>Implements the IVcnManagementService Binder interface.
854      */
855     @Override
856     public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull String opPkgName) {
857         requireNonNull(subscriptionGroup, "subscriptionGroup was null");
858         requireNonNull(opPkgName, "opPkgName was null");
859         logInfo("VCN config cleared for subGrp: " + subscriptionGroup);
860 
861         mContext.getSystemService(AppOpsManager.class)
862                 .checkPackage(mDeps.getBinderCallingUid(), opPkgName);
863         enforceCarrierPrivilegeOrProvisioningPackage(subscriptionGroup, opPkgName);
864 
865         BinderUtils.withCleanCallingIdentity(() -> {
866             synchronized (mLock) {
867                 stopAndClearVcnConfigInternalLocked(subscriptionGroup);
868                 writeConfigsToDiskLocked();
869             }
870         });
871     }
872 
873     private void stopAndClearVcnConfigInternalLocked(@NonNull ParcelUuid subscriptionGroup) {
874         mConfigs.remove(subscriptionGroup);
875         final boolean vcnExists = mVcns.containsKey(subscriptionGroup);
876 
877         stopVcnLocked(subscriptionGroup);
878 
879         if (vcnExists) {
880             // TODO(b/181789060): invoke asynchronously after Vcn notifies through
881             // VcnCallback
882             notifyAllPermissionedStatusCallbacksLocked(
883                     subscriptionGroup, VCN_STATUS_CODE_NOT_CONFIGURED);
884         }
885     }
886 
887     private void garbageCollectAndWriteVcnConfigsLocked() {
888         final SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class);
889         final Set<ParcelUuid> subGroups = mLastSnapshot.getAllSubscriptionGroups();
890 
891         boolean shouldWrite = false;
892 
893         final Iterator<ParcelUuid> configsIterator = mConfigs.keySet().iterator();
894         while (configsIterator.hasNext()) {
895             final ParcelUuid subGrp = configsIterator.next();
896 
897             if (!subGroups.contains(subGrp)) {
898                 // Trim subGrps with no more subscriptions; must have moved to another subGrp
899                 logDbg("Garbage collect VcnConfig for group=" + subGrp);
900                 configsIterator.remove();
901                 shouldWrite = true;
902             }
903         }
904 
905         if (shouldWrite) {
906             writeConfigsToDiskLocked();
907         }
908     }
909 
910     /**
911      * Retrieves the list of subscription groups with configured VcnConfigs
912      *
913      * <p>Limited to subscription groups for which the caller had configured.
914      *
915      * <p>Implements the IVcnManagementService Binder interface.
916      */
917     @Override
918     @NonNull
919     public List<ParcelUuid> getConfiguredSubscriptionGroups(@NonNull String opPkgName) {
920         requireNonNull(opPkgName, "opPkgName was null");
921 
922         mContext.getSystemService(AppOpsManager.class)
923                 .checkPackage(mDeps.getBinderCallingUid(), opPkgName);
924         enforcePrimaryUser();
925 
926         final List<ParcelUuid> result = new ArrayList<>();
927         synchronized (mLock) {
928             for (ParcelUuid subGrp : mConfigs.keySet()) {
929                 if (mLastSnapshot.packageHasPermissionsForSubscriptionGroup(subGrp, opPkgName)
930                         || isProvisioningPackageForConfig(subGrp, opPkgName)) {
931                     result.add(subGrp);
932                 }
933             }
934         }
935 
936         return result;
937     }
938 
939     @GuardedBy("mLock")
940     private void writeConfigsToDiskLocked() {
941         try {
942             PersistableBundle bundle =
943                     PersistableBundleUtils.fromMap(
944                             mConfigs,
945                             PersistableBundleUtils::fromParcelUuid,
946                             VcnConfig::toPersistableBundle);
947             mConfigDiskRwHelper.writeToDisk(bundle);
948         } catch (IOException e) {
949             logErr("Failed to save configs to disk", e);
950             throw new ServiceSpecificException(0, "Failed to save configs");
951         }
952     }
953 
954     /** Get current configuration list for testing purposes */
955     @VisibleForTesting(visibility = Visibility.PRIVATE)
956     Map<ParcelUuid, VcnConfig> getConfigs() {
957         synchronized (mLock) {
958             return Collections.unmodifiableMap(mConfigs);
959         }
960     }
961 
962     /** Get current VCNs for testing purposes */
963     @VisibleForTesting(visibility = Visibility.PRIVATE)
964     public Map<ParcelUuid, Vcn> getAllVcns() {
965         synchronized (mLock) {
966             return Collections.unmodifiableMap(mVcns);
967         }
968     }
969 
970     /** Get current VcnStatusCallbacks for testing purposes. */
971     @VisibleForTesting(visibility = Visibility.PRIVATE)
972     public Map<IBinder, VcnStatusCallbackInfo> getAllStatusCallbacks() {
973         synchronized (mLock) {
974             return Collections.unmodifiableMap(mRegisteredStatusCallbacks);
975         }
976     }
977 
978     /** Binder death recipient used to remove a registered policy listener. */
979     private class PolicyListenerBinderDeath implements Binder.DeathRecipient {
980         @NonNull private final IVcnUnderlyingNetworkPolicyListener mListener;
981 
982         PolicyListenerBinderDeath(@NonNull IVcnUnderlyingNetworkPolicyListener listener) {
983             mListener = listener;
984         }
985 
986         @Override
987         public void binderDied() {
988             Log.e(TAG, "app died without removing VcnUnderlyingNetworkPolicyListener");
989             removeVcnUnderlyingNetworkPolicyListener(mListener);
990         }
991     }
992 
993     /** Adds the provided listener for receiving VcnUnderlyingNetworkPolicy updates. */
994     @Override
995     public void addVcnUnderlyingNetworkPolicyListener(
996             @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
997         requireNonNull(listener, "listener was null");
998 
999         PermissionUtils.enforceAnyPermissionOf(
1000                 mContext,
1001                 android.Manifest.permission.NETWORK_FACTORY,
1002                 android.Manifest.permission.MANAGE_TEST_NETWORKS);
1003 
1004         BinderUtils.withCleanCallingIdentity(() -> {
1005             PolicyListenerBinderDeath listenerBinderDeath = new PolicyListenerBinderDeath(listener);
1006 
1007             synchronized (mLock) {
1008                 mRegisteredPolicyListeners.put(listener.asBinder(), listenerBinderDeath);
1009 
1010                 try {
1011                     listener.asBinder().linkToDeath(listenerBinderDeath, 0 /* flags */);
1012                 } catch (RemoteException e) {
1013                     // Remote binder already died - cleanup registered Listener
1014                     listenerBinderDeath.binderDied();
1015                 }
1016             }
1017         });
1018     }
1019 
1020     /** Removes the provided listener from receiving VcnUnderlyingNetworkPolicy updates. */
1021     @Override
1022     public void removeVcnUnderlyingNetworkPolicyListener(
1023             @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
1024         requireNonNull(listener, "listener was null");
1025 
1026         PermissionUtils.enforceAnyPermissionOf(
1027                 mContext,
1028                 android.Manifest.permission.NETWORK_FACTORY,
1029                 android.Manifest.permission.MANAGE_TEST_NETWORKS);
1030 
1031         BinderUtils.withCleanCallingIdentity(() -> {
1032             synchronized (mLock) {
1033                 PolicyListenerBinderDeath listenerBinderDeath =
1034                         mRegisteredPolicyListeners.remove(listener.asBinder());
1035 
1036                 if (listenerBinderDeath != null) {
1037                     listener.asBinder().unlinkToDeath(listenerBinderDeath, 0 /* flags */);
1038                 }
1039             }
1040         });
1041     }
1042 
1043     private ParcelUuid getSubGroupForNetworkCapabilities(
1044             @NonNull NetworkCapabilities networkCapabilities) {
1045         ParcelUuid subGrp = null;
1046         final TelephonySubscriptionSnapshot snapshot;
1047 
1048         // Always access mLastSnapshot under lock. Technically this can be treated as a volatile
1049         // but for consistency and safety, always access under lock.
1050         synchronized (mLock) {
1051             snapshot = mLastSnapshot;
1052         }
1053 
1054         // If multiple subscription IDs exist, they MUST all point to the same subscription
1055         // group. Otherwise undefined behavior may occur.
1056         for (int subId : networkCapabilities.getSubscriptionIds()) {
1057             // Verify that all subscriptions point to the same group
1058             if (subGrp != null && !subGrp.equals(snapshot.getGroupForSubId(subId))) {
1059                 logWtf("Got multiple subscription groups for a single network");
1060             }
1061 
1062             subGrp = snapshot.getGroupForSubId(subId);
1063         }
1064 
1065         return subGrp;
1066     }
1067 
1068     /**
1069      * Gets the UnderlyingNetworkPolicy as determined by the provided NetworkCapabilities and
1070      * LinkProperties.
1071      */
1072     @NonNull
1073     @Override
1074     public VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(
1075             @NonNull NetworkCapabilities networkCapabilities,
1076             @NonNull LinkProperties linkProperties) {
1077         requireNonNull(networkCapabilities, "networkCapabilities was null");
1078         requireNonNull(linkProperties, "linkProperties was null");
1079 
1080         PermissionUtils.enforceAnyPermissionOf(
1081                 mContext,
1082                 android.Manifest.permission.NETWORK_FACTORY,
1083                 android.Manifest.permission.MANAGE_TEST_NETWORKS);
1084 
1085         final boolean isUsingManageTestNetworks =
1086                 mContext.checkCallingOrSelfPermission(android.Manifest.permission.NETWORK_FACTORY)
1087                         != PackageManager.PERMISSION_GRANTED;
1088 
1089         if (isUsingManageTestNetworks && !networkCapabilities.hasTransport(TRANSPORT_TEST)) {
1090             throw new IllegalStateException(
1091                     "NetworkCapabilities must be for Test Network if using permission"
1092                             + " MANAGE_TEST_NETWORKS");
1093         }
1094 
1095         return BinderUtils.withCleanCallingIdentity(() -> {
1096             // Defensive copy in case this call is in-process and the given NetworkCapabilities
1097             // mutates
1098             final NetworkCapabilities ncCopy = new NetworkCapabilities(networkCapabilities);
1099 
1100             final ParcelUuid subGrp = getSubGroupForNetworkCapabilities(ncCopy);
1101             boolean isVcnManagedNetwork = false;
1102             boolean isRestricted = false;
1103             synchronized (mLock) {
1104                 final Vcn vcn = mVcns.get(subGrp);
1105                 final VcnConfig vcnConfig = mConfigs.get(subGrp);
1106                 if (vcn != null && vcnConfig != null) {
1107                     if (vcn.getStatus() == VCN_STATUS_CODE_ACTIVE) {
1108                         isVcnManagedNetwork = true;
1109                     }
1110 
1111                     final Set<Integer> restrictedTransports = mDeps.getRestrictedTransports(
1112                             subGrp, mLastSnapshot, vcnConfig);
1113                     for (int restrictedTransport : restrictedTransports) {
1114                         if (ncCopy.hasTransport(restrictedTransport)) {
1115                             if (restrictedTransport == TRANSPORT_CELLULAR
1116                                     || restrictedTransport == TRANSPORT_TEST) {
1117                                 // For cell or test network, only mark it as restricted when
1118                                 // the VCN is in active mode.
1119                                 isRestricted |= (vcn.getStatus() == VCN_STATUS_CODE_ACTIVE);
1120                             } else {
1121                                 isRestricted = true;
1122                                 break;
1123                             }
1124                         }
1125                     }
1126                 } else if (vcn != null && vcnConfig == null) {
1127                     logWtf("Vcn instance exists but VcnConfig does not for " + subGrp);
1128                 }
1129             }
1130 
1131             final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder(ncCopy);
1132 
1133             if (isVcnManagedNetwork) {
1134                 ncBuilder.removeCapability(
1135                         NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
1136             } else {
1137                 ncBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
1138             }
1139 
1140             if (isRestricted) {
1141                 ncBuilder.removeCapability(
1142                         NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
1143             }
1144 
1145             final NetworkCapabilities result = ncBuilder.build();
1146             final VcnUnderlyingNetworkPolicy policy = new VcnUnderlyingNetworkPolicy(
1147                     mTrackingNetworkCallback
1148                             .requiresRestartForImmutableCapabilityChanges(result, linkProperties),
1149                     result);
1150 
1151             logVdbg("getUnderlyingNetworkPolicy() called for caps: " + networkCapabilities
1152                         + "; and lp: " + linkProperties + "; result = " + policy);
1153             return policy;
1154         });
1155     }
1156 
1157     /** Binder death recipient used to remove registered VcnStatusCallbacks. */
1158     @VisibleForTesting(visibility = Visibility.PRIVATE)
1159     class VcnStatusCallbackInfo implements Binder.DeathRecipient {
1160         @NonNull final ParcelUuid mSubGroup;
1161         @NonNull final IVcnStatusCallback mCallback;
1162         @NonNull final String mPkgName;
1163         final int mUid;
1164 
1165         private VcnStatusCallbackInfo(
1166                 @NonNull ParcelUuid subGroup,
1167                 @NonNull IVcnStatusCallback callback,
1168                 @NonNull String pkgName,
1169                 int uid) {
1170             mSubGroup = subGroup;
1171             mCallback = callback;
1172             mPkgName = pkgName;
1173             mUid = uid;
1174         }
1175 
1176         @Override
1177         public void binderDied() {
1178             Log.e(TAG, "app died without unregistering VcnStatusCallback");
1179             unregisterVcnStatusCallback(mCallback);
1180         }
1181     }
1182 
1183     private boolean isCallbackPermissioned(
1184             @NonNull VcnStatusCallbackInfo cbInfo, @NonNull ParcelUuid subgroup) {
1185         if (!subgroup.equals(cbInfo.mSubGroup)) {
1186             return false;
1187         }
1188 
1189         if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(subgroup, cbInfo.mPkgName)) {
1190             return false;
1191         }
1192 
1193         return true;
1194     }
1195 
1196     /** Registers the provided callback for receiving VCN status updates. */
1197     @Override
1198     public void registerVcnStatusCallback(
1199             @NonNull ParcelUuid subGroup,
1200             @NonNull IVcnStatusCallback callback,
1201             @NonNull String opPkgName) {
1202         final int callingUid = mDeps.getBinderCallingUid();
1203         final long identity = Binder.clearCallingIdentity();
1204         try {
1205             requireNonNull(subGroup, "subGroup must not be null");
1206             requireNonNull(callback, "callback must not be null");
1207             requireNonNull(opPkgName, "opPkgName must not be null");
1208 
1209             mContext.getSystemService(AppOpsManager.class).checkPackage(callingUid, opPkgName);
1210 
1211             final IBinder cbBinder = callback.asBinder();
1212             final VcnStatusCallbackInfo cbInfo =
1213                     new VcnStatusCallbackInfo(subGroup, callback, opPkgName, callingUid);
1214 
1215             try {
1216                 cbBinder.linkToDeath(cbInfo, 0 /* flags */);
1217             } catch (RemoteException e) {
1218                 // Remote binder already died - don't add to mRegisteredStatusCallbacks and exit
1219                 return;
1220             }
1221 
1222             synchronized (mLock) {
1223                 if (mRegisteredStatusCallbacks.containsKey(cbBinder)) {
1224                     throw new IllegalStateException(
1225                             "Attempting to register a callback that is already in use");
1226                 }
1227 
1228                 mRegisteredStatusCallbacks.put(cbBinder, cbInfo);
1229 
1230                 // now that callback is registered, send it the VCN's current status
1231                 final VcnConfig vcnConfig = mConfigs.get(subGroup);
1232                 final Vcn vcn = mVcns.get(subGroup);
1233                 final int vcnStatus =
1234                         vcn == null ? VCN_STATUS_CODE_NOT_CONFIGURED : vcn.getStatus();
1235                 final int resultStatus;
1236                 if (vcnConfig == null || !isCallbackPermissioned(cbInfo, subGroup)) {
1237                     resultStatus = VCN_STATUS_CODE_NOT_CONFIGURED;
1238                 } else if (vcn == null) {
1239                     resultStatus = VCN_STATUS_CODE_INACTIVE;
1240                 } else if (vcnStatus == VCN_STATUS_CODE_ACTIVE
1241                         || vcnStatus == VCN_STATUS_CODE_SAFE_MODE) {
1242                     resultStatus = vcnStatus;
1243                 } else {
1244                     logWtf("Unknown VCN status: " + vcnStatus);
1245                     resultStatus = VCN_STATUS_CODE_NOT_CONFIGURED;
1246                 }
1247 
1248                 try {
1249                     cbInfo.mCallback.onVcnStatusChanged(resultStatus);
1250                 } catch (RemoteException e) {
1251                     logDbg("VcnStatusCallback threw on VCN status change", e);
1252                 }
1253             }
1254         } finally {
1255             Binder.restoreCallingIdentity(identity);
1256         }
1257     }
1258 
1259     /** Unregisters the provided callback from receiving future VCN status updates. */
1260     @Override
1261     public void unregisterVcnStatusCallback(@NonNull IVcnStatusCallback callback) {
1262         final long identity = Binder.clearCallingIdentity();
1263         try {
1264             requireNonNull(callback, "callback must not be null");
1265 
1266             final IBinder cbBinder = callback.asBinder();
1267             synchronized (mLock) {
1268                 VcnStatusCallbackInfo cbInfo = mRegisteredStatusCallbacks.remove(cbBinder);
1269 
1270                 if (cbInfo != null) {
1271                     cbBinder.unlinkToDeath(cbInfo, 0 /* flags */);
1272                 }
1273             }
1274         } finally {
1275             Binder.restoreCallingIdentity(identity);
1276         }
1277     }
1278 
1279     @VisibleForTesting(visibility = Visibility.PRIVATE)
1280     void setLastSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
1281         mLastSnapshot = Objects.requireNonNull(snapshot);
1282     }
1283 
1284     private void logVdbg(String msg) {
1285         if (VDBG) {
1286             Slog.v(TAG, msg);
1287         }
1288     }
1289 
1290     private void logDbg(String msg) {
1291         Slog.d(TAG, msg);
1292     }
1293 
1294     private void logDbg(String msg, Throwable tr) {
1295         Slog.d(TAG, msg, tr);
1296     }
1297 
1298     private void logInfo(String msg) {
1299         Slog.i(TAG, msg);
1300         LOCAL_LOG.log("[INFO] [" + TAG + "] " + msg);
1301     }
1302 
1303     private void logInfo(String msg, Throwable tr) {
1304         Slog.i(TAG, msg, tr);
1305         LOCAL_LOG.log("[INFO] [" + TAG + "] " + msg + tr);
1306     }
1307 
1308     private void logErr(String msg) {
1309         Slog.e(TAG, msg);
1310         LOCAL_LOG.log("[ERR] [" + TAG + "] " + msg);
1311     }
1312 
1313     private void logErr(String msg, Throwable tr) {
1314         Slog.e(TAG, msg, tr);
1315         LOCAL_LOG.log("[ERR ] [" + TAG + "] " + msg + tr);
1316     }
1317 
1318     private void logWtf(String msg) {
1319         Slog.wtf(TAG, msg);
1320         LOCAL_LOG.log("[WTF] [" + TAG + "] " + msg);
1321     }
1322 
1323     private void logWtf(String msg, Throwable tr) {
1324         Slog.wtf(TAG, msg, tr);
1325         LOCAL_LOG.log("[WTF ] [" + TAG + "] " + msg + tr);
1326     }
1327 
1328     /**
1329      * Dumps the state of the VcnManagementService for logging and debugging purposes.
1330      *
1331      * <p>PII and credentials MUST NEVER be dumped here.
1332      */
1333     @Override
1334     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
1335         mContext.enforceCallingOrSelfPermission(DUMP, TAG);
1336 
1337         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "| ");
1338 
1339         // Post to handler thread to prevent ConcurrentModificationExceptions, and avoid lock-hell.
1340         HandlerUtils.runWithScissorsForDump(
1341                 mHandler,
1342                 () -> {
1343                     mNetworkProvider.dump(pw);
1344                     pw.println();
1345 
1346                     mTrackingNetworkCallback.dump(pw);
1347                     pw.println();
1348 
1349                     synchronized (mLock) {
1350                         mLastSnapshot.dump(pw);
1351                         pw.println();
1352 
1353                         pw.println("mConfigs:");
1354                         pw.increaseIndent();
1355                         for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
1356                             pw.println(
1357                                     entry.getKey()
1358                                             + ": "
1359                                             + entry.getValue().getProvisioningPackageName());
1360                         }
1361                         pw.decreaseIndent();
1362                         pw.println();
1363 
1364                         pw.println("mVcns:");
1365                         pw.increaseIndent();
1366                         for (Vcn vcn : mVcns.values()) {
1367                             vcn.dump(pw);
1368                         }
1369                         pw.decreaseIndent();
1370                         pw.println();
1371                     }
1372 
1373                     pw.println("Local log:");
1374                     pw.increaseIndent();
1375                     LOCAL_LOG.dump(pw);
1376                     pw.decreaseIndent();
1377                     pw.println();
1378                 },
1379                 DUMP_TIMEOUT_MILLIS);
1380     }
1381 
1382     // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService
1383     /** Callback for Vcn signals sent up to VcnManagementService. */
1384     public interface VcnCallback {
1385         /** Called by a Vcn to signal that its safe mode status has changed. */
1386         void onSafeModeStatusChanged(boolean isInSafeMode);
1387 
1388         /** Called by a Vcn to signal that an error occurred. */
1389         void onGatewayConnectionError(
1390                 @NonNull String gatewayConnectionName,
1391                 @VcnErrorCode int errorCode,
1392                 @Nullable String exceptionClass,
1393                 @Nullable String exceptionMessage);
1394     }
1395 
1396     /**
1397      * TrackingNetworkCallback tracks all active networks
1398      *
1399      * <p>This is used to ensure that no underlying networks have immutable capabilities changed
1400      * without requiring a Network restart.
1401      */
1402     private class TrackingNetworkCallback extends ConnectivityManager.NetworkCallback {
1403         private final Object mLockObject = new Object();
1404         private final Map<Network, NetworkCapabilities> mCaps = new ArrayMap<>();
1405         private final Map<Network, LinkProperties> mLinkProperties = new ArrayMap<>();
1406 
1407         @Override
1408         public void onCapabilitiesChanged(Network network, NetworkCapabilities caps) {
1409             synchronized (mLockObject) {
1410                 mCaps.put(network, caps);
1411             }
1412         }
1413 
1414         @Override
1415         public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
1416             synchronized (mLockObject) {
1417                 mLinkProperties.put(network, lp);
1418             }
1419         }
1420 
1421         @Override
1422         public void onLost(Network network) {
1423             synchronized (mLockObject) {
1424                 mCaps.remove(network);
1425                 mLinkProperties.remove(network);
1426             }
1427         }
1428 
1429         private Set<Integer> getNonTestTransportTypes(NetworkCapabilities caps) {
1430             final Set<Integer> transportTypes = new ArraySet<>();
1431             for (int t : caps.getTransportTypes()) {
1432                 transportTypes.add(t);
1433             }
1434             return transportTypes;
1435         }
1436 
1437         private boolean hasSameTransportsAndCapabilities(
1438                 NetworkCapabilities caps, NetworkCapabilities capsOther) {
1439             if (!Objects.equals(
1440                     getNonTestTransportTypes(caps), getNonTestTransportTypes(capsOther))) {
1441                 return false;
1442             }
1443 
1444             for (int capability : ALLOWED_CAPABILITIES) {
1445                 if (caps.hasCapability(capability) != capsOther.hasCapability(capability)) {
1446                     return false;
1447                 }
1448             }
1449             return true;
1450         }
1451 
1452         private boolean requiresRestartForImmutableCapabilityChanges(
1453                 NetworkCapabilities caps, LinkProperties lp) {
1454             if (caps.getSubscriptionIds() == null) {
1455                 return false;
1456             }
1457 
1458             synchronized (mLockObject) {
1459                 // Search for an existing network (using interfce names)
1460                 // TODO: Get network from NetworkFactory (if exists) for this match.
1461                 for (Entry<Network, LinkProperties> lpEntry : mLinkProperties.entrySet()) {
1462                     if (lp.getInterfaceName() != null
1463                             && !lp.getInterfaceName().isEmpty()
1464                             && Objects.equals(
1465                                     lp.getInterfaceName(), lpEntry.getValue().getInterfaceName())) {
1466                         return mCaps.get(lpEntry.getKey())
1467                                         .hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
1468                                 != caps.hasCapability(NET_CAPABILITY_NOT_RESTRICTED);
1469                     }
1470                 }
1471             }
1472 
1473             // If no network found, by definition does not need restart.
1474             return false;
1475         }
1476 
1477         /** Dumps the state of this snapshot for logging and debugging purposes. */
1478         public void dump(IndentingPrintWriter pw) {
1479             pw.println("TrackingNetworkCallback:");
1480             pw.increaseIndent();
1481 
1482             pw.println("mCaps:");
1483             pw.increaseIndent();
1484             synchronized (mCaps) {
1485                 for (Entry<Network, NetworkCapabilities> entry : mCaps.entrySet()) {
1486                     pw.println(entry.getKey() + ": " + entry.getValue());
1487                 }
1488             }
1489             pw.decreaseIndent();
1490             pw.println();
1491 
1492             pw.decreaseIndent();
1493         }
1494     }
1495 
1496     /** VcnCallbackImpl for Vcn signals sent up to VcnManagementService. */
1497     private class VcnCallbackImpl implements VcnCallback {
1498         @NonNull private final ParcelUuid mSubGroup;
1499 
1500         private VcnCallbackImpl(@NonNull final ParcelUuid subGroup) {
1501             mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
1502         }
1503 
1504         @Override
1505         public void onSafeModeStatusChanged(boolean isInSafeMode) {
1506             synchronized (mLock) {
1507                 // Ignore if this subscription group doesn't exist anymore
1508                 if (!mVcns.containsKey(mSubGroup)) {
1509                     return;
1510                 }
1511 
1512                 final int status =
1513                         isInSafeMode ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
1514 
1515                 notifyAllPolicyListenersLocked();
1516                 notifyAllPermissionedStatusCallbacksLocked(mSubGroup, status);
1517             }
1518         }
1519 
1520         @Override
1521         public void onGatewayConnectionError(
1522                 @NonNull String gatewayConnectionName,
1523                 @VcnErrorCode int errorCode,
1524                 @Nullable String exceptionClass,
1525                 @Nullable String exceptionMessage) {
1526             synchronized (mLock) {
1527                 // Ignore if this subscription group doesn't exist anymore
1528                 if (!mVcns.containsKey(mSubGroup)) {
1529                     return;
1530                 }
1531 
1532                 // Notify all registered StatusCallbacks for this subGroup
1533                 for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
1534                     if (isCallbackPermissioned(cbInfo, mSubGroup)) {
1535                         BinderUtils.withCleanCallingIdentity(() -> {
1536                             try {
1537                                 cbInfo.mCallback.onGatewayConnectionError(
1538                                         gatewayConnectionName,
1539                                         errorCode,
1540                                         exceptionClass,
1541                                         exceptionMessage);
1542                             } catch (RemoteException e) {
1543                                 logDbg("VcnStatusCallback threw on VCN status change", e);
1544                             }
1545                         });
1546                     }
1547                 }
1548             }
1549         }
1550     }
1551 }
1552