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