diff options
26 files changed, 916 insertions, 233 deletions
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 9ab7c215d82d..564e9186b868 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -3061,7 +3061,9 @@ message ProcessStartTime { * frameworks/base/packages/NetworkStack/ */ message NetworkStackReported { - optional int32 eventId = 1; + // The id that indicates the event reported from NetworkStack. + optional int32 event_id = 1; + // The data for the reported events. optional android.stats.connectivity.NetworkStackEventData network_stack_event = 2 [(log_mode) = MODE_BYTES]; } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index ae93cf019776..4a64128f146b 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -1934,6 +1934,8 @@ public class ConnectivityManager { @NonNull Callback callback) { ParcelFileDescriptor dup; try { + // Dup is needed here as the pfd inside the socket is owned by the IpSecService, + // which cannot be obtained by the app process. dup = ParcelFileDescriptor.dup(socket.getFileDescriptor()); } catch (IOException ignored) { // Construct an invalid fd, so that if the user later calls start(), it will fail with @@ -1975,6 +1977,7 @@ public class ConnectivityManager { @NonNull Callback callback) { ParcelFileDescriptor dup; try { + // TODO: Consider remove unnecessary dup. dup = pfd.dup(); } catch (IOException ignored) { // Construct an invalid fd, so that if the user later calls start(), it will fail with diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index a7d2ee98b45c..b90a60e5bca0 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -373,7 +373,8 @@ public final class NfcAdapter { * A callback to be invoked when the system successfully delivers your {@link NdefMessage} * to another device. * @see #setOnNdefPushCompleteCallback - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public interface OnNdefPushCompleteCallback { @@ -398,7 +399,8 @@ public final class NfcAdapter { * content currently visible to the user. Alternatively, you can call {@link * #setNdefPushMessage setNdefPushMessage()} if the {@link NdefMessage} always contains the * same data. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public interface CreateNdefMessageCallback { @@ -427,7 +429,8 @@ public final class NfcAdapter { /** - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public interface CreateBeamUrisCallback { @@ -981,7 +984,8 @@ public final class NfcAdapter { * @param uris an array of Uri(s) to push over Android Beam * @param activity activity for which the Uri(s) will be pushed * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setBeamPushUris(Uri[] uris, Activity activity) { @@ -1068,7 +1072,8 @@ public final class NfcAdapter { * @param callback callback, or null to disable * @param activity activity for which the Uri(s) will be pushed * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) { @@ -1157,7 +1162,8 @@ public final class NfcAdapter { * to only register one at a time, and to do so in that activity's * {@link Activity#onCreate} * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setNdefPushMessage(NdefMessage message, Activity activity, @@ -1275,7 +1281,8 @@ public final class NfcAdapter { * to only register one at a time, and to do so in that activity's * {@link Activity#onCreate} * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity, @@ -1361,7 +1368,8 @@ public final class NfcAdapter { * to only register one at a time, and to do so in that activity's * {@link Activity#onCreate} * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback, @@ -1577,7 +1585,8 @@ public final class NfcAdapter { * @param activity the current foreground Activity that has registered data to share * @return whether the Beam animation was successfully invoked * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public boolean invokeBeam(Activity activity) { @@ -1821,7 +1830,8 @@ public final class NfcAdapter { * @see android.provider.Settings#ACTION_NFCSHARING_SETTINGS * @return true if NDEF Push feature is enabled * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 71879194ccf6..9aedb7618142 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -608,6 +608,9 @@ <protected-broadcast android:name="android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL" /> + <!-- For tether entitlement recheck--> + <protected-broadcast + android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" /> <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> <!-- ====================================================================== --> diff --git a/core/xsd/permission.xsd b/core/xsd/permission.xsd index d90863b2c716..12281242e9f7 100644 --- a/core/xsd/permission.xsd +++ b/core/xsd/permission.xsd @@ -60,8 +60,6 @@ <xs:attribute name="uid" type="xs:int"/> </xs:complexType> <xs:complexType name="split-permission"> - <xs:attribute name="name" type="xs:string"/> - <xs:attribute name="targetSdk" type="xs:int"/> <xs:sequence> <xs:element name="library" maxOccurs="unbounded"> <xs:complexType> @@ -69,6 +67,8 @@ </xs:complexType> </xs:element> </xs:sequence> + <xs:attribute name="name" type="xs:string"/> + <xs:attribute name="targetSdk" type="xs:int"/> </xs:complexType> <xs:complexType name="library"> <xs:attribute name="name" type="xs:string"/> @@ -124,7 +124,6 @@ <xs:attribute name="package" type="xs:string"/> </xs:complexType> <xs:complexType name="privapp-permissions"> - <xs:attribute name="package" type="xs:string"/> <xs:sequence> <xs:element name="permission" maxOccurs="unbounded"> <xs:complexType> @@ -137,9 +136,9 @@ </xs:complexType> </xs:element> </xs:sequence> + <xs:attribute name="package" type="xs:string"/> </xs:complexType> <xs:complexType name="oem-permissions"> - <xs:attribute name="package" type="xs:string"/> <xs:sequence> <xs:element name="permission" maxOccurs="unbounded"> <xs:complexType> @@ -152,6 +151,7 @@ </xs:complexType> </xs:element> </xs:sequence> + <xs:attribute name="package" type="xs:string"/> </xs:complexType> <xs:complexType name="hidden-api-whitelisted-app"> <xs:attribute name="package" type="xs:string"/> diff --git a/packages/ExternalStorageProvider/Android.bp b/packages/ExternalStorageProvider/Android.bp new file mode 100644 index 000000000000..973fef3e666d --- /dev/null +++ b/packages/ExternalStorageProvider/Android.bp @@ -0,0 +1,19 @@ +android_app { + name: "ExternalStorageProvider", + + manifest: "AndroidManifest.xml", + + resource_dirs: [ + "res", + ], + + srcs: [ + "src/**/*.java", + ], + + platform_apis: true, + + certificate: "platform", + + privileged: true, +} diff --git a/packages/ExternalStorageProvider/Android.mk b/packages/ExternalStorageProvider/Android.mk deleted file mode 100644 index 9e99313cd03a..000000000000 --- a/packages/ExternalStorageProvider/Android.mk +++ /dev/null @@ -1,13 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := optional - -LOCAL_SRC_FILES := $(call all-subdir-java-files) - -LOCAL_PACKAGE_NAME := ExternalStorageProvider -LOCAL_PRIVATE_PLATFORM_APIS := true -LOCAL_CERTIFICATE := platform -LOCAL_PRIVILEGED_MODULE := true - -include $(BUILD_PACKAGE) diff --git a/packages/ExternalStorageProvider/tests/Android.bp b/packages/ExternalStorageProvider/tests/Android.bp new file mode 100644 index 000000000000..83427d4ebf00 --- /dev/null +++ b/packages/ExternalStorageProvider/tests/Android.bp @@ -0,0 +1,25 @@ +android_test { + name: "ExternalStorageProviderTests", + + manifest: "AndroidManifest.xml", + + srcs: [ + "src/**/*.java", + ], + + libs: [ + "android.test.base", + "android.test.mock", + "android.test.runner", + ], + + static_libs: [ + "android-support-test", + "mockito-target", + "truth-prebuilt", + ], + + certificate: "platform", + + instrumentation_for: "ExternalStorageProvider", +} diff --git a/packages/ExternalStorageProvider/tests/AndroidManifest.xml b/packages/ExternalStorageProvider/tests/AndroidManifest.xml new file mode 100644 index 000000000000..58b6e86dfc77 --- /dev/null +++ b/packages/ExternalStorageProvider/tests/AndroidManifest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.externalstorage.tests"> + + <application android:label="ExternalStorageProvider Tests"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.externalstorage" + android:label="ExternalStorageProvider Tests" /> +</manifest> + diff --git a/packages/ExternalStorageProvider/tests/AndroidTest.xml b/packages/ExternalStorageProvider/tests/AndroidTest.xml new file mode 100644 index 000000000000..e5fa73f59836 --- /dev/null +++ b/packages/ExternalStorageProvider/tests/AndroidTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs Tests for ExternalStorageProvider."> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="ExternalStorageProviderTests.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="ExternalStorageProviderTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.externalstorage.tests" /> + <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java new file mode 100644 index 000000000000..a88b3e146ea1 --- /dev/null +++ b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.externalstorage; + +import static com.android.externalstorage.ExternalStorageProvider.AUTHORITY; + +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.pm.ProviderInfo; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class ExternalStorageProviderTest { + @Test + public void onCreate_shouldUpdateVolumes() throws Exception { + ExternalStorageProvider externalStorageProvider = new ExternalStorageProvider(); + ExternalStorageProvider spyProvider = spy(externalStorageProvider); + ProviderInfo providerInfo = new ProviderInfo(); + providerInfo.authority = AUTHORITY; + providerInfo.grantUriPermissions = true; + providerInfo.exported = true; + + InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + spyProvider.attachInfoForTesting( + InstrumentationRegistry.getTargetContext(), providerInfo); + } + }); + + verify(spyProvider, atLeast(1)).updateVolumes(); + } +} diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp index 52534a8fbae7..0bd5c5f59133 100644 --- a/packages/NetworkStack/Android.bp +++ b/packages/NetworkStack/Android.bp @@ -49,6 +49,9 @@ java_defaults { // Resources already included in NetworkStackBase resource_dirs: [], jarjar_rules: "jarjar-rules-shared.txt", + optimize: { + proguard_flags_files: ["proguard.flags"], + }, // The permission configuration *must* be included to ensure security of the device required: ["NetworkStackPermissionStub"], } diff --git a/packages/NetworkStack/proguard.flags b/packages/NetworkStack/proguard.flags new file mode 100644 index 000000000000..c60f6c338d83 --- /dev/null +++ b/packages/NetworkStack/proguard.flags @@ -0,0 +1,9 @@ +-keepclassmembers class android.net.ip.IpClient { + static final int CMD_*; + static final int EVENT_*; +} + +-keepclassmembers class android.net.dhcp.DhcpClient { + static final int CMD_*; + static final int EVENT_*; +} diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java index c6dd0117477b..79d6a554e251 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java @@ -126,6 +126,7 @@ public class DhcpClient extends StateMachine { // DhcpClient uses IpClient's handler. private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE; + // Below constants are picked up by MessageUtils and exempt from ProGuard optimization. /* Commands from controller to start/stop DHCP */ public static final int CMD_START_DHCP = PUBLIC_BASE + 1; public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2; diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java index 346ac68407de..7a06af41f951 100644 --- a/packages/NetworkStack/src/android/net/ip/IpClient.java +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -282,6 +282,7 @@ public class IpClient extends StateMachine { public static final String DUMP_ARG_CONFIRM = "confirm"; + // Below constants are picked up by MessageUtils and exempt from ProGuard optimization. private static final int CMD_TERMINATE_AFTER_STOP = 1; private static final int CMD_STOP = 2; private static final int CMD_START = 3; diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java index ce887eb4f0fe..d7a57b992eef 100644 --- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -154,12 +154,19 @@ public class KeepaliveTracker { // keepalives are sent cannot be reused by another app even if the fd gets closed by // the user. A null is acceptable here for backward compatibility of PacketKeepalive // API. - // TODO: don't accept null fd after legacy packetKeepalive API is removed. try { if (fd != null) { mFd = Os.dup(fd); } else { - Log.d(TAG, "uid/pid " + mUid + "/" + mPid + " calls with null fd"); + Log.d(TAG, toString() + " calls with null fd"); + if (!mPrivileged) { + throw new SecurityException( + "null fd is not allowed for unprivileged access."); + } + if (mType == TYPE_TCP) { + throw new IllegalArgumentException( + "null fd is not allowed for tcp socket keepalives."); + } mFd = null; } } catch (ErrnoException e) { @@ -480,7 +487,6 @@ public class KeepaliveTracker { } } else { // Keepalive successfully stopped, or error. - ki.mStartedState = KeepaliveInfo.NOT_STARTED; if (reason == SUCCESS) { // The message indicated success stopping : don't call handleStopKeepalive. if (DBG) Log.d(TAG, "Successfully stopped keepalive " + slot + " on " + nai.name()); @@ -490,6 +496,7 @@ public class KeepaliveTracker { handleStopKeepalive(nai, slot, reason); if (DBG) Log.d(TAG, "Keepalive " + slot + " on " + nai.name() + " error " + reason); } + ki.mStartedState = KeepaliveInfo.NOT_STARTED; } } @@ -531,7 +538,8 @@ public class KeepaliveTracker { try { ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds, KeepaliveInfo.TYPE_NATT, fd); - } catch (InvalidSocketException e) { + } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) { + Log.e(TAG, "Fail to construct keepalive", e); notifyErrorCallback(cb, ERROR_INVALID_SOCKET); return; } @@ -570,7 +578,8 @@ public class KeepaliveTracker { try { ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds, KeepaliveInfo.TYPE_TCP, fd); - } catch (InvalidSocketException e) { + } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) { + Log.e(TAG, "Fail to construct keepalive e=" + e); notifyErrorCallback(cb, ERROR_INVALID_SOCKET); return; } diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 828a1e58868a..ac3d6def6f80 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -19,6 +19,7 @@ package com.android.server.connectivity; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; import android.app.Notification; import android.app.NotificationManager; @@ -26,9 +27,12 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.net.NetworkSpecifier; +import android.net.StringNetworkSpecifier; import android.net.wifi.WifiInfo; import android.os.UserHandle; import android.telephony.AccessNetworkConstants.TransportType; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Slog; @@ -195,7 +199,20 @@ public class NetworkNotificationManager { title = r.getString(R.string.network_available_sign_in, 0); // TODO: Change this to pull from NetworkInfo once a printable // name has been added to it - details = mTelephonyManager.getNetworkOperatorName(); + NetworkSpecifier specifier = nai.networkCapabilities.getNetworkSpecifier(); + int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; + if (specifier instanceof StringNetworkSpecifier) { + try { + subId = Integer.parseInt( + ((StringNetworkSpecifier) specifier).specifier); + } catch (NumberFormatException e) { + Slog.e(TAG, "NumberFormatException on " + + ((StringNetworkSpecifier) specifier).specifier); + } + } + + details = mTelephonyManager.createForSubscriptionId(subId) + .getNetworkOperatorName(); break; default: title = r.getString(R.string.network_available_sign_in, 0); diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 37fe3d094179..0b1a98ee6c55 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -82,7 +82,6 @@ import android.os.Handler; import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; -import android.os.Parcel; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; @@ -230,8 +229,11 @@ public class Tethering extends BaseNetworkObserver { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); - mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM, - mLog, systemProperties); + // EntitlementManager will send EVENT_UPSTREAM_PERMISSION_CHANGED when cellular upstream + // permission is changed according to entitlement check result. + mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM, mLog, + TetherMasterSM.EVENT_UPSTREAM_PERMISSION_CHANGED, systemProperties); + mCarrierConfigChange = new VersionedBroadcastListener( "CarrierConfigChangeListener", mContext, mHandler, filter, (Intent ignored) -> { @@ -363,55 +365,28 @@ public class Tethering extends BaseNetworkObserver { } public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) { - mEntitlementMgr.startTethering(type); - if (!mEntitlementMgr.isTetherProvisioningRequired()) { - enableTetheringInternal(type, true, receiver); - return; - } - - final ResultReceiver proxyReceiver = getProxyReceiver(type, receiver); - if (showProvisioningUi) { - mEntitlementMgr.runUiTetherProvisioningAndEnable(type, proxyReceiver); - } else { - mEntitlementMgr.runSilentTetherProvisioningAndEnable(type, proxyReceiver); - } + mEntitlementMgr.startProvisioningIfNeeded(type, showProvisioningUi); + enableTetheringInternal(type, true /* enabled */, receiver); } public void stopTethering(int type) { - enableTetheringInternal(type, false, null); - mEntitlementMgr.stopTethering(type); - if (mEntitlementMgr.isTetherProvisioningRequired()) { - // There are lurking bugs where the notion of "provisioning required" or - // "tethering supported" may change without notifying tethering properly, then - // tethering can't shutdown correctly. - // TODO: cancel re-check all the time - if (mDeps.isTetheringSupported()) { - mEntitlementMgr.cancelTetherProvisioningRechecks(type); - } - } + enableTetheringInternal(type, false /* disabled */, null); + mEntitlementMgr.stopProvisioningIfNeeded(type); } /** - * Enables or disables tethering for the given type. This should only be called once - * provisioning has succeeded or is not necessary. It will also schedule provisioning rechecks - * for the specified interface. + * Enables or disables tethering for the given type. If provisioning is required, it will + * schedule provisioning rechecks for the specified interface. */ private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) { - boolean isProvisioningRequired = enable && mEntitlementMgr.isTetherProvisioningRequired(); int result; switch (type) { case TETHERING_WIFI: result = setWifiTethering(enable); - if (isProvisioningRequired && result == TETHER_ERROR_NO_ERROR) { - mEntitlementMgr.scheduleProvisioningRechecks(type); - } sendTetherResult(receiver, result); break; case TETHERING_USB: result = setUsbTethering(enable); - if (isProvisioningRequired && result == TETHER_ERROR_NO_ERROR) { - mEntitlementMgr.scheduleProvisioningRechecks(type); - } sendTetherResult(receiver, result); break; case TETHERING_BLUETOOTH: @@ -469,46 +444,11 @@ public class Tethering extends BaseNetworkObserver { ? TETHER_ERROR_NO_ERROR : TETHER_ERROR_MASTER_ERROR; sendTetherResult(receiver, result); - if (enable && mEntitlementMgr.isTetherProvisioningRequired()) { - mEntitlementMgr.scheduleProvisioningRechecks(TETHERING_BLUETOOTH); - } adapter.closeProfileProxy(BluetoothProfile.PAN, proxy); } }, BluetoothProfile.PAN); } - /** - * Creates a proxy {@link ResultReceiver} which enables tethering if the provisioning result - * is successful before firing back up to the wrapped receiver. - * - * @param type The type of tethering being enabled. - * @param receiver A ResultReceiver which will be called back with an int resultCode. - * @return The proxy receiver. - */ - private ResultReceiver getProxyReceiver(final int type, final ResultReceiver receiver) { - ResultReceiver rr = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - // If provisioning is successful, enable tethering, otherwise just send the error. - if (resultCode == TETHER_ERROR_NO_ERROR) { - enableTetheringInternal(type, true, receiver); - } else { - sendTetherResult(receiver, resultCode); - } - mEntitlementMgr.updateEntitlementCacheValue(type, resultCode); - } - }; - - // The following is necessary to avoid unmarshalling issues when sending the receiver - // across processes. - Parcel parcel = Parcel.obtain(); - rr.writeToParcel(parcel,0); - parcel.setDataPosition(0); - ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel); - parcel.recycle(); - return receiverForSending; - } - public int tether(String iface) { return tether(iface, IpServer.STATE_TETHERED); } @@ -787,6 +727,7 @@ public class Tethering extends BaseNetworkObserver { if (!usbConnected && mRndisEnabled) { // Turn off tethering if it was enabled and there is a disconnect. tetherMatchingInterfaces(IpServer.STATE_AVAILABLE, TETHERING_USB); + mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_USB); } else if (usbConfigured && rndisEnabled) { // Tether if rndis is enabled and usb is configured. tetherMatchingInterfaces(IpServer.STATE_TETHERED, TETHERING_USB); @@ -813,6 +754,7 @@ public class Tethering extends BaseNetworkObserver { case WifiManager.WIFI_AP_STATE_FAILED: default: disableWifiIpServingLocked(ifname, curState); + mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_WIFI); break; } } @@ -1090,6 +1032,8 @@ public class Tethering extends BaseNetworkObserver { // we treated the error and want now to clear it static final int CMD_CLEAR_ERROR = BASE_MASTER + 6; static final int EVENT_IFACE_UPDATE_LINKPROPERTIES = BASE_MASTER + 7; + // Events from EntitlementManager to choose upstream again. + static final int EVENT_UPSTREAM_PERMISSION_CHANGED = BASE_MASTER + 8; private final State mInitialState; private final State mTetherModeAliveState; @@ -1504,6 +1448,7 @@ public class Tethering extends BaseNetworkObserver { } break; } + case EVENT_UPSTREAM_PERMISSION_CHANGED: case CMD_UPSTREAM_CHANGED: updateUpstreamWanted(); if (!mUpstreamWanted) break; @@ -1694,7 +1639,8 @@ public class Tethering extends BaseNetworkObserver { } public void systemReady() { - mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest()); + mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest(), + mEntitlementMgr); } /** Get the latest value of the tethering entitlement check. */ @@ -1755,6 +1701,11 @@ public class Tethering extends BaseNetworkObserver { cfg.dump(pw); pw.decreaseIndent(); + pw.println("Entitlement:"); + pw.increaseIndent(); + mEntitlementMgr.dump(pw); + pw.decreaseIndent(); + synchronized (mPublicSync) { pw.println("Tether state:"); pw.increaseIndent(); diff --git a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java index 70ab38983446..5c4539741cae 100644 --- a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java +++ b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java @@ -18,9 +18,11 @@ package com.android.server.connectivity.tethering; import static android.net.ConnectivityManager.EXTRA_ADD_TETHER_TYPE; import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK; -import static android.net.ConnectivityManager.EXTRA_REM_TETHER_TYPE; import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION; -import static android.net.ConnectivityManager.EXTRA_SET_ALARM; +import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; +import static android.net.ConnectivityManager.TETHERING_INVALID; +import static android.net.ConnectivityManager.TETHERING_USB; +import static android.net.ConnectivityManager.TETHERING_WIFI; import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; @@ -28,17 +30,24 @@ import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; import static com.android.internal.R.string.config_wifi_tether_enable; import android.annotation.Nullable; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Resources; import android.net.util.SharedLog; import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.Parcel; import android.os.PersistableBundle; import android.os.ResultReceiver; +import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.telephony.CarrierConfigManager; @@ -46,48 +55,78 @@ import android.util.ArraySet; import android.util.Log; import android.util.SparseIntArray; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.StateMachine; import com.android.server.connectivity.MockableSystemProperties; +import java.io.PrintWriter; + /** - * This class encapsulates entitlement/provisioning mechanics - * provisioning check only applies to the use of the mobile network as an upstream + * Re-check tethering provisioning for enabled downstream tether types. + * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. * + * All methods of this class must be accessed from the thread of tethering + * state machine. * @hide */ public class EntitlementManager { private static final String TAG = EntitlementManager.class.getSimpleName(); private static final boolean DBG = false; + @VisibleForTesting + protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning"; + private static final String ACTION_PROVISIONING_ALARM = + "com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM"; + // {@link ComponentName} of the Service used to run tether provisioning. private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString( Resources.getSystem().getString(config_wifi_tether_enable)); - protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning"; + private static final int MS_PER_HOUR = 60 * 60 * 1000; + private static final int EVENT_START_PROVISIONING = 0; + private static final int EVENT_STOP_PROVISIONING = 1; + private static final int EVENT_UPSTREAM_CHANGED = 2; + private static final int EVENT_MAYBE_RUN_PROVISIONING = 3; + private static final int EVENT_GET_ENTITLEMENT_VALUE = 4; + // The ArraySet contains enabled downstream types, ex: // {@link ConnectivityManager.TETHERING_WIFI} // {@link ConnectivityManager.TETHERING_USB} // {@link ConnectivityManager.TETHERING_BLUETOOTH} - @GuardedBy("mCurrentTethers") private final ArraySet<Integer> mCurrentTethers; private final Context mContext; + private final int mPermissionChangeMessageCode; private final MockableSystemProperties mSystemProperties; private final SharedLog mLog; - private final Handler mMasterHandler; private final SparseIntArray mEntitlementCacheValue; - @Nullable - private TetheringConfiguration mConfig; + private final EntitlementHandler mHandler; + private @Nullable TetheringConfiguration mConfig; + private final StateMachine mTetherMasterSM; + // Key: ConnectivityManager.TETHERING_*(downstream). + // Value: ConnectivityManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result). + private final SparseIntArray mCellularPermitted; + private PendingIntent mProvisioningRecheckAlarm; + private boolean mCellularUpstreamPermitted = true; + private boolean mUsingCellularAsUpstream = false; + private boolean mNeedReRunProvisioningUi = false; public EntitlementManager(Context ctx, StateMachine tetherMasterSM, SharedLog log, - MockableSystemProperties systemProperties) { + int permissionChangeMessageCode, MockableSystemProperties systemProperties) { + mContext = ctx; mLog = log.forSubComponent(TAG); mCurrentTethers = new ArraySet<Integer>(); + mCellularPermitted = new SparseIntArray(); mSystemProperties = systemProperties; mEntitlementCacheValue = new SparseIntArray(); - mMasterHandler = tetherMasterSM.getHandler(); + mTetherMasterSM = tetherMasterSM; + mPermissionChangeMessageCode = permissionChangeMessageCode; + final Handler masterHandler = tetherMasterSM.getHandler(); + // Create entitlement's own handler which is associated with TetherMaster thread + // let all entitlement processes run in the same thread. + mHandler = new EntitlementHandler(masterHandler.getLooper()); + mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM), + null, mHandler); } /** @@ -99,24 +138,118 @@ public class EntitlementManager { } /** - * Tell EntitlementManager that a given type of tethering has been enabled + * Check if cellular upstream is permitted. + */ + public boolean isCellularUpstreamPermitted() { + return mCellularUpstreamPermitted; + } + + /** + * This is called when tethering starts. + * Launch provisioning app if upstream is cellular. * - * @param type Tethering type + * @param downstreamType tethering type from ConnectivityManager.TETHERING_{@code *} + * @param showProvisioningUi a boolean indicating whether to show the + * provisioning app UI if there is one. */ - public void startTethering(int type) { - synchronized (mCurrentTethers) { - mCurrentTethers.add(type); + public void startProvisioningIfNeeded(int downstreamType, boolean showProvisioningUi) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_START_PROVISIONING, + downstreamType, encodeBool(showProvisioningUi))); + } + + private void handleStartProvisioningIfNeeded(int type, boolean showProvisioningUi) { + if (!isValidDownstreamType(type)) return; + + if (!mCurrentTethers.contains(type)) mCurrentTethers.add(type); + + if (isTetherProvisioningRequired()) { + // If provisioning is required and the result is not available yet, + // cellular upstream should not be allowed. + if (mCellularPermitted.size() == 0) { + mCellularUpstreamPermitted = false; + } + // If upstream is not cellular, provisioning app would not be launched + // till upstream change to cellular. + if (mUsingCellularAsUpstream) { + if (showProvisioningUi) { + runUiTetherProvisioning(type); + } else { + runSilentTetherProvisioning(type); + } + mNeedReRunProvisioningUi = false; + } else { + mNeedReRunProvisioningUi |= showProvisioningUi; + } + } else { + mCellularUpstreamPermitted = true; } } /** * Tell EntitlementManager that a given type of tethering has been disabled * - * @param type Tethering type + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + */ + public void stopProvisioningIfNeeded(int type) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_STOP_PROVISIONING, type, 0)); + } + + private void handleStopProvisioningIfNeeded(int type) { + if (!isValidDownstreamType(type)) return; + + mCurrentTethers.remove(type); + // There are lurking bugs where the notion of "provisioning required" or + // "tethering supported" may change without without tethering being notified properly. + // Remove the mapping all the time no matter provisioning is required or not. + removeDownstreamMapping(type); + } + + /** + * Notify EntitlementManager if upstream is cellular or not. + * + * @param isCellular whether tethering upstream is cellular. */ - public void stopTethering(int type) { - synchronized (mCurrentTethers) { - mCurrentTethers.remove(type); + public void notifyUpstream(boolean isCellular) { + mHandler.sendMessage(mHandler.obtainMessage( + EVENT_UPSTREAM_CHANGED, encodeBool(isCellular), 0)); + } + + private void handleNotifyUpstream(boolean isCellular) { + if (DBG) { + Log.d(TAG, "notifyUpstream: " + isCellular + + ", mCellularUpstreamPermitted: " + mCellularUpstreamPermitted + + ", mNeedReRunProvisioningUi: " + mNeedReRunProvisioningUi); + } + mUsingCellularAsUpstream = isCellular; + + if (mUsingCellularAsUpstream) { + handleMaybeRunProvisioning(); + } + } + + /** Run provisioning if needed */ + public void maybeRunProvisioning() { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_MAYBE_RUN_PROVISIONING)); + } + + private void handleMaybeRunProvisioning() { + if (mCurrentTethers.size() == 0 || !isTetherProvisioningRequired()) { + return; + } + + // Whenever any entitlement value changes, all downstreams will re-evaluate whether they + // are allowed. Therefore even if the silent check here ends in a failure and the UI later + // yields success, then the downstream that got a failure will re-evaluate as a result of + // the change and get the new correct value. + for (Integer downstream : mCurrentTethers) { + if (mCellularPermitted.indexOfKey(downstream) < 0) { + if (mNeedReRunProvisioningUi) { + mNeedReRunProvisioningUi = false; + runUiTetherProvisioning(downstream); + } else { + runSilentTetherProvisioning(downstream); + } + } } } @@ -138,23 +271,32 @@ public class EntitlementManager { } /** - * Re-check tethering provisioning for enabled downstream tether types. + * Re-check tethering provisioning for all enabled tether types. * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. + * + * Note: this method is only called from TetherMaster on the handler thread. + * If there are new callers from different threads, the logic should move to + * masterHandler to avoid race conditions. */ public void reevaluateSimCardProvisioning() { - synchronized (mEntitlementCacheValue) { - mEntitlementCacheValue.clear(); - } + if (DBG) Log.d(TAG, "reevaluateSimCardProvisioning"); - if (!mConfig.hasMobileHotspotProvisionApp()) return; - if (carrierConfigAffirmsEntitlementCheckNotRequired()) return; + if (!mHandler.getLooper().isCurrentThread()) { + // Except for test, this log should not appear in normal flow. + mLog.log("reevaluateSimCardProvisioning() don't run in TetherMaster thread"); + } + mEntitlementCacheValue.clear(); + mCellularPermitted.clear(); - final ArraySet<Integer> reevaluateType; - synchronized (mCurrentTethers) { - reevaluateType = new ArraySet<Integer>(mCurrentTethers); + // TODO: refine provisioning check to isTetherProvisioningRequired() ?? + if (!mConfig.hasMobileHotspotProvisionApp() + || carrierConfigAffirmsEntitlementCheckNotRequired()) { + evaluateCellularPermission(); + return; } - for (Integer type : reevaluateType) { - startProvisionIntent(type); + + if (mUsingCellularAsUpstream) { + handleMaybeRunProvisioning(); } } @@ -189,7 +331,14 @@ public class EntitlementManager { return !isEntitlementCheckRequired; } - public void runSilentTetherProvisioningAndEnable(int type, ResultReceiver receiver) { + /** + * Run no UI tethering provisioning check. + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + */ + protected void runSilentTetherProvisioning(int type) { + if (DBG) Log.d(TAG, "runSilentTetherProvisioning: " + type); + ResultReceiver receiver = buildProxyReceiver(type, null); + Intent intent = new Intent(); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); intent.putExtra(EXTRA_RUN_PROVISION, true); @@ -203,12 +352,20 @@ public class EntitlementManager { } } - public void runUiTetherProvisioningAndEnable(int type, ResultReceiver receiver) { + /** + * Run the UI-enabled tethering provisioning check. + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + */ + @VisibleForTesting + protected void runUiTetherProvisioning(int type) { + ResultReceiver receiver = buildProxyReceiver(type, null); runUiTetherProvisioning(type, receiver); } @VisibleForTesting protected void runUiTetherProvisioning(int type, ResultReceiver receiver) { + if (DBG) Log.d(TAG, "runUiTetherProvisioning: " + type); + Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); @@ -221,56 +378,206 @@ public class EntitlementManager { } } - // Used by the SIM card change observation code. - // TODO: De-duplicate with above code, where possible. - private void startProvisionIntent(int tetherType) { - final Intent startProvIntent = new Intent(); - startProvIntent.putExtra(EXTRA_ADD_TETHER_TYPE, tetherType); - startProvIntent.putExtra(EXTRA_RUN_PROVISION, true); - startProvIntent.setComponent(TETHER_SERVICE); - mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT); + // Not needed to check if this don't run on the handler thread because it's private. + private void scheduleProvisioningRechecks() { + if (mProvisioningRecheckAlarm == null) { + final int period = mConfig.provisioningCheckPeriod; + if (period <= 0) return; + + Intent intent = new Intent(ACTION_PROVISIONING_ALARM); + mProvisioningRecheckAlarm = PendingIntent.getBroadcast(mContext, 0, intent, 0); + AlarmManager alarmManager = (AlarmManager) mContext.getSystemService( + Context.ALARM_SERVICE); + long periodMs = period * MS_PER_HOUR; + long firstAlarmTime = SystemClock.elapsedRealtime() + periodMs; + alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, firstAlarmTime, periodMs, + mProvisioningRecheckAlarm); + } } - public void scheduleProvisioningRechecks(int type) { - Intent intent = new Intent(); - intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); - intent.putExtra(EXTRA_SET_ALARM, true); - intent.setComponent(TETHER_SERVICE); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.startServiceAsUser(intent, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(ident); + private void cancelTetherProvisioningRechecks() { + if (mProvisioningRecheckAlarm != null) { + AlarmManager alarmManager = (AlarmManager) mContext.getSystemService( + Context.ALARM_SERVICE); + alarmManager.cancel(mProvisioningRecheckAlarm); + mProvisioningRecheckAlarm = null; } } - public void cancelTetherProvisioningRechecks(int type) { - Intent intent = new Intent(); - intent.putExtra(EXTRA_REM_TETHER_TYPE, type); - intent.setComponent(TETHER_SERVICE); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.startServiceAsUser(intent, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(ident); + private void evaluateCellularPermission() { + final boolean oldPermitted = mCellularUpstreamPermitted; + mCellularUpstreamPermitted = (!isTetherProvisioningRequired() + || mCellularPermitted.indexOfValue(TETHER_ERROR_NO_ERROR) > -1); + + if (DBG) { + Log.d(TAG, "Cellular permission change from " + oldPermitted + + " to " + mCellularUpstreamPermitted); + } + + if (mCellularUpstreamPermitted != oldPermitted) { + mLog.log("Cellular permission change: " + mCellularUpstreamPermitted); + mTetherMasterSM.sendMessage(mPermissionChangeMessageCode); + } + // Only schedule periodic re-check when tether is provisioned + // and the result is ok. + if (mCellularUpstreamPermitted && mCellularPermitted.size() > 0) { + scheduleProvisioningRechecks(); + } else { + cancelTetherProvisioningRechecks(); + } + } + + /** + * Add the mapping between provisioning result and tethering type. + * Notify UpstreamNetworkMonitor if Cellular permission changes. + * + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param resultCode Provisioning result + */ + protected void addDownstreamMapping(int type, int resultCode) { + if (DBG) { + Log.d(TAG, "addDownstreamMapping: " + type + ", result: " + resultCode + + " ,TetherTypeRequested: " + mCurrentTethers.contains(type)); + } + if (!mCurrentTethers.contains(type)) return; + + mCellularPermitted.put(type, resultCode); + evaluateCellularPermission(); + } + + /** + * Remove the mapping for input tethering type. + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + */ + protected void removeDownstreamMapping(int type) { + if (DBG) Log.d(TAG, "removeDownstreamMapping: " + type); + mCellularPermitted.delete(type); + evaluateCellularPermission(); + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_PROVISIONING_ALARM.equals(intent.getAction())) { + mLog.log("Received provisioning alarm"); + reevaluateSimCardProvisioning(); + } + } + }; + + private class EntitlementHandler extends Handler { + EntitlementHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_START_PROVISIONING: + handleStartProvisioningIfNeeded(msg.arg1, toBool(msg.arg2)); + break; + case EVENT_STOP_PROVISIONING: + handleStopProvisioningIfNeeded(msg.arg1); + break; + case EVENT_UPSTREAM_CHANGED: + handleNotifyUpstream(toBool(msg.arg1)); + break; + case EVENT_MAYBE_RUN_PROVISIONING: + handleMaybeRunProvisioning(); + break; + case EVENT_GET_ENTITLEMENT_VALUE: + handleGetLatestTetheringEntitlementValue(msg.arg1, (ResultReceiver) msg.obj, + toBool(msg.arg2)); + break; + default: + mLog.log("Unknown event: " + msg.what); + break; + } + } + } + + private static boolean toBool(int encodedBoolean) { + return encodedBoolean != 0; + } + + private static int encodeBool(boolean b) { + return b ? 1 : 0; + } + + private static boolean isValidDownstreamType(int type) { + switch (type) { + case TETHERING_BLUETOOTH: + case TETHERING_USB: + case TETHERING_WIFI: + return true; + default: + return false; + } + } + + /** + * Dump the infromation of EntitlementManager. + * @param pw {@link PrintWriter} is used to print formatted + */ + public void dump(PrintWriter pw) { + pw.print("mCellularUpstreamPermitted: "); + pw.println(mCellularUpstreamPermitted); + for (Integer type : mCurrentTethers) { + pw.print("Type: "); + pw.print(typeString(type)); + if (mCellularPermitted.indexOfKey(type) > -1) { + pw.print(", Value: "); + pw.println(errorString(mCellularPermitted.get(type))); + } else { + pw.println(", Value: empty"); + } + } + } + + private static String typeString(int type) { + switch (type) { + case TETHERING_BLUETOOTH: return "TETHERING_BLUETOOTH"; + case TETHERING_INVALID: return "TETHERING_INVALID"; + case TETHERING_USB: return "TETHERING_USB"; + case TETHERING_WIFI: return "TETHERING_WIFI"; + default: + return String.format("TETHERING UNKNOWN TYPE (%d)", type); + } + } + + private static String errorString(int value) { + switch (value) { + case TETHER_ERROR_ENTITLEMENT_UNKONWN: return "TETHER_ERROR_ENTITLEMENT_UNKONWN"; + case TETHER_ERROR_NO_ERROR: return "TETHER_ERROR_NO_ERROR"; + case TETHER_ERROR_PROVISION_FAILED: return "TETHER_ERROR_PROVISION_FAILED"; + default: + return String.format("UNKNOWN ERROR (%d)", value); } } private ResultReceiver buildProxyReceiver(int type, final ResultReceiver receiver) { - ResultReceiver rr = new ResultReceiver(mMasterHandler) { + ResultReceiver rr = new ResultReceiver(mHandler) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { int updatedCacheValue = updateEntitlementCacheValue(type, resultCode); - receiver.send(updatedCacheValue, null); + addDownstreamMapping(type, updatedCacheValue); + if (receiver != null) receiver.send(updatedCacheValue, null); } }; return writeToParcel(rr); } + // Instances of ResultReceiver need to be public classes for remote processes to be able + // to load them (otherwise, ClassNotFoundException). For private classes, this method + // performs a trick : round-trip parceling any instance of ResultReceiver will return a + // vanilla instance of ResultReceiver sharing the binder token with the original receiver. + // The binder token has a reference to the original instance of the private class and will + // still call its methods, and can be sent over. However it cannot be used for anything + // else than sending over a Binder call. + // While round-trip parceling is not great, there is currently no other way of generating + // a vanilla instance of ResultReceiver because all its fields are private. private ResultReceiver writeToParcel(final ResultReceiver receiver) { - // This is necessary to avoid unmarshalling issues when sending the receiver - // across processes. Parcel parcel = Parcel.obtain(); receiver.writeToParcel(parcel, 0); parcel.setDataPosition(0); @@ -286,34 +593,37 @@ public class EntitlementManager { * @param resultCode last entitlement value * @return the last updated entitlement value */ - public int updateEntitlementCacheValue(int type, int resultCode) { + private int updateEntitlementCacheValue(int type, int resultCode) { if (DBG) { Log.d(TAG, "updateEntitlementCacheValue: " + type + ", result: " + resultCode); } - synchronized (mEntitlementCacheValue) { - if (resultCode == TETHER_ERROR_NO_ERROR) { - mEntitlementCacheValue.put(type, resultCode); - return resultCode; - } else { - mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISION_FAILED); - return TETHER_ERROR_PROVISION_FAILED; - } + if (resultCode == TETHER_ERROR_NO_ERROR) { + mEntitlementCacheValue.put(type, resultCode); + return resultCode; + } else { + mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISION_FAILED); + return TETHER_ERROR_PROVISION_FAILED; } } /** Get the last value of the tethering entitlement check. */ public void getLatestTetheringEntitlementResult(int downstream, ResultReceiver receiver, boolean showEntitlementUi) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_GET_ENTITLEMENT_VALUE, + downstream, encodeBool(showEntitlementUi), receiver)); + + } + + private void handleGetLatestTetheringEntitlementValue(int downstream, ResultReceiver receiver, + boolean showEntitlementUi) { + if (!isTetherProvisioningRequired()) { receiver.send(TETHER_ERROR_NO_ERROR, null); return; } - final int cacheValue; - synchronized (mEntitlementCacheValue) { - cacheValue = mEntitlementCacheValue.get( + final int cacheValue = mEntitlementCacheValue.get( downstream, TETHER_ERROR_ENTITLEMENT_UNKONWN); - } if (cacheValue == TETHER_ERROR_NO_ERROR || !showEntitlementUi) { receiver.send(cacheValue, null); } else { diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java index 935b79546d63..8427b6eceab9 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java @@ -30,6 +30,7 @@ import static com.android.internal.R.array.config_tether_upstream_types; import static com.android.internal.R.array.config_tether_usb_regexs; import static com.android.internal.R.array.config_tether_wifi_regexs; import static com.android.internal.R.bool.config_tether_upstream_automatic; +import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period; import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui; import android.content.ContentResolver; @@ -94,6 +95,7 @@ public class TetheringConfiguration { public final String[] provisioningApp; public final String provisioningAppNoUi; + public final int provisioningCheckPeriod; public final int subId; @@ -121,6 +123,9 @@ public class TetheringConfiguration { provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app); provisioningAppNoUi = getProvisioningAppNoUi(res); + provisioningCheckPeriod = getResourceInteger(res, + config_mobile_hotspot_provision_check_period, + 0 /* No periodic re-check */); configLog.log(toString()); } @@ -311,6 +316,14 @@ public class TetheringConfiguration { } } + private static int getResourceInteger(Resources res, int resId, int defaultValue) { + try { + return res.getInteger(resId); + } catch (Resources.NotFoundException e404) { + return defaultValue; + } + } + private static boolean getEnableLegacyDhcpServer(Context ctx) { final ContentResolver cr = ctx.getContentResolver(); final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java index 173d7860e4ac..a0aad7c50481 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java @@ -83,8 +83,8 @@ public class TetheringDependencies { * Get a reference to the EntitlementManager to be used by tethering. */ public EntitlementManager getEntitlementManager(Context ctx, StateMachine target, - SharedLog log, MockableSystemProperties systemProperties) { - return new EntitlementManager(ctx, target, log, systemProperties); + SharedLog log, int what, MockableSystemProperties systemProperties) { + return new EntitlementManager(ctx, target, log, what, systemProperties); } /** diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index 3ac311b3e13a..3a9e21f943d8 100644 --- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -16,36 +16,32 @@ package com.android.server.connectivity.tethering; -import static android.net.ConnectivityManager.getNetworkTypeName; -import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; +import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.os.Process; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.IpPrefix; -import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.NetworkState; -import android.net.util.NetworkConstants; import android.net.util.PrefixUtils; import android.net.util.SharedLog; +import android.os.Handler; +import android.os.Process; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.StateMachine; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Set; @@ -97,10 +93,13 @@ public class UpstreamNetworkMonitor { private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>(); private HashSet<IpPrefix> mLocalPrefixes; private ConnectivityManager mCM; + private EntitlementManager mEntitlementMgr; private NetworkCallback mListenAllCallback; private NetworkCallback mDefaultNetworkCallback; private NetworkCallback mMobileNetworkCallback; private boolean mDunRequired; + // Whether the current default upstream is mobile or not. + private boolean mIsDefaultCellularUpstream; // The current system default network (not really used yet). private Network mDefaultInternetNetwork; // The current upstream network used for tethering. @@ -113,6 +112,7 @@ public class UpstreamNetworkMonitor { mLog = log.forSubComponent(TAG); mWhat = what; mLocalPrefixes = new HashSet<>(); + mIsDefaultCellularUpstream = false; } @VisibleForTesting @@ -122,7 +122,15 @@ public class UpstreamNetworkMonitor { mCM = cm; } - public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest) { + /** + * Tracking the system default network. This method should be called when system is ready. + * + * @param defaultNetworkRequest should be the same as ConnectivityService default request + * @param entitle a EntitlementManager object to communicate between EntitlementManager and + * UpstreamNetworkMonitor + */ + public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest, + EntitlementManager entitle) { // This is not really a "request", just a way of tracking the system default network. // It's guaranteed not to actually bring up any networks because it's the same request // as the ConnectivityService default request, and thus shares fate with it. We can't @@ -133,6 +141,9 @@ public class UpstreamNetworkMonitor { mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET); cm().requestNetwork(trackDefaultRequest, mDefaultNetworkCallback, mHandler); } + if (mEntitlementMgr == null) { + mEntitlementMgr = entitle; + } } public void startObserveAllNetworks() { @@ -168,11 +179,15 @@ public class UpstreamNetworkMonitor { } public void registerMobileNetworkRequest() { + if (!isCellularUpstreamPermitted()) { + mLog.i("registerMobileNetworkRequest() is not permitted"); + releaseMobileNetworkRequest(); + return; + } if (mMobileNetworkCallback != null) { mLog.e("registerMobileNetworkRequest() already registered"); return; } - // The following use of the legacy type system cannot be removed until // after upstream selection no longer finds networks by legacy type. // See also http://b/34364553 . @@ -206,29 +221,32 @@ public class UpstreamNetworkMonitor { // becomes available and useful we (a) file a request to keep it up as // necessary and (b) change all upstream tracking state accordingly (by // passing LinkProperties up to Tethering). - // - // Next TODO: return NetworkState instead of just the type. public NetworkState selectPreferredUpstreamType(Iterable<Integer> preferredTypes) { final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType( - mNetworkMap.values(), preferredTypes); + mNetworkMap.values(), preferredTypes, isCellularUpstreamPermitted()); mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type)); switch (typeStatePair.type) { case TYPE_MOBILE_DUN: case TYPE_MOBILE_HIPRI: + // Tethering just selected mobile upstream in spite of the default network being + // not mobile. This can happen because of the priority list. + // Notify EntitlementManager to check permission for using mobile upstream. + if (!mIsDefaultCellularUpstream) { + mEntitlementMgr.maybeRunProvisioning(); + } // If we're on DUN, put our own grab on it. registerMobileNetworkRequest(); break; case TYPE_NONE: + // If we found NONE and mobile upstream is permitted we don't want to do this + // as we want any previous requests to keep trying to bring up something we can use. + if (!isCellularUpstreamPermitted()) releaseMobileNetworkRequest(); break; default: - /* If we've found an active upstream connection that's not DUN/HIPRI - * we should stop any outstanding DUN/HIPRI requests. - * - * If we found NONE we don't want to do this as we want any previous - * requests to keep trying to bring up something we can use. - */ + // If we've found an active upstream connection that's not DUN/HIPRI + // we should stop any outstanding DUN/HIPRI requests. releaseMobileNetworkRequest(); break; } @@ -241,10 +259,12 @@ public class UpstreamNetworkMonitor { final NetworkState dfltState = (mDefaultInternetNetwork != null) ? mNetworkMap.get(mDefaultInternetNetwork) : null; - if (!mDunRequired) return dfltState; - if (isNetworkUsableAndNotCellular(dfltState)) return dfltState; + if (!isCellularUpstreamPermitted()) return null; + + if (!mDunRequired) return dfltState; + // Find a DUN network. Note that code in Tethering causes a DUN request // to be filed, but this might be moved into this class in future. return findFirstDunNetwork(mNetworkMap.values()); @@ -258,6 +278,15 @@ public class UpstreamNetworkMonitor { return (Set<IpPrefix>) mLocalPrefixes.clone(); } + private boolean isCellularUpstreamPermitted() { + if (mEntitlementMgr != null) { + return mEntitlementMgr.isCellularUpstreamPermitted(); + } else { + // This flow should only happens in testing. + return true; + } + } + private void handleAvailable(Network network) { if (mNetworkMap.containsKey(network)) return; @@ -388,8 +417,14 @@ public class UpstreamNetworkMonitor { public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) { if (mCallbackType == CALLBACK_DEFAULT_INTERNET) { mDefaultInternetNetwork = network; + final boolean newIsCellular = isCellular(newNc); + if (mIsDefaultCellularUpstream != newIsCellular) { + mIsDefaultCellularUpstream = newIsCellular; + mEntitlementMgr.notifyUpstream(newIsCellular); + } return; } + handleNetCap(network, newNc); } @@ -424,8 +459,11 @@ public class UpstreamNetworkMonitor { public void onLost(Network network) { if (mCallbackType == CALLBACK_DEFAULT_INTERNET) { mDefaultInternetNetwork = null; + mIsDefaultCellularUpstream = false; + mEntitlementMgr.notifyUpstream(false); return; } + handleLost(network); // Any non-LISTEN_ALL callback will necessarily concern a network that will // also match the LISTEN_ALL callback by construction of the LISTEN_ALL callback. @@ -454,7 +492,8 @@ public class UpstreamNetworkMonitor { } private static TypeStatePair findFirstAvailableUpstreamByType( - Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) { + Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes, + boolean isCellularUpstreamPermitted) { final TypeStatePair result = new TypeStatePair(); for (int type : preferredTypes) { @@ -466,6 +505,10 @@ public class UpstreamNetworkMonitor { ConnectivityManager.getNetworkTypeName(type)); continue; } + if (!isCellularUpstreamPermitted && isCellular(nc)) { + continue; + } + nc.setSingleUid(Process.myUid()); for (NetworkState value : netStates) { diff --git a/services/net/Android.bp b/services/net/Android.bp index 67fbdc4d95f2..7ef0ac4e1b84 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -69,7 +69,7 @@ java_library_static { srcs: [ ":framework-annotations", "java/android/net/IpMemoryStoreClient.java", - "java/android/net/ipmemorystore/**.java", + "java/android/net/ipmemorystore/**/*.java", ], static_libs: [ "ipmemorystore-aidl-interfaces-java", diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index a95db2242371..6f48da3c34d1 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -4066,8 +4066,6 @@ public class ConnectivityServiceTest { // TODO: 1. Move this outside of ConnectivityServiceTest. // 2. Make test to verify that Nat-T keepalive socket is created by IpSecService. // 3. Mock ipsec service. - // 4. Find a free port instead of a fixed port. - final int srcPort = 12345; final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1"); @@ -4078,7 +4076,8 @@ public class ConnectivityServiceTest { final int invalidKaInterval = 9; final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE); - final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort); + final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(); + final int srcPort = testSocket.getPort(); LinkProperties lp = new LinkProperties(); lp.setInterfaceName("wlan12"); @@ -4198,6 +4197,7 @@ public class ConnectivityServiceTest { // Check that keepalive slots start from 1 and increment. The first one gets slot 1. mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + int srcPort2 = 0; try (SocketKeepalive ka = mCm.createSocketKeepalive( myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { ka.start(validKaInterval); @@ -4205,7 +4205,8 @@ public class ConnectivityServiceTest { // The second one gets slot 2. mWiFiNetworkAgent.setExpectedKeepaliveSlot(2); - final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(6789); + final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(); + srcPort2 = testSocket2.getPort(); TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback(executor); try (SocketKeepalive ka2 = mCm.createSocketKeepalive( myNet, testSocket2, myIPv4, dstIPv4, executor, callback2)) { @@ -4223,6 +4224,10 @@ public class ConnectivityServiceTest { } } + // Check that there is no port leaked after all keepalives and sockets are closed. + assertFalse(isUdpPortInUse(srcPort)); + assertFalse(isUdpPortInUse(srcPort2)); + mWiFiNetworkAgent.disconnect(); waitFor(mWiFiNetworkAgent.getDisconnectedCV()); mWiFiNetworkAgent = null; @@ -4305,7 +4310,6 @@ public class ConnectivityServiceTest { } private void doTestNattSocketKeepalivesFdWithExecutor(Executor executor) throws Exception { - final int srcPort = 12345; final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); final InetAddress anyIPv4 = InetAddress.getByName("0.0.0.0"); final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); @@ -4324,7 +4328,8 @@ public class ConnectivityServiceTest { // Prepare the target file descriptor, keep only one instance. final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE); - final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort); + final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(); + final int srcPort = testSocket.getPort(); final ParcelFileDescriptor testPfd = ParcelFileDescriptor.dup(testSocket.getFileDescriptor()); testSocket.close(); diff --git a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java index bac509802258..9eab4bec2973 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -16,6 +16,7 @@ package com.android.server.connectivity.tethering; +import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; import static android.net.ConnectivityManager.TETHERING_USB; import static android.net.ConnectivityManager.TETHERING_WIFI; import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; @@ -72,6 +73,7 @@ public final class EntitlementManagerTest { private static final int EVENT_EM_UPDATE = 1; private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; + private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app"; @Mock private CarrierConfigManager mCarrierConfigManager; @Mock private Context mContext; @@ -108,10 +110,12 @@ public final class EntitlementManagerTest { public class WrappedEntitlementManager extends EntitlementManager { public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; public boolean everRunUiEntitlement = false; + public int uiProvisionCount = 0; + public int silentProvisionCount = 0; public WrappedEntitlementManager(Context ctx, StateMachine target, - SharedLog log, MockableSystemProperties systemProperties) { - super(ctx, target, log, systemProperties); + SharedLog log, int what, MockableSystemProperties systemProperties) { + super(ctx, target, log, what, systemProperties); } @Override @@ -119,6 +123,16 @@ public final class EntitlementManagerTest { everRunUiEntitlement = true; receiver.send(fakeEntitlementResult, null); } + + @Override + protected void runUiTetherProvisioning(int type) { + uiProvisionCount++; + } + + @Override + protected void runSilentTetherProvisioning(int type) { + silentProvisionCount++; + } } @Before @@ -141,7 +155,8 @@ public final class EntitlementManagerTest { mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); mMockContext = new MockContext(mContext); mSM = new TestStateMachine(); - mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, mSystemProperties); + mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE, + mSystemProperties); mEnMgr.updateConfiguration( new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)); } @@ -158,7 +173,9 @@ public final class EntitlementManagerTest { // Produce some acceptable looking provision app setting if requested. when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) .thenReturn(PROVISIONING_APP_NAME); - // Don't disable tethering provisioning unless requested. + when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui)) + .thenReturn(PROVISIONING_NO_UI_APP_NAME); + // Don't disable tethering provisioning unless requested. when(mSystemProperties.getBoolean(eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), anyBoolean())).thenReturn(false); // Act like the CarrierConfigManager is present and ready unless told otherwise. @@ -238,6 +255,7 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); assertFalse(mEnMgr.everRunUiEntitlement); @@ -254,6 +272,7 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); assertFalse(mEnMgr.everRunUiEntitlement); // 3. No cache value and ui entitlement check is needed. @@ -281,6 +300,7 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); assertFalse(mEnMgr.everRunUiEntitlement); // 5. Cache value is TETHER_ERROR_PROVISION_FAILED and ui entitlement check is needed. @@ -308,6 +328,7 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); assertFalse(mEnMgr.everRunUiEntitlement); // 7. Test get value for other downstream type. @@ -320,19 +341,128 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_USB, receiver, false); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); assertFalse(mEnMgr.everRunUiEntitlement); } void callbackTimeoutHelper(final CountDownLatch latch) throws Exception { if (!latch.await(1, TimeUnit.SECONDS)) { - fail("Timout, fail to recieve callback"); + fail("Timout, fail to receive callback"); } } + + @Test + public void verifyPermissionResult() { + setupForRequiredProvisioning(); + mEnMgr.notifyUpstream(true); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_PROVISION_FAILED); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_NO_ERROR); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + } + + @Test + public void verifyPermissionIfAllNotApproved() { + setupForRequiredProvisioning(); + mEnMgr.notifyUpstream(true); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_PROVISION_FAILED); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); + mLooper.dispatchAll(); + mEnMgr.addDownstreamMapping(TETHERING_USB, TETHER_ERROR_PROVISION_FAILED); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true); + mLooper.dispatchAll(); + mEnMgr.addDownstreamMapping(TETHERING_BLUETOOTH, TETHER_ERROR_PROVISION_FAILED); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + } + + @Test + public void verifyPermissionIfAnyApproved() { + setupForRequiredProvisioning(); + mEnMgr.notifyUpstream(true); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_NO_ERROR); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); + mLooper.dispatchAll(); + mEnMgr.addDownstreamMapping(TETHERING_USB, TETHER_ERROR_PROVISION_FAILED); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + + } + + @Test + public void testRunTetherProvisioning() { + setupForRequiredProvisioning(); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + // 1. start ui provisioning, upstream is mobile + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); + mLooper.dispatchAll(); + assertTrue(mEnMgr.uiProvisionCount == 1); + assertTrue(mEnMgr.silentProvisionCount == 0); + mEnMgr.addDownstreamMapping(TETHERING_USB, TETHER_ERROR_PROVISION_FAILED); + // 2. start no-ui provisioning + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, false); + mLooper.dispatchAll(); + assertTrue(mEnMgr.silentProvisionCount == 1); + assertTrue(mEnMgr.uiProvisionCount == 1); + mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_PROVISION_FAILED); + // 3. tear down mobile, then start ui provisioning + mEnMgr.notifyUpstream(false); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true); + mLooper.dispatchAll(); + assertTrue(mEnMgr.uiProvisionCount == 1); + assertTrue(mEnMgr.silentProvisionCount == 1); + // 4. switch upstream back to mobile + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + assertTrue(mEnMgr.uiProvisionCount == 2); + assertTrue(mEnMgr.silentProvisionCount == 1); + mEnMgr.addDownstreamMapping(TETHERING_BLUETOOTH, TETHER_ERROR_PROVISION_FAILED); + // 5. tear down mobile, then switch SIM + mEnMgr.notifyUpstream(false); + mLooper.dispatchAll(); + mEnMgr.reevaluateSimCardProvisioning(); + assertTrue(mEnMgr.uiProvisionCount == 2); + assertTrue(mEnMgr.silentProvisionCount == 1); + // 6. switch upstream back to mobile again + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + assertTrue(mEnMgr.uiProvisionCount == 2); + assertTrue(mEnMgr.silentProvisionCount == 4); + mEnMgr.addDownstreamMapping(TETHERING_USB, TETHER_ERROR_PROVISION_FAILED); + mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_PROVISION_FAILED); + mEnMgr.addDownstreamMapping(TETHERING_BLUETOOTH, TETHER_ERROR_PROVISION_FAILED); + } + public class TestStateMachine extends StateMachine { public final ArrayList<Message> messages = new ArrayList<>(); - private final State mLoggingState = - new EntitlementManagerTest.TestStateMachine.LoggingState(); + private final State + mLoggingState = new EntitlementManagerTest.TestStateMachine.LoggingState(); class LoggingState extends State { @Override public void enter() { diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java index 5a1f853e75a9..0d276cbd1b85 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java @@ -90,6 +90,7 @@ public class UpstreamNetworkMonitorTest { private static final NetworkRequest mDefaultRequest = new NetworkRequest.Builder().build(); @Mock private Context mContext; + @Mock private EntitlementManager mEntitleMgr; @Mock private IConnectivityManager mCS; @Mock private SharedLog mLog; @@ -103,6 +104,7 @@ public class UpstreamNetworkMonitorTest { reset(mCS); reset(mLog); when(mLog.forSubComponent(anyString())).thenReturn(mLog); + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); mCM = spy(new TestConnectivityManager(mContext, mCS)); mSM = new TestStateMachine(); @@ -138,7 +140,7 @@ public class UpstreamNetworkMonitorTest { @Test public void testDefaultNetworkIsTracked() throws Exception { assertTrue(mCM.hasNoCallbacks()); - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); assertEquals(1, mCM.trackingDefault.size()); @@ -151,7 +153,7 @@ public class UpstreamNetworkMonitorTest { public void testListensForAllNetworks() throws Exception { assertTrue(mCM.listening.isEmpty()); - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); assertFalse(mCM.listening.isEmpty()); assertTrue(mCM.isListeningForAll()); @@ -162,7 +164,7 @@ public class UpstreamNetworkMonitorTest { @Test public void testCallbacksRegistered() { - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); verify(mCM, times(1)).requestNetwork( eq(mDefaultRequest), any(NetworkCallback.class), any(Handler.class)); mUNM.startObserveAllNetworks(); @@ -285,7 +287,7 @@ public class UpstreamNetworkMonitorTest { final Collection<Integer> preferredTypes = new ArrayList<>(); preferredTypes.add(TYPE_WIFI); - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); // There are no networks, so there is nothing to select. assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); @@ -319,6 +321,14 @@ public class UpstreamNetworkMonitorTest { NetworkRequest netReq = (NetworkRequest) mCM.requested.values().toArray()[0]; assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)); assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); + // mobile is not permitted, we should not use HIPRI. + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); + assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); + assertEquals(0, mCM.requested.size()); + // mobile change back to permitted, HIRPI should come back + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); + assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI, + mUNM.selectPreferredUpstreamType(preferredTypes)); wifiAgent.fakeConnect(); // WiFi is up, and we should prefer it over cell. @@ -347,11 +357,19 @@ public class UpstreamNetworkMonitorTest { netReq = (NetworkRequest) mCM.requested.values().toArray()[0]; assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)); assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); + // mobile is not permitted, we should not use DUN. + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); + assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); + assertEquals(0, mCM.requested.size()); + // mobile change back to permitted, DUN should come back + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); + assertSatisfiesLegacyType(TYPE_MOBILE_DUN, + mUNM.selectPreferredUpstreamType(preferredTypes)); } @Test public void testGetCurrentPreferredUpstream() throws Exception { - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); mUNM.updateMobileRequiresDun(false); @@ -361,37 +379,46 @@ public class UpstreamNetworkMonitorTest { mCM.makeDefaultNetwork(cellAgent); assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - // [1] WiFi connects but not validated/promoted to default -> mobile selected. + // [1] Mobile connects but not permitted -> null selected + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); + assertEquals(null, mUNM.getCurrentPreferredUpstream()); + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); + + // [2] WiFi connects but not validated/promoted to default -> mobile selected. final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); wifiAgent.fakeConnect(); assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - // [2] WiFi validates and is promoted to the default network -> WiFi selected. + // [3] WiFi validates and is promoted to the default network -> WiFi selected. mCM.makeDefaultNetwork(wifiAgent); assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - // [3] DUN required, no other changes -> WiFi still selected + // [4] DUN required, no other changes -> WiFi still selected mUNM.updateMobileRequiresDun(true); assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - // [4] WiFi no longer validated, mobile becomes default, DUN required -> null selected. + // [5] WiFi no longer validated, mobile becomes default, DUN required -> null selected. mCM.makeDefaultNetwork(cellAgent); assertEquals(null, mUNM.getCurrentPreferredUpstream()); // TODO: make sure that a DUN request has been filed. This is currently // triggered by code over in Tethering, but once that has been moved // into UNM we should test for this here. - // [5] DUN network arrives -> DUN selected + // [6] DUN network arrives -> DUN selected final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN); dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET); dunAgent.fakeConnect(); assertEquals(dunAgent.networkId, mUNM.getCurrentPreferredUpstream().network); + + // [7] Mobile is not permitted -> null selected + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); + assertEquals(null, mUNM.getCurrentPreferredUpstream()); } @Test public void testLocalPrefixes() throws Exception { - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); // [0] Test minimum set of local prefixes. @@ -492,6 +519,26 @@ public class UpstreamNetworkMonitorTest { assertTrue(local.isEmpty()); } + @Test + public void testSelectMobileWhenMobileIsNotDefault() { + final Collection<Integer> preferredTypes = new ArrayList<>(); + // Mobile has higher pirority than wifi. + preferredTypes.add(TYPE_MOBILE_HIPRI); + preferredTypes.add(TYPE_WIFI); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); + mUNM.startObserveAllNetworks(); + // Setup wifi and make wifi as default network. + final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); + wifiAgent.fakeConnect(); + mCM.makeDefaultNetwork(wifiAgent); + // Setup mobile network. + final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); + cellAgent.fakeConnect(); + + assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI, + mUNM.selectPreferredUpstreamType(preferredTypes)); + verify(mEntitleMgr, times(1)).maybeRunProvisioning(); + } private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) { if (legacyType == TYPE_NONE) { assertTrue(ns == null); |