diff options
Diffstat (limited to 'core/java')
| -rw-r--r-- | core/java/android/net/vcn/VcnConfig.java | 109 | ||||
| -rw-r--r-- | core/java/android/net/vcn/VcnGatewayConnectionConfig.java | 401 | ||||
| -rw-r--r-- | core/java/android/net/vcn/VcnManager.java | 16 |
3 files changed, 505 insertions, 21 deletions
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java index 148acf130857..d4a3fa7411b1 100644 --- a/core/java/android/net/vcn/VcnConfig.java +++ b/core/java/android/net/vcn/VcnConfig.java @@ -15,30 +15,104 @@ */ package android.net.vcn; +import static com.android.internal.annotations.VisibleForTesting.Visibility; + import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; +import android.os.PersistableBundle; +import android.util.ArraySet; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; /** * This class represents a configuration for a Virtual Carrier Network. * + * <p>Each {@link VcnGatewayConnectionConfig} instance added represents a connection that will be + * brought up on demand based on active {@link NetworkRequest}(s). + * + * @see VcnManager for more information on the Virtual Carrier Network feature * @hide */ public final class VcnConfig implements Parcelable { @NonNull private static final String TAG = VcnConfig.class.getSimpleName(); - private VcnConfig() { + private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs"; + @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs; + + private VcnConfig(@NonNull Set<VcnGatewayConnectionConfig> tunnelConfigs) { + mGatewayConnectionConfigs = Collections.unmodifiableSet(tunnelConfigs); + validate(); } - // TODO: Implement getters, validators, etc /** - * Validates this configuration. + * Deserializes a VcnConfig from a PersistableBundle. * * @hide */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public VcnConfig(@NonNull PersistableBundle in) { + final PersistableBundle gatewayConnectionConfigsBundle = + in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY); + mGatewayConnectionConfigs = + new ArraySet<>( + PersistableBundleUtils.toList( + gatewayConnectionConfigsBundle, VcnGatewayConnectionConfig::new)); + + validate(); + } + private void validate() { - // TODO: implement validation logic + Preconditions.checkCollectionNotEmpty( + mGatewayConnectionConfigs, "gatewayConnectionConfigs"); + } + + /** Retrieves the set of configured tunnels. */ + @NonNull + public Set<VcnGatewayConnectionConfig> getGatewayConnectionConfigs() { + return Collections.unmodifiableSet(mGatewayConnectionConfigs); + } + + /** + * Serializes this object to a PersistableBundle. + * + * @hide + */ + @NonNull + public PersistableBundle toPersistableBundle() { + final PersistableBundle result = new PersistableBundle(); + + final PersistableBundle gatewayConnectionConfigsBundle = + PersistableBundleUtils.fromList( + new ArrayList<>(mGatewayConnectionConfigs), + VcnGatewayConnectionConfig::toPersistableBundle); + result.putPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY, gatewayConnectionConfigsBundle); + + return result; + } + + @Override + public int hashCode() { + return Objects.hash(mGatewayConnectionConfigs); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof VcnConfig)) { + return false; + } + + final VcnConfig rhs = (VcnConfig) other; + return mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs); } // Parcelable methods @@ -49,15 +123,16 @@ public final class VcnConfig implements Parcelable { } @Override - public void writeToParcel(Parcel out, int flags) {} + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(toPersistableBundle(), flags); + } @NonNull public static final Parcelable.Creator<VcnConfig> CREATOR = new Parcelable.Creator<VcnConfig>() { @NonNull public VcnConfig createFromParcel(Parcel in) { - // TODO: Ensure all methods are pulled from the parcels - return new VcnConfig(); + return new VcnConfig((PersistableBundle) in.readParcelable(null)); } @NonNull @@ -68,7 +143,23 @@ public final class VcnConfig implements Parcelable { /** This class is used to incrementally build {@link VcnConfig} objects. */ public static class Builder { - // TODO: Implement this builder + @NonNull + private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>(); + + /** + * Adds a configuration for an individual gateway connection. + * + * @param gatewayConnectionConfig the configuration for an individual gateway connection + * @return this {@link Builder} instance, for chaining + */ + @NonNull + public Builder addGatewayConnectionConfig( + @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) { + Objects.requireNonNull(gatewayConnectionConfig, "gatewayConnectionConfig was null"); + + mGatewayConnectionConfigs.add(gatewayConnectionConfig); + return this; + } /** * Builds and validates the VcnConfig. @@ -77,7 +168,7 @@ public final class VcnConfig implements Parcelable { */ @NonNull public VcnConfig build() { - return new VcnConfig(); + return new VcnConfig(mGatewayConnectionConfigs); } } } diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index 8160edc87440..039360a69a3a 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -15,7 +15,27 @@ */ package android.net.vcn; +import static android.net.NetworkCapabilities.NetCapability; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.NetworkCapabilities; +import android.os.PersistableBundle; +import android.util.ArraySet; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; +import com.android.server.vcn.util.PersistableBundleUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; /** * This class represents a configuration for a connection to a Virtual Carrier Network gateway. @@ -49,38 +69,399 @@ import android.annotation.NonNull; * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MCX} * </ul> * + * <p>The meteredness and roaming of the VCN {@link Network} will be determined by that of the + * underlying Network(s). + * * @hide */ public final class VcnGatewayConnectionConfig { - private VcnGatewayConnectionConfig() { + // TODO: Use MIN_MTU_V6 once it is public, @hide + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int MIN_MTU_V6 = 1280; + + private static final Set<Integer> ALLOWED_CAPABILITIES; + + static { + Set<Integer> allowedCaps = new ArraySet<>(); + allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_MMS); + allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_SUPL); + allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_DUN); + allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_FOTA); + allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_IMS); + allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_CBS); + allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_IA); + allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_RCS); + allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_XCAP); + allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_EIMS); + allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_INTERNET); + allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_MCX); + + ALLOWED_CAPABILITIES = Collections.unmodifiableSet(allowedCaps); + } + + private static final int DEFAULT_MAX_MTU = 1500; + + /** + * The maximum number of retry intervals that may be specified. + * + * <p>Limited to ensure an upper bound on config sizes. + */ + private static final int MAX_RETRY_INTERVAL_COUNT = 10; + + /** + * The minimum allowable repeating retry interval + * + * <p>To ensure the device is not constantly being woken up, this retry interval MUST be greater + * than this value. + * + * @see {@link Builder#setRetryInterval()} + */ + private static final long MINIMUM_REPEATING_RETRY_INTERVAL_MS = TimeUnit.MINUTES.toMillis(15); + + private static final long[] DEFAULT_RETRY_INTERVALS_MS = + new long[] { + TimeUnit.SECONDS.toMillis(1), + TimeUnit.SECONDS.toMillis(2), + TimeUnit.SECONDS.toMillis(5), + TimeUnit.SECONDS.toMillis(30), + TimeUnit.MINUTES.toMillis(1), + TimeUnit.MINUTES.toMillis(5), + TimeUnit.MINUTES.toMillis(15) + }; + + private static final String EXPOSED_CAPABILITIES_KEY = "mExposedCapabilities"; + @NonNull private final Set<Integer> mExposedCapabilities; + + private static final String UNDERLYING_CAPABILITIES_KEY = "mUnderlyingCapabilities"; + @NonNull private final Set<Integer> mUnderlyingCapabilities; + + // TODO: Add Ike/ChildSessionParams as a subclass - maybe VcnIkeGatewayConnectionConfig + + private static final String MAX_MTU_KEY = "mMaxMtu"; + private final int mMaxMtu; + + private static final String RETRY_INTERVAL_MS_KEY = "mRetryIntervalsMs"; + @NonNull private final long[] mRetryIntervalsMs; + + @VisibleForTesting(visibility = Visibility.PRIVATE) + public VcnGatewayConnectionConfig( + @NonNull Set<Integer> exposedCapabilities, + @NonNull Set<Integer> underlyingCapabilities, + @NonNull long[] retryIntervalsMs, + @IntRange(from = MIN_MTU_V6) int maxMtu) { + mExposedCapabilities = exposedCapabilities; + mUnderlyingCapabilities = underlyingCapabilities; + mRetryIntervalsMs = retryIntervalsMs; + mMaxMtu = maxMtu; + + validate(); + } + + /** @hide */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public VcnGatewayConnectionConfig(@NonNull PersistableBundle in) { + final PersistableBundle exposedCapsBundle = + in.getPersistableBundle(EXPOSED_CAPABILITIES_KEY); + final PersistableBundle underlyingCapsBundle = + in.getPersistableBundle(UNDERLYING_CAPABILITIES_KEY); + + mExposedCapabilities = new ArraySet<>(PersistableBundleUtils.toList( + exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER)); + mUnderlyingCapabilities = new ArraySet<>(PersistableBundleUtils.toList( + underlyingCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER)); + mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY); + mMaxMtu = in.getInt(MAX_MTU_KEY); + validate(); } - // TODO: Implement getters, validators, etc + private void validate() { + Preconditions.checkArgument( + mExposedCapabilities != null && !mExposedCapabilities.isEmpty(), + "exposedCapsBundle was null or empty"); + for (Integer cap : getAllExposedCapabilities()) { + checkValidCapability(cap); + } + + Preconditions.checkArgument( + mUnderlyingCapabilities != null && !mUnderlyingCapabilities.isEmpty(), + "underlyingCapabilities was null or empty"); + for (Integer cap : getAllUnderlyingCapabilities()) { + checkValidCapability(cap); + } + + Objects.requireNonNull(mRetryIntervalsMs, "retryIntervalsMs was null"); + validateRetryInterval(mRetryIntervalsMs); + + Preconditions.checkArgument( + mMaxMtu >= MIN_MTU_V6, "maxMtu must be at least IPv6 min MTU (1280)"); + } + + private static void checkValidCapability(int capability) { + Preconditions.checkArgument( + ALLOWED_CAPABILITIES.contains(capability), + "NetworkCapability " + capability + "out of range"); + } + + private static void validateRetryInterval(@Nullable long[] retryIntervalsMs) { + Preconditions.checkArgument( + retryIntervalsMs != null + && retryIntervalsMs.length > 0 + && retryIntervalsMs.length <= MAX_RETRY_INTERVAL_COUNT, + "retryIntervalsMs was null, empty or exceed max interval count"); + + final long repeatingInterval = retryIntervalsMs[retryIntervalsMs.length - 1]; + if (repeatingInterval < MINIMUM_REPEATING_RETRY_INTERVAL_MS) { + throw new IllegalArgumentException( + "Repeating retry interval was too short, must be a minimum of 15 minutes: " + + repeatingInterval); + } + } /** - * Validates this configuration + * Returns all exposed capabilities. * * @hide */ - private void validate() { - // TODO: implement validation logic + @NonNull + public Set<Integer> getAllExposedCapabilities() { + return Collections.unmodifiableSet(mExposedCapabilities); + } + + /** + * Checks if this config is configured to support/expose a specific capability. + * + * @param capability the capability to check for + */ + public boolean hasExposedCapability(@NetCapability int capability) { + checkValidCapability(capability); + + return mExposedCapabilities.contains(capability); + } + + /** + * Returns all capabilities required of underlying networks. + * + * @hide + */ + @NonNull + public Set<Integer> getAllUnderlyingCapabilities() { + return Collections.unmodifiableSet(mUnderlyingCapabilities); } - // Parcelable methods + /** + * Checks if this config requires an underlying network to have the specified capability. + * + * @param capability the capability to check for + */ + public boolean requiresUnderlyingCapability(@NetCapability int capability) { + checkValidCapability(capability); + + return mUnderlyingCapabilities.contains(capability); + } - /** This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects */ + /** Retrieves the configured retry intervals. */ + @NonNull + public long[] getRetryIntervalsMs() { + return Arrays.copyOf(mRetryIntervalsMs, mRetryIntervalsMs.length); + } + + /** Retrieves the maximum MTU allowed for this Gateway Connection. */ + @IntRange(from = MIN_MTU_V6) + public int getMaxMtu() { + return mMaxMtu; + } + + /** + * Converts this config to a PersistableBundle. + * + * @hide + */ + @NonNull + @VisibleForTesting(visibility = Visibility.PROTECTED) + public PersistableBundle toPersistableBundle() { + final PersistableBundle result = new PersistableBundle(); + + final PersistableBundle exposedCapsBundle = + PersistableBundleUtils.fromList( + new ArrayList<>(mExposedCapabilities), + PersistableBundleUtils.INTEGER_SERIALIZER); + final PersistableBundle underlyingCapsBundle = + PersistableBundleUtils.fromList( + new ArrayList<>(mUnderlyingCapabilities), + PersistableBundleUtils.INTEGER_SERIALIZER); + + result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle); + result.putPersistableBundle(UNDERLYING_CAPABILITIES_KEY, underlyingCapsBundle); + result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs); + result.putInt(MAX_MTU_KEY, mMaxMtu); + + return result; + } + + @Override + public int hashCode() { + return Objects.hash( + mExposedCapabilities, + mUnderlyingCapabilities, + Arrays.hashCode(mRetryIntervalsMs), + mMaxMtu); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof VcnGatewayConnectionConfig)) { + return false; + } + + final VcnGatewayConnectionConfig rhs = (VcnGatewayConnectionConfig) other; + return mExposedCapabilities.equals(rhs.mExposedCapabilities) + && mUnderlyingCapabilities.equals(rhs.mUnderlyingCapabilities) + && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs) + && mMaxMtu == rhs.mMaxMtu; + } + + /** This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects. */ public static class Builder { - // TODO: Implement this builder + @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet(); + @NonNull private final Set<Integer> mUnderlyingCapabilities = new ArraySet(); + @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS; + private int mMaxMtu = DEFAULT_MAX_MTU; + + // TODO: (b/175829816) Consider VCN-exposed capabilities that may be transport dependent. + // Consider the case where the VCN might only expose MMS on WiFi, but defer to MMS + // when on Cell. + + /** + * Add a capability that this VCN Gateway Connection will support. + * + * @param exposedCapability the app-facing capability to be exposed by this VCN Gateway + * Connection (i.e., the capabilities that this VCN Gateway Connection will support). + * @return this {@link Builder} instance, for chaining + * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway + * Connection + */ + public Builder addExposedCapability(@NetCapability int exposedCapability) { + checkValidCapability(exposedCapability); + + mExposedCapabilities.add(exposedCapability); + return this; + } + + /** + * Remove a capability that this VCN Gateway Connection will support. + * + * @param exposedCapability the app-facing capability to not be exposed by this VCN Gateway + * Connection (i.e., the capabilities that this VCN Gateway Connection will support) + * @return this {@link Builder} instance, for chaining + * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway + * Connection + */ + public Builder removeExposedCapability(@NetCapability int exposedCapability) { + checkValidCapability(exposedCapability); + + mExposedCapabilities.remove(exposedCapability); + return this; + } + + /** + * Require a capability for Networks underlying this VCN Gateway Connection. + * + * @param underlyingCapability the capability that a network MUST have in order to be an + * underlying network for this VCN Gateway Connection. + * @return this {@link Builder} instance, for chaining + * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying + * networks + */ + public Builder addRequiredUnderlyingCapability(@NetCapability int underlyingCapability) { + checkValidCapability(underlyingCapability); + + mUnderlyingCapabilities.add(underlyingCapability); + return this; + } + + /** + * Remove a requirement of a capability for Networks underlying this VCN Gateway Connection. + * + * <p>Calling this method will allow Networks that do NOT have this capability to be + * selected as an underlying network for this VCN Gateway Connection. However, underlying + * networks MAY still have the removed capability. + * + * @param underlyingCapability the capability that a network DOES NOT need to have in order + * to be an underlying network for this VCN Gateway Connection. + * @return this {@link Builder} instance, for chaining + * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying + * networks + */ + public Builder removeRequiredUnderlyingCapability(@NetCapability int underlyingCapability) { + checkValidCapability(underlyingCapability); + + mUnderlyingCapabilities.remove(underlyingCapability); + return this; + } + + /** + * Set the retry interval between VCN establishment attempts upon successive failures. + * + * <p>The last retry interval will be repeated until safe mode is entered, or a connection + * is successfully established, at which point the retry timers will be reset. For power + * reasons, the last (repeated) retry interval MUST be at least 15 minutes. + * + * <p>Retry intervals MAY be subject to system power saving modes. That is to say that if + * the system enters a power saving mode, the retry may not occur until the device leaves + * the specified power saving mode. Intervals are sequential, and intervals will NOT be + * skipped if system power saving results in delaying retries (even if it exceed multiple + * retry intervals). + * + * <p>Each Gateway Connection will retry according to the retry intervals configured, but if + * safe mode is enabled, all Gateway Connection(s) will be disabled. + * + * @param retryIntervalsMs an array of between 1 and 10 millisecond intervals after which + * the VCN will attempt to retry a session initiation. The last (repeating) retry + * interval must be at least 15 minutes. Defaults to: {@code [1s, 2s, 5s, 30s, 1m, 5m, + * 15m]} + * @return this {@link Builder} instance, for chaining + * @see VcnManager for additional discussion on fail-safe mode + */ + @NonNull + public Builder setRetryInterval(@NonNull long[] retryIntervalsMs) { + validateRetryInterval(retryIntervalsMs); + + mRetryIntervalsMs = retryIntervalsMs; + return this; + } + + /** + * Sets the maximum MTU allowed for this VCN Gateway Connection. + * + * <p>This MTU is applied to the VCN Gateway Connection exposed Networks, and represents the + * MTU of the virtualized network. + * + * <p>The system may reduce the MTU below the maximum specified based on signals such as the + * MTU of the underlying networks (and adjusted for Gateway Connection overhead). + * + * @param maxMtu the maximum MTU allowed for this Gateway Connection. Must be greater than + * the IPv6 minimum MTU of 1280. Defaults to 1500. + * @return this {@link Builder} instance, for chaining + */ + @NonNull + public Builder setMaxMtu(@IntRange(from = MIN_MTU_V6) int maxMtu) { + Preconditions.checkArgument( + maxMtu >= MIN_MTU_V6, "maxMtu must be at least IPv6 min MTU (1280)"); + + mMaxMtu = maxMtu; + return this; + } /** - * Builds and validates the VcnGatewayConnectionConfig + * Builds and validates the VcnGatewayConnectionConfig. * * @return an immutable VcnGatewayConnectionConfig instance */ @NonNull public VcnGatewayConnectionConfig build() { - return new VcnGatewayConnectionConfig(); + return new VcnGatewayConnectionConfig( + mExposedCapabilities, mUnderlyingCapabilities, mRetryIntervalsMs, mMaxMtu); } } } diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index 6769b9e46e4c..46d1c1c7a23a 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -23,6 +23,9 @@ import android.annotation.SystemService; import android.content.Context; import android.os.ParcelUuid; import android.os.RemoteException; +import android.os.ServiceSpecificException; + +import java.io.IOException; /** * VcnManager publishes APIs for applications to configure and manage Virtual Carrier Networks. @@ -63,15 +66,20 @@ public final class VcnManager { * @param config the configuration parameters for the VCN * @throws SecurityException if the caller does not have carrier privileges, or is not running * as the primary user + * @throws IOException if the configuration failed to be persisted. A caller encountering this + * exception should attempt to retry (possibly after a delay). * @hide */ @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant - public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { + public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) + throws IOException { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); requireNonNull(config, "config was null"); try { mService.setVcnConfig(subscriptionGroup, config); + } catch (ServiceSpecificException e) { + throw new IOException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -88,14 +96,18 @@ public final class VcnManager { * @param subscriptionGroup the subscription group that the configuration should be applied to * @throws SecurityException if the caller does not have carrier privileges, or is not running * as the primary user + * @throws IOException if the configuration failed to be cleared. A caller encountering this + * exception should attempt to retry (possibly after a delay). * @hide */ @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant - public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) { + public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) throws IOException { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); try { mService.clearVcnConfig(subscriptionGroup); + } catch (ServiceSpecificException e) { + throw new IOException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } |
