Merge "Make TimeDetectorService / Strategy consistent"
diff --git a/Android.bp b/Android.bp
index 5f06a63..6b2883c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -222,7 +222,6 @@
":framework-mca-effect-sources",
":framework-mca-filterfw-sources",
":framework-mca-filterpacks-sources",
- ":framework-mime-sources",
":framework-mms-sources",
":framework-opengl-sources",
":framework-rs-sources",
@@ -264,10 +263,19 @@
}
filegroup {
+ name: "framework-updatable-sources",
+ srcs: [
+ ":framework-sdkext-sources",
+ ":updatable-media-srcs",
+ ]
+}
+
+filegroup {
name: "framework-all-sources",
srcs: [
+ ":framework-mime-sources",
":framework-non-updatable-sources",
- ":updatable-media-srcs",
+ ":framework-updatable-sources",
],
}
@@ -365,7 +373,6 @@
static_libs: [
"framework-internal-utils",
- "mimemap",
],
required: [
@@ -426,6 +433,11 @@
"libcore-platform-compat-config",
"services-platform-compat-config",
],
+ static_libs: [
+ // If MimeMap ever becomes its own APEX, then this dependency would need to be removed
+ // in favor of an API stubs dependency in java_library "framework" below.
+ "mimemap",
+ ],
// For backwards compatibility.
stem: "framework",
}
@@ -556,6 +568,12 @@
],
}
+java_library {
+ name: "framework-annotations-lib",
+ srcs: [ ":framework-annotations" ],
+ sdk_version: "current",
+}
+
filegroup {
name: "framework-networkstack-shared-srcs",
srcs: [
@@ -949,7 +967,8 @@
"--hide CallbackInterface " +
"--hide MissingPermission --hide BroadcastBehavior " +
"--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
- "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo "
+ "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo " +
+ "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.*"
packages_to_document = [
"android",
@@ -967,14 +986,15 @@
stubs_defaults {
name: "framework-doc-stubs-default",
srcs: [
+ ":framework-mime-sources",
":framework-non-updatable-sources",
+ ":framework-updatable-sources",
"core/java/**/*.logtags",
"test-base/src/**/*.java",
":opt-telephony-srcs",
":opt-net-voip-srcs",
":core-current-stubs-source",
":core_public_api_files",
- ":updatable-media-srcs",
"test-mock/src/**/*.java",
"test-runner/src/**/*.java",
],
@@ -1032,12 +1052,12 @@
name: "metalava-api-stubs-default",
srcs: [
":framework-non-updatable-sources",
+ ":framework-updatable-sources",
"core/java/**/*.logtags",
":opt-telephony-srcs",
":opt-net-voip-srcs",
":core-current-stubs-source",
":core_public_api_files",
- ":updatable-media-srcs",
":ike-api-srcs",
],
libs: ["framework-internal-utils"],
diff --git a/apex/sdkext/Android.bp b/apex/sdkext/Android.bp
new file mode 100644
index 0000000..b8dcb90
--- /dev/null
+++ b/apex/sdkext/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 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.
+
+apex {
+ name: "com.android.sdkext",
+ manifest: "manifest.json",
+ java_libs: [ "framework-sdkext" ],
+ key: "com.android.sdkext.key",
+ certificate: ":com.android.sdkext.certificate",
+}
+
+apex_key {
+ name: "com.android.sdkext.key",
+ public_key: "com.android.sdkext.avbpubkey",
+ private_key: "com.android.sdkext.pem",
+}
+
+android_app_certificate {
+ name: "com.android.sdkext.certificate",
+ certificate: "com.android.sdkext",
+}
diff --git a/apex/sdkext/OWNERS b/apex/sdkext/OWNERS
new file mode 100644
index 0000000..feb2742
--- /dev/null
+++ b/apex/sdkext/OWNERS
@@ -0,0 +1 @@
+hansson@google.com
diff --git a/apex/sdkext/com.android.sdkext.avbpubkey b/apex/sdkext/com.android.sdkext.avbpubkey
new file mode 100644
index 0000000..8f47741
--- /dev/null
+++ b/apex/sdkext/com.android.sdkext.avbpubkey
Binary files differ
diff --git a/apex/sdkext/com.android.sdkext.pem b/apex/sdkext/com.android.sdkext.pem
new file mode 100644
index 0000000..8164601
--- /dev/null
+++ b/apex/sdkext/com.android.sdkext.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAr72pTSavrziDP54AtQZlRclDxJf9HXRZwFRbYx9hWZ4z7ZtO
+pNBDPvPJCiAOVUsILgCQhBUolz2dyLob25Fd0PVp0n9ibIPEQYjTfHjOK40qb57N
+LhEp2ceGiAfsywPSi0TH1PQ6JgbCe/RM4TefI/sj3gYJPka3ksMvylhMIgUVLgME
+kYizhzwHqlLMspB858SioREZdGwcdZrMMIajGFU69Q9ZRDBzhPvxyKhYoObcOtk1
+uVaiE/fNoi3wKGJz2l2vhUuNrQW7MWlVMag+Qes4YACUTk9LZrOVFEJFjWc8xGUi
+ABtfKGs5JgNr/sWnhvifLn8lJuf0/BJmjD+L5QwXYs2cS7gcZJtTM12J94r0Twgw
+wF2lNmIxAE9sYqj5Rh3dIlTPE5vMUECmQEGjIBB/hzT65VxVqSjU/IlS506JTg3p
+IOQtZ15cUzTBpda4jrvqcq6RNVvgBCu2bV5D8Z4z9FUlPyvD+Zq/6lcoJfLtznAs
+G2463hyPAHTGBIcZ5p5bTuGxoAb6ivyqo4b9Qi4yYA6je9HJmuy8T3Mn5JROoeu9
+BH1K54r/mpT4TQPwuKUvRRtBAV2OPHjo+zp0Gd4Y6rxDYxEIdfEae7pQr/QExSPB
+q/QCr9RhixR1mO373LHuja+MxdAxIxugb2HTS61PQo+PbYrhJMcVuxTwJOECAwEA
+AQKCAgAH7ToRrMkH0ji5SdsmTx+KQkW4PFLCXVke/68PjX7KmAQnl3W4oVwnHr/W
+oROEbVn1GTlre7jU+YaAY0SWZrwgjLE1OWGrG1ZizlUbrCdAd6GOX09J4KROml1L
+DXB0x7tbZMLOrCVjSbLD/ITrM6MN8Gnxvbv0/yOQjxU8vzbP4gLOjHxMRCo001RV
+Ll7lPvcjTQ84zJilU6sE8vJ6zdfVZSK/ou2X0cekG+kP7+fvefo8/UcbEPlGhUrV
+IdVPPQGUu90K2hmN0FBdLi8Vik0klAN68Qu/bHwuKbNzsnmIoztucFFUR+fG3u84
+87aPS0L/J3+mjT2Tv6qhJANUGBmrK/h7MkelpKXlRTCITJLX9xP7hfSbJ4f6aLVq
+ZYPPciGxSBbUDgAwvPtOlMDzccg7YsYyiBBO28wh8MN97rePmc0z6nGmjeXhcbCC
+QktG50VYFCyqp5muKgqQmRfRjHFHLWs8GEqgxMeEL3U3HjYfCYr+6E8Sr5OnOBeH
+3buCi1+zgnNYCvbamgY/OJmW7f9h5O31hxmTplc2E1ZuxUGQZthabt1rN3bmNkyf
+KUmPwnIYkDkWBSV5lzyQExfS3/EVvj0EnHhx8faamimNrGo8xCcfnLT3c0WEFVmo
+yIyVRX3EpXJFM2JkeJ21/IEZXTzHSoNxk12CBG8i8lLSflWSMQKCAQEA2ZqVnOuV
+SZfLCUYUUh8Hvhc5zONstfq7ma1Zsttsdaj9t68nLRiBDvLOGnMjDkYZzoSd4fyl
+oy+YqWGBqcqa5kg1NOCH0I46p9d8RcWAfDnB4sqbLgWh70qsvni6igRijmsMDvkA
+U9HeEdPaLCjQ4UXw7GQvN5rRxuRt+OSqV3tV/Pk9JYyYjz7faC8dmbKDrWHHuOvm
+/9y6Xy+L5IgftykNlUeddSCIoMOAadM7BiRjsrHnOYBQ8xBcn0OYafpIswItrgVi
+IrsPJaBFidx8QYK4MVibyka6U0cm28OocDSPtSk/4jrnCEEhLjFUnWwuMXuBGlrd
+W7wP/muoJqb1VwKCAQEAzsAT90kkOCvAcrfGRE3KkUjwWAsQyP8u2+27JIQPqrpW
+GfWAzJXFt80TSp0Zf/Lrq3/SQ9n4AaL4K9dcMoreedoQN9C9JI7zPtZAWNrJVUcV
+dq2gZjBQ78+oK7uQgvFNWxga5D+Nh+Y+9Tp537fc5HIh0Y13PgsxxPk2OnZJTvLX
+HM5H7Aua9ssmqChsrKihuUsDSPozfBz+H7FNHEdKMqVLqJJSK6m0uMxuLovdVfka
+5S7iBMjEGZc46Iz3ckE0pdOiQLooNqfEQqFe5Uou/KZxxKI1NW25rEEBTVyQWt+2
+BNUCfKP7noZ45u5sUY3eJrgI7BrPEDbNS81WYaLchwKCAQA8Q4mHyd6wYO+EA/qA
+u8NDK9+AFMP4qhXme5HJ7Obetwx9IG7zGEQ1xZy6yoQ84cEn5qZq/bNJvFbFIhHs
+2gWIHRtPJ5e1dI5eCVmLYSUyQjSmAIJ1fm3YfY/VuE3BB3HcC11tkBw9GnQr78YO
+UMd4fAw7C4vgFGpgcMbcFUfvrmKkCsqaaZOeqETq75F9DWlWTSwo1HxHA/RBhENz
+6RcPfLkcTJcY5wevrjUUGcHQ86cAyDBHRngkuLVODkRZpU0Y9lN8TFVfVPre6sIX
+ag6nffJRCD8tB+V2RtBGMKunV4ctHt1oY/Oz34W260aJynoIjjG1ANEpJK4xQdNx
+0O9FAoIBAQCz2AGGKemHswdEwveEkuaSWpA3Bekj7lYkmTchHH9EU7JyAkx3qhDD
+QXB2hxGXawf1tsqAmypQwiJ+gGeCz6mW9UkGRF1DX9XX4yc2I5rew2a4RXAxc/Xz
+pP70i8O5I43Wn7FEusOyY2aAis1Y/eb4EQ+56QTAw5wXa3DwidRbCIJ2XDnT6oRy
+CWUnAYMG7ek/9TB2Wq5OWCn2B5S79IdmZsLZb+5qbMT3u1xcwO1Xy8jJc27IGpv6
+ZsDqCTV1/aJ+XQnWpBg28tiV3Sle6pjUzTRJh5AhWcEZRbKMSOiJI/CBY4k2Qq6t
+xuuEdgFjL7T+mTepqehUglcyiPuLEtAhAoIBAQDDQ5pTFOlunfYzm+CIvvImAgy7
+vrEabJYABATytAbXRMMbrKoEdU2ApEDyEW7PgysDnYLAZ+icObnJlBTYvNdlWFly
+XiviGVfpjFWDT9U/gUwFQu2lfEjoiivoGS92USHUy4UMVHiiznnm20VLLkgd3xna
+HUNSDdHIEgzOTmDsKJwMfA9zGckx23JJimPR5ekv6vr6QllYtECs4lTC1gVQAp2f
+5daxHRbkmO6gw1RgQADLkAnYz3aI1jNuHm5VyAZGt/d3JCtZ3Wwwejll8uJ4J09G
+oEtqyY9RVeHK50bLO4lyAXFiE+J6qqXjsGC20cpxeZYW5llMY/dhA6WV4YXV
+-----END RSA PRIVATE KEY-----
diff --git a/apex/sdkext/com.android.sdkext.pk8 b/apex/sdkext/com.android.sdkext.pk8
new file mode 100644
index 0000000..ccc0bf4
--- /dev/null
+++ b/apex/sdkext/com.android.sdkext.pk8
Binary files differ
diff --git a/apex/sdkext/com.android.sdkext.x509.pem b/apex/sdkext/com.android.sdkext.x509.pem
new file mode 100644
index 0000000..45d2ade
--- /dev/null
+++ b/apex/sdkext/com.android.sdkext.x509.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGIzCCBAugAwIBAgIUXuDL7QvzQh7S6rihWz2KRvCFVT0wDQYJKoZIhvcNAQEL
+BQAwgZ8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMRswGQYDVQQDDBJjb20uYW5kcm9pZC5zZGtleHQxIjAgBgkqhkiG9w0BCQEW
+E2FuZHJvaWRAYW5kcm9pZC5jb20wIBcNMTkxMjAyMTQyNDM0WhgPNDc1NzEwMjgx
+NDI0MzRaMIGfMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG
+A1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwH
+QW5kcm9pZDEbMBkGA1UEAwwSY29tLmFuZHJvaWQuc2RrZXh0MSIwIAYJKoZIhvcN
+AQkBFhNhbmRyb2lkQGFuZHJvaWQuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEAxFvZZ6ES1oqAu1K74/ZxnC3SOhHnLISLBgJEe7DqtdpuNFAwvdVO
+RL/HULhDbjYlOhpU2x3SavDIZZ2lRfiS9Q+M25WftxTRHVjBcpgwbV77TVxPKlAa
+tVN2lUVOY+s4QAVMNIXjC4kCKK/pCQtacH715EtdV47fWdg/Nx4iP/Aord8k3KGI
+9iI2ZOUjaugTRxu5lKRNDrv0bw5rEzyYmDyMud+kR/iS3/5oog57wPE0ffAkZXWE
+p3L2Cejre3ekCizsvVh6EmH6ForKLtL6f0z5Zir1f4R9+YcENspTlJR3pDhg7y3I
+uTQT/iDCtV0l+g2PjGZPEeAQHND3+kDQR7Sno/WC1Nhws6vcu1MdrC+kIh1ewx4y
+8moy/yqb5M98PJDzTSi/AOTB/OiqLXo/T8rjLBmirs9y3fTT6gJ6qXxOWgt8dle9
+7TBfa84Xi8uVY61c+A+YI0nLal7QDPsP3RPv5sJSQ9x9YnweVfD9Q0EOi52sSNu+
+QuN/kcUrMgPgha20VhfH/CkkPDyIp6aZyHHM69MIl+cYEm8vPa5uy3dosuRomT0f
+I4HOBjULDIuj+rIi+Rg3qHvmpuejwZXI/FBNWIhLEUG3ytgksjMaBoYAYflGdrcj
+BQexuF3EO+j4uo7JGjNcaT7wRoCH9gt29VHckDg2qz6VWXrlpmME4UkCAwEAAaNT
+MFEwHQYDVR0OBBYEFISN2nmUHllgPZMZ62U7mU3ZxzlXMB8GA1UdIwQYMBaAFISN
+2nmUHllgPZMZ62U7mU3ZxzlXMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggIBAFHIwyBNIVyHXUsDUdcjxfojXQsF/BCL9ehE3pgdkvDfQanaIREWn0nc
+oCFDFkYMRqaXOGC5TKq4OCjXOLsdfODt8HQ3F9J1B0ghQ5tfOdw7xDugNAszqP/Q
+h7kpvqLTycjrqOeZ5KjxEEYtP/KlUmALgOKcTcSH+XhWyxhjF4j24T9F2yJRr3/A
+r1NGU/djH953bHKC8OpJ2teUpDLA4TxVp/EhslH2eVigF80c/w74QPLEWkD9zv/4
+YeRg/R5N83zHs99NtlWMIeHfK6fUbzMyaSZtvm+jK20tkByQb/OQRed+drk25MtL
+68IRvxqri367qRScdpTZbu0ByLO4X8gFdubRUWr+tcO4pZX+DJRVriExbOkU2xhS
+Vtslq23V/hwTuUNm1CXjR70mPS13BTmHrIQDqLoIw/WTQlGh+vxnlAFRIHM3KB2c
+OdzUBu+NcB4aZEd0KKtct600A0DKPr1MQPb5jDq9wEtPSQYwMF0nRFNnXltbrXMd
+4hhwnhKr74fVMUmb+7eQP56XE/Nk4D+npMO54vv1pav+DI2/nxCND9BOLBweY38p
+Tvd2RjesMok0zXuVXiCIu4GEpwo7WkSnv25xrb0Ey2M8QWnGNnCcX7Kv6ip3RdWy
+HiN0G8RJrs/yNEVSDRx8ZhtwTpXVPQxbARbmhNF4/fnolElkmrMP
+-----END CERTIFICATE-----
diff --git a/apex/sdkext/framework/Android.bp b/apex/sdkext/framework/Android.bp
new file mode 100644
index 0000000..b17f0f8
--- /dev/null
+++ b/apex/sdkext/framework/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2019 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.
+
+filegroup {
+ name: "framework-sdkext-sources",
+ srcs: [
+ "java/**/*.java",
+ ],
+ path: "java",
+}
+
+java_library {
+ name: "framework-sdkext",
+ srcs: [ ":framework-sdkext-sources" ],
+ sdk_version: "system_current",
+ libs: [ "framework-annotations-lib" ],
+ permitted_packages: [ "android.os.ext" ],
+ installable: true,
+}
diff --git a/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
new file mode 100644
index 0000000..c039a82
--- /dev/null
+++ b/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 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 android.os.ext;
+
+import android.annotation.IntDef;
+import android.os.Build.VERSION_CODES;
+import android.os.SystemProperties;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** @hide */
+public class SdkExtensions {
+
+ private static final int R_EXTENSION_INT;
+ static {
+ R_EXTENSION_INT = SystemProperties.getInt("persist.com.android.sdkext.sdk_info", 0);
+ }
+
+ /** Values suitable as parameters for {@link #getExtensionVersion(int)}. */
+ @IntDef(value = { VERSION_CODES.R })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SdkVersion {}
+
+ /**
+ * Return the version of the extension to the given SDK.
+ *
+ * @param sdk the SDK version to get the extension version of.
+ * @see SdkVersion
+ * @throws IllegalArgumentException if sdk is not an sdk version with extensions
+ */
+ public static int getExtensionVersion(@SdkVersion int sdk) {
+ if (sdk < VERSION_CODES.R) {
+ throw new IllegalArgumentException();
+ }
+ return R_EXTENSION_INT;
+ }
+
+}
diff --git a/apex/sdkext/framework/java/android/os/ext/package.html b/apex/sdkext/framework/java/android/os/ext/package.html
new file mode 100644
index 0000000..34c1697
--- /dev/null
+++ b/apex/sdkext/framework/java/android/os/ext/package.html
@@ -0,0 +1,5 @@
+<HTML>
+<BODY>
+Provides APIs to interface with the SDK extensions.
+</BODY>
+</HTML>
diff --git a/apex/sdkext/framework/tests/Android.bp b/apex/sdkext/framework/tests/Android.bp
new file mode 100644
index 0000000..3d5dbb3
--- /dev/null
+++ b/apex/sdkext/framework/tests/Android.bp
@@ -0,0 +1,10 @@
+android_test {
+ name: "framework-sdkext-tests",
+ srcs: ["src/**/*.java"],
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+ static_libs: [ "framework-sdkext" ],
+ platform_apis: true,
+}
diff --git a/apex/sdkext/framework/tests/AndroidManifest.xml b/apex/sdkext/framework/tests/AndroidManifest.xml
new file mode 100644
index 0000000..831f132
--- /dev/null
+++ b/apex/sdkext/framework/tests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.sdkext.tests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.sdkext.tests" />
+
+</manifest>
diff --git a/apex/sdkext/framework/tests/src/android/os/ext/SdkExtensionsTest.java b/apex/sdkext/framework/tests/src/android/os/ext/SdkExtensionsTest.java
new file mode 100644
index 0000000..6885110
--- /dev/null
+++ b/apex/sdkext/framework/tests/src/android/os/ext/SdkExtensionsTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 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 android.os.ext;
+
+import android.os.Build;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+public class SdkExtensionsTest extends TestCase {
+
+ @SmallTest
+ public void testBadArgument() throws Exception {
+ try {
+ SdkExtensions.getExtensionVersion(Build.VERSION_CODES.Q);
+ fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) { }
+
+ try {
+ SdkExtensions.getExtensionVersion(999999);
+ fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) { }
+ }
+
+ @SmallTest
+ public void testDefault() throws Exception {
+ int r = SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R);
+ assertTrue(r >= 0);
+ }
+
+}
diff --git a/apex/sdkext/manifest.json b/apex/sdkext/manifest.json
new file mode 100644
index 0000000..048f5c4
--- /dev/null
+++ b/apex/sdkext/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.sdkext",
+ "version": 1
+}
diff --git a/api/current.txt b/api/current.txt
index bf3b525..de008f7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9773,6 +9773,7 @@
method public abstract void sendOrderedBroadcast(@RequiresPermission android.content.Intent, @Nullable String);
method public abstract void sendOrderedBroadcast(@NonNull @RequiresPermission android.content.Intent, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
method public void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
+ method public void sendOrderedBroadcast(@NonNull @RequiresPermission android.content.Intent, @Nullable String, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendOrderedBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
method @Deprecated @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY) public abstract void sendStickyBroadcast(@RequiresPermission android.content.Intent);
method @Deprecated @RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void sendStickyBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle);
@@ -28696,6 +28697,7 @@
method @Nullable public String getInterfaceName();
method @NonNull public java.util.List<android.net.LinkAddress> getLinkAddresses();
method public int getMtu();
+ method @Nullable public android.net.IpPrefix getNat64Prefix();
method @Nullable public String getPrivateDnsServerName();
method @NonNull public java.util.List<android.net.RouteInfo> getRoutes();
method public boolean isPrivateDnsActive();
@@ -28706,6 +28708,7 @@
method public void setInterfaceName(@Nullable String);
method public void setLinkAddresses(@NonNull java.util.Collection<android.net.LinkAddress>);
method public void setMtu(int);
+ method public void setNat64Prefix(@Nullable android.net.IpPrefix);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkProperties> CREATOR;
}
@@ -34211,6 +34214,7 @@
field public static final int O_MR1 = 27; // 0x1b
field public static final int P = 28; // 0x1c
field public static final int Q = 29; // 0x1d
+ field public static final int R = 10000; // 0x2710
}
public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 8a4d11c..994bdd0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4088,6 +4088,7 @@
public final class IpConfiguration implements android.os.Parcelable {
ctor public IpConfiguration();
ctor public IpConfiguration(@NonNull android.net.IpConfiguration);
+ method public int describeContents();
method @Nullable public android.net.ProxyInfo getHttpProxy();
method @NonNull public android.net.IpConfiguration.IpAssignment getIpAssignment();
method @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings();
@@ -4096,6 +4097,7 @@
method public void setIpAssignment(@NonNull android.net.IpConfiguration.IpAssignment);
method public void setProxySettings(@NonNull android.net.IpConfiguration.ProxySettings);
method public void setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.IpConfiguration> CREATOR;
}
@@ -4148,7 +4150,6 @@
ctor public LinkProperties(@Nullable android.net.LinkProperties);
method public boolean addDnsServer(@NonNull java.net.InetAddress);
method public boolean addLinkAddress(@NonNull android.net.LinkAddress);
- method @Nullable public android.net.IpPrefix getNat64Prefix();
method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers();
method @Nullable public String getTcpBufferSizes();
method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
@@ -4162,7 +4163,6 @@
method public boolean removeDnsServer(@NonNull java.net.InetAddress);
method public boolean removeLinkAddress(@NonNull android.net.LinkAddress);
method public boolean removeRoute(@NonNull android.net.RouteInfo);
- method public void setNat64Prefix(@Nullable android.net.IpPrefix);
method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>);
method public void setPrivateDnsServerName(@Nullable String);
method public void setTcpBufferSizes(@Nullable String);
@@ -7373,6 +7373,10 @@
field public static final int PROPERTY_REMOTELY_HOSTED = 2048; // 0x800
}
+ public final class ConnectionRequest implements android.os.Parcelable {
+ method @Nullable public String getTelecomCallId();
+ }
+
public abstract class ConnectionService extends android.app.Service {
method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference);
}
@@ -7579,6 +7583,7 @@
method public java.util.List<android.telecom.PhoneAccountHandle> getAllPhoneAccountHandles();
method public java.util.List<android.telecom.PhoneAccount> getAllPhoneAccounts();
method public int getAllPhoneAccountsCount();
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(boolean);
method public int getCallState();
method public android.telecom.PhoneAccountHandle getConnectionManager();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCurrentTtyMode();
diff --git a/api/test-current.txt b/api/test-current.txt
index 1fe4274..cbc3b6a 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1384,7 +1384,6 @@
ctor public LinkProperties(@Nullable android.net.LinkProperties);
method public boolean addDnsServer(@NonNull java.net.InetAddress);
method public boolean addLinkAddress(@NonNull android.net.LinkAddress);
- method @Nullable public android.net.IpPrefix getNat64Prefix();
method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers();
method @Nullable public String getTcpBufferSizes();
method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
@@ -1398,7 +1397,6 @@
method public boolean removeDnsServer(@NonNull java.net.InetAddress);
method public boolean removeLinkAddress(@NonNull android.net.LinkAddress);
method public boolean removeRoute(@NonNull android.net.RouteInfo);
- method public void setNat64Prefix(@Nullable android.net.IpPrefix);
method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>);
method public void setPrivateDnsServerName(@Nullable String);
method public void setTcpBufferSizes(@Nullable String);
@@ -2834,6 +2832,23 @@
field public static final int PROPERTY_REMOTELY_HOSTED = 2048; // 0x800
}
+ public final class ConnectionRequest implements android.os.Parcelable {
+ method @Nullable public String getTelecomCallId();
+ }
+
+ public static final class ConnectionRequest.Builder {
+ ctor public ConnectionRequest.Builder();
+ method @NonNull public android.telecom.ConnectionRequest build();
+ method @NonNull public android.telecom.ConnectionRequest.Builder setAccountHandle(@NonNull android.telecom.PhoneAccountHandle);
+ method @NonNull public android.telecom.ConnectionRequest.Builder setAddress(@NonNull android.net.Uri);
+ method @NonNull public android.telecom.ConnectionRequest.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public android.telecom.ConnectionRequest.Builder setRttPipeFromInCall(@NonNull android.os.ParcelFileDescriptor);
+ method @NonNull public android.telecom.ConnectionRequest.Builder setRttPipeToInCall(@NonNull android.os.ParcelFileDescriptor);
+ method @NonNull public android.telecom.ConnectionRequest.Builder setShouldShowIncomingCallUi(boolean);
+ method @NonNull public android.telecom.ConnectionRequest.Builder setTelecomCallId(@NonNull String);
+ method @NonNull public android.telecom.ConnectionRequest.Builder setVideoState(int);
+ }
+
public static class PhoneAccount.Builder {
method @NonNull public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
}
@@ -2847,6 +2862,7 @@
}
public class TelecomManager {
+ method @NonNull @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(boolean);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public int getCurrentTtyMode();
method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDefaultDialerPackage(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
diff --git a/core/TEST_MAPPING b/core/TEST_MAPPING
new file mode 100644
index 0000000..fd571c9
--- /dev/null
+++ b/core/TEST_MAPPING
@@ -0,0 +1,24 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "include-filter": "android.view.inputmethod"
+ },
+ {
+ "include-filter": "com.android.internal.inputmethod"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ],
+ "file_patterns": [
+ "core/java/com/android/internal/inputmethod/.*",
+ "core/java/android/view/inputmethod/.*",
+ "core/tests/coretests/src/android/view/inputmethod/.*",
+ "core/tests/coretests/src/com/android/internal/inputmethod/.*"
+ ]
+ }
+ ]
+}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 0e0e7f7..d317c34 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1351,6 +1351,18 @@
}
@Override
+ public void sendOrderedBroadcast(Intent intent, String receiverPermission, String receiverAppOp,
+ Bundle options, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+ String initialData, @Nullable Bundle initialExtras) {
+ int intAppOp = AppOpsManager.OP_NONE;
+ if (!TextUtils.isEmpty(receiverAppOp)) {
+ intAppOp = AppOpsManager.strOpToOp(receiverAppOp);
+ }
+ sendOrderedBroadcastAsUser(intent, getUser(), receiverPermission, intAppOp, options,
+ resultReceiver, scheduler, initialCode, initialData, initialExtras);
+ }
+
+ @Override
@Deprecated
public void sendStickyBroadcast(Intent intent) {
warnIfCallingFromSystemProcess();
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 29cb3c1..7531b6c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -31,6 +31,7 @@
import android.app.slice.SliceManager;
import android.app.timedetector.TimeDetector;
import android.app.timezone.RulesManager;
+import android.app.timezonedetector.TimeZoneDetector;
import android.app.trust.TrustManager;
import android.app.usage.IStorageStatsManager;
import android.app.usage.IUsageStatsManager;
@@ -1235,6 +1236,14 @@
return new TimeDetector();
}});
+ registerService(Context.TIME_ZONE_DETECTOR_SERVICE, TimeZoneDetector.class,
+ new CachedServiceFetcher<TimeZoneDetector>() {
+ @Override
+ public TimeZoneDetector createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ return new TimeZoneDetector();
+ }});
+
registerService(Context.PERMISSION_SERVICE, PermissionManager.class,
new CachedServiceFetcher<PermissionManager>() {
@Override
diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
new file mode 100644
index 0000000..260c7df
--- /dev/null
+++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 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 android.app.timezonedetector;
+
+import android.app.timezonedetector.PhoneTimeZoneSuggestion;
+
+/**
+ * System private API to communicate with time zone detector service.
+ *
+ * <p>Used to provide information to the Time Zone Detector Service from other parts of the Android
+ * system that have access to time zone-related signals, e.g. telephony.
+ *
+ * <p>Use the {@link android.app.timezonedetector.TimeZoneDetector} class rather than going through
+ * this Binder interface directly. See {@link android.app.timezonedetector.TimeZoneDetectorService}
+ * for more complete documentation.
+ *
+ *
+ * {@hide}
+ */
+interface ITimeZoneDetectorService {
+ void suggestPhoneTimeZone(in PhoneTimeZoneSuggestion timeZoneSuggestion);
+}
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl
new file mode 100644
index 0000000..3ad903b
--- /dev/null
+++ b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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 android.app.timezonedetector;
+
+parcelable PhoneTimeZoneSuggestion;
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
new file mode 100644
index 0000000..e8162488
--- /dev/null
+++ b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2019 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 android.app.timezonedetector;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A suggested time zone from a Phone-based signal, e.g. from MCC and NITZ information.
+ *
+ * @hide
+ */
+public final class PhoneTimeZoneSuggestion implements Parcelable {
+
+ @NonNull
+ public static final Creator<PhoneTimeZoneSuggestion> CREATOR =
+ new Creator<PhoneTimeZoneSuggestion>() {
+ public PhoneTimeZoneSuggestion createFromParcel(Parcel in) {
+ return PhoneTimeZoneSuggestion.createFromParcel(in);
+ }
+
+ public PhoneTimeZoneSuggestion[] newArray(int size) {
+ return new PhoneTimeZoneSuggestion[size];
+ }
+ };
+
+ /**
+ * Creates an empty time zone suggestion, i.e. one that will cancel previous suggestions with
+ * the same {@code phoneId}.
+ */
+ @NonNull
+ public static PhoneTimeZoneSuggestion createEmptySuggestion(
+ int phoneId, @NonNull String debugInfo) {
+ return new Builder(phoneId).addDebugInfo(debugInfo).build();
+ }
+
+ @IntDef({ MATCH_TYPE_NA, MATCH_TYPE_NETWORK_COUNTRY_ONLY, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET,
+ MATCH_TYPE_EMULATOR_ZONE_ID, MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MatchType {}
+
+ /** Used when match type is not applicable. */
+ public static final int MATCH_TYPE_NA = 0;
+
+ /**
+ * Only the network country is known.
+ */
+ public static final int MATCH_TYPE_NETWORK_COUNTRY_ONLY = 2;
+
+ /**
+ * Both the network county and offset were known.
+ */
+ public static final int MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET = 3;
+
+ /**
+ * The device is running in an emulator and an NITZ signal was simulated containing an
+ * Android extension with an explicit Olson ID.
+ */
+ public static final int MATCH_TYPE_EMULATOR_ZONE_ID = 4;
+
+ /**
+ * The phone is most likely running in a test network not associated with a country (this is
+ * distinct from the country just not being known yet).
+ * Historically, Android has just picked an arbitrary time zone with the correct offset when
+ * on a test network.
+ */
+ public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5;
+
+ @IntDef({ QUALITY_NA, QUALITY_SINGLE_ZONE, QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET,
+ QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Quality {}
+
+ /** Used when quality is not applicable. */
+ public static final int QUALITY_NA = 0;
+
+ /** There is only one answer */
+ public static final int QUALITY_SINGLE_ZONE = 1;
+
+ /**
+ * There are multiple answers, but they all shared the same offset / DST state at the time
+ * the suggestion was created. i.e. it might be the wrong zone but the user won't notice
+ * immediately if it is wrong.
+ */
+ public static final int QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET = 2;
+
+ /**
+ * There are multiple answers with different offsets. The one given is just one possible.
+ */
+ public static final int QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS = 3;
+
+ /**
+ * The ID of the phone this suggestion is associated with. For multiple-sim devices this
+ * helps to establish origin so filtering / stickiness can be implemented.
+ */
+ private final int mPhoneId;
+
+ /**
+ * The suggestion. {@code null} means there is no current suggestion and any previous suggestion
+ * should be forgotten.
+ */
+ private final String mZoneId;
+
+ /**
+ * The type of "match" used to establish the time zone.
+ */
+ @MatchType
+ private final int mMatchType;
+
+ /**
+ * A measure of the quality of the time zone suggestion, i.e. how confident one could be in
+ * it.
+ */
+ @Quality
+ private final int mQuality;
+
+ /**
+ * Free-form debug information about how the signal was derived. Used for debug only,
+ * intentionally not used in equals(), etc.
+ */
+ private List<String> mDebugInfo;
+
+ private PhoneTimeZoneSuggestion(Builder builder) {
+ mPhoneId = builder.mPhoneId;
+ mZoneId = builder.mZoneId;
+ mMatchType = builder.mMatchType;
+ mQuality = builder.mQuality;
+ mDebugInfo = builder.mDebugInfo != null ? new ArrayList<>(builder.mDebugInfo) : null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static PhoneTimeZoneSuggestion createFromParcel(Parcel in) {
+ // Use the Builder so we get validation during build().
+ int phoneId = in.readInt();
+ PhoneTimeZoneSuggestion suggestion = new Builder(phoneId)
+ .setZoneId(in.readString())
+ .setMatchType(in.readInt())
+ .setQuality(in.readInt())
+ .build();
+ List<String> debugInfo = in.readArrayList(PhoneTimeZoneSuggestion.class.getClassLoader());
+ if (debugInfo != null) {
+ suggestion.addDebugInfo(debugInfo);
+ }
+ return suggestion;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mPhoneId);
+ dest.writeString(mZoneId);
+ dest.writeInt(mMatchType);
+ dest.writeInt(mQuality);
+ dest.writeList(mDebugInfo);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public int getPhoneId() {
+ return mPhoneId;
+ }
+
+ @Nullable
+ public String getZoneId() {
+ return mZoneId;
+ }
+
+ @MatchType
+ public int getMatchType() {
+ return mMatchType;
+ }
+
+ @Quality
+ public int getQuality() {
+ return mQuality;
+ }
+
+ @NonNull
+ public List<String> getDebugInfo() {
+ return mDebugInfo == null
+ ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
+ }
+
+ /**
+ * Associates information with the instance that can be useful for debugging / logging. The
+ * information is present in {@link #toString()} but is not considered for
+ * {@link #equals(Object)} and {@link #hashCode()}.
+ */
+ public void addDebugInfo(@NonNull String debugInfo) {
+ if (mDebugInfo == null) {
+ mDebugInfo = new ArrayList<>();
+ }
+ mDebugInfo.add(debugInfo);
+ }
+
+ /**
+ * Associates information with the instance that can be useful for debugging / logging. The
+ * information is present in {@link #toString()} but is not considered for
+ * {@link #equals(Object)} and {@link #hashCode()}.
+ */
+ public void addDebugInfo(@NonNull List<String> debugInfo) {
+ if (mDebugInfo == null) {
+ mDebugInfo = new ArrayList<>(debugInfo.size());
+ }
+ mDebugInfo.addAll(debugInfo);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PhoneTimeZoneSuggestion that = (PhoneTimeZoneSuggestion) o;
+ return mPhoneId == that.mPhoneId
+ && mMatchType == that.mMatchType
+ && mQuality == that.mQuality
+ && Objects.equals(mZoneId, that.mZoneId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPhoneId, mZoneId, mMatchType, mQuality);
+ }
+
+ @Override
+ public String toString() {
+ return "PhoneTimeZoneSuggestion{"
+ + "mPhoneId=" + mPhoneId
+ + ", mZoneId='" + mZoneId + '\''
+ + ", mMatchType=" + mMatchType
+ + ", mQuality=" + mQuality
+ + ", mDebugInfo=" + mDebugInfo
+ + '}';
+ }
+
+ /**
+ * Builds {@link PhoneTimeZoneSuggestion} instances.
+ *
+ * @hide
+ */
+ public static class Builder {
+ private final int mPhoneId;
+ private String mZoneId;
+ @MatchType private int mMatchType;
+ @Quality private int mQuality;
+ private List<String> mDebugInfo;
+
+ public Builder(int phoneId) {
+ mPhoneId = phoneId;
+ }
+
+ /** Returns the builder for call chaining. */
+ public Builder setZoneId(String zoneId) {
+ mZoneId = zoneId;
+ return this;
+ }
+
+ /** Returns the builder for call chaining. */
+ public Builder setMatchType(@MatchType int matchType) {
+ mMatchType = matchType;
+ return this;
+ }
+
+ /** Returns the builder for call chaining. */
+ public Builder setQuality(@Quality int quality) {
+ mQuality = quality;
+ return this;
+ }
+
+ /** Returns the builder for call chaining. */
+ public Builder addDebugInfo(@NonNull String debugInfo) {
+ if (mDebugInfo == null) {
+ mDebugInfo = new ArrayList<>();
+ }
+ mDebugInfo.add(debugInfo);
+ return this;
+ }
+
+ /**
+ * Performs basic structural validation of this instance. e.g. Are all the fields populated
+ * that must be? Are the enum ints set to valid values?
+ */
+ void validate() {
+ int quality = mQuality;
+ int matchType = mMatchType;
+ if (mZoneId == null) {
+ if (quality != QUALITY_NA || matchType != MATCH_TYPE_NA) {
+ throw new RuntimeException("Invalid quality or match type for null zone ID."
+ + " quality=" + quality + ", matchType=" + matchType);
+ }
+ } else {
+ boolean qualityValid = (quality == QUALITY_SINGLE_ZONE
+ || quality == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET
+ || quality == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
+ boolean matchTypeValid = (matchType == MATCH_TYPE_NETWORK_COUNTRY_ONLY
+ || matchType == MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET
+ || matchType == MATCH_TYPE_EMULATOR_ZONE_ID
+ || matchType == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY);
+ if (!qualityValid || !matchTypeValid) {
+ throw new RuntimeException("Invalid quality or match type with zone ID."
+ + " quality=" + quality + ", matchType=" + matchType);
+ }
+ }
+ }
+
+ /** Returns the {@link PhoneTimeZoneSuggestion}. */
+ public PhoneTimeZoneSuggestion build() {
+ validate();
+ return new PhoneTimeZoneSuggestion(this);
+ }
+ }
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
new file mode 100644
index 0000000..909cbc2
--- /dev/null
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 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 android.app.timezonedetector;
+
+import android.annotation.NonNull;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.util.Log;
+
+/**
+ * The interface through which system components can send signals to the TimeZoneDetectorService.
+ * @hide
+ */
+@SystemService(Context.TIME_ZONE_DETECTOR_SERVICE)
+public final class TimeZoneDetector {
+ private static final String TAG = "timezonedetector.TimeZoneDetector";
+ private static final boolean DEBUG = false;
+
+ private final ITimeZoneDetectorService mITimeZoneDetectorService;
+
+ public TimeZoneDetector() throws ServiceNotFoundException {
+ mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
+ }
+
+ /**
+ * Suggests the current time zone to the detector. The detector may ignore the signal if better
+ * signals are available such as those that come from more reliable sources or were
+ * determined more recently.
+ */
+ public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) {
+ if (DEBUG) {
+ Log.d(TAG, "suggestPhoneTimeZone called: " + timeZoneSuggestion);
+ }
+ try {
+ mITimeZoneDetectorService.suggestPhoneTimeZone(timeZoneSuggestion);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 507d1d8..dbd84a4 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2432,6 +2432,48 @@
}
/**
+ * Version of
+ * {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String,
+ * Bundle)} that allows you to specify the App Op to enforce restrictions on which receivers
+ * the broadcast will be sent to as well as supply an optional sending options
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermission String naming a permissions that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ * @param receiverAppOp The app op associated with the broadcast. If null, no appOp is
+ * required. If both receiverAppOp and receiverPermission are non-null,
+ * a receiver must have both of them to
+ * receive the broadcast
+ * @param options (optional) Additional sending options, generated from a
+ * {@link android.app.BroadcastOptions}.
+ * @param resultReceiver Your own BroadcastReceiver to treat as the final
+ * receiver of the broadcast.
+ * @param scheduler A custom Handler with which to schedule the
+ * resultReceiver callback; if null it will be
+ * scheduled in the Context's main thread.
+ * @param initialCode An initial value for the result code. Often
+ * Activity.RESULT_OK.
+ * @param initialData An initial value for the result data. Often
+ * null.
+ * @param initialExtras An initial value for the result extras. Often
+ * null.
+ *
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ * @see android.app.BroadcastOptions
+ */
+ public void sendOrderedBroadcast(@RequiresPermission @NonNull Intent intent,
+ @Nullable String receiverPermission, @Nullable String receiverAppOp,
+ @Nullable Bundle options, @Nullable BroadcastReceiver resultReceiver,
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* <p>Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the
* Intent you are sending stays around after the broadcast is complete,
* so that others can quickly retrieve that data through the return
@@ -3329,6 +3371,7 @@
CROSS_PROFILE_APPS_SERVICE,
//@hide: SYSTEM_UPDATE_SERVICE,
//@hide: TIME_DETECTOR_SERVICE,
+ //@hide: TIME_ZONE_DETECTOR_SERVICE,
PERMISSION_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
@@ -4743,7 +4786,7 @@
/**
* Use with {@link #getSystemService(String)} to retrieve an
- * {@link android.app.timedetector.ITimeDetectorService}.
+ * {@link android.app.timedetector.TimeDetector}.
* @hide
*
* @see #getSystemService(String)
@@ -4751,6 +4794,15 @@
public static final String TIME_DETECTOR_SERVICE = "time_detector";
/**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.app.timezonedetector.TimeZoneDetector}.
+ * @hide
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String TIME_ZONE_DETECTOR_SERVICE = "time_zone_detector";
+
+ /**
* Binder service name for {@link AppBindingService}.
* @hide
*/
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 9b24bee..22b345f 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -583,6 +583,16 @@
}
@Override
+ public void sendOrderedBroadcast(@RequiresPermission @NonNull Intent intent,
+ @Nullable String receiverPermission, @Nullable String receiverAppOp,
+ @Nullable Bundle options, @Nullable BroadcastReceiver resultReceiver,
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras) {
+ mBase.sendOrderedBroadcast(intent, receiverPermission, receiverAppOp, options,
+ resultReceiver, scheduler, initialCode, initialData, initialExtras);
+ }
+
+ @Override
@Deprecated
public void sendStickyBroadcast(Intent intent) {
mBase.sendStickyBroadcast(intent);
diff --git a/core/java/android/net/IpConfiguration.java b/core/java/android/net/IpConfiguration.java
index 143467b..dddb64d 100644
--- a/core/java/android/net/IpConfiguration.java
+++ b/core/java/android/net/IpConfiguration.java
@@ -191,18 +191,12 @@
83 * httpProxy.hashCode();
}
- /**
- * Implement the Parcelable interface
- * @hide
- */
+ /** Implement the Parcelable interface */
public int describeContents() {
return 0;
}
- /**
- * Implement the Parcelable interface
- * @hide
- */
+ /** Implement the Parcelable interface */
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(ipAssignment.name());
dest.writeString(proxySettings.name());
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 0706e75..8e18341 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -762,10 +762,7 @@
* Returns the NAT64 prefix in use on this link, if any.
*
* @return the NAT64 prefix or {@code null}.
- * @hide
*/
- @SystemApi
- @TestApi
public @Nullable IpPrefix getNat64Prefix() {
return mNat64Prefix;
}
@@ -777,10 +774,7 @@
* 128-bit IPv6 address) are supported or {@code null} for no prefix.
*
* @param prefix the NAT64 prefix.
- * @hide
*/
- @SystemApi
- @TestApi
public void setNat64Prefix(@Nullable IpPrefix prefix) {
if (prefix != null && prefix.getPrefixLength() != 96) {
throw new IllegalArgumentException("Only 96-bit prefixes are supported: " + prefix);
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 357c0c9..2e6d2cc 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -502,6 +502,19 @@
public static final native void restoreCallingWorkSource(long token);
/**
+ * Mark as being built with VINTF-level stability promise. This API should
+ * only ever be invoked by the build system. It means that the interface
+ * represented by this binder is guaranteed to be kept stable for several
+ * years, and the build system also keeps snapshots of these APIs and
+ * invokes the AIDL compiler to make sure that these snapshots are
+ * backwards compatible. Instead of using this API, use an @VintfStability
+ * interface.
+ *
+ * @hide
+ */
+ public final native void markVintfStability();
+
+ /**
* Flush any Binder commands pending in the current thread to the kernel
* driver. This can be
* useful to call before performing an operation that may block for a long
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index f21956b..1eda4d9 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -992,6 +992,11 @@
* engaged. It's now time to see if you can dance.</em>
*/
public static final int Q = 29;
+
+ /**
+ * R.
+ */
+ public static final int R = CUR_DEVELOPMENT;
}
/** The type of build, like "user" or "eng". */
@@ -1077,7 +1082,7 @@
return result == 0;
}
- final String system = SystemProperties.get("ro.build.fingerprint");
+ final String system = SystemProperties.get("ro.system.build.fingerprint");
final String vendor = SystemProperties.get("ro.vendor.build.fingerprint");
final String bootimage = SystemProperties.get("ro.bootimage.build.fingerprint");
final String requiredBootloader = SystemProperties.get("ro.build.expect.bootloader");
@@ -1086,7 +1091,7 @@
final String currentRadio = SystemProperties.get("gsm.version.baseband");
if (TextUtils.isEmpty(system)) {
- Slog.e(TAG, "Required ro.build.fingerprint is empty!");
+ Slog.e(TAG, "Required ro.system.build.fingerprint is empty!");
return false;
}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index c237222..7a131e5 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -55,6 +55,7 @@
private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT";
private static final String ENV_PRODUCT_ROOT = "PRODUCT_ROOT";
private static final String ENV_SYSTEM_EXT_ROOT = "SYSTEM_EXT_ROOT";
+ private static final String ENV_APEX_ROOT = "APEX_ROOT";
/** {@hide} */
public static final String DIR_ANDROID = "Android";
@@ -78,7 +79,9 @@
private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor");
private static final File DIR_PRODUCT_ROOT = getDirectory(ENV_PRODUCT_ROOT, "/product");
private static final File DIR_SYSTEM_EXT_ROOT = getDirectory(ENV_SYSTEM_EXT_ROOT,
- "/system_ext");
+ "/system_ext");
+ private static final File DIR_APEX_ROOT = getDirectory(ENV_APEX_ROOT,
+ "/apex");
@UnsupportedAppUsage
private static UserEnvironment sCurrentUser;
@@ -246,6 +249,16 @@
}
/**
+ * Return root directory of the apex mount point, where all the apex modules are made available
+ * to the rest of the system.
+ *
+ * @hide
+ */
+ public static @NonNull File getApexDirectory() {
+ return DIR_APEX_ROOT;
+ }
+
+ /**
* Return the system directory for a user. This is for use by system
* services to store files relating to the user. This directory will be
* automatically deleted when the user is removed.
diff --git a/core/java/android/os/ResultReceiver.aidl b/core/java/android/os/ResultReceiver.aidl
index 28ce6d5..9fd5bc9 100644
--- a/core/java/android/os/ResultReceiver.aidl
+++ b/core/java/android/os/ResultReceiver.aidl
@@ -2,19 +2,19 @@
**
** Copyright 2007, 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
+** 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
+** 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
+** 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 android.os;
-parcelable ResultReceiver;
+@JavaOnlyStableParcelable parcelable ResultReceiver;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ade0caf..d87e9ca 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -101,7 +101,6 @@
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -12464,105 +12463,6 @@
"adb_allowed_connection_time";
/**
- * Get the key that retrieves a bluetooth headset's priority.
- * @hide
- */
- public static final String getBluetoothHeadsetPriorityKey(String address) {
- return BLUETOOTH_HEADSET_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
- }
-
- /**
- * Get the key that retrieves a bluetooth a2dp sink's priority.
- * @hide
- */
- public static final String getBluetoothA2dpSinkPriorityKey(String address) {
- return BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
- }
-
- /**
- * Get the key that retrieves a bluetooth a2dp src's priority.
- * @hide
- */
- public static final String getBluetoothA2dpSrcPriorityKey(String address) {
- return BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
- }
-
- /**
- * Get the key that retrieves a bluetooth a2dp device's ability to support optional codecs.
- * @hide
- */
- public static final String getBluetoothA2dpSupportsOptionalCodecsKey(String address) {
- return BLUETOOTH_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX +
- address.toUpperCase(Locale.ROOT);
- }
-
- /**
- * Get the key that retrieves whether a bluetooth a2dp device should have optional codecs
- * enabled.
- * @hide
- */
- public static final String getBluetoothA2dpOptionalCodecsEnabledKey(String address) {
- return BLUETOOTH_A2DP_OPTIONAL_CODECS_ENABLED_PREFIX +
- address.toUpperCase(Locale.ROOT);
- }
-
- /**
- * Get the key that retrieves a bluetooth Input Device's priority.
- * @hide
- */
- public static final String getBluetoothHidHostPriorityKey(String address) {
- return BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
- }
-
- /**
- * Get the key that retrieves a bluetooth pan client priority.
- * @hide
- */
- public static final String getBluetoothPanPriorityKey(String address) {
- return BLUETOOTH_PAN_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
- }
-
- /**
- * Get the key that retrieves a bluetooth hearing aid priority.
- * @hide
- */
- public static final String getBluetoothHearingAidPriorityKey(String address) {
- return BLUETOOTH_HEARING_AID_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
- }
-
- /**
- * Get the key that retrieves a bluetooth map priority.
- * @hide
- */
- public static final String getBluetoothMapPriorityKey(String address) {
- return BLUETOOTH_MAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
- }
-
- /**
- * Get the key that retrieves a bluetooth map client priority.
- * @hide
- */
- public static final String getBluetoothMapClientPriorityKey(String address) {
- return BLUETOOTH_MAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
- }
-
- /**
- * Get the key that retrieves a bluetooth pbap client priority.
- * @hide
- */
- public static final String getBluetoothPbapClientPriorityKey(String address) {
- return BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
- }
-
- /**
- * Get the key that retrieves a bluetooth sap priority.
- * @hide
- */
- public static final String getBluetoothSapPriorityKey(String address) {
- return BLUETOOTH_SAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
- }
-
- /**
* Scaling factor for normal window animations. Setting to 0 will
* disable window animations.
*/
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index fe7e508..a236f31 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -30,12 +30,13 @@
#include <unistd.h>
#include <android-base/stringprintf.h>
-#include <binder/IInterface.h>
-#include <binder/IServiceManager.h>
-#include <binder/IPCThreadState.h>
-#include <binder/Parcel.h>
#include <binder/BpBinder.h>
+#include <binder/IInterface.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/Parcel.h>
#include <binder/ProcessState.h>
+#include <binder/Stability.h>
#include <cutils/atomic.h>
#include <log/log.h>
#include <utils/KeyedVector.h>
@@ -459,6 +460,9 @@
sp<JavaBBinder> b = mBinder.promote();
if (b == NULL) {
b = new JavaBBinder(env, obj);
+ if (mVintf) {
+ ::android::internal::Stability::markVintf(b.get());
+ }
mBinder = b;
ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\n",
b.get(), b->getWeakRefs(), obj, b->getWeakRefs()->getWeakCount());
@@ -473,9 +477,18 @@
return mBinder.promote();
}
+ void markVintf() {
+ mVintf = true;
+ }
+
private:
Mutex mLock;
wp<JavaBBinder> mBinder;
+
+ // in the future, we might condense this into int32_t stability, or if there
+ // is too much binder state here, we can think about making JavaBBinder an
+ // sp here (avoid recreating it)
+ bool mVintf = false;
};
// ----------------------------------------------------------------------------
@@ -962,6 +975,12 @@
IPCThreadState::self()->restoreCallingWorkSource(token);
}
+static void android_os_Binder_markVintfStability(JNIEnv* env, jobject clazz) {
+ JavaBBinderHolder* jbh =
+ (JavaBBinderHolder*) env->GetLongField(clazz, gBinderOffsets.mObject);
+ jbh->markVintf();
+}
+
static void android_os_Binder_flushPendingCommands(JNIEnv* env, jobject clazz)
{
IPCThreadState::self()->flushCommands();
@@ -1038,6 +1057,7 @@
// @CriticalNative
{ "clearCallingWorkSource", "()J", (void*)android_os_Binder_clearCallingWorkSource },
{ "restoreCallingWorkSource", "(J)V", (void*)android_os_Binder_restoreCallingWorkSource },
+ { "markVintfStability", "()V", (void*)android_os_Binder_markVintfStability},
{ "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
{ "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder },
{ "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer },
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 8fabb23..fd6984b 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -36,6 +36,7 @@
"/apex/com.android.conscrypt/javalib/conscrypt.jar",
"/apex/com.android.ipsec/javalib/ike.jar",
"/apex/com.android.media/javalib/updatable-media.jar",
+ "/apex/com.android.sdkext/javalib/framework-sdkext.jar",
"/dev/null",
"/dev/socket/zygote",
"/dev/socket/zygote_secondary",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2c39673..b5c4ba0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -633,6 +633,11 @@
<protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" />
+ <!-- NETWORK_SET_TIME / NETWORK_SET_TIMEZONE moved from com.android.phone to system server.
+ They should ultimately be removed. -->
+ <protected-broadcast android:name="android.intent.action.NETWORK_SET_TIME" />
+ <protected-broadcast android:name="android.intent.action.NETWORK_SET_TIMEZONE" />
+
<!-- For tether entitlement recheck-->
<protected-broadcast
android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" />
diff --git a/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java
new file mode 100644
index 0000000..ae91edc
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2019 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 android.app.timezonedetector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import org.junit.Test;
+
+public class PhoneTimeZoneSuggestionTest {
+ private static final int PHONE_ID = 99999;
+
+ @Test
+ public void testEquals() {
+ PhoneTimeZoneSuggestion.Builder builder1 = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+ {
+ PhoneTimeZoneSuggestion one = builder1.build();
+ assertEquals(one, one);
+ }
+
+ PhoneTimeZoneSuggestion.Builder builder2 = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+ {
+ PhoneTimeZoneSuggestion one = builder1.build();
+ PhoneTimeZoneSuggestion two = builder2.build();
+ assertEquals(one, two);
+ assertEquals(two, one);
+ }
+
+ PhoneTimeZoneSuggestion.Builder builder3 =
+ new PhoneTimeZoneSuggestion.Builder(PHONE_ID + 1);
+ {
+ PhoneTimeZoneSuggestion one = builder1.build();
+ PhoneTimeZoneSuggestion three = builder3.build();
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+
+ builder1.setZoneId("Europe/London");
+ builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
+ builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+ {
+ PhoneTimeZoneSuggestion one = builder1.build();
+ PhoneTimeZoneSuggestion two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder2.setZoneId("Europe/Paris");
+ builder2.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
+ builder2.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+ {
+ PhoneTimeZoneSuggestion one = builder1.build();
+ PhoneTimeZoneSuggestion two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder1.setZoneId("Europe/Paris");
+ {
+ PhoneTimeZoneSuggestion one = builder1.build();
+ PhoneTimeZoneSuggestion two = builder2.build();
+ assertEquals(one, two);
+ }
+
+ builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID);
+ builder2.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
+ {
+ PhoneTimeZoneSuggestion one = builder1.build();
+ PhoneTimeZoneSuggestion two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
+ {
+ PhoneTimeZoneSuggestion one = builder1.build();
+ PhoneTimeZoneSuggestion two = builder2.build();
+ assertEquals(one, two);
+ }
+
+ builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+ builder2.setQuality(PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
+ {
+ PhoneTimeZoneSuggestion one = builder1.build();
+ PhoneTimeZoneSuggestion two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
+ {
+ PhoneTimeZoneSuggestion one = builder1.build();
+ PhoneTimeZoneSuggestion two = builder2.build();
+ assertEquals(one, two);
+ }
+
+ // DebugInfo must not be considered in equals().
+ {
+ PhoneTimeZoneSuggestion one = builder1.build();
+ PhoneTimeZoneSuggestion two = builder2.build();
+ one.addDebugInfo("Debug info 1");
+ two.addDebugInfo("Debug info 2");
+ assertEquals(one, two);
+ }
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void testBuilderValidates_emptyZone_badMatchType() {
+ PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+ // No zone ID, so match type should be left unset.
+ builder.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET);
+ builder.build();
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void testBuilderValidates_zoneSet_badMatchType() {
+ PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+ builder.setZoneId("Europe/London");
+ builder.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+ builder.build();
+ }
+
+ @Test
+ public void testParcelable() {
+ PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+ assertRoundTripParcelable(builder.build());
+
+ builder.setZoneId("Europe/London");
+ builder.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID);
+ builder.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+ PhoneTimeZoneSuggestion suggestion1 = builder.build();
+ assertRoundTripParcelable(suggestion1);
+
+ // DebugInfo should also be stored (but is not checked by equals()
+ String debugString = "This is debug info";
+ suggestion1.addDebugInfo(debugString);
+ PhoneTimeZoneSuggestion suggestion1_2 = roundTripParcelable(suggestion1);
+ assertEquals(suggestion1, suggestion1_2);
+ assertTrue(suggestion1_2.getDebugInfo().contains(debugString));
+ }
+
+ private static void assertRoundTripParcelable(PhoneTimeZoneSuggestion instance) {
+ assertEquals(instance, roundTripParcelable(instance));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T extends Parcelable> T roundTripParcelable(T one) {
+ Parcel parcel = Parcel.obtain();
+ parcel.writeTypedObject(one, 0);
+ parcel.setDataPosition(0);
+
+ T toReturn = (T) parcel.readTypedObject(PhoneTimeZoneSuggestion.CREATOR);
+ parcel.recycle();
+ return toReturn;
+ }
+}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 142078e..9e49826 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -32,9 +32,11 @@
import static android.os.image.DynamicSystemClient.STATUS_NOT_STARTED;
import static android.os.image.DynamicSystemClient.STATUS_READY;
+import static com.android.dynsystem.InstallationAsyncTask.RESULT_CANCELLED;
import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_EXCEPTION;
-import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_INVALID_URL;
import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_IO;
+import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_UNSUPPORTED_FORMAT;
+import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_UNSUPPORTED_URL;
import static com.android.dynsystem.InstallationAsyncTask.RESULT_OK;
import android.app.Notification;
@@ -66,11 +68,10 @@
* cancel and confirm commnands.
*/
public class DynamicSystemInstallationService extends Service
- implements InstallationAsyncTask.InstallStatusListener {
+ implements InstallationAsyncTask.ProgressListener {
private static final String TAG = "DynSystemInstallationService";
-
// TODO (b/131866826): This is currently for test only. Will move this to System API.
static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
@@ -121,9 +122,12 @@
private DynamicSystemManager mDynSystem;
private NotificationManager mNM;
- private long mSystemSize;
- private long mUserdataSize;
- private long mInstalledSize;
+ private int mNumInstalledPartitions;
+
+ private String mCurrentPartitionName;
+ private long mCurrentPartitionSize;
+ private long mCurrentPartitionInstalledSize;
+
private boolean mJustCancelledByUser;
// This is for testing only now
@@ -176,8 +180,12 @@
}
@Override
- public void onProgressUpdate(long installedSize) {
- mInstalledSize = installedSize;
+ public void onProgressUpdate(InstallationAsyncTask.Progress progress) {
+ mCurrentPartitionName = progress.mPartitionName;
+ mCurrentPartitionSize = progress.mPartitionSize;
+ mCurrentPartitionInstalledSize = progress.mInstalledSize;
+ mNumInstalledPartitions = progress.mNumInstalledPartitions;
+
postStatus(STATUS_IN_PROGRESS, CAUSE_NOT_SPECIFIED, null);
}
@@ -197,11 +205,16 @@
resetTaskAndStop();
switch (result) {
+ case RESULT_CANCELLED:
+ postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED, null);
+ break;
+
case RESULT_ERROR_IO:
postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_IO, detail);
break;
- case RESULT_ERROR_INVALID_URL:
+ case RESULT_ERROR_UNSUPPORTED_URL:
+ case RESULT_ERROR_UNSUPPORTED_FORMAT:
postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_INVALID_URL, detail);
break;
@@ -211,12 +224,6 @@
}
}
- @Override
- public void onCancelled() {
- resetTaskAndStop();
- postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED, null);
- }
-
private void executeInstallCommand(Intent intent) {
if (!verifyRequest(intent)) {
Log.e(TAG, "Verification failed. Did you use VerificationActivity?");
@@ -234,12 +241,13 @@
}
String url = intent.getDataString();
- mSystemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0);
- mUserdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
+ long systemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0);
+ long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false);
+ // TODO: better constructor or builder
mInstallTask = new InstallationAsyncTask(
- url, mSystemSize, mUserdataSize, this, mDynSystem, this);
+ url, systemSize, userdataSize, this, mDynSystem, this);
mInstallTask.execute();
@@ -257,7 +265,7 @@
mJustCancelledByUser = true;
if (mInstallTask.cancel(false)) {
- // Will cleanup and post status in onCancelled()
+ // Will cleanup and post status in onResult()
Log.d(TAG, "Cancel request filed successfully");
} else {
Log.e(TAG, "Trying to cancel installation while it's already completed.");
@@ -288,7 +296,7 @@
private void executeRebootToDynSystemCommand() {
boolean enabled = false;
- if (mInstallTask != null && mInstallTask.getResult() == RESULT_OK) {
+ if (mInstallTask != null && mInstallTask.isCompleted()) {
enabled = mInstallTask.commit();
} else if (isDynamicSystemInstalled()) {
enabled = mDynSystem.setEnable(true, true);
@@ -380,8 +388,16 @@
case STATUS_IN_PROGRESS:
builder.setContentText(getString(R.string.notification_install_inprogress));
- int max = (int) Math.max((mSystemSize + mUserdataSize) >> 20, 1);
- int progress = (int) (mInstalledSize >> 20);
+ int max = 1024;
+ int progress = 0;
+
+ int currentMax = max >> (mNumInstalledPartitions + 1);
+ progress = max - currentMax * 2;
+
+ long currentProgress = (mCurrentPartitionInstalledSize >> 20) * currentMax
+ / Math.max(mCurrentPartitionSize >> 20, 1);
+
+ progress += (int) currentProgress;
builder.setProgress(max, progress, false);
@@ -464,7 +480,8 @@
throws RemoteException {
Bundle bundle = new Bundle();
- bundle.putLong(DynamicSystemClient.KEY_INSTALLED_SIZE, mInstalledSize);
+ // TODO: send more info to the clients
+ bundle.putLong(DynamicSystemClient.KEY_INSTALLED_SIZE, mCurrentPartitionInstalledSize);
if (detail != null) {
bundle.putSerializable(DynamicSystemClient.KEY_EXCEPTION_DETAIL,
@@ -492,9 +509,7 @@
return STATUS_IN_PROGRESS;
case FINISHED:
- int result = mInstallTask.getResult();
-
- if (result == RESULT_OK) {
+ if (mInstallTask.isCompleted()) {
return STATUS_READY;
} else {
throw new IllegalStateException("A failed InstallationTask is not reset");
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index 19ae970..b206a1f 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -17,7 +17,6 @@
package com.android.dynsystem;
import android.content.Context;
-import android.gsi.GsiProgress;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.MemoryFile;
@@ -27,35 +26,70 @@
import android.webkit.URLUtil;
import java.io.BufferedInputStream;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.List;
import java.util.Locale;
import java.util.zip.GZIPInputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
-class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> {
+class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Progress, Throwable> {
private static final String TAG = "InstallationAsyncTask";
private static final int READ_BUFFER_SIZE = 1 << 13;
+ private static final long MIN_PROGRESS_TO_PUBLISH = 1 << 27;
- private class InvalidImageUrlException extends RuntimeException {
- private InvalidImageUrlException(String message) {
+ private static final List<String> UNSUPPORTED_PARTITIONS =
+ Arrays.asList("vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other");
+
+ private class UnsupportedUrlException extends RuntimeException {
+ private UnsupportedUrlException(String message) {
super(message);
}
}
- /** Not completed, including being cancelled */
- static final int NO_RESULT = 0;
+ private class UnsupportedFormatException extends RuntimeException {
+ private UnsupportedFormatException(String message) {
+ super(message);
+ }
+ }
+
+ /** UNSET means the installation is not completed */
+ static final int RESULT_UNSET = 0;
static final int RESULT_OK = 1;
- static final int RESULT_ERROR_IO = 2;
- static final int RESULT_ERROR_INVALID_URL = 3;
+ static final int RESULT_CANCELLED = 2;
+ static final int RESULT_ERROR_IO = 3;
+ static final int RESULT_ERROR_UNSUPPORTED_URL = 4;
+ static final int RESULT_ERROR_UNSUPPORTED_FORMAT = 5;
static final int RESULT_ERROR_EXCEPTION = 6;
- interface InstallStatusListener {
- void onProgressUpdate(long installedSize);
+ class Progress {
+ String mPartitionName;
+ long mPartitionSize;
+ long mInstalledSize;
+
+ int mNumInstalledPartitions;
+
+ Progress(String partitionName, long partitionSize, long installedSize,
+ int numInstalled) {
+ mPartitionName = partitionName;
+ mPartitionSize = partitionSize;
+ mInstalledSize = installedSize;
+
+ mNumInstalledPartitions = numInstalled;
+ }
+ }
+
+ interface ProgressListener {
+ void onProgressUpdate(Progress progress);
void onResult(int resultCode, Throwable detail);
- void onCancelled();
}
private final String mUrl;
@@ -63,16 +97,17 @@
private final long mUserdataSize;
private final Context mContext;
private final DynamicSystemManager mDynSystem;
- private final InstallStatusListener mListener;
+ private final ProgressListener mListener;
private DynamicSystemManager.Session mInstallationSession;
- private int mResult = NO_RESULT;
+ private boolean mIsZip;
+ private boolean mIsCompleted;
private InputStream mStream;
-
+ private ZipFile mZipFile;
InstallationAsyncTask(String url, long systemSize, long userdataSize, Context context,
- DynamicSystemManager dynSystem, InstallStatusListener listener) {
+ DynamicSystemManager dynSystem, ProgressListener listener) {
mUrl = url;
mSystemSize = systemSize;
mUserdataSize = userdataSize;
@@ -82,133 +117,292 @@
}
@Override
- protected void onPreExecute() {
- mListener.onProgressUpdate(0);
- }
-
- @Override
protected Throwable doInBackground(String... voids) {
Log.d(TAG, "Start doInBackground(), URL: " + mUrl);
try {
- long installedSize = 0;
- long reportedInstalledSize = 0;
+ // call DynamicSystemManager to cleanup stuff
+ mDynSystem.remove();
- long minStepToReport = (mSystemSize + mUserdataSize) / 100;
+ verifyAndPrepare();
- // init input stream before calling startInstallation(), which takes 90 seconds.
- initInputStream();
+ mDynSystem.startInstallation();
- Thread thread =
- new Thread(
- () -> {
- mDynSystem.startInstallation();
- mDynSystem.createPartition("userdata", mUserdataSize, false);
- mInstallationSession =
- mDynSystem.createPartition("system", mSystemSize, true);
- });
-
- thread.start();
-
- while (thread.isAlive()) {
- if (isCancelled()) {
- boolean aborted = mDynSystem.abort();
- Log.d(TAG, "Called DynamicSystemManager.abort(), result = " + aborted);
- return null;
- }
-
- GsiProgress progress = mDynSystem.getInstallationProgress();
- installedSize = progress.bytes_processed;
-
- if (installedSize > reportedInstalledSize + minStepToReport) {
- publishProgress(installedSize);
- reportedInstalledSize = installedSize;
- }
-
- Thread.sleep(10);
+ installUserdata();
+ if (isCancelled()) {
+ mDynSystem.remove();
+ return null;
}
- if (mInstallationSession == null) {
- throw new IOException(
- "Failed to start installation with requested size: "
- + (mSystemSize + mUserdataSize));
+ installImages();
+ if (isCancelled()) {
+ mDynSystem.remove();
+ return null;
}
- installedSize = mUserdataSize;
-
- MemoryFile memoryFile = new MemoryFile("dsu", READ_BUFFER_SIZE);
- byte[] bytes = new byte[READ_BUFFER_SIZE];
- mInstallationSession.setAshmem(
- new ParcelFileDescriptor(memoryFile.getFileDescriptor()), READ_BUFFER_SIZE);
- int numBytesRead;
- Log.d(TAG, "Start installation loop");
- while ((numBytesRead = mStream.read(bytes, 0, READ_BUFFER_SIZE)) != -1) {
- memoryFile.writeBytes(bytes, 0, 0, numBytesRead);
- if (isCancelled()) {
- break;
- }
- if (!mInstallationSession.submitFromAshmem(numBytesRead)) {
- throw new IOException("Failed write() to DynamicSystem");
- }
-
- installedSize += numBytesRead;
-
- if (installedSize > reportedInstalledSize + minStepToReport) {
- publishProgress(installedSize);
- reportedInstalledSize = installedSize;
- }
- }
mDynSystem.finishInstallation();
- return null;
-
} catch (Exception e) {
e.printStackTrace();
+ mDynSystem.remove();
return e;
} finally {
close();
}
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Throwable detail) {
+ int result = RESULT_UNSET;
+
+ if (detail == null) {
+ result = RESULT_OK;
+ mIsCompleted = true;
+ } else if (detail instanceof IOException) {
+ result = RESULT_ERROR_IO;
+ } else if (detail instanceof UnsupportedUrlException) {
+ result = RESULT_ERROR_UNSUPPORTED_URL;
+ } else if (detail instanceof UnsupportedFormatException) {
+ result = RESULT_ERROR_UNSUPPORTED_FORMAT;
+ } else {
+ result = RESULT_ERROR_EXCEPTION;
+ }
+
+ Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + result);
+
+ mListener.onResult(result, detail);
}
@Override
protected void onCancelled() {
Log.d(TAG, "onCancelled(), URL: " + mUrl);
- mListener.onCancelled();
- }
-
- @Override
- protected void onPostExecute(Throwable detail) {
- if (detail == null) {
- mResult = RESULT_OK;
- } else if (detail instanceof IOException) {
- mResult = RESULT_ERROR_IO;
- } else if (detail instanceof InvalidImageUrlException) {
- mResult = RESULT_ERROR_INVALID_URL;
+ if (mDynSystem.abort()) {
+ Log.d(TAG, "Installation aborted");
} else {
- mResult = RESULT_ERROR_EXCEPTION;
+ Log.w(TAG, "DynamicSystemManager.abort() returned false");
}
- Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + mResult);
-
- mListener.onResult(mResult, detail);
+ mListener.onResult(RESULT_CANCELLED, null);
}
@Override
- protected void onProgressUpdate(Long... values) {
- long progress = values[0];
+ protected void onProgressUpdate(Progress... values) {
+ Progress progress = values[0];
mListener.onProgressUpdate(progress);
}
- private void initInputStream() throws IOException, InvalidImageUrlException {
- if (URLUtil.isNetworkUrl(mUrl) || URLUtil.isFileUrl(mUrl)) {
- mStream = new BufferedInputStream(new GZIPInputStream(new URL(mUrl).openStream()));
- } else if (URLUtil.isContentUrl(mUrl)) {
- Uri uri = Uri.parse(mUrl);
- mStream = new BufferedInputStream(new GZIPInputStream(
- mContext.getContentResolver().openInputStream(uri)));
+ private void verifyAndPrepare() throws Exception {
+ String extension = mUrl.substring(mUrl.lastIndexOf('.') + 1);
+
+ if ("gz".equals(extension) || "gzip".equals(extension)) {
+ mIsZip = false;
+ } else if ("zip".equals(extension)) {
+ mIsZip = true;
} else {
- throw new InvalidImageUrlException(
- String.format(Locale.US, "Unsupported file source: %s", mUrl));
+ throw new UnsupportedFormatException(
+ String.format(Locale.US, "Unsupported file format: %s", mUrl));
+ }
+
+ if (URLUtil.isNetworkUrl(mUrl)) {
+ mStream = new URL(mUrl).openStream();
+ } else if (URLUtil.isFileUrl(mUrl)) {
+ if (mIsZip) {
+ mZipFile = new ZipFile(new File(new URL(mUrl).toURI()));
+ } else {
+ mStream = new URL(mUrl).openStream();
+ }
+ } else if (URLUtil.isContentUrl(mUrl)) {
+ mStream = mContext.getContentResolver().openInputStream(Uri.parse(mUrl));
+ } else {
+ throw new UnsupportedUrlException(
+ String.format(Locale.US, "Unsupported URL: %s", mUrl));
+ }
+ }
+
+ private void installUserdata() throws Exception {
+ Thread thread = new Thread(() -> {
+ mInstallationSession = mDynSystem.createPartition("userdata", mUserdataSize, false);
+ });
+
+ Log.d(TAG, "Creating partition: userdata");
+ thread.start();
+
+ long installedSize = 0;
+ Progress progress = new Progress("userdata", mUserdataSize, installedSize, 0);
+
+ while (thread.isAlive()) {
+ if (isCancelled()) {
+ return;
+ }
+
+ installedSize = mDynSystem.getInstallationProgress().bytes_processed;
+
+ if (installedSize > progress.mInstalledSize + MIN_PROGRESS_TO_PUBLISH) {
+ progress.mInstalledSize = installedSize;
+ publishProgress(progress);
+ }
+
+ Thread.sleep(10);
+ }
+
+ if (mInstallationSession == null) {
+ throw new IOException(
+ "Failed to start installation with requested size: " + mUserdataSize);
+ }
+ }
+
+ private void installImages() throws IOException, InterruptedException {
+ if (mStream != null) {
+ if (mIsZip) {
+ installStreamingZipUpdate();
+ } else {
+ installStreamingGzUpdate();
+ }
+ } else {
+ installLocalZipUpdate();
+ }
+ }
+
+ private void installStreamingGzUpdate() throws IOException, InterruptedException {
+ Log.d(TAG, "To install a streaming GZ update");
+ installImage("system", mSystemSize, new GZIPInputStream(mStream), 1);
+ }
+
+ private void installStreamingZipUpdate() throws IOException, InterruptedException {
+ Log.d(TAG, "To install a streaming ZIP update");
+
+ ZipInputStream zis = new ZipInputStream(mStream);
+ ZipEntry zipEntry = null;
+
+ int numInstalledPartitions = 1;
+
+ while ((zipEntry = zis.getNextEntry()) != null) {
+ if (installImageFromAnEntry(zipEntry, zis, numInstalledPartitions)) {
+ numInstalledPartitions++;
+ }
+
+ if (isCancelled()) {
+ break;
+ }
+ }
+ }
+
+ private void installLocalZipUpdate() throws IOException, InterruptedException {
+ Log.d(TAG, "To install a local ZIP update");
+
+ Enumeration<? extends ZipEntry> entries = mZipFile.entries();
+ int numInstalledPartitions = 1;
+
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ if (installImageFromAnEntry(
+ entry, mZipFile.getInputStream(entry), numInstalledPartitions)) {
+ numInstalledPartitions++;
+ }
+
+ if (isCancelled()) {
+ break;
+ }
+ }
+ }
+
+ private boolean installImageFromAnEntry(ZipEntry entry, InputStream is,
+ int numInstalledPartitions) throws IOException, InterruptedException {
+ String name = entry.getName();
+
+ Log.d(TAG, "ZipEntry: " + name);
+
+ if (!name.endsWith(".img")) {
+ return false;
+ }
+
+ String partitionName = name.substring(0, name.length() - 4);
+
+ if (UNSUPPORTED_PARTITIONS.contains(partitionName)) {
+ Log.d(TAG, name + " installation is not supported, skip it.");
+ return false;
+ }
+
+ long uncompressedSize = entry.getSize();
+
+ installImage(partitionName, uncompressedSize, is, numInstalledPartitions);
+
+ return true;
+ }
+
+ private void installImage(String partitionName, long uncompressedSize, InputStream is,
+ int numInstalledPartitions) throws IOException, InterruptedException {
+
+ SparseInputStream sis = new SparseInputStream(new BufferedInputStream(is));
+
+ long unsparseSize = sis.getUnsparseSize();
+
+ final long partitionSize;
+
+ if (unsparseSize != -1) {
+ partitionSize = unsparseSize;
+ Log.d(TAG, partitionName + " is sparse, raw size = " + unsparseSize);
+ } else if (uncompressedSize != -1) {
+ partitionSize = uncompressedSize;
+ Log.d(TAG, partitionName + " is already unsparse, raw size = " + uncompressedSize);
+ } else {
+ throw new IOException("Cannot get raw size for " + partitionName);
+ }
+
+ Thread thread = new Thread(() -> {
+ mInstallationSession =
+ mDynSystem.createPartition(partitionName, partitionSize, true);
+ });
+
+ Log.d(TAG, "Start creating partition: " + partitionName);
+ thread.start();
+
+ while (thread.isAlive()) {
+ if (isCancelled()) {
+ return;
+ }
+
+ Thread.sleep(10);
+ }
+
+ if (mInstallationSession == null) {
+ throw new IOException(
+ "Failed to start installation with requested size: " + partitionSize);
+ }
+
+ Log.d(TAG, "Start installing: " + partitionName);
+
+ MemoryFile memoryFile = new MemoryFile("dsu_" + partitionName, READ_BUFFER_SIZE);
+ ParcelFileDescriptor pfd = new ParcelFileDescriptor(memoryFile.getFileDescriptor());
+
+ mInstallationSession.setAshmem(pfd, READ_BUFFER_SIZE);
+
+ long installedSize = 0;
+ Progress progress = new Progress(
+ partitionName, partitionSize, installedSize, numInstalledPartitions);
+
+ byte[] bytes = new byte[READ_BUFFER_SIZE];
+ int numBytesRead;
+
+ while ((numBytesRead = sis.read(bytes, 0, READ_BUFFER_SIZE)) != -1) {
+ if (isCancelled()) {
+ return;
+ }
+
+ memoryFile.writeBytes(bytes, 0, 0, numBytesRead);
+
+ if (!mInstallationSession.submitFromAshmem(numBytesRead)) {
+ throw new IOException("Failed write() to DynamicSystem");
+ }
+
+ installedSize += numBytesRead;
+
+ if (installedSize > progress.mInstalledSize + MIN_PROGRESS_TO_PUBLISH) {
+ progress.mInstalledSize = installedSize;
+ publishProgress(progress);
+ }
}
}
@@ -218,20 +412,20 @@
mStream.close();
mStream = null;
}
+ if (mZipFile != null) {
+ mZipFile.close();
+ mZipFile = null;
+ }
} catch (IOException e) {
// ignore
}
}
- int getResult() {
- return mResult;
+ boolean isCompleted() {
+ return mIsCompleted;
}
boolean commit() {
- if (mInstallationSession == null) {
- return false;
- }
-
- return mInstallationSession.commit();
+ return mDynSystem.setEnable(true, true);
}
}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java
new file mode 100644
index 0000000..72230b4
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2019 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.dynsystem;
+
+import static java.lang.Math.min;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * SparseInputStream read from upstream and detects the data format. If the upstream is a valid
+ * sparse data, it will unsparse it on the fly. Otherwise, it just passthrough as is.
+ */
+public class SparseInputStream extends InputStream {
+ static final int FILE_HDR_SIZE = 28;
+ static final int CHUNK_HDR_SIZE = 12;
+
+ /**
+ * This class represents a chunk in the Android sparse image.
+ *
+ * @see system/core/libsparse/sparse_format.h
+ */
+ private class SparseChunk {
+ static final short RAW = (short) 0xCAC1;
+ static final short FILL = (short) 0xCAC2;
+ static final short DONTCARE = (short) 0xCAC3;
+ public short mChunkType;
+ public int mChunkSize;
+ public int mTotalSize;
+ public byte[] fill;
+ public String toString() {
+ return String.format(
+ "type: %x, chunk_size: %d, total_size: %d", mChunkType, mChunkSize, mTotalSize);
+ }
+ }
+
+ private byte[] readFull(InputStream in, int size) throws IOException {
+ byte[] buf = new byte[size];
+ for (int done = 0, n = 0; done < size; done += n) {
+ if ((n = in.read(buf, done, size - done)) < 0) {
+ throw new IOException("Failed to readFull");
+ }
+ }
+ return buf;
+ }
+
+ private ByteBuffer readBuffer(InputStream in, int size) throws IOException {
+ return ByteBuffer.wrap(readFull(in, size)).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ private SparseChunk readChunk(InputStream in) throws IOException {
+ SparseChunk chunk = new SparseChunk();
+ ByteBuffer buf = readBuffer(in, CHUNK_HDR_SIZE);
+ chunk.mChunkType = buf.getShort();
+ buf.getShort();
+ chunk.mChunkSize = buf.getInt();
+ chunk.mTotalSize = buf.getInt();
+ return chunk;
+ }
+
+ private BufferedInputStream mIn;
+ private boolean mIsSparse;
+ private long mBlockSize;
+ private long mTotalBlocks;
+ private long mTotalChunks;
+ private SparseChunk mCur;
+ private long mLeft;
+ private int mCurChunks;
+
+ public SparseInputStream(BufferedInputStream in) throws IOException {
+ mIn = in;
+ in.mark(FILE_HDR_SIZE * 2);
+ ByteBuffer buf = readBuffer(mIn, FILE_HDR_SIZE);
+ mIsSparse = (buf.getInt() == 0xed26ff3a);
+ if (!mIsSparse) {
+ mIn.reset();
+ return;
+ }
+ int major = buf.getShort();
+ int minor = buf.getShort();
+
+ if (major > 0x1 || minor > 0x0) {
+ throw new IOException("Unsupported sparse version: " + major + "." + minor);
+ }
+
+ if (buf.getShort() != FILE_HDR_SIZE) {
+ throw new IOException("Illegal file header size");
+ }
+ if (buf.getShort() != CHUNK_HDR_SIZE) {
+ throw new IOException("Illegal chunk header size");
+ }
+ mBlockSize = buf.getInt();
+ if ((mBlockSize & 0x3) != 0) {
+ throw new IOException("Illegal block size, must be a multiple of 4");
+ }
+ mTotalBlocks = buf.getInt();
+ mTotalChunks = buf.getInt();
+ mLeft = mCurChunks = 0;
+ }
+
+ /**
+ * Check if it needs to open a new chunk.
+ *
+ * @return true if it's EOF
+ */
+ private boolean prepareChunk() throws IOException {
+ if (mCur == null || mLeft <= 0) {
+ if (++mCurChunks > mTotalChunks) return true;
+ mCur = readChunk(mIn);
+ if (mCur.mChunkType == SparseChunk.FILL) {
+ mCur.fill = readFull(mIn, 4);
+ }
+ mLeft = mCur.mChunkSize * mBlockSize;
+ }
+ return mLeft == 0;
+ }
+
+ /**
+ * It overrides the InputStream.read(byte[] buf)
+ */
+ public int read(byte[] buf) throws IOException {
+ if (!mIsSparse) {
+ return mIn.read(buf);
+ }
+ if (prepareChunk()) return -1;
+ int n = -1;
+ switch (mCur.mChunkType) {
+ case SparseChunk.RAW:
+ n = mIn.read(buf, 0, (int) min(mLeft, buf.length));
+ mLeft -= n;
+ return n;
+ case SparseChunk.DONTCARE:
+ n = (int) min(mLeft, buf.length);
+ Arrays.fill(buf, 0, n - 1, (byte) 0);
+ mLeft -= n;
+ return n;
+ case SparseChunk.FILL:
+ // The FILL type is rarely used, so use a simple implmentation.
+ return super.read(buf);
+ default:
+ throw new IOException("Unsupported Chunk:" + mCur.toString());
+ }
+ }
+
+ /**
+ * It overrides the InputStream.read()
+ */
+ public int read() throws IOException {
+ if (!mIsSparse) {
+ return mIn.read();
+ }
+ if (prepareChunk()) return -1;
+ int ret = -1;
+ switch (mCur.mChunkType) {
+ case SparseChunk.RAW:
+ ret = mIn.read();
+ break;
+ case SparseChunk.DONTCARE:
+ ret = 0;
+ break;
+ case SparseChunk.FILL:
+ ret = mCur.fill[(4 - ((int) mLeft & 0x3)) & 0x3];
+ break;
+ default:
+ throw new IOException("Unsupported Chunk:" + mCur.toString());
+ }
+ mLeft--;
+ return ret;
+ }
+
+ /**
+ * Get the unsparse size
+ * @return -1 if unknown
+ */
+ public long getUnsparseSize() {
+ if (!mIsSparse) {
+ return -1;
+ }
+ return mBlockSize * mTotalBlocks;
+ }
+}
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 7b35f4d..7e8721d 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -22,13 +22,12 @@
":framework-tethering-shared-srcs",
":net-module-utils-srcs",
":services-tethering-shared-srcs",
- ":servicescore-tethering-src",
],
static_libs: [
"androidx.annotation_annotation",
- "netd_aidl_interface-java",
+ "netd_aidl_interface-unstable-java",
"netlink-client",
- "networkstack-aidl-interfaces-java",
+ "networkstack-aidl-interfaces-unstable-java",
"android.hardware.tetheroffload.control-V1.0-java",
"tethering-client",
],
@@ -41,20 +40,26 @@
defaults: ["TetheringAndroidLibraryDefaults"],
}
-cc_library_shared {
+// Due to b/143733063, APK can't access a jni lib that is in APEX (but not in the APK).
+cc_library {
name: "libtetheroffloadjni",
srcs: [
"jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp",
],
shared_libs: [
- "libnativehelper",
- "libcutils",
- "android.hardware.tetheroffload.config@1.0",
+ "libcgrouprc",
+ "libnativehelper_compat_libc++",
+ "libvndksupport",
],
static_libs: [
+ "android.hardware.tetheroffload.config@1.0",
"liblog",
"libbase",
+ "libbinderthreadstate",
+ "libcutils",
"libhidlbase",
+ "libjsoncpp",
+ "libprocessgroup",
"libutils",
],
@@ -64,6 +69,8 @@
"-Wno-unused-parameter",
"-Wthread-safety",
],
+
+ ldflags: ["-Wl,--exclude-libs=ALL,-error-limit=0"],
}
// Common defaults for compiling the actual APK.
@@ -71,7 +78,12 @@
name: "TetheringAppDefaults",
platform_apis: true,
privileged: true,
+ // Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
+ // explicitly.
jni_libs: [
+ "libcgrouprc",
+ "libnativehelper_compat_libc++",
+ "libvndksupport",
"libtetheroffloadjni",
],
resource_dirs: [
@@ -83,7 +95,16 @@
}
// Non-updatable tethering running in the system server process for devices not using the module
-// TODO: build in-process tethering APK here.
+android_app {
+ name: "InProcessTethering",
+ defaults: ["TetheringAppDefaults"],
+ static_libs: ["TetheringApiCurrentLib"],
+ certificate: "platform",
+ manifest: "AndroidManifest_InProcess.xml",
+ // InProcessTethering is a replacement for Tethering
+ overrides: ["Tethering"],
+ // TODO: use PlatformNetworkPermissionConfig.
+}
// Updatable tethering packaged as an application
android_app {
@@ -96,36 +117,3 @@
// The permission configuration *must* be included to ensure security of the device
required: ["NetworkPermissionConfig"],
}
-
-// This group will be removed when tethering migration is done.
-filegroup {
- name: "tethering-servicescore-srcs",
- srcs: [
- "src/com/android/server/connectivity/tethering/EntitlementManager.java",
- "src/com/android/server/connectivity/tethering/OffloadController.java",
- "src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java",
- "src/com/android/server/connectivity/tethering/TetheringConfiguration.java",
- "src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java",
- ],
-}
-
-// This group will be removed when tethering migration is done.
-filegroup {
- name: "tethering-servicesnet-srcs",
- srcs: [
- "src/android/net/dhcp/DhcpServerCallbacks.java",
- "src/android/net/dhcp/DhcpServingParamsParcelExt.java",
- "src/android/net/ip/IpServer.java",
- "src/android/net/ip/RouterAdvertisementDaemon.java",
- "src/android/net/util/InterfaceSet.java",
- "src/android/net/util/PrefixUtils.java",
- ],
-}
-
-// This group would be removed when tethering migration is done.
-filegroup {
- name: "tethering-jni-srcs",
- srcs: [
- "jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp",
- ],
-}
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index eb51593..1430ed0 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -25,5 +25,11 @@
android:process="com.android.networkstack.process"
android:extractNativeLibs="false"
android:persistent="true">
+ <service android:name="com.android.server.connectivity.tethering.TetheringService"
+ android:permission="android.permission.MAINLINE_NETWORK_STACK">
+ <intent-filter>
+ <action android:name="android.net.ITetheringConnector"/>
+ </intent-filter>
+ </service>
</application>
</manifest>
diff --git a/packages/Tethering/AndroidManifest_InProcess.xml b/packages/Tethering/AndroidManifest_InProcess.xml
new file mode 100644
index 0000000..28d405c
--- /dev/null
+++ b/packages/Tethering/AndroidManifest_InProcess.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2019 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tethering.inprocess"
+ android:sharedUserId="android.uid.system"
+ android:process="system">
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+ <application>
+ <!-- TODO: Using MAINLINE_NETWORK_STACK instead of NETWORK_STACK when tethering run in the
+ same process with networkstack -->
+ <service android:name="com.android.server.connectivity.tethering.TetheringService"
+ android:process="system"
+ android:permission="android.permission.NETWORK_STACK">
+ <intent-filter>
+ <action android:name="android.net.ITetheringConnector.InProcess"/>
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/packages/Tethering/CleanSpec.mk b/packages/Tethering/CleanSpec.mk
new file mode 100644
index 0000000..70db351
--- /dev/null
+++ b/packages/Tethering/CleanSpec.mk
@@ -0,0 +1,52 @@
+# Copyright (C) 2019 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.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list. These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list. E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# *****************************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THE BANNER
+# *****************************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/Tethering)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
+
+# ******************************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
+# ******************************************************************
diff --git a/packages/Tethering/apex/Android.bp b/packages/Tethering/apex/Android.bp
new file mode 100644
index 0000000..bca01ebd
--- /dev/null
+++ b/packages/Tethering/apex/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2019 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.
+//
+
+apex {
+ name: "com.android.tethering.apex",
+ apps: ["Tethering"],
+ manifest: "manifest.json",
+ key: "com.android.tethering.apex.key",
+
+ androidManifest: "AndroidManifest.xml",
+}
+
+apex_key {
+ name: "com.android.tethering.apex.key",
+ public_key: "com.android.tethering.apex.avbpubkey",
+ private_key: "com.android.tethering.apex.pem",
+}
+
+android_app_certificate {
+ name: "com.android.tethering.apex.certificate",
+ certificate: "com.android.tethering.apex",
+}
diff --git a/packages/Tethering/apex/AndroidManifest.xml b/packages/Tethering/apex/AndroidManifest.xml
new file mode 100644
index 0000000..7769b79
--- /dev/null
+++ b/packages/Tethering/apex/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tethering.apex">
+ <!-- APEX does not have classes.dex -->
+ <application android:hasCode="false" />
+ <!-- b/145383354: Current minSdk is locked to Q for development cycle, lock it to next version
+ before ship. -->
+ <uses-sdk
+ android:minSdkVersion="29"
+ android:targetSdkVersion="29"
+ />
+</manifest>
diff --git a/packages/Tethering/apex/com.android.tethering.apex.avbpubkey b/packages/Tethering/apex/com.android.tethering.apex.avbpubkey
new file mode 100644
index 0000000..9c87111
--- /dev/null
+++ b/packages/Tethering/apex/com.android.tethering.apex.avbpubkey
Binary files differ
diff --git a/packages/Tethering/apex/com.android.tethering.apex.pem b/packages/Tethering/apex/com.android.tethering.apex.pem
new file mode 100644
index 0000000..a8cd12e
--- /dev/null
+++ b/packages/Tethering/apex/com.android.tethering.apex.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAwloHpMmwszNBEgUVion141BTvF/oJ5g5DlQIYBtmht4tSpc3
+6elWXd+dhMzFxf/RkxSNRsU+dhD11cPKGp9nUYQQGrHEf3xEKwAHJKRMq26TkJ3o
+1TwOO70TaRKKA4ThNiM3VFDX2vy1ijArhZDIBTGVJCUl9HOHiO+ZJG5DKCx3KXbO
+QWz3c+Lbprr1L76dwIsl5kuoAFwgG0J+9BZhHEzIG1lVpGG7RRLxc8eDIxNN/oKT
+gPYBcOxFYqOECKGBBvElf6MxdRv6xG7gooALY2/HDMYUjAJSOosfwzeymugCzMhK
+e+6CSTAaEfUzuVZvMc2qnd1ly7zpLo9x+TOdH5LEVZpSwqmu2n5bqrUnSEAJUvMz
+SSw0YbsLWJZuTiTV7lecSITgqsmwuZyDexDmUkDQChzrTixsQV6S8vsh/FanjWoi
+zBlPneX8Q7/LME3hxHyLbrabxX0zWiyj8iM9h/8Y4mpO/MjEmmavglTAP4J8zrKD
+FBsntCoch9I49IpYBuO6NfKw1h7AUpLf8gARAjFjRxiJVcSgGY/Wt4/pBzJ57T5g
+xPvqxfpPQP0OA2CT8LqqzZIR8jXs8/TquvwLkkY2kRRPXx+azd5oU2A0uonrUY31
+Bc1obfmWPuEMz9bO/i06ETHuWPd4RiUNaB8qEmjYuKJfhv72YNcRwhrAYJECAwEA
+AQKCAgAaQn3b5yCH5fn5zFQPxvpBP35A6ph8mRXEeNg03B7rRCPMe0gjw9JWlrs6
+0Uw7p4gSnmlEUaxR2ZLN0kmBdV5JZlWitbg+HXU8diGA8u4lD6jCloN6JEYsDi0M
+OmQJe6/OV83HB7FStmh1BnMq9dgA06U6IAbT07RRbUY85OUQDYoAQTw3HNkGgHV7
+PrGYROIdvO9fAYPuoIP6Cu8KXee7Iii7gUOQFWBvQdL7+M4gNCCKrevuNc8WCeaK
+IFvbqq67WGPfrhYlo6UrW2vgqPpg8h5r/GuUS0/+9wNQpjrssUKHltxxiFV0PBqZ
+qI7XkPUvPoG6GMsDT0AWeW1F5ZJqEGPN67Xek0BCD0cpUli+nHD0yWGVHtkpHU2D
+qUOZdB2COfBuXRdW1LsYNPg8YjTCPsmGhISLTwiTNcZJeTxoK1y0CcVW9d7Af2aD
+lYzCegscQlXkSZiFj9s90Vd3KdD2XKrH/ADxzsOxQJ89ka004efdQa5/MKs9aChG
+/5XrwBEfN4O92OjY7KqXUAwB7CcVzNymOjD6r07LM24zbkRpwwXlkP0wmjsHBXkh
+8p0ISmY9QRdvhBgYmFmoPWZncM0zym9LI8atBs4CijQ7JjuOQ8HgHg+Se2eppWfe
+t8r6TVkDB8JeNAMxjX9q0G7icf3JjlIrgERZfyXLmpduR9NdkQKCAQEA5rp2fSKh
+RwihHNtJhNktFJuLR9OA++vyfjqhWnB8CrLPo3//LGWW/+WBr8EwXi/76hQpeKlf
+u8SmkTtxIHlTP2Brh2koh1Qf8HKzPHGjZeDFOoVPKHPqe3nV+cv3srd1mS0Eq3BA
+ZFQq+l61f2iiTZKxDroCahNEa8VMzirW6nKb5xhyMPHXgncCUdphHbwAGatas6be
+RUFg4ChH8BwX6jYw7leRUy2K6OqEl0fckT4Laitlb/ezKtwmD4PPE95q5hH0v3SO
+wetHWafiNrOXPn2wQqBrI2y+AfbTjNmQiaIPgcFKAQ7V3n+c3XfGZ9Xfv4L8m/wo
+RZ4ika1zur021QKCAQEA16OUBPA7BnWd+RJFri2kJBG5JZElaV9chO2ZHcXUbFR9
+HIPkWN19bJbki8Ca0w8FUQuS/M7JeeFjoZ194NlczbR899GVmb0X2AUKXilMacs3
+IONxIDczx3KFtsge8ewXRAjQvgE7M3NpmmJfPLPog7spMCbUIxbc3jzjiZgB/J1s
+WytlUTUY/Zy4V1wujkoydgK2KcHcEWG2oIy7EP0RwnL1NhTksXOtBH6+MoRMAT+H
+fcBK6yfJBNBRQzJ0PdkCCLdQPN1VtwRlWjPXZ3ey4fWvZ399wSLUkM2V1jB4GcOZ
++DAgtwFKs9+HfOdV42GgFWFcjP+bkM3bcdrQFnmYzQKCAQAQnf1KpePXqddwrJpu
+5vVINquhUKpJeoTMcoyMZu2IF7i8nctS9z4Yz/63GcLSBcKu6STTe99ZNqCIdS+A
+lzxXpCoaZoh0tqpWNuyRvd12yOlrfY5l63NH0U6H3xjH1k6x6XwcnMkGcMlnnsqT
+koWd8KKv3NWvrhOPb3ZIou03lWmFC02uGLzcuJWCL6gu7AtVzfGKXspDUqIXgs8r
+i9ptE9oSUFw3EWCfxcQm4RYRn9ZSny1/EufkflZ/Z47Sb4Jjb4ehAlQFw1wwKNcx
++V07MvIu2j7dHkfQ/GXgDwtJ3lIfljwuN1NP4wD5Mlcnw0+KC3UGBvMfkHQM6eEb
+4eTBAoIBAQDWfZsqHlpX3n431XkB+9wdFJP5ThrMaVJ51mxLNRBKgO/BgV+NFSNA
+9AZ5DCf0cCh1qPGYDYhSd2LGywT+trac1j7Hse0AcxpYgQsDBkk/oic/y3wm80HJ
+zZw7Z2uAb7nkrnATzt24G8CbE+ZvVvScs3oQr06raH5hgGdD4bN4No4lUVECKbKl
+8VFbdBHK7vqqb6AKgQ4JLAygPduE1nTn2bkXBklESS98HSXK0dVYGH0JFFBw/63v
+39Y05ObC7iwbx1tEb1RnKzQ1OQO1o1aHc/35ENNhXOfa8ONtneCYn/ty50xjPCG2
+MU1vbBv+hIjbO3D3vvhaXKk+4svAz0qxAoIBAQC84FJEjKHJHx17jLeoTuDfuxwX
+6bOQrI3nHbtnFRvPrMryWRDtHLv89Zma3o68/n4vTn5+AnvgYMZifOYlTlIPxinH
+tlE+qCD8KBXUlZdrc+5GGM18lp5tF3Ro4LireH+OhiOAWawaSzDIDYdiR6Kz9NU+
+SjcHKjDObeM6iMEukoaRsufMedpUSrnbzMraAJgBZGay1NZs/o8Icl3OySYPZWEK
+MJxVBMXU9QcUp2GEioYd/eNuP9rwyjq/EIUDJbP2vESAe6+FdGbIgvyYTV/gnKaH
+GcvyMNVZbCMp/wCYNonjlu+18m2w+pVs2uuZLqORkrKYhisK83TKxh4YOWJh
+-----END RSA PRIVATE KEY-----
diff --git a/packages/Tethering/apex/com.android.tethering.apex.pk8 b/packages/Tethering/apex/com.android.tethering.apex.pk8
new file mode 100644
index 0000000..5663246
--- /dev/null
+++ b/packages/Tethering/apex/com.android.tethering.apex.pk8
Binary files differ
diff --git a/packages/Tethering/apex/com.android.tethering.apex.x509.pem b/packages/Tethering/apex/com.android.tethering.apex.x509.pem
new file mode 100644
index 0000000..a5e9401
--- /dev/null
+++ b/packages/Tethering/apex/com.android.tethering.apex.x509.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIGMzCCBBugAwIBAgIUXVtoDaXanhs7ma8VIICambMkj5UwDQYJKoZIhvcNAQEL
+BQAwgacxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMSMwIQYDVQQDDBpjb20uYW5kcm9pZC50ZXRoZXJpbmcuYXBleDEiMCAGCSqG
+SIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAgFw0xOTExMjgwNjU4MTRaGA80
+NzU3MTAyNDA2NTgxNFowgacxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9y
+bmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAw
+DgYDVQQLDAdBbmRyb2lkMSMwIQYDVQQDDBpjb20uYW5kcm9pZC50ZXRoZXJpbmcu
+YXBleDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBANwzufMBdOj9XlNwiX+bXl/94G0DklWW
+nzob0jPlubCFfRqYkjCf2eOd28Mu/O1pOBcvobnrs9OTpGzcHkz2h58L5/0UMVTS
+tBugwCE49XF5FHawqVHNZE+s5tDmnp2cufhNc5HXHY4oZKh80/WVdbcKxiLjSY2T
+PgRAfB6E6XByKD3t1cSsc3liRVKADoJOVDvmF+xnyvSV/SN38bvTQk9aVs95mj0W
+yov6gzXBnqN7iQlvkhcijZBnFWxvoNbJ5KFy1abYOrm+ueXje4BcNhVOeRMb4E9N
+eo7+9k1GEI7TYG7laNNcp7UJ1IXCJzv/wBFKRg3f1HB3unKfx2rtKerDnVsr3o7V
+KProkgRNKNhhQ6opNguiH1YMzKpWMaC988n4AQPryPdIOmVIxIC5jJrixdxgzDXT
+qeiwFiXis291uyls08B03PQFlY9oWaY9P8s+4hIUjB6rLl+XZXsLDtDFxXeJ97NB
+8XZN1gBJoBoLknFs0C4LKpmJZB/EBao9tXV9dL/5lydRo6HzQDpjW8QX06CTUM6z
+Lr3LVelhqbsuZsV42yBKl+/LfrvNjBLEPdSevt2oMrlJW7m4iSNaMtDtJ2Oy8fA5
+WSIgLWuMbkaFDza3JzwiMzxbtbJHYiy6rY7aVywo3Vqwr1+KO3cq4eLQq62zUjRY
+e6KJwvgE2YmpAgMBAAGjUzBRMB0GA1UdDgQWBBQ8h1oF5JfKFmJCN8nfimbUK+IR
+wjAfBgNVHSMEGDAWgBQ8h1oF5JfKFmJCN8nfimbUK+IRwjAPBgNVHRMBAf8EBTAD
+AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAP5hIkAxSyt9hRafMKiFlmXcL277bgoxNd
+qGZYbdcCFjfvM2r0QQcM/K7x2ZslFe7mJSzcyMifpm4YQTEo2yYbzKItXQV+eV1K
+9RNksRGFG9umsdWaSHhfQmcmxtr2nu9rGAgxy5OQFtyXmJPUPEM2cb/YeILwYhuQ
+Ux3kaj/fxGltX1JBag7HnMzCTZK++fRo5nqFVOJQgJH8ZpuzGeM9kZvP1+b55046
+PhSnlqmZoKhG4i5POPvvZvaakh/lM3x/N7lIlSaQpCGf7jmldni4L0/GenULVKzH
+iN73aBfh4GEvE0HRcOoH3L7V6kc3WMMLve0chZBHpoVYbzUJEJOUL4yrmwEehqtf
+xm4vlYg3vqtcE3UnU/UGdMb16t77Nz88LlpBY5ierIt0jZMU0M81ppRhr1uiD2Lj
+091sEA0Bxcw/6Q8QNF2eR7SG7Qwipnms+lw6Vcxve+7DdTrdEA0k3XgpdXp8Ya+2
+PAp9SLVp1UHiGq3qD9Jvm34QmlUWAIUTHZs3DSgs1y3K5eyw/cnzTvUUOljc/n2y
+VF0FFZtJ1dVLrzQ80Ik7apEXpBqkgBGV04/L3QYk4C0/sP+1yk6zjeeeAvDtUcHS
+gLtjAfacQl/kwfVQWfrF7VByLcivApC6EUdvT3cURM5DfZRQ4RcKr1D61VYPnNRH
++/NVbMObwQ==
+-----END CERTIFICATE-----
diff --git a/packages/Tethering/apex/manifest.json b/packages/Tethering/apex/manifest.json
new file mode 100644
index 0000000..3fb62f3
--- /dev/null
+++ b/packages/Tethering/apex/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.tethering.apex",
+ "version": 290000000
+}
diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp
index 5b01b1e..adc5a72 100644
--- a/packages/Tethering/common/TetheringLib/Android.bp
+++ b/packages/Tethering/common/TetheringLib/Android.bp
@@ -18,8 +18,12 @@
aidl_interface {
name: "tethering-aidl-interfaces",
local_include_dir: "src",
+ include_dirs: ["frameworks/base/core/java"], // For framework parcelables.
srcs: [
+ "src/android/net/ITetherInternalCallback.aidl",
"src/android/net/ITetheringConnector.aidl",
+ "src/android/net/TetheringConfigurationParcel.aidl",
+ "src/android/net/TetherStatesParcel.aidl",
],
backend: {
ndk: {
@@ -33,8 +37,15 @@
java_library {
name: "tethering-client",
- platform_apis: true,
+ sdk_version: "system_current",
static_libs: [
"tethering-aidl-interfaces-java",
],
}
+
+// This is temporary file group which would be removed after TetheringManager is built
+// into tethering-client. Will be done by aosp/1156906.
+filegroup {
+ name: "tethering-manager",
+ srcs: ["src/android/net/TetheringManager.java"],
+}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/ITetherInternalCallback.aidl b/packages/Tethering/common/TetheringLib/src/android/net/ITetherInternalCallback.aidl
new file mode 100644
index 0000000..abb00e8
--- /dev/null
+++ b/packages/Tethering/common/TetheringLib/src/android/net/ITetherInternalCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 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 android.net;
+
+import android.net.Network;
+import android.net.TetheringConfigurationParcel;
+import android.net.TetherStatesParcel;
+
+/**
+ * Callback class for receiving tethering changed events
+ * @hide
+ */
+oneway interface ITetherInternalCallback
+{
+ void onUpstreamChanged(in Network network);
+ void onConfigurationChanged(in TetheringConfigurationParcel config);
+ void onTetherStatesChanged(in TetherStatesParcel states);
+ void onCallbackCreated(in Network network, in TetheringConfigurationParcel config,
+ in TetherStatesParcel states);
+}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
index 443481e..bfe502f 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
+++ b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
@@ -15,6 +15,23 @@
*/
package android.net;
+import android.net.ITetherInternalCallback;
+import android.os.ResultReceiver;
+
/** @hide */
oneway interface ITetheringConnector {
+ void tether(String iface);
+
+ void untether(String iface);
+
+ void setUsbTethering(boolean enable);
+
+ void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi);
+
+ void stopTethering(int type);
+
+ void requestLatestTetheringEntitlementResult(int type, in ResultReceiver receiver,
+ boolean showEntitlementUi);
+
+ void registerTetherInternalCallback(ITetherInternalCallback callback);
}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetherStatesParcel.aidl b/packages/Tethering/common/TetheringLib/src/android/net/TetherStatesParcel.aidl
new file mode 100644
index 0000000..3d842b3
--- /dev/null
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetherStatesParcel.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 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 android.net;
+
+/**
+ * Status details for tethering downstream interfaces.
+ * {@hide}
+ */
+parcelable TetherStatesParcel {
+ String[] availableList;
+ String[] tetheredList;
+ String[] localOnlyList;
+ String[] erroredIfaceList;
+ // List of Last error code corresponding to each errored iface in erroredIfaceList. */
+ // TODO: Improve this as b/143122247.
+ int[] lastErrorList;
+}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConfigurationParcel.aidl b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConfigurationParcel.aidl
new file mode 100644
index 0000000..89f3813
--- /dev/null
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConfigurationParcel.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 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 android.net;
+
+/**
+ * Configuration details for tethering.
+ * @hide
+ */
+parcelable TetheringConfigurationParcel {
+ int subId;
+ String[] tetherableUsbRegexs;
+ String[] tetherableWifiRegexs;
+ String[] tetherableBluetoothRegexs;
+ boolean isDunRequired;
+ boolean chooseUpstreamAutomatically;
+ int[] preferredUpstreamIfaceTypes;
+ String[] legacyDhcpRanges;
+ String[] defaultIPv4DNS;
+ boolean enableLegacyDhcpServer;
+ String[] provisioningApp;
+ String provisioningAppNoUi;
+ int provisioningCheckPeriod;
+}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
new file mode 100644
index 0000000..eb0d443
--- /dev/null
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2019 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 android.net;
+
+import static android.Manifest.permission.NETWORK_STACK;
+import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.util.SharedLog;
+import android.os.ConditionVariable;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.StringJoiner;
+
+/**
+ * Service used to communicate with the tethering, which is running in a separate module.
+ * @hide
+ */
+public class TetheringManager {
+ private static final String TAG = TetheringManager.class.getSimpleName();
+
+ private static TetheringManager sInstance;
+
+ @Nullable
+ private ITetheringConnector mConnector;
+ private TetherInternalCallback mCallback;
+ private Network mTetherUpstream;
+ private TetheringConfigurationParcel mTetheringConfiguration;
+ private TetherStatesParcel mTetherStatesParcel;
+
+ private final RemoteCallbackList<ITetheringEventCallback> mTetheringEventCallbacks =
+ new RemoteCallbackList<>();
+ @GuardedBy("mLog")
+ private final SharedLog mLog = new SharedLog(TAG);
+
+ private TetheringManager() { }
+
+ /**
+ * Get the TetheringManager singleton instance.
+ */
+ public static synchronized TetheringManager getInstance() {
+ if (sInstance == null) {
+ sInstance = new TetheringManager();
+ }
+ return sInstance;
+ }
+
+ private class TetheringConnection implements
+ ConnectivityModuleConnector.ModuleServiceCallback {
+ @Override
+ public void onModuleServiceConnected(@NonNull IBinder service) {
+ logi("Tethering service connected");
+ registerTetheringService(service);
+ }
+ }
+
+ private void registerTetheringService(@NonNull IBinder service) {
+ final ITetheringConnector connector = ITetheringConnector.Stub.asInterface(service);
+
+ log("Tethering service registered");
+
+ // Currently TetheringManager instance is only used by ConnectivityService and mConnector
+ // only expect to assign once when system server start and bind tethering service.
+ // STOPSHIP: Change mConnector to final before TetheringManager put into boot classpath.
+ mConnector = connector;
+ mCallback = new TetherInternalCallback();
+ try {
+ mConnector.registerTetherInternalCallback(mCallback);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ private class TetherInternalCallback extends ITetherInternalCallback.Stub {
+ private final ConditionVariable mWaitForCallback = new ConditionVariable(false);
+ private static final int EVENT_CALLBACK_TIMEOUT_MS = 60_000;
+
+ @Override
+ public void onUpstreamChanged(Network network) {
+ mTetherUpstream = network;
+ reportUpstreamChanged(network);
+ }
+
+ @Override
+ public void onConfigurationChanged(TetheringConfigurationParcel config) {
+ mTetheringConfiguration = config;
+ }
+
+ @Override
+ public void onTetherStatesChanged(TetherStatesParcel states) {
+ mTetherStatesParcel = states;
+ }
+
+ @Override
+ public void onCallbackCreated(Network network, TetheringConfigurationParcel config,
+ TetherStatesParcel states) {
+ mTetherUpstream = network;
+ mTetheringConfiguration = config;
+ mTetherStatesParcel = states;
+ mWaitForCallback.open();
+ }
+
+ boolean awaitCallbackCreation() {
+ return mWaitForCallback.block(EVENT_CALLBACK_TIMEOUT_MS);
+ }
+ }
+
+ private void reportUpstreamChanged(Network network) {
+ final int length = mTetheringEventCallbacks.beginBroadcast();
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ mTetheringEventCallbacks.getBroadcastItem(i).onUpstreamChanged(network);
+ } catch (RemoteException e) {
+ // Not really very much to do here.
+ }
+ }
+ } finally {
+ mTetheringEventCallbacks.finishBroadcast();
+ }
+ }
+
+ /**
+ * Start the tethering service. Should be called only once on device startup.
+ *
+ * <p>This method will start the tethering service either in the network stack process,
+ * or inside the system server on devices that do not support the tethering module.
+ *
+ * {@hide}
+ */
+ public void start() {
+ // Using MAINLINE_NETWORK_STACK permission after cutting off the dpendency of system server.
+ ConnectivityModuleConnector.getInstance().startModuleService(
+ ITetheringConnector.class.getName(), NETWORK_STACK,
+ new TetheringConnection());
+ log("Tethering service start requested");
+ }
+
+ /**
+ * Attempt to tether the named interface. This will setup a dhcp server
+ * on the interface, forward and NAT IP v4 packets and forward DNS requests
+ * to the best active upstream network interface. Note that if no upstream
+ * IP network interface is available, dhcp will still run and traffic will be
+ * allowed between the tethered devices and this device, though upstream net
+ * access will of course fail until an upstream network interface becomes
+ * active. Note: return value do not have any meaning. It is better to use
+ * #getTetherableIfaces() to ensure corresponding interface is available for
+ * tethering before calling #tether().
+ *
+ * @deprecated The only usages should be in PanService and Wifi P2P which
+ * need direct access.
+ *
+ * {@hide}
+ */
+ @Deprecated
+ public int tether(@NonNull String iface) {
+ try {
+ mConnector.tether(iface);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return TETHER_ERROR_NO_ERROR;
+ }
+
+ /**
+ * Stop tethering the named interface.
+ *
+ * @deprecated
+ * {@hide}
+ */
+ @Deprecated
+ public int untether(@NonNull String iface) {
+ try {
+ mConnector.untether(iface);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return TETHER_ERROR_NO_ERROR;
+ }
+
+ /**
+ * Attempt to both alter the mode of USB and Tethering of USB. WARNING: New client should not
+ * use this API anymore. All clients should use #startTethering or #stopTethering which
+ * encapsulate proper entitlement logic. If the API is used and an entitlement check is needed,
+ * downstream USB tethering will be enabled but will not have any upstream.
+ *
+ * @deprecated
+ * {@hide}
+ */
+ @Deprecated
+ public int setUsbTethering(boolean enable) {
+ try {
+ mConnector.setUsbTethering(enable);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return TETHER_ERROR_NO_ERROR;
+ }
+
+ /**
+ * Starts tethering and runs tether provisioning for the given type if needed. If provisioning
+ * fails, stopTethering will be called automatically.
+ *
+ * {@hide}
+ */
+ // TODO: improve the usage of ResultReceiver, b/145096122
+ public void startTethering(int type, @NonNull ResultReceiver receiver,
+ boolean showProvisioningUi) {
+ try {
+ mConnector.startTethering(type, receiver, showProvisioningUi);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Stops tethering for the given type. Also cancels any provisioning rechecks for that type if
+ * applicable.
+ *
+ * {@hide}
+ */
+ public void stopTethering(int type) {
+ try {
+ mConnector.stopTethering(type);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Request the latest value of the tethering entitlement check.
+ *
+ * Note: Allow privileged apps who have TETHER_PRIVILEGED permission to access. If it turns
+ * out some such apps are observed to abuse this API, change to per-UID limits on this API
+ * if it's really needed.
+ */
+ // TODO: improve the usage of ResultReceiver, b/145096122
+ public void requestLatestTetheringEntitlementResult(int type, @NonNull ResultReceiver receiver,
+ boolean showEntitlementUi) {
+ try {
+ mConnector.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Register tethering event callback.
+ *
+ * {@hide}
+ */
+ public void registerTetheringEventCallback(@NonNull ITetheringEventCallback callback) {
+ mTetheringEventCallbacks.register(callback);
+ }
+
+ /**
+ * Unregister tethering event callback.
+ *
+ * {@hide}
+ */
+ public void unregisterTetheringEventCallback(@NonNull ITetheringEventCallback callback) {
+ mTetheringEventCallbacks.unregister(callback);
+ }
+
+ /**
+ * Get a more detailed error code after a Tethering or Untethering
+ * request asynchronously failed.
+ *
+ * {@hide}
+ */
+ public int getLastTetherError(@NonNull String iface) {
+ if (!mCallback.awaitCallbackCreation()) {
+ throw new NullPointerException("callback was not ready yet");
+ }
+ if (mTetherStatesParcel == null) return TETHER_ERROR_NO_ERROR;
+
+ int i = 0;
+ for (String errored : mTetherStatesParcel.erroredIfaceList) {
+ if (iface.equals(errored)) return mTetherStatesParcel.lastErrorList[i];
+
+ i++;
+ }
+ return TETHER_ERROR_NO_ERROR;
+ }
+
+ /**
+ * Get the list of regular expressions that define any tetherable
+ * USB network interfaces. If USB tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * {@hide}
+ */
+ public @NonNull String[] getTetherableUsbRegexs() {
+ if (!mCallback.awaitCallbackCreation()) {
+ throw new NullPointerException("callback was not ready yet");
+ }
+ return mTetheringConfiguration.tetherableUsbRegexs;
+ }
+
+ /**
+ * Get the list of regular expressions that define any tetherable
+ * Wifi network interfaces. If Wifi tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * {@hide}
+ */
+ public @NonNull String[] getTetherableWifiRegexs() {
+ if (!mCallback.awaitCallbackCreation()) {
+ throw new NullPointerException("callback was not ready yet");
+ }
+ return mTetheringConfiguration.tetherableWifiRegexs;
+ }
+
+ /**
+ * Get the list of regular expressions that define any tetherable
+ * Bluetooth network interfaces. If Bluetooth tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * {@hide}
+ */
+ public @NonNull String[] getTetherableBluetoothRegexs() {
+ if (!mCallback.awaitCallbackCreation()) {
+ throw new NullPointerException("callback was not ready yet");
+ }
+ return mTetheringConfiguration.tetherableBluetoothRegexs;
+ }
+
+ /**
+ * Get the set of tetherable, available interfaces. This list is limited by
+ * device configuration and current interface existence.
+ *
+ * {@hide}
+ */
+ public @NonNull String[] getTetherableIfaces() {
+ if (!mCallback.awaitCallbackCreation()) {
+ throw new NullPointerException("callback was not ready yet");
+ }
+ if (mTetherStatesParcel == null) return new String[0];
+ return mTetherStatesParcel.availableList;
+ }
+
+ /**
+ * Get the set of tethered interfaces.
+ *
+ * {@hide}
+ */
+ public @NonNull String[] getTetheredIfaces() {
+ if (!mCallback.awaitCallbackCreation()) {
+ throw new NullPointerException("callback was not ready yet");
+ }
+ if (mTetherStatesParcel == null) return new String[0];
+ return mTetherStatesParcel.tetheredList;
+ }
+
+ /**
+ * Get the set of interface names which attempted to tether but
+ * failed.
+ *
+ * {@hide}
+ */
+ public @NonNull String[] getTetheringErroredIfaces() {
+ if (!mCallback.awaitCallbackCreation()) {
+ throw new NullPointerException("callback was not ready yet");
+ }
+ if (mTetherStatesParcel == null) return new String[0];
+ return mTetherStatesParcel.erroredIfaceList;
+ }
+
+ /**
+ * Get the set of tethered dhcp ranges.
+ *
+ * @deprecated This API just return the default value which is not used in DhcpServer.
+ * {@hide}
+ */
+ @Deprecated
+ public @NonNull String[] getTetheredDhcpRanges() {
+ if (!mCallback.awaitCallbackCreation()) {
+ throw new NullPointerException("callback was not ready yet");
+ }
+ return mTetheringConfiguration.legacyDhcpRanges;
+ }
+
+ /**
+ * Check if the device allows for tethering.
+ *
+ * {@hide}
+ */
+ public boolean hasTetherableConfiguration() {
+ if (!mCallback.awaitCallbackCreation()) {
+ throw new NullPointerException("callback was not ready yet");
+ }
+ final boolean hasDownstreamConfiguration =
+ (mTetheringConfiguration.tetherableUsbRegexs.length != 0)
+ || (mTetheringConfiguration.tetherableWifiRegexs.length != 0)
+ || (mTetheringConfiguration.tetherableBluetoothRegexs.length != 0);
+ final boolean hasUpstreamConfiguration =
+ (mTetheringConfiguration.preferredUpstreamIfaceTypes.length != 0)
+ || mTetheringConfiguration.chooseUpstreamAutomatically;
+
+ return hasDownstreamConfiguration && hasUpstreamConfiguration;
+ }
+
+ /**
+ * Log a message in the local log.
+ */
+ private void log(@NonNull String message) {
+ synchronized (mLog) {
+ mLog.log(message);
+ }
+ }
+
+ /**
+ * Log a condition that should never happen.
+ */
+ private void logWtf(@NonNull String message, @Nullable Throwable e) {
+ Slog.wtf(TAG, message);
+ synchronized (mLog) {
+ mLog.e(message, e);
+ }
+ }
+
+ /**
+ * Log a ERROR level message in the local and system logs.
+ */
+ private void loge(@NonNull String message, @Nullable Throwable e) {
+ synchronized (mLog) {
+ mLog.e(message, e);
+ }
+ }
+
+ /**
+ * Log a INFO level message in the local and system logs.
+ */
+ private void logi(@NonNull String message) {
+ synchronized (mLog) {
+ mLog.i(message);
+ }
+ }
+
+ /**
+ * Dump TetheringManager logs to the specified {@link PrintWriter}.
+ */
+ public void dump(@NonNull PrintWriter pw) {
+ // dump is thread-safe on SharedLog
+ mLog.dump(null, pw, null);
+
+ pw.print("subId: ");
+ pw.println(mTetheringConfiguration.subId);
+
+ dumpStringArray(pw, "tetherableUsbRegexs",
+ mTetheringConfiguration.tetherableUsbRegexs);
+ dumpStringArray(pw, "tetherableWifiRegexs",
+ mTetheringConfiguration.tetherableWifiRegexs);
+ dumpStringArray(pw, "tetherableBluetoothRegexs",
+ mTetheringConfiguration.tetherableBluetoothRegexs);
+
+ pw.print("isDunRequired: ");
+ pw.println(mTetheringConfiguration.isDunRequired);
+
+ pw.print("chooseUpstreamAutomatically: ");
+ pw.println(mTetheringConfiguration.chooseUpstreamAutomatically);
+
+ dumpStringArray(pw, "legacyDhcpRanges", mTetheringConfiguration.legacyDhcpRanges);
+ dumpStringArray(pw, "defaultIPv4DNS", mTetheringConfiguration.defaultIPv4DNS);
+
+ dumpStringArray(pw, "provisioningApp", mTetheringConfiguration.provisioningApp);
+ pw.print("provisioningAppNoUi: ");
+ pw.println(mTetheringConfiguration.provisioningAppNoUi);
+
+ pw.print("enableLegacyDhcpServer: ");
+ pw.println(mTetheringConfiguration.enableLegacyDhcpServer);
+
+ pw.println();
+ }
+
+ private static void dumpStringArray(@NonNull PrintWriter pw, @NonNull String label,
+ @Nullable String[] values) {
+ pw.print(label);
+ pw.print(": ");
+
+ if (values != null) {
+ final StringJoiner sj = new StringJoiner(", ", "[", "]");
+ for (String value : values) sj.add(value);
+
+ pw.print(sj.toString());
+ } else {
+ pw.print("null");
+ }
+
+ pw.println();
+ }
+}
diff --git a/packages/Tethering/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp b/packages/Tethering/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
index 3eaf488..663154a 100644
--- a/packages/Tethering/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
+++ b/packages/Tethering/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
@@ -145,4 +145,18 @@
gMethods, NELEM(gMethods));
}
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ ALOGE("ERROR: GetEnv failed");
+ return JNI_ERR;
+ }
+
+ if (register_android_server_connectivity_tethering_OffloadHardwareInterface(env) < 0) {
+ return JNI_ERR;
+ }
+
+ return JNI_VERSION_1_6;
+}
+
}; // namespace android
diff --git a/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java b/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java
new file mode 100644
index 0000000..3218c0b
--- /dev/null
+++ b/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 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 android.net.util;
+
+import android.net.INetdUnsolicitedEventListener;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Base {@link INetdUnsolicitedEventListener} that provides no-op implementations which can be
+ * overridden.
+ */
+public class BaseNetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub {
+
+ @Override
+ public void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs,
+ int uid) { }
+
+ @Override
+ public void onQuotaLimitReached(@NonNull String alertName, @NonNull String ifName) { }
+
+ @Override
+ public void onInterfaceDnsServerInfo(@NonNull String ifName, long lifetimeS,
+ @NonNull String[] servers) { }
+
+ @Override
+ public void onInterfaceAddressUpdated(@NonNull String addr, String ifName, int flags,
+ int scope) { }
+
+ @Override
+ public void onInterfaceAddressRemoved(@NonNull String addr, @NonNull String ifName, int flags,
+ int scope) { }
+
+ @Override
+ public void onInterfaceAdded(@NonNull String ifName) { }
+
+ @Override
+ public void onInterfaceRemoved(@NonNull String ifName) { }
+
+ @Override
+ public void onInterfaceChanged(@NonNull String ifName, boolean up) { }
+
+ @Override
+ public void onInterfaceLinkStateChanged(@NonNull String ifName, boolean up) { }
+
+ @Override
+ public void onRouteChanged(boolean updated, @NonNull String route, @NonNull String gateway,
+ @NonNull String ifName) { }
+
+ @Override
+ public void onStrictCleartextDetected(int uid, @NonNull String hex) { }
+
+ @Override
+ public int getInterfaceVersion() {
+ return INetdUnsolicitedEventListener.VERSION;
+ }
+}
diff --git a/services/net/java/android/net/util/VersionedBroadcastListener.java b/packages/Tethering/src/android/net/util/VersionedBroadcastListener.java
similarity index 91%
rename from services/net/java/android/net/util/VersionedBroadcastListener.java
rename to packages/Tethering/src/android/net/util/VersionedBroadcastListener.java
index 107c404..e2804ab 100644
--- a/services/net/java/android/net/util/VersionedBroadcastListener.java
+++ b/packages/Tethering/src/android/net/util/VersionedBroadcastListener.java
@@ -39,10 +39,6 @@
public class VersionedBroadcastListener {
private static final boolean DBG = false;
- public interface IntentCallback {
- public void run(Intent intent);
- }
-
private final String mTag;
private final Context mContext;
private final Handler mHandler;
@@ -61,6 +57,7 @@
mGenerationNumber = new AtomicInteger(0);
}
+ /** Start listening to intent broadcast. */
public void startListening() {
if (DBG) Log.d(mTag, "startListening");
if (mReceiver != null) return;
@@ -69,6 +66,7 @@
mContext.registerReceiver(mReceiver, mFilter, null, mHandler);
}
+ /** Stop listening to intent broadcast. */
public void stopListening() {
if (DBG) Log.d(mTag, "stopListening");
if (mReceiver == null) return;
@@ -85,8 +83,7 @@
// Used to verify this receiver is still current.
public final int generationNumber;
- public Receiver(
- String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) {
+ Receiver(String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) {
this.tag = tag;
this.atomicGenerationNumber = atomicGenerationNumber;
this.callback = callback;
@@ -98,8 +95,8 @@
final int currentGenerationNumber = atomicGenerationNumber.get();
if (DBG) {
- Log.d(tag, "receiver generationNumber=" + generationNumber +
- ", current generationNumber=" + currentGenerationNumber);
+ Log.d(tag, "receiver generationNumber=" + generationNumber
+ + ", current generationNumber=" + currentGenerationNumber);
}
if (generationNumber != currentGenerationNumber) return;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
index 6b0f1de..ba5d08d 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -47,6 +47,7 @@
import android.os.PersistableBundle;
import android.os.ResultReceiver;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
@@ -55,7 +56,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StateMachine;
-import com.android.server.connectivity.MockableSystemProperties;
import java.io.PrintWriter;
@@ -94,7 +94,6 @@
private final ArraySet<Integer> mCurrentTethers;
private final Context mContext;
private final int mPermissionChangeMessageCode;
- private final MockableSystemProperties mSystemProperties;
private final SharedLog mLog;
private final SparseIntArray mEntitlementCacheValue;
private final EntitlementHandler mHandler;
@@ -110,12 +109,12 @@
private TetheringConfigurationFetcher mFetcher;
public EntitlementManager(Context ctx, StateMachine tetherMasterSM, SharedLog log,
- int permissionChangeMessageCode, MockableSystemProperties systemProperties) {
+ int permissionChangeMessageCode) {
+
mContext = ctx;
mLog = log.forSubComponent(TAG);
mCurrentTethers = new ArraySet<Integer>();
mCellularPermitted = new SparseIntArray();
- mSystemProperties = systemProperties;
mEntitlementCacheValue = new SparseIntArray();
mTetherMasterSM = tetherMasterSM;
mPermissionChangeMessageCode = permissionChangeMessageCode;
@@ -287,7 +286,7 @@
*/
@VisibleForTesting
protected boolean isTetherProvisioningRequired(final TetheringConfiguration config) {
- if (mSystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false)
+ if (SystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false)
|| config.provisioningApp.length == 0) {
return false;
}
@@ -526,8 +525,8 @@
handleMaybeRunProvisioning(config);
break;
case EVENT_GET_ENTITLEMENT_VALUE:
- handleGetLatestTetheringEntitlementValue(msg.arg1, (ResultReceiver) msg.obj,
- toBool(msg.arg2));
+ handleRequestLatestTetheringEntitlementValue(msg.arg1,
+ (ResultReceiver) msg.obj, toBool(msg.arg2));
break;
default:
mLog.log("Unknown event: " + msg.what);
@@ -651,15 +650,15 @@
}
/** Get the last value of the tethering entitlement check. */
- public void getLatestTetheringEntitlementResult(int downstream, ResultReceiver receiver,
+ public void requestLatestTetheringEntitlementResult(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) {
+ private void handleRequestLatestTetheringEntitlementValue(int downstream,
+ ResultReceiver receiver, boolean showEntitlementUi) {
final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
if (!isTetherProvisioningRequired(config)) {
receiver.send(TETHER_ERROR_NO_ERROR, null);
diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
similarity index 92%
rename from services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
rename to packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
index 8186343..edfe3ca 100644
--- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
@@ -82,6 +82,7 @@
mNextSubnetId = 0;
}
+ /** Add active downstream to ipv6 tethering candidate list. */
public void addActiveDownstream(IpServer downstream, int mode) {
if (findDownstream(downstream) == null) {
// Adding a new downstream appends it to the list. Adding a
@@ -97,6 +98,7 @@
}
}
+ /** Remove downstream from ipv6 tethering candidate list. */
public void removeActiveDownstream(IpServer downstream) {
stopIPv6TetheringOn(downstream);
if (mActiveDownstreams.remove(findDownstream(downstream))) {
@@ -112,6 +114,11 @@
}
}
+ /**
+ * Call when upstream NetworkState may be changed.
+ * If upstream has ipv6 for tethering, update this new NetworkState
+ * to IpServer. Otherwise stop ipv6 tethering on downstream interfaces.
+ */
public void updateUpstreamNetworkState(NetworkState ns) {
if (VDBG) {
Log.d(TAG, "updateUpstreamNetworkState: " + toDebugString(ns));
@@ -122,8 +129,8 @@
return;
}
- if (mUpstreamNetworkState != null &&
- !ns.network.equals(mUpstreamNetworkState.network)) {
+ if (mUpstreamNetworkState != null
+ && !ns.network.equals(mUpstreamNetworkState.network)) {
stopIPv6TetheringOnAllInterfaces();
}
@@ -221,8 +228,8 @@
for (RouteInfo routeInfo : lp.getRoutes()) {
final IpPrefix destination = routeInfo.getDestination();
- if ((destination.getAddress() instanceof Inet6Address) &&
- (destination.getPrefixLength() <= 64)) {
+ if ((destination.getAddress() instanceof Inet6Address)
+ && (destination.getPrefixLength() <= 64)) {
v6only.addRoute(routeInfo);
}
}
@@ -242,12 +249,12 @@
// TODO: Delete this and switch to LinkAddress#isGlobalPreferred once we
// announce our own IPv6 address as DNS server.
private static boolean isIPv6GlobalAddress(InetAddress ip) {
- return (ip instanceof Inet6Address) &&
- !ip.isAnyLocalAddress() &&
- !ip.isLoopbackAddress() &&
- !ip.isLinkLocalAddress() &&
- !ip.isSiteLocalAddress() &&
- !ip.isMulticastAddress();
+ return (ip instanceof Inet6Address)
+ && !ip.isAnyLocalAddress()
+ && !ip.isLoopbackAddress()
+ && !ip.isLinkLocalAddress()
+ && !ip.isSiteLocalAddress()
+ && !ip.isMulticastAddress();
}
private static LinkProperties getUniqueLocalConfig(byte[] ulp, short subnetId) {
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 01339a4..00a6773 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -107,6 +107,8 @@
public OffloadHardwareInterface(Handler h, SharedLog log) {
mHandler = h;
mLog = log.forSubComponent(TAG);
+
+ System.loadLibrary("tetheroffloadjni");
}
/** Get default value indicating whether offload is supported. */
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
similarity index 89%
rename from services/core/java/com/android/server/connectivity/Tethering.java
rename to packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index acedc36..f1228129 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.connectivity;
+package com.android.server.connectivity.tethering;
import static android.hardware.usb.UsbManager.USB_CONFIGURED;
import static android.hardware.usb.UsbManager.USB_CONNECTED;
@@ -47,8 +47,6 @@
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-import static com.android.server.ConnectivityService.SHORT_ARG;
-
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -62,9 +60,10 @@
import android.content.IntentFilter;
import android.content.res.Resources;
import android.hardware.usb.UsbManager;
+import android.net.INetd;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
-import android.net.ITetheringEventCallback;
+import android.net.ITetherInternalCallback;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -72,7 +71,10 @@
import android.net.NetworkInfo;
import android.net.NetworkState;
import android.net.NetworkUtils;
+import android.net.TetherStatesParcel;
+import android.net.TetheringConfigurationParcel;
import android.net.ip.IpServer;
+import android.net.util.BaseNetdUnsolicitedEventListener;
import android.net.util.InterfaceSet;
import android.net.util.PrefixUtils;
import android.net.util.SharedLog;
@@ -87,13 +89,10 @@
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Message;
-import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.UserManagerInternal;
-import android.os.UserManagerInternal.UserRestrictionsListener;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -110,15 +109,6 @@
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import com.android.server.LocalServices;
-import com.android.server.connectivity.tethering.EntitlementManager;
-import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
-import com.android.server.connectivity.tethering.OffloadController;
-import com.android.server.connectivity.tethering.TetheringConfiguration;
-import com.android.server.connectivity.tethering.TetheringDependencies;
-import com.android.server.connectivity.tethering.TetheringInterfaceUtils;
-import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
-import com.android.server.net.BaseNetworkObserver;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -127,32 +117,32 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Set;
/**
- * @hide
*
* This class holds much of the business logic to allow Android devices
* to act as IP gateways via USB, BT, and WiFi interfaces.
*/
-public class Tethering extends BaseNetworkObserver {
+public class Tethering {
- private final static String TAG = Tethering.class.getSimpleName();
- private final static boolean DBG = false;
- private final static boolean VDBG = false;
+ private static final String TAG = Tethering.class.getSimpleName();
+ private static final boolean DBG = false;
+ private static final boolean VDBG = false;
- private static final Class[] messageClasses = {
+ private static final Class[] sMessageClasses = {
Tethering.class, TetherMasterSM.class, IpServer.class
};
private static final SparseArray<String> sMagicDecoderRing =
- MessageUtils.findMessageNames(messageClasses);
+ MessageUtils.findMessageNames(sMessageClasses);
private static class TetherState {
public final IpServer ipServer;
public int lastState;
public int lastError;
- public TetherState(IpServer ipServer) {
+ TetherState(IpServer ipServer) {
this.ipServer = ipServer;
// Assume all state machines start out available and with no errors.
lastState = IpServer.STATE_AVAILABLE;
@@ -177,6 +167,7 @@
private final Context mContext;
private final ArrayMap<String, TetherState> mTetherStates;
private final BroadcastReceiver mStateReceiver;
+ // Stopship: replace mNMService before production.
private final INetworkManagementService mNMService;
private final INetworkStatsService mStatsService;
private final INetworkPolicyManager mPolicyManager;
@@ -191,10 +182,13 @@
private final TetheringDependencies mDeps;
private final EntitlementManager mEntitlementMgr;
private final Handler mHandler;
- private final RemoteCallbackList<ITetheringEventCallback> mTetheringEventCallbacks =
- new RemoteCallbackList<>();
private final PhoneStateListener mPhoneStateListener;
+ private final INetd mNetd;
+ private final NetdCallback mNetdCallback;
+ private final UserRestrictionActionListener mTetheringRestriction;
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
+ // All the usage of mTetherInternalCallback should run in the same thread.
+ private ITetherInternalCallback mTetherInternalCallback = null;
private volatile TetheringConfiguration mConfig;
private InterfaceSet mCurrentUpstreamIfaceSet;
@@ -205,18 +199,17 @@
// True iff. WiFi tethering should be started when soft AP is ready.
private boolean mWifiTetherRequested;
private Network mTetherUpstream;
+ private TetherStatesParcel mTetherStatesParcel;
- public Tethering(Context context, INetworkManagementService nmService,
- INetworkStatsService statsService, INetworkPolicyManager policyManager,
- Looper looper, MockableSystemProperties systemProperties,
- TetheringDependencies deps) {
- mLog.mark("constructed");
- mContext = context;
- mNMService = nmService;
- mStatsService = statsService;
- mPolicyManager = policyManager;
- mLooper = looper;
+ public Tethering(TetheringDependencies deps) {
+ mLog.mark("Tethering.constructed");
mDeps = deps;
+ mContext = mDeps.getContext();
+ mNMService = mDeps.getINetworkManagementService();
+ mStatsService = mDeps.getINetworkStatsService();
+ mPolicyManager = mDeps.getINetworkPolicyManager();
+ mNetd = mDeps.getINetd(mContext);
+ mLooper = mDeps.getTetheringLooper();
mPublicSync = new Object();
@@ -239,7 +232,7 @@
// 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);
+ TetherMasterSM.EVENT_UPSTREAM_PERMISSION_CHANGED);
mEntitlementMgr.setOnUiEntitlementFailedListener((int downstream) -> {
mLog.log("OBSERVED UiEnitlementFailed");
stopTethering(downstream);
@@ -278,10 +271,22 @@
mStateReceiver = new StateReceiver();
+ mNetdCallback = new NetdCallback();
+ try {
+ mNetd.registerUnsolicitedEventListener(mNetdCallback);
+ } catch (RemoteException e) {
+ mLog.e("Unable to register netd UnsolicitedEventListener");
+ }
+
+ final UserManager userManager = (UserManager) mContext.getSystemService(
+ Context.USER_SERVICE);
+ mTetheringRestriction = new UserRestrictionActionListener(userManager, this);
+
// Load tethering configuration.
updateConfiguration();
startStateMachineUpdaters(mHandler);
+ startTrackDefaultNetwork();
}
private void startStateMachineUpdaters(Handler handler) {
@@ -295,6 +300,7 @@
filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+ filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
mContext.registerReceiver(mStateReceiver, filter, null, handler);
filter = new IntentFilter();
@@ -303,11 +309,6 @@
filter.addDataScheme("file");
mContext.registerReceiver(mStateReceiver, filter, null, handler);
- final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
- // This check is useful only for some unit tests; example: ConnectivityServiceTest.
- if (umi != null) {
- umi.addUserRestrictionsListener(new TetheringUserRestrictionListener(this));
- }
}
private WifiManager getWifiManager() {
@@ -318,6 +319,7 @@
private void updateConfiguration() {
mConfig = mDeps.generateTetheringConfiguration(mContext, mLog, mActiveDataSubId);
mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired);
+ reportConfigurationChanged(mConfig.toStableParcelable());
}
private void maybeDunSettingChanged() {
@@ -327,10 +329,31 @@
updateConfiguration();
}
- @Override
- public void interfaceStatusChanged(String iface, boolean up) {
+ private class NetdCallback extends BaseNetdUnsolicitedEventListener {
+ @Override
+ public void onInterfaceChanged(String ifName, boolean up) {
+ mHandler.post(() -> interfaceStatusChanged(ifName, up));
+ }
+
+ @Override
+ public void onInterfaceLinkStateChanged(String ifName, boolean up) {
+ mHandler.post(() -> interfaceLinkStateChanged(ifName, up));
+ }
+
+ @Override
+ public void onInterfaceAdded(String ifName) {
+ mHandler.post(() -> interfaceAdded(ifName));
+ }
+
+ @Override
+ public void onInterfaceRemoved(String ifName) {
+ mHandler.post(() -> interfaceRemoved(ifName));
+ }
+ }
+
+ void interfaceStatusChanged(String iface, boolean up) {
// Never called directly: only called from interfaceLinkStateChanged.
- // See NetlinkHandler.cpp:71.
+ // See NetlinkHandler.cpp: notifyInterfaceChanged.
if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
synchronized (mPublicSync) {
if (up) {
@@ -349,8 +372,7 @@
}
}
- @Override
- public void interfaceLinkStateChanged(String iface, boolean up) {
+ void interfaceLinkStateChanged(String iface, boolean up) {
interfaceStatusChanged(iface, up);
}
@@ -369,28 +391,27 @@
return TETHERING_INVALID;
}
- @Override
- public void interfaceAdded(String iface) {
+ void interfaceAdded(String iface) {
if (VDBG) Log.d(TAG, "interfaceAdded " + iface);
synchronized (mPublicSync) {
maybeTrackNewInterfaceLocked(iface);
}
}
- @Override
- public void interfaceRemoved(String iface) {
+
+ void interfaceRemoved(String iface) {
if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
synchronized (mPublicSync) {
stopTrackingInterfaceLocked(iface);
}
}
- public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
+ void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
mEntitlementMgr.startProvisioningIfNeeded(type, showProvisioningUi);
enableTetheringInternal(type, true /* enabled */, receiver);
}
- public void stopTethering(int type) {
+ void stopTethering(int type) {
enableTetheringInternal(type, false /* disabled */, null);
mEntitlementMgr.stopProvisioningIfNeeded(type);
}
@@ -434,8 +455,8 @@
mLog.e("setWifiTethering: failed to get WifiManager!");
return TETHER_ERROR_SERVICE_UNAVAIL;
}
- if ((enable && mgr.startSoftAp(null /* use existing wifi config */)) ||
- (!enable && mgr.stopSoftAp())) {
+ if ((enable && mgr.startSoftAp(null /* use existing wifi config */))
+ || (!enable && mgr.stopSoftAp())) {
mWifiTetherRequested = enable;
return TETHER_ERROR_NO_ERROR;
}
@@ -450,8 +471,8 @@
private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) {
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null || !adapter.isEnabled()) {
- Log.w(TAG, "Tried to enable bluetooth tethering with null or disabled adapter. null: " +
- (adapter == null));
+ Log.w(TAG, "Tried to enable bluetooth tethering with null or disabled adapter. null: "
+ + (adapter == null));
sendTetherResult(receiver, TETHER_ERROR_SERVICE_UNAVAIL);
return;
}
@@ -487,7 +508,7 @@
}, BluetoothProfile.PAN);
}
- public int tether(String iface) {
+ int tether(String iface) {
return tether(iface, IpServer.STATE_TETHERED);
}
@@ -515,7 +536,7 @@
}
}
- public int untether(String iface) {
+ int untether(String iface) {
if (DBG) Log.d(TAG, "Untethering " + iface);
synchronized (mPublicSync) {
TetherState tetherState = mTetherStates.get(iface);
@@ -532,19 +553,19 @@
}
}
- public void untetherAll() {
+ void untetherAll() {
stopTethering(TETHERING_WIFI);
stopTethering(TETHERING_WIFI_P2P);
stopTethering(TETHERING_USB);
stopTethering(TETHERING_BLUETOOTH);
}
- public int getLastTetherError(String iface) {
+ int getLastTetherError(String iface) {
synchronized (mPublicSync) {
TetherState tetherState = mTetherStates.get(iface);
if (tetherState == null) {
- Log.e(TAG, "Tried to getLastTetherError on an unknown iface :" + iface +
- ", ignoring");
+ Log.e(TAG, "Tried to getLastTetherError on an unknown iface :" + iface
+ + ", ignoring");
return TETHER_ERROR_UNKNOWN_IFACE;
}
return tetherState.lastError;
@@ -559,12 +580,14 @@
final ArrayList<String> tetherList = new ArrayList<>();
final ArrayList<String> localOnlyList = new ArrayList<>();
final ArrayList<String> erroredList = new ArrayList<>();
+ final ArrayList<Integer> lastErrorList = new ArrayList<>();
boolean wifiTethered = false;
boolean usbTethered = false;
boolean bluetoothTethered = false;
final TetheringConfiguration cfg = mConfig;
+ final TetherStatesParcel mTetherStatesParcel = new TetherStatesParcel();
synchronized (mPublicSync) {
for (int i = 0; i < mTetherStates.size(); i++) {
@@ -572,6 +595,7 @@
String iface = mTetherStates.keyAt(i);
if (tetherState.lastError != TETHER_ERROR_NO_ERROR) {
erroredList.add(iface);
+ lastErrorList.add(tetherState.lastError);
} else if (tetherState.lastState == IpServer.STATE_AVAILABLE) {
availableList.add(iface);
} else if (tetherState.lastState == IpServer.STATE_LOCAL_ONLY) {
@@ -588,9 +612,21 @@
}
}
}
+
+ mTetherStatesParcel.availableList = availableList.toArray(new String[0]);
+ mTetherStatesParcel.tetheredList = tetherList.toArray(new String[0]);
+ mTetherStatesParcel.localOnlyList = localOnlyList.toArray(new String[0]);
+ mTetherStatesParcel.erroredIfaceList = erroredList.toArray(new String[0]);
+ mTetherStatesParcel.lastErrorList = new int[lastErrorList.size()];
+ Iterator<Integer> iterator = lastErrorList.iterator();
+ for (int i = 0; i < lastErrorList.size(); i++) {
+ mTetherStatesParcel.lastErrorList[i] = iterator.next().intValue();
+ }
+ reportTetherStateChanged(mTetherStatesParcel);
+
final Intent bcast = new Intent(ACTION_TETHER_STATE_CHANGED);
- bcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
- Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ bcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+ | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
bcast.putStringArrayListExtra(EXTRA_AVAILABLE_TETHER, availableList);
bcast.putStringArrayListExtra(EXTRA_ACTIVE_LOCAL_ONLY, localOnlyList);
bcast.putStringArrayListExtra(EXTRA_ACTIVE_TETHER, tetherList);
@@ -638,16 +674,16 @@
}
int icon = 0;
switch(id) {
- case SystemMessage.NOTE_TETHER_USB:
- icon = com.android.internal.R.drawable.stat_sys_tether_usb;
- break;
- case SystemMessage.NOTE_TETHER_BLUETOOTH:
- icon = com.android.internal.R.drawable.stat_sys_tether_bluetooth;
- break;
- case SystemMessage.NOTE_TETHER_GENERAL:
- default:
- icon = com.android.internal.R.drawable.stat_sys_tether_general;
- break;
+ case SystemMessage.NOTE_TETHER_USB:
+ icon = com.android.internal.R.drawable.stat_sys_tether_usb;
+ break;
+ case SystemMessage.NOTE_TETHER_BLUETOOTH:
+ icon = com.android.internal.R.drawable.stat_sys_tether_bluetooth;
+ break;
+ case SystemMessage.NOTE_TETHER_GENERAL:
+ default:
+ icon = com.android.internal.R.drawable.stat_sys_tether_general;
+ break;
}
if (mLastNotificationId != 0) {
@@ -679,8 +715,8 @@
}
if (mTetheredNotificationBuilder == null) {
- mTetheredNotificationBuilder =
- new Notification.Builder(mContext, SystemNotificationChannels.NETWORK_STATUS);
+ mTetheredNotificationBuilder = new Notification.Builder(mContext,
+ SystemNotificationChannels.NETWORK_STATUS);
mTetheredNotificationBuilder.setWhen(0)
.setOngoing(true)
.setColor(mContext.getColor(
@@ -701,7 +737,7 @@
@VisibleForTesting
protected void clearTetheredNotification() {
NotificationManager notificationManager =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null && mLastNotificationId != 0) {
notificationManager.cancelAsUser(null, mLastNotificationId,
UserHandle.ALL);
@@ -726,14 +762,17 @@
} else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
mLog.log("OBSERVED configuration changed");
updateConfiguration();
+ } else if (action.equals(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)) {
+ mLog.log("OBSERVED user restrictions changed");
+ handleUserRestrictionAction();
}
}
private void handleConnectivityAction(Intent intent) {
final NetworkInfo networkInfo =
(NetworkInfo) intent.getParcelableExtra(EXTRA_NETWORK_INFO);
- if (networkInfo == null ||
- networkInfo.getDetailedState() == NetworkInfo.DetailedState.FAILED) {
+ if (networkInfo == null
+ || networkInfo.getDetailedState() == NetworkInfo.DetailedState.FAILED) {
return;
}
@@ -832,25 +871,35 @@
}
}
}
+
+ private void handleUserRestrictionAction() {
+ mTetheringRestriction.onUserRestrictionsChanged();
+ }
}
@VisibleForTesting
- protected static class TetheringUserRestrictionListener implements UserRestrictionsListener {
+ protected static class UserRestrictionActionListener {
+ private final UserManager mUserManager;
private final Tethering mWrapper;
+ public boolean mDisallowTethering;
- public TetheringUserRestrictionListener(Tethering wrapper) {
+ public UserRestrictionActionListener(UserManager um, Tethering wrapper) {
+ mUserManager = um;
mWrapper = wrapper;
+ mDisallowTethering = false;
}
- public void onUserRestrictionsChanged(int userId,
- Bundle newRestrictions,
- Bundle prevRestrictions) {
+ public void onUserRestrictionsChanged() {
+ // getUserRestrictions gets restriction for this process' user, which is the primary
+ // user. This is fine because DISALLOW_CONFIG_TETHERING can only be set on the primary
+ // user. See UserManager.DISALLOW_CONFIG_TETHERING.
+ final Bundle restrictions = mUserManager.getUserRestrictions();
final boolean newlyDisallowed =
- newRestrictions.getBoolean(UserManager.DISALLOW_CONFIG_TETHERING);
- final boolean previouslyDisallowed =
- prevRestrictions.getBoolean(UserManager.DISALLOW_CONFIG_TETHERING);
- final boolean tetheringDisallowedChanged = (newlyDisallowed != previouslyDisallowed);
+ restrictions.getBoolean(UserManager.DISALLOW_CONFIG_TETHERING);
+ final boolean prevDisallowed = mDisallowTethering;
+ mDisallowTethering = newlyDisallowed;
+ final boolean tetheringDisallowedChanged = (newlyDisallowed != prevDisallowed);
if (!tetheringDisallowedChanged) {
return;
}
@@ -888,8 +937,8 @@
}
}
- mLog.log("Error disabling Wi-Fi IP serving; " +
- (TextUtils.isEmpty(ifname) ? "no interface name specified"
+ mLog.log("Error disabling Wi-Fi IP serving; "
+ + (TextUtils.isEmpty(ifname) ? "no interface name specified"
: "specified interface: " + ifname));
}
@@ -928,8 +977,8 @@
changeInterfaceState(ifname, ipServingMode);
} else {
mLog.e(String.format(
- "Cannot enable IP serving in mode %s on missing interface name",
- ipServingMode));
+ "Cannot enable IP serving in mode %s on missing interface name",
+ ipServingMode));
}
}
@@ -989,11 +1038,11 @@
}
}
- public TetheringConfiguration getTetheringConfiguration() {
+ TetheringConfiguration getTetheringConfiguration() {
return mConfig;
}
- public boolean hasTetherableConfiguration() {
+ boolean hasTetherableConfiguration() {
final TetheringConfiguration cfg = mConfig;
final boolean hasDownstreamConfiguration =
(cfg.tetherableUsbRegexs.length != 0)
@@ -1007,19 +1056,19 @@
// TODO - update callers to use getTetheringConfiguration(),
// which has only final members.
- public String[] getTetherableUsbRegexs() {
+ String[] getTetherableUsbRegexs() {
return copy(mConfig.tetherableUsbRegexs);
}
- public String[] getTetherableWifiRegexs() {
+ String[] getTetherableWifiRegexs() {
return copy(mConfig.tetherableWifiRegexs);
}
- public String[] getTetherableBluetoothRegexs() {
+ String[] getTetherableBluetoothRegexs() {
return copy(mConfig.tetherableBluetoothRegexs);
}
- public int setUsbTethering(boolean enable) {
+ int setUsbTethering(boolean enable) {
if (VDBG) Log.d(TAG, "setUsbTethering(" + enable + ")");
UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
if (usbManager == null) {
@@ -1035,7 +1084,7 @@
}
// TODO review API - figure out how to delete these entirely.
- public String[] getTetheredIfaces() {
+ String[] getTetheredIfaces() {
ArrayList<String> list = new ArrayList<String>();
synchronized (mPublicSync) {
for (int i = 0; i < mTetherStates.size(); i++) {
@@ -1048,7 +1097,7 @@
return list.toArray(new String[list.size()]);
}
- public String[] getTetherableIfaces() {
+ String[] getTetherableIfaces() {
ArrayList<String> list = new ArrayList<String>();
synchronized (mPublicSync) {
for (int i = 0; i < mTetherStates.size(); i++) {
@@ -1061,13 +1110,13 @@
return list.toArray(new String[list.size()]);
}
- public String[] getTetheredDhcpRanges() {
+ String[] getTetheredDhcpRanges() {
// TODO: this is only valid for the old DHCP server. Latest search suggests it is only used
// by WifiP2pServiceImpl to start dnsmasq: remove/deprecate after migrating callers.
return mConfig.legacyDhcpRanges;
}
- public String[] getErroredIfaces() {
+ String[] getErroredIfaces() {
ArrayList<String> list = new ArrayList<String>();
synchronized (mPublicSync) {
for (int i = 0; i < mTetherStates.size(); i++) {
@@ -1518,8 +1567,8 @@
}
if (DBG) {
- Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size() +
- " live requests:");
+ Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size()
+ + " live requests:");
for (IpServer o : mNotifyList) {
Log.d(TAG, " " + o);
}
@@ -1588,7 +1637,7 @@
transitionTo(mInitialState);
break;
default:
- retValue = false;
+ retValue = false;
}
return retValue;
}
@@ -1625,7 +1674,7 @@
notify(IpServer.CMD_START_TETHERING_ERROR);
try {
mNMService.setIpForwardingEnabled(false);
- } catch (Exception e) {}
+ } catch (Exception e) { }
}
}
@@ -1636,7 +1685,7 @@
notify(IpServer.CMD_STOP_TETHERING_ERROR);
try {
mNMService.setIpForwardingEnabled(false);
- } catch (Exception e) {}
+ } catch (Exception e) { }
}
}
@@ -1647,10 +1696,10 @@
notify(IpServer.CMD_SET_DNS_FORWARDERS_ERROR);
try {
mNMService.stopTethering();
- } catch (Exception e) {}
+ } catch (Exception e) { }
try {
mNMService.setIpForwardingEnabled(false);
- } catch (Exception e) {}
+ } catch (Exception e) { }
}
}
@@ -1731,55 +1780,66 @@
}
}
- public void systemReady() {
+ private void startTrackDefaultNetwork() {
mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest(),
mEntitlementMgr);
}
/** Get the latest value of the tethering entitlement check. */
- public void getLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
+ void requestLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
boolean showEntitlementUi) {
if (receiver != null) {
- mEntitlementMgr.getLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
+ mEntitlementMgr.requestLatestTetheringEntitlementResult(type, receiver,
+ showEntitlementUi);
}
}
/** Register tethering event callback */
- public void registerTetheringEventCallback(ITetheringEventCallback callback) {
+ void registerTetherInternalCallback(ITetherInternalCallback callback) {
mHandler.post(() -> {
+ mTetherInternalCallback = callback;
try {
- callback.onUpstreamChanged(mTetherUpstream);
+ mTetherInternalCallback.onCallbackCreated(mTetherUpstream,
+ mConfig.toStableParcelable(), mTetherStatesParcel);
} catch (RemoteException e) {
// Not really very much to do here.
}
- mTetheringEventCallbacks.register(callback);
- });
- }
-
- /** Unregister tethering event callback */
- public void unregisterTetheringEventCallback(ITetheringEventCallback callback) {
- mHandler.post(() -> {
- mTetheringEventCallbacks.unregister(callback);
});
}
private void reportUpstreamChanged(Network network) {
- final int length = mTetheringEventCallbacks.beginBroadcast();
+ // Don't need to synchronized mTetherInternalCallback because all the usage of this variable
+ // should run at the same thread.
+ if (mTetherInternalCallback == null) return;
+
try {
- for (int i = 0; i < length; i++) {
- try {
- mTetheringEventCallbacks.getBroadcastItem(i).onUpstreamChanged(network);
- } catch (RemoteException e) {
- // Not really very much to do here.
- }
- }
- } finally {
- mTetheringEventCallbacks.finishBroadcast();
+ mTetherInternalCallback.onUpstreamChanged(network);
+ } catch (RemoteException e) {
+ // Not really very much to do here.
}
}
- @Override
- public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ private void reportConfigurationChanged(TetheringConfigurationParcel config) {
+ if (mTetherInternalCallback == null) return;
+
+ try {
+ mTetherInternalCallback.onConfigurationChanged(config);
+ } catch (RemoteException e) {
+ // Not really very much to do here.
+ }
+ }
+
+ private void reportTetherStateChanged(TetherStatesParcel states) {
+ if (mTetherInternalCallback == null) return;
+
+ try {
+ mTetherInternalCallback.onTetherStatesChanged(states);
+ } catch (RemoteException e) {
+ // Not really very much to do here.
+ }
+ }
+
+ void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
// Binder.java closes the resource for us.
@SuppressWarnings("resource")
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -1838,7 +1898,7 @@
pw.println("Log:");
pw.increaseIndent();
- if (argsContain(args, SHORT_ARG)) {
+ if (argsContain(args, "--short")) {
pw.println("<log removed for brevity>");
} else {
mLog.dump(fd, pw, args);
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
index ca9b168..0ab4d63 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -38,6 +38,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.net.ConnectivityManager;
+import android.net.TetheringConfigurationParcel;
import android.net.util.SharedLog;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
@@ -384,4 +385,32 @@
}
return false;
}
+
+ /**
+ * Convert this TetheringConfiguration to a TetheringConfigurationParcel.
+ */
+ public TetheringConfigurationParcel toStableParcelable() {
+ final TetheringConfigurationParcel parcel = new TetheringConfigurationParcel();
+ parcel.subId = subId;
+ parcel.tetherableUsbRegexs = tetherableUsbRegexs;
+ parcel.tetherableWifiRegexs = tetherableWifiRegexs;
+ parcel.tetherableBluetoothRegexs = tetherableBluetoothRegexs;
+ parcel.isDunRequired = isDunRequired;
+ parcel.chooseUpstreamAutomatically = chooseUpstreamAutomatically;
+
+ int[] preferredTypes = new int[preferredUpstreamIfaceTypes.size()];
+ int index = 0;
+ for (Integer type : preferredUpstreamIfaceTypes) {
+ preferredTypes[index++] = type;
+ }
+ parcel.preferredUpstreamIfaceTypes = preferredTypes;
+
+ parcel.legacyDhcpRanges = legacyDhcpRanges;
+ parcel.defaultIPv4DNS = defaultIPv4DNS;
+ parcel.enableLegacyDhcpServer = enableLegacyDhcpServer;
+ parcel.provisioningApp = provisioningApp;
+ parcel.provisioningAppNoUi = provisioningAppNoUi;
+ parcel.provisioningCheckPeriod = provisioningCheckPeriod;
+ return parcel;
+ }
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
new file mode 100644
index 0000000..0ba8412
--- /dev/null
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2017 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.server.connectivity.tethering;
+
+import android.content.Context;
+import android.net.INetd;
+import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
+import android.net.NetworkRequest;
+import android.net.ip.IpServer;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.ServiceManager;
+
+import com.android.internal.util.StateMachine;
+
+import java.util.ArrayList;
+
+
+/**
+ * Capture tethering dependencies, for injection.
+ *
+ * @hide
+ */
+public class TetheringDependencies {
+ /**
+ * Get a reference to the offload hardware interface to be used by tethering.
+ */
+ public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) {
+ return new OffloadHardwareInterface(h, log);
+ }
+
+ /**
+ * Get a reference to the UpstreamNetworkMonitor to be used by tethering.
+ */
+ public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target,
+ SharedLog log, int what) {
+ return new UpstreamNetworkMonitor(ctx, target, log, what);
+ }
+
+ /**
+ * Get a reference to the IPv6TetheringCoordinator to be used by tethering.
+ */
+ public IPv6TetheringCoordinator getIPv6TetheringCoordinator(
+ ArrayList<IpServer> notifyList, SharedLog log) {
+ return new IPv6TetheringCoordinator(notifyList, log);
+ }
+
+ /**
+ * Get dependencies to be used by IpServer.
+ */
+ public IpServer.Dependencies getIpServerDependencies() {
+ return new IpServer.Dependencies();
+ }
+
+ /**
+ * Indicates whether tethering is supported on the device.
+ */
+ public boolean isTetheringSupported() {
+ return true;
+ }
+
+ /**
+ * Get the NetworkRequest that should be fulfilled by the default network.
+ */
+ public NetworkRequest getDefaultNetworkRequest() {
+ return null;
+ }
+
+ /**
+ * Get a reference to the EntitlementManager to be used by tethering.
+ */
+ public EntitlementManager getEntitlementManager(Context ctx, StateMachine target,
+ SharedLog log, int what) {
+ return new EntitlementManager(ctx, target, log, what);
+ }
+
+ /**
+ * Generate a new TetheringConfiguration according to input sub Id.
+ */
+ public TetheringConfiguration generateTetheringConfiguration(Context ctx, SharedLog log,
+ int subId) {
+ return new TetheringConfiguration(ctx, log, subId);
+ }
+
+ /**
+ * Get a reference to INetworkManagementService to registerTetheringStatsProvider from
+ * OffloadController. Note: This should be removed soon by Usage refactor work in R
+ * development cycle.
+ */
+ public INetworkManagementService getINetworkManagementService() {
+ return INetworkManagementService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+ }
+
+ /**
+ * Get a reference to INetworkStatsService to force update tethering usage.
+ * Note: This should be removed in R development cycle.
+ */
+ public INetworkStatsService getINetworkStatsService() {
+ return INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+ }
+
+ /**
+ * Get a reference to INetworkPolicyManager to be used by tethering.
+ */
+ public INetworkPolicyManager getINetworkPolicyManager() {
+ return INetworkPolicyManager.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
+ }
+
+ /**
+ * Get a reference to INetd to be used by tethering.
+ */
+ public INetd getINetd(Context context) {
+ return INetd.Stub.asInterface(
+ (IBinder) context.getSystemService(Context.NETD_SERVICE));
+ }
+
+ /**
+ * Get tethering thread looper.
+ */
+ public Looper getTetheringLooper() {
+ return null;
+ }
+
+ /**
+ * Get Context of TetheringSerice.
+ */
+ public Context getContext() {
+ return null;
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
similarity index 100%
rename from services/core/java/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
rename to packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
new file mode 100644
index 0000000..456f2f7
--- /dev/null
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2019 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.server.connectivity.tethering;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.ITetherInternalCallback;
+import android.net.ITetheringConnector;
+import android.net.NetworkRequest;
+import android.net.util.SharedLog;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.os.SystemProperties;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Android service used to manage tethering.
+ *
+ * <p>The service returns a binder for the system server to communicate with the tethering.
+ */
+public class TetheringService extends Service {
+ private static final String TAG = TetheringService.class.getSimpleName();
+
+ private final SharedLog mLog = new SharedLog(TAG);
+ private TetheringConnector mConnector;
+ private Context mContext;
+ private TetheringDependencies mDeps;
+ private Tethering mTethering;
+
+ @Override
+ public void onCreate() {
+ mLog.mark("onCreate");
+ mDeps = getTetheringDependencies();
+ mContext = mDeps.getContext();
+ mTethering = makeTethering(mDeps);
+ }
+
+ /**
+ * Make a reference to Tethering object.
+ */
+ @VisibleForTesting
+ public Tethering makeTethering(TetheringDependencies deps) {
+ return new Tethering(deps);
+ }
+
+ /**
+ * Create a binder connector for the system server to communicate with the tethering.
+ */
+ private synchronized IBinder makeConnector() {
+ if (mConnector == null) {
+ mConnector = new TetheringConnector(mTethering);
+ }
+ return mConnector;
+ }
+
+ @NonNull
+ @Override
+ public IBinder onBind(Intent intent) {
+ mLog.mark("onBind");
+ return makeConnector();
+ }
+
+ private static class TetheringConnector extends ITetheringConnector.Stub {
+ private final Tethering mService;
+
+ TetheringConnector(Tethering tether) {
+ mService = tether;
+ }
+
+ @Override
+ public void tether(String iface) {
+ mService.tether(iface);
+ }
+
+ @Override
+ public void untether(String iface) {
+ mService.untether(iface);
+ }
+
+ @Override
+ public void setUsbTethering(boolean enable) {
+ mService.setUsbTethering(enable);
+ }
+
+ @Override
+ public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
+ mService.startTethering(type, receiver, showProvisioningUi);
+ }
+
+ @Override
+ public void stopTethering(int type) {
+ mService.stopTethering(type);
+ }
+
+ @Override
+ public void requestLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
+ boolean showEntitlementUi) {
+ mService.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
+ }
+
+ @Override
+ public void registerTetherInternalCallback(ITetherInternalCallback callback) {
+ mService.registerTetherInternalCallback(callback);
+ }
+ }
+
+ @Override
+ protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
+ @Nullable String[] args) {
+ mTethering.dump(fd, writer, args);
+ }
+
+ /**
+ * An injection method for testing.
+ */
+ @VisibleForTesting
+ public TetheringDependencies getTetheringDependencies() {
+ if (mDeps == null) {
+ mDeps = new TetheringDependencies() {
+ @Override
+ public NetworkRequest getDefaultNetworkRequest() {
+ ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ return cm.getDefaultRequest();
+ }
+
+ @Override
+ public Looper getTetheringLooper() {
+ final HandlerThread tetherThread = new HandlerThread("android.tethering");
+ tetherThread.start();
+ return tetherThread.getLooper();
+ }
+
+ @Override
+ public boolean isTetheringSupported() {
+ int defaultVal =
+ SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1;
+ boolean tetherSupported = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.TETHER_SUPPORTED, defaultVal) != 0;
+ return tetherSupported;
+ }
+
+ @Override
+ public Context getContext() {
+ return TetheringService.this;
+ }
+ };
+ }
+
+ return mDeps;
+ }
+}
diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp
index 363be18..5b018df 100644
--- a/packages/Tethering/tests/unit/Android.bp
+++ b/packages/Tethering/tests/unit/Android.bp
@@ -18,7 +18,6 @@
name: "TetheringTests",
certificate: "platform",
srcs: [
- ":servicescore-tethering-src",
"src/**/*.java",
],
test_suites: ["device-tests"],
@@ -41,17 +40,3 @@
"libstaticjvmtiagent",
],
}
-
-// This group would be removed when tethering migration is done.
-filegroup {
- name: "tethering-tests-src",
- srcs: [
- "src/com/android/server/connectivity/tethering/EntitlementManagerTest.java",
- "src/com/android/server/connectivity/tethering/OffloadControllerTest.java",
- "src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java",
- "src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java",
- "src/android/net/dhcp/DhcpServingParamsParcelExtTest.java",
- "src/android/net/ip/IpServerTest.java",
- "src/android/net/util/InterfaceSetTest.java",
- ],
-}
diff --git a/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java b/packages/Tethering/tests/unit/src/android/net/util/VersionedBroadcastListenerTest.java
similarity index 96%
rename from tests/net/java/android/net/util/VersionedBroadcastListenerTest.java
rename to packages/Tethering/tests/unit/src/android/net/util/VersionedBroadcastListenerTest.java
index 0d27d5b..5a9b6e3 100644
--- a/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/util/VersionedBroadcastListenerTest.java
@@ -51,7 +51,9 @@
private VersionedBroadcastListener mListener;
private int mCallbackCount;
- private void doCallback() { mCallbackCount++; }
+ private void doCallback() {
+ mCallbackCount++;
+ }
private class MockContext extends BroadcastInterceptingContext {
MockContext(Context base) {
@@ -96,7 +98,7 @@
mListener.startListening();
for (int i = 0; i < 5; i++) {
sendBroadcast();
- assertEquals(i+1, mCallbackCount);
+ assertEquals(i + 1, mCallbackCount);
}
mListener.stopListening();
}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index 5217e26..99cf9e9 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -24,6 +24,9 @@
import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -44,6 +47,7 @@
import android.os.Message;
import android.os.PersistableBundle;
import android.os.ResultReceiver;
+import android.os.SystemProperties;
import android.os.test.TestLooper;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
@@ -57,7 +61,6 @@
import com.android.internal.util.StateMachine;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.connectivity.MockableSystemProperties;
import org.junit.After;
import org.junit.Before;
@@ -65,6 +68,8 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
@@ -80,7 +85,6 @@
@Mock private CarrierConfigManager mCarrierConfigManager;
@Mock private Context mContext;
- @Mock private MockableSystemProperties mSystemProperties;
@Mock private Resources mResources;
@Mock private SharedLog mLog;
@Mock private EntitlementManager.OnUiEntitlementFailedListener mEntitlementFailedListener;
@@ -95,6 +99,7 @@
private TestStateMachine mSM;
private WrappedEntitlementManager mEnMgr;
private TetheringConfiguration mConfig;
+ private MockitoSession mMockingSession;
private class MockContext extends BroadcastInterceptingContext {
MockContext(Context base) {
@@ -118,8 +123,8 @@
public int silentProvisionCount = 0;
public WrappedEntitlementManager(Context ctx, StateMachine target,
- SharedLog log, int what, MockableSystemProperties systemProperties) {
- super(ctx, target, log, what, systemProperties);
+ SharedLog log, int what) {
+ super(ctx, target, log, what);
}
public void reset() {
@@ -144,6 +149,15 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .spyStatic(SystemProperties.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ // Don't disable tethering provisioning unless requested.
+ doReturn(false).when(
+ () -> SystemProperties.getBoolean(
+ eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), anyBoolean()));
when(mResources.getStringArray(R.array.config_tether_dhcp_range))
.thenReturn(new String[0]);
@@ -161,8 +175,7 @@
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
mMockContext = new MockContext(mContext);
mSM = new TestStateMachine();
- mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE,
- mSystemProperties);
+ mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE);
mEnMgr.setOnUiEntitlementFailedListener(mEntitlementFailedListener);
mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
mEnMgr.setTetheringConfigurationFetcher(() -> {
@@ -176,6 +189,7 @@
mSM.quit();
mSM = null;
}
+ mMockingSession.finishMocking();
}
private void setupForRequiredProvisioning() {
@@ -184,9 +198,6 @@
.thenReturn(PROVISIONING_APP_NAME);
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.
when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
.thenReturn(mCarrierConfigManager);
@@ -244,7 +255,7 @@
}
@Test
- public void testGetLastEntitlementCacheValue() throws Exception {
+ public void testRequestLastEntitlementCacheValue() throws Exception {
final CountDownLatch mCallbacklatch = new CountDownLatch(1);
// 1. Entitlement check is not required.
mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
@@ -255,7 +266,7 @@
mCallbacklatch.countDown();
}
};
- mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
+ mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
mLooper.dispatchAll();
callbackTimeoutHelper(mCallbacklatch);
assertEquals(0, mEnMgr.uiProvisionCount);
@@ -270,7 +281,7 @@
mCallbacklatch.countDown();
}
};
- mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
+ mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
mLooper.dispatchAll();
callbackTimeoutHelper(mCallbacklatch);
assertEquals(0, mEnMgr.uiProvisionCount);
@@ -284,7 +295,7 @@
mCallbacklatch.countDown();
}
};
- mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
+ mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
mLooper.dispatchAll();
callbackTimeoutHelper(mCallbacklatch);
assertEquals(1, mEnMgr.uiProvisionCount);
@@ -298,7 +309,7 @@
mCallbacklatch.countDown();
}
};
- mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
+ mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
mLooper.dispatchAll();
callbackTimeoutHelper(mCallbacklatch);
assertEquals(0, mEnMgr.uiProvisionCount);
@@ -312,7 +323,7 @@
mCallbacklatch.countDown();
}
};
- mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
+ mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
mLooper.dispatchAll();
callbackTimeoutHelper(mCallbacklatch);
assertEquals(1, mEnMgr.uiProvisionCount);
@@ -326,7 +337,7 @@
mCallbacklatch.countDown();
}
};
- mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
+ mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
mLooper.dispatchAll();
callbackTimeoutHelper(mCallbacklatch);
assertEquals(0, mEnMgr.uiProvisionCount);
@@ -339,7 +350,7 @@
mCallbacklatch.countDown();
}
};
- mEnMgr.getLatestTetheringEntitlementResult(TETHERING_USB, receiver, false);
+ mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_USB, receiver, false);
mLooper.dispatchAll();
callbackTimeoutHelper(mCallbacklatch);
assertEquals(0, mEnMgr.uiProvisionCount);
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
similarity index 83%
rename from tests/net/java/com/android/server/connectivity/TetheringTest.java
rename to packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index 9e5717b..0273ed3 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.connectivity;
+package com.android.server.connectivity.tethering;
import static android.hardware.usb.UsbManager.USB_CONFIGURED;
import static android.hardware.usb.UsbManager.USB_CONNECTED;
@@ -39,7 +39,9 @@
import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.argThat;
@@ -70,7 +72,7 @@
import android.net.INetd;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
-import android.net.ITetheringEventCallback;
+import android.net.ITetherInternalCallback;
import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
import android.net.LinkAddress;
@@ -82,6 +84,8 @@
import android.net.NetworkState;
import android.net.NetworkUtils;
import android.net.RouteInfo;
+import android.net.TetherStatesParcel;
+import android.net.TetheringConfigurationParcel;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpServer;
@@ -98,6 +102,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.INetworkManagementService;
+import android.os.Looper;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -109,6 +114,7 @@
import android.telephony.TelephonyManager;
import android.test.mock.MockContentResolver;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -116,11 +122,6 @@
import com.android.internal.util.StateMachine;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
-import com.android.server.connectivity.tethering.OffloadHardwareInterface;
-import com.android.server.connectivity.tethering.TetheringConfiguration;
-import com.android.server.connectivity.tethering.TetheringDependencies;
-import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
import org.junit.After;
import org.junit.Before;
@@ -154,7 +155,6 @@
@Mock private INetworkManagementService mNMService;
@Mock private INetworkStatsService mStatsService;
@Mock private INetworkPolicyManager mPolicyManager;
- @Mock private MockableSystemProperties mSystemProperties;
@Mock private OffloadHardwareInterface mOffloadHardwareInterface;
@Mock private Resources mResources;
@Mock private TelephonyManager mTelephonyManager;
@@ -166,6 +166,7 @@
@Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon;
@Mock private IDhcpServer mDhcpServer;
@Mock private INetd mNetd;
+ @Mock private UserManager mUserManager;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -184,28 +185,37 @@
private Tethering mTethering;
private PhoneStateListener mPhoneStateListener;
- private class MockContext extends BroadcastInterceptingContext {
- MockContext(Context base) {
+ private class TestContext extends BroadcastInterceptingContext {
+ TestContext(Context base) {
super(base);
}
@Override
- public ApplicationInfo getApplicationInfo() { return mApplicationInfo; }
+ public ApplicationInfo getApplicationInfo() {
+ return mApplicationInfo;
+ }
@Override
- public ContentResolver getContentResolver() { return mContentResolver; }
+ public ContentResolver getContentResolver() {
+ return mContentResolver;
+ }
@Override
- public String getPackageName() { return "TetheringTest"; }
+ public String getPackageName() {
+ return "TetheringTest";
+ }
@Override
- public Resources getResources() { return mResources; }
+ public Resources getResources() {
+ return mResources;
+ }
@Override
public Object getSystemService(String name) {
if (Context.WIFI_SERVICE.equals(name)) return mWifiManager;
if (Context.USB_SERVICE.equals(name)) return mUsbManager;
if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
+ if (Context.USER_SERVICE.equals(name)) return mUserManager;
return super.getSystemService(name);
}
@@ -266,14 +276,14 @@
}
public class MockTetheringDependencies extends TetheringDependencies {
- StateMachine upstreamNetworkMonitorMasterSM;
- ArrayList<IpServer> ipv6CoordinatorNotifyList;
- int isTetheringSupportedCalls;
+ StateMachine mUpstreamNetworkMonitorMasterSM;
+ ArrayList<IpServer> mIpv6CoordinatorNotifyList;
+ int mIsTetheringSupportedCalls;
public void reset() {
- upstreamNetworkMonitorMasterSM = null;
- ipv6CoordinatorNotifyList = null;
- isTetheringSupportedCalls = 0;
+ mUpstreamNetworkMonitorMasterSM = null;
+ mIpv6CoordinatorNotifyList = null;
+ mIsTetheringSupportedCalls = 0;
}
@Override
@@ -284,14 +294,14 @@
@Override
public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx,
StateMachine target, SharedLog log, int what) {
- upstreamNetworkMonitorMasterSM = target;
+ mUpstreamNetworkMonitorMasterSM = target;
return mUpstreamNetworkMonitor;
}
@Override
public IPv6TetheringCoordinator getIPv6TetheringCoordinator(
ArrayList<IpServer> notifyList, SharedLog log) {
- ipv6CoordinatorNotifyList = notifyList;
+ mIpv6CoordinatorNotifyList = notifyList;
return mIPv6TetheringCoordinator;
}
@@ -302,7 +312,7 @@
@Override
public boolean isTetheringSupported() {
- isTetheringSupportedCalls++;
+ mIsTetheringSupportedCalls++;
return true;
}
@@ -311,6 +321,36 @@
int subId) {
return new MockTetheringConfiguration(ctx, log, subId);
}
+
+ @Override
+ public INetworkManagementService getINetworkManagementService() {
+ return mNMService;
+ }
+
+ @Override
+ public INetworkStatsService getINetworkStatsService() {
+ return mStatsService;
+ }
+
+ @Override
+ public INetworkPolicyManager getINetworkPolicyManager() {
+ return mPolicyManager;
+ }
+
+ @Override
+ public INetd getINetd(Context context) {
+ return mNetd;
+ }
+
+ @Override
+ public Looper getTetheringLooper() {
+ return mLooper.getLooper();
+ }
+
+ @Override
+ public Context getContext() {
+ return mServiceContext;
+ }
}
private static NetworkState buildMobileUpstreamState(boolean withIPv4, boolean withIPv6,
@@ -345,7 +385,7 @@
final NetworkCapabilities capabilities = new NetworkCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);;
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
return new NetworkState(info, prop, capabilities, new Network(100), null, "netid");
}
@@ -390,7 +430,7 @@
when(mRouterAdvertisementDaemon.start())
.thenReturn(true);
- mServiceContext = new MockContext(mContext);
+ mServiceContext = new TestContext(mContext);
mContentResolver = new MockContentResolver(mServiceContext);
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
@@ -403,9 +443,9 @@
};
mServiceContext.registerReceiver(mBroadcastReceiver,
new IntentFilter(ACTION_TETHER_STATE_CHANGED));
- mTetheringDependencies.reset();
mTethering = makeTethering();
verify(mNMService).registerTetheringStatsProvider(any(), anyString());
+ verify(mNetd).registerUnsolicitedEventListener(any());
final ArgumentCaptor<PhoneStateListener> phoneListenerCaptor =
ArgumentCaptor.forClass(PhoneStateListener.class);
verify(mTelephonyManager).listen(phoneListenerCaptor.capture(),
@@ -414,9 +454,8 @@
}
private Tethering makeTethering() {
- return new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager,
- mLooper.getLooper(), mSystemProperties,
- mTetheringDependencies);
+ mTetheringDependencies.reset();
+ return new Tethering(mTetheringDependencies);
}
@After
@@ -507,7 +546,7 @@
// it creates a IpServer and sends out a broadcast indicating that the
// interface is "available".
if (emulateInterfaceStatusChanged) {
- assertEquals(1, mTetheringDependencies.isTetheringSupportedCalls);
+ assertEquals(1, mTetheringDependencies.mIsTetheringSupportedCalls);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
@@ -584,7 +623,7 @@
verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
// This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
// and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
- assertEquals(2, mTetheringDependencies.isTetheringSupportedCalls);
+ assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
// Emulate externally-visible WifiManager effects, when hotspot mode
// is being torn down.
@@ -617,8 +656,7 @@
argThat(sm -> sm.linkProperties().getInterfaceName().equals(TEST_USB_IFNAME)),
eq(IpServer.STATE_TETHERED));
- for (IpServer ipSrv :
- mTetheringDependencies.ipv6CoordinatorNotifyList) {
+ for (IpServer ipSrv : mTetheringDependencies.mIpv6CoordinatorNotifyList) {
NetworkState ipv6OnlyState = buildMobileUpstreamState(false, true, false);
ipSrv.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0,
upstreamState.linkProperties.isIpv6Provisioned()
@@ -650,7 +688,7 @@
@Test
public void workingMobileUsbTethering_IPv4LegacyDhcp() {
Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
- mTethering = makeTethering();
+ sendConfigurationChanged();
final NetworkState upstreamState = buildMobileIPv4UpstreamState();
runUsbTethering(upstreamState);
sendIPv6TetherUpdates(upstreamState);
@@ -719,7 +757,7 @@
.thenReturn(upstreamState);
// Upstream LinkProperties changed: UpstreamNetworkMonitor sends EVENT_ON_LINKPROPERTIES.
- mTetheringDependencies.upstreamNetworkMonitorMasterSM.sendMessage(
+ mTetheringDependencies.mUpstreamNetworkMonitorMasterSM.sendMessage(
Tethering.TetherMasterSM.EVENT_UPSTREAM_CALLBACK,
UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
0,
@@ -784,7 +822,7 @@
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
mLooper.dispatchAll();
- assertEquals(1, mTetheringDependencies.isTetheringSupportedCalls);
+ assertEquals(1, mTetheringDependencies.mIsTetheringSupportedCalls);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
@@ -828,7 +866,7 @@
verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest();
// This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
// and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
- assertEquals(2, mTetheringDependencies.isTetheringSupportedCalls);
+ assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
/////
// We do not currently emulate any upstream being found.
@@ -901,7 +939,7 @@
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
// There are 3 state change event:
// AVAILABLE -> STATE_TETHERED -> STATE_AVAILABLE.
- assertEquals(3, mTetheringDependencies.isTetheringSupportedCalls);
+ assertEquals(3, mTetheringDependencies.mIsTetheringSupportedCalls);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
// This is called, but will throw.
verify(mNMService, times(1)).setIpForwardingEnabled(true);
@@ -918,26 +956,26 @@
verifyNoMoreInteractions(mNMService);
}
- private void userRestrictionsListenerBehaviour(
- boolean currentDisallow, boolean nextDisallow, String[] activeTetheringIfacesList,
- int expectedInteractionsWithShowNotification) throws Exception {
- final int userId = 0;
- final Bundle currRestrictions = new Bundle();
+ private void runUserRestrictionsChange(
+ boolean currentDisallow, boolean nextDisallow, String[] activeTetheringIfacesList,
+ int expectedInteractionsWithShowNotification) throws Exception {
final Bundle newRestrictions = new Bundle();
- Tethering tethering = mock(Tethering.class);
- Tethering.TetheringUserRestrictionListener turl =
- new Tethering.TetheringUserRestrictionListener(tethering);
-
- currRestrictions.putBoolean(UserManager.DISALLOW_CONFIG_TETHERING, currentDisallow);
newRestrictions.putBoolean(UserManager.DISALLOW_CONFIG_TETHERING, nextDisallow);
- when(tethering.getTetheredIfaces()).thenReturn(activeTetheringIfacesList);
+ final Tethering mockTethering = mock(Tethering.class);
+ when(mockTethering.getTetheredIfaces()).thenReturn(activeTetheringIfacesList);
+ when(mUserManager.getUserRestrictions()).thenReturn(newRestrictions);
- turl.onUserRestrictionsChanged(userId, newRestrictions, currRestrictions);
+ final Tethering.UserRestrictionActionListener ural =
+ new Tethering.UserRestrictionActionListener(mUserManager, mockTethering);
+ ural.mDisallowTethering = currentDisallow;
- verify(tethering, times(expectedInteractionsWithShowNotification))
+ ural.onUserRestrictionsChanged();
+
+ verify(mockTethering, times(expectedInteractionsWithShowNotification))
.showTetheredNotification(anyInt(), eq(false));
- verify(tethering, times(expectedInteractionsWithShowNotification)).untetherAll();
+ verify(mockTethering, times(expectedInteractionsWithShowNotification))
+ .untetherAll();
}
@Test
@@ -947,7 +985,7 @@
final boolean nextDisallow = true;
final int expectedInteractionsWithShowNotification = 0;
- userRestrictionsListenerBehaviour(currDisallow, nextDisallow, emptyActiveIfacesList,
+ runUserRestrictionsChange(currDisallow, nextDisallow, emptyActiveIfacesList,
expectedInteractionsWithShowNotification);
}
@@ -958,7 +996,7 @@
final boolean nextDisallow = true;
final int expectedInteractionsWithShowNotification = 1;
- userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
+ runUserRestrictionsChange(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
expectedInteractionsWithShowNotification);
}
@@ -969,7 +1007,7 @@
final boolean nextDisallow = false;
final int expectedInteractionsWithShowNotification = 0;
- userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
+ runUserRestrictionsChange(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
expectedInteractionsWithShowNotification);
}
@@ -980,7 +1018,7 @@
final boolean nextDisallow = false;
final int expectedInteractionsWithShowNotification = 0;
- userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
+ runUserRestrictionsChange(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
expectedInteractionsWithShowNotification);
}
@@ -991,27 +1029,59 @@
boolean currDisallow = true;
boolean nextDisallow = true;
- userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
+ runUserRestrictionsChange(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
expectedInteractionsWithShowNotification);
currDisallow = false;
nextDisallow = false;
- userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
+ runUserRestrictionsChange(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
expectedInteractionsWithShowNotification);
}
- private class TestTetheringEventCallback extends ITetheringEventCallback.Stub {
+ private class TestTetherInternalCallback extends ITetherInternalCallback.Stub {
private final ArrayList<Network> mActualUpstreams = new ArrayList<>();
+ private final ArrayList<TetheringConfigurationParcel> mTetheringConfigs =
+ new ArrayList<>();
+ private final ArrayList<TetherStatesParcel> mTetherStates = new ArrayList<>();
+ // This function will remove the recorded callbacks, so it must be called once for
+ // each callback. If this is called after multiple callback, the order matters.
+ // onCallbackCreated counts as the first call to expectUpstreamChanged with
+ // @see onCallbackCreated.
public void expectUpstreamChanged(Network... networks) {
+ if (networks == null) {
+ assertNoUpstreamChangeCallback();
+ return;
+ }
+
final ArrayList<Network> expectedUpstreams =
new ArrayList<Network>(Arrays.asList(networks));
for (Network upstream : expectedUpstreams) {
// throws OOB if no expectations
assertEquals(mActualUpstreams.remove(0), upstream);
}
- assertNoCallback();
+ assertNoUpstreamChangeCallback();
+ }
+
+ // This function will remove the recorded callbacks, so it must be called once
+ // for each callback. If this is called after multiple callback, the order matters.
+ // onCallbackCreated counts as the first call to onConfigurationChanged with
+ // @see onCallbackCreated.
+ public void expectConfigurationChanged(TetheringConfigurationParcel... tetherConfigs) {
+ final ArrayList<TetheringConfigurationParcel> expectedTetherConfig =
+ new ArrayList<TetheringConfigurationParcel>(Arrays.asList(tetherConfigs));
+ for (TetheringConfigurationParcel config : expectedTetherConfig) {
+ // throws OOB if no expectations
+ final TetheringConfigurationParcel actualConfig = mTetheringConfigs.remove(0);
+ assertTetherConfigParcelEqual(actualConfig, config);
+ }
+ assertNoConfigChangeCallback();
+ }
+
+ public TetherStatesParcel pollTetherStatesChanged() {
+ assertStateChangeCallback();
+ return mTetherStates.remove(0);
}
@Override
@@ -1019,48 +1089,93 @@
mActualUpstreams.add(network);
}
- public void assertNoCallback() {
+ @Override
+ public void onConfigurationChanged(TetheringConfigurationParcel config) {
+ mTetheringConfigs.add(config);
+ }
+
+ @Override
+ public void onTetherStatesChanged(TetherStatesParcel states) {
+ mTetherStates.add(states);
+ }
+
+ @Override
+ public void onCallbackCreated(Network network, TetheringConfigurationParcel config,
+ TetherStatesParcel states) {
+ mActualUpstreams.add(network);
+ mTetheringConfigs.add(config);
+ mTetherStates.add(states);
+ }
+
+ public void assertNoUpstreamChangeCallback() {
assertTrue(mActualUpstreams.isEmpty());
}
+
+ public void assertNoConfigChangeCallback() {
+ assertTrue(mTetheringConfigs.isEmpty());
+ }
+
+ public void assertStateChangeCallback() {
+ assertFalse(mTetherStates.isEmpty());
+ }
+
+ private void assertTetherConfigParcelEqual(@NonNull TetheringConfigurationParcel actual,
+ @NonNull TetheringConfigurationParcel expect) {
+ assertEquals(actual.subId, expect.subId);
+ assertArrayEquals(actual.tetherableUsbRegexs, expect.tetherableUsbRegexs);
+ assertArrayEquals(actual.tetherableWifiRegexs, expect.tetherableWifiRegexs);
+ assertArrayEquals(actual.tetherableBluetoothRegexs, expect.tetherableBluetoothRegexs);
+ assertEquals(actual.isDunRequired, expect.isDunRequired);
+ assertEquals(actual.chooseUpstreamAutomatically, expect.chooseUpstreamAutomatically);
+ assertArrayEquals(actual.preferredUpstreamIfaceTypes,
+ expect.preferredUpstreamIfaceTypes);
+ assertArrayEquals(actual.legacyDhcpRanges, expect.legacyDhcpRanges);
+ assertArrayEquals(actual.defaultIPv4DNS, expect.defaultIPv4DNS);
+ assertEquals(actual.enableLegacyDhcpServer, expect.enableLegacyDhcpServer);
+ assertArrayEquals(actual.provisioningApp, expect.provisioningApp);
+ assertEquals(actual.provisioningAppNoUi, expect.provisioningAppNoUi);
+ assertEquals(actual.provisioningCheckPeriod, expect.provisioningCheckPeriod);
+ }
}
@Test
- public void testRegisterTetheringEventCallback() throws Exception {
- TestTetheringEventCallback callback1 = new TestTetheringEventCallback();
- TestTetheringEventCallback callback2 = new TestTetheringEventCallback();
+ public void testRegisterTetherInternalCallback() throws Exception {
+ TestTetherInternalCallback callback = new TestTetherInternalCallback();
- // 1. Register one callback and run usb tethering.
- mTethering.registerTetheringEventCallback(callback1);
+ // 1. Register one callback before running any tethering.
+ mTethering.registerTetherInternalCallback(callback);
mLooper.dispatchAll();
- callback1.expectUpstreamChanged(new Network[] {null});
+ callback.expectUpstreamChanged(new Network[] {null});
+ callback.expectConfigurationChanged(
+ mTethering.getTetheringConfiguration().toStableParcelable());
+ TetherStatesParcel tetherState = callback.pollTetherStatesChanged();
+ assertEquals(tetherState, null);
+ // 2. Enable wifi tethering
NetworkState upstreamState = buildMobileDualStackUpstreamState();
- runUsbTethering(upstreamState);
- callback1.expectUpstreamChanged(upstreamState.network);
- // 2. Register second callback.
- mTethering.registerTetheringEventCallback(callback2);
- mLooper.dispatchAll();
- callback2.expectUpstreamChanged(upstreamState.network);
- // 3. Disable usb tethering.
- mTethering.stopTethering(TETHERING_USB);
- mLooper.dispatchAll();
- sendUsbBroadcast(false, false, false);
- mLooper.dispatchAll();
- callback1.expectUpstreamChanged(new Network[] {null});
- callback2.expectUpstreamChanged(new Network[] {null});
- // 4. Unregister first callback and run hotspot.
- mTethering.unregisterTetheringEventCallback(callback1);
- mLooper.dispatchAll();
when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any()))
.thenReturn(upstreamState);
when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
- mTethering.startTethering(TETHERING_WIFI, null, false);
- mLooper.dispatchAll();
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
+ mLooper.dispatchAll();
+ tetherState = callback.pollTetherStatesChanged();
+ assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME});
+
+ mTethering.startTethering(TETHERING_WIFI, null, false);
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
mLooper.dispatchAll();
- callback1.assertNoCallback();
- callback2.expectUpstreamChanged(upstreamState.network);
+ tetherState = callback.pollTetherStatesChanged();
+ assertArrayEquals(tetherState.tetheredList, new String[] {TEST_WLAN_IFNAME});
+ callback.expectUpstreamChanged(upstreamState.network);
+
+ // 3. Disable wifi tethering.
+ mTethering.stopTethering(TETHERING_WIFI);
+ sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ mLooper.dispatchAll();
+ tetherState = callback.pollTetherStatesChanged();
+ assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME});
+ mLooper.dispatchAll();
+ callback.expectUpstreamChanged(new Network[] {null});
}
@Test
@@ -1091,7 +1206,7 @@
verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
// This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
// and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
- assertEquals(2, mTetheringDependencies.isTetheringSupportedCalls);
+ assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastTetherError(TEST_P2P_IFNAME));
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 084a747..4e75e00 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -20,7 +20,6 @@
":vold_aidl",
":gsiservice_aidl",
":platform-compat-config",
- ":tethering-servicescore-srcs",
"java/com/android/server/EventLogTags.logtags",
"java/com/android/server/am/EventLogTags.logtags",
"java/com/android/server/policy/EventLogTags.logtags",
@@ -50,7 +49,6 @@
"android.hardware.biometrics.face-V1.0-java",
"android.hardware.biometrics.fingerprint-V2.1-java",
"android.hardware.oemlock-V1.0-java",
- "android.hardware.tetheroffload.control-V1.0-java",
"android.hardware.configstore-V1.0-java",
"android.hardware.contexthub-V1.0-java",
"android.hidl.manager-V1.2-java",
@@ -82,11 +80,3 @@
name: "gps_debug.conf",
src: "java/com/android/server/location/gps_debug.conf",
}
-
-// TODO: this should be removed after tethering migration done.
-filegroup {
- name: "servicescore-tethering-src",
- srcs: [
- "java/com/android/server/connectivity/MockableSystemProperties.java",
- ],
-}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index bb7406a..a3a6172 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -108,6 +108,7 @@
import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.SocketKeepalive;
+import android.net.TetheringManager;
import android.net.UidRange;
import android.net.Uri;
import android.net.VpnService;
@@ -187,9 +188,7 @@
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import com.android.server.connectivity.PermissionMonitor;
import com.android.server.connectivity.ProxyTracker;
-import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
-import com.android.server.connectivity.tethering.TetheringDependencies;
import com.android.server.net.BaseNetdEventCallback;
import com.android.server.net.BaseNetworkObserver;
import com.android.server.net.LockdownVpnTracker;
@@ -233,7 +232,6 @@
private static final String DIAG_ARG = "--diag";
public static final String SHORT_ARG = "--short";
- private static final String TETHERING_ARG = "tethering";
private static final String NETWORK_ARG = "networks";
private static final String REQUEST_ARG = "requests";
@@ -280,7 +278,7 @@
private MockableSystemProperties mSystemProperties;
- private Tethering mTethering;
+ private TetheringManager mTetheringManager;
@VisibleForTesting
protected final PermissionMonitor mPermissionMonitor;
@@ -869,15 +867,10 @@
}
/**
- * @see Tethering
+ * Get a reference to the TetheringManager.
*/
- public Tethering makeTethering(@NonNull Context context,
- @NonNull INetworkManagementService nms,
- @NonNull INetworkStatsService statsService,
- @NonNull INetworkPolicyManager policyManager,
- @NonNull TetheringDependencies tetheringDeps) {
- return new Tethering(context, nms, statsService, policyManager,
- IoThread.get().getLooper(), getSystemProperties(), tetheringDeps);
+ public TetheringManager getTetheringManager() {
+ return TetheringManager.getInstance();
}
/**
@@ -932,6 +925,10 @@
return IIpConnectivityMetrics.Stub.asInterface(
ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
}
+
+ public IBatteryStats getBatteryStatsService() {
+ return BatteryStatsService.getService();
+ }
}
public ConnectivityService(Context context, INetworkManagementService netManager,
@@ -1075,8 +1072,7 @@
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- mTethering = deps.makeTethering(mContext, mNMS, mStatsService, mPolicyManager,
- makeTetheringDependencies());
+ mTetheringManager = mDeps.getTetheringManager();
mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
@@ -1111,7 +1107,6 @@
mHandler);
try {
- mNMS.registerObserver(mTethering);
mNMS.registerObserver(mDataActivityObserver);
} catch (RemoteException e) {
loge("Error registering observer :" + e);
@@ -1145,19 +1140,6 @@
registerPrivateDnsSettingsCallbacks();
}
- private TetheringDependencies makeTetheringDependencies() {
- return new TetheringDependencies() {
- @Override
- public boolean isTetheringSupported() {
- return ConnectivityService.this.isTetheringSupported();
- }
- @Override
- public NetworkRequest getDefaultNetworkRequest() {
- return mDefaultRequest;
- }
- };
- }
-
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
@@ -1909,7 +1891,9 @@
// TODO: relocate this specific callback in Tethering.
if (restrictBackground) {
log("onRestrictBackgroundChanged(true): disabling tethering");
- mTethering.untetherAll();
+ mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
+ mTetheringManager.stopTethering(ConnectivityManager.TETHERING_USB);
+ mTetheringManager.stopTethering(ConnectivityManager.TETHERING_BLUETOOTH);
}
}
};
@@ -2164,7 +2148,7 @@
opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M);
options = opts.toBundle();
}
- final IBatteryStats bs = BatteryStatsService.getService();
+ final IBatteryStats bs = mDeps.getBatteryStatsService();
try {
bs.noteConnectivityChanged(intent.getIntExtra(
ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_NONE),
@@ -2193,7 +2177,6 @@
mPermissionMonitor.startMonitoring();
mProxyTracker.loadGlobalProxy();
registerNetdEventCallback();
- mTethering.systemReady();
synchronized (this) {
mSystemReady = true;
@@ -2405,9 +2388,6 @@
if (ArrayUtils.contains(args, DIAG_ARG)) {
dumpNetworkDiagnostics(pw);
return;
- } else if (ArrayUtils.contains(args, TETHERING_ARG)) {
- mTethering.dump(fd, pw, args);
- return;
} else if (ArrayUtils.contains(args, NETWORK_ARG)) {
dumpNetworks(pw);
return;
@@ -2469,10 +2449,13 @@
mLegacyTypeTracker.dump(pw);
pw.println();
- mTethering.dump(fd, pw, args);
+ mKeepaliveTracker.dump(pw);
pw.println();
- mKeepaliveTracker.dump(pw);
+ pw.println("TetheringManager logs:");
+ pw.increaseIndent();
+ TetheringManager.getInstance().dump(pw);
+ pw.decreaseIndent();
pw.println();
dumpAvoidBadWifiSettings(pw);
@@ -4004,7 +3987,7 @@
public int tether(String iface, String callerPkg) {
ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
if (isTetheringSupported()) {
- return mTethering.tether(iface);
+ return mTetheringManager.tether(iface);
} else {
return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
}
@@ -4016,7 +3999,7 @@
ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
if (isTetheringSupported()) {
- return mTethering.untether(iface);
+ return mTetheringManager.untether(iface);
} else {
return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
}
@@ -4028,7 +4011,7 @@
enforceTetherAccessPermission();
if (isTetheringSupported()) {
- return mTethering.getLastTetherError(iface);
+ return mTetheringManager.getLastTetherError(iface);
} else {
return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
}
@@ -4039,7 +4022,7 @@
public String[] getTetherableUsbRegexs() {
enforceTetherAccessPermission();
if (isTetheringSupported()) {
- return mTethering.getTetherableUsbRegexs();
+ return mTetheringManager.getTetherableUsbRegexs();
} else {
return new String[0];
}
@@ -4049,7 +4032,7 @@
public String[] getTetherableWifiRegexs() {
enforceTetherAccessPermission();
if (isTetheringSupported()) {
- return mTethering.getTetherableWifiRegexs();
+ return mTetheringManager.getTetherableWifiRegexs();
} else {
return new String[0];
}
@@ -4059,7 +4042,7 @@
public String[] getTetherableBluetoothRegexs() {
enforceTetherAccessPermission();
if (isTetheringSupported()) {
- return mTethering.getTetherableBluetoothRegexs();
+ return mTetheringManager.getTetherableBluetoothRegexs();
} else {
return new String[0];
}
@@ -4069,7 +4052,7 @@
public int setUsbTethering(boolean enable, String callerPkg) {
ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
if (isTetheringSupported()) {
- return mTethering.setUsbTethering(enable);
+ return mTetheringManager.setUsbTethering(enable);
} else {
return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
}
@@ -4080,25 +4063,25 @@
@Override
public String[] getTetherableIfaces() {
enforceTetherAccessPermission();
- return mTethering.getTetherableIfaces();
+ return mTetheringManager.getTetherableIfaces();
}
@Override
public String[] getTetheredIfaces() {
enforceTetherAccessPermission();
- return mTethering.getTetheredIfaces();
+ return mTetheringManager.getTetheredIfaces();
}
@Override
public String[] getTetheringErroredIfaces() {
enforceTetherAccessPermission();
- return mTethering.getErroredIfaces();
+ return mTetheringManager.getTetheringErroredIfaces();
}
@Override
public String[] getTetheredDhcpRanges() {
enforceConnectivityInternalPermission();
- return mTethering.getTetheredDhcpRanges();
+ return mTetheringManager.getTetheredDhcpRanges();
}
@Override
@@ -4126,7 +4109,8 @@
Binder.restoreCallingIdentity(token);
}
- return tetherEnabledInSettings && adminUser && mTethering.hasTetherableConfiguration();
+ return tetherEnabledInSettings && adminUser
+ && mTetheringManager.hasTetherableConfiguration();
}
@Override
@@ -4137,13 +4121,13 @@
receiver.send(ConnectivityManager.TETHER_ERROR_UNSUPPORTED, null);
return;
}
- mTethering.startTethering(type, receiver, showProvisioningUi);
+ mTetheringManager.startTethering(type, receiver, showProvisioningUi);
}
@Override
public void stopTethering(int type, String callerPkg) {
ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
- mTethering.stopTethering(type);
+ mTetheringManager.stopTethering(type);
}
/**
@@ -4157,7 +4141,8 @@
public void getLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
boolean showEntitlementUi, String callerPkg) {
ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
- mTethering.getLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
+ mTetheringManager.requestLatestTetheringEntitlementResult(
+ type, receiver, showEntitlementUi);
}
/** Register tethering event callback. */
@@ -4165,7 +4150,7 @@
public void registerTetheringEventCallback(ITetheringEventCallback callback,
String callerPkg) {
ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
- mTethering.registerTetheringEventCallback(callback);
+ mTetheringManager.registerTetheringEventCallback(callback);
}
/** Unregister tethering event callback. */
@@ -4173,7 +4158,7 @@
public void unregisterTetheringEventCallback(ITetheringEventCallback callback,
String callerPkg) {
ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
- mTethering.unregisterTetheringEventCallback(callback);
+ mTetheringManager.unregisterTetheringEventCallback(callback);
}
// Called when we lose the default network and have no replacement yet.
@@ -5647,7 +5632,8 @@
// are accurate.
networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
- updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities);
+ updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities,
+ networkAgent.networkInfo.getType());
// update filtering rules, need to happen after the interface update so netd knows about the
// new interface (the interface name -> index map becomes initialized)
@@ -5726,21 +5712,26 @@
}
- private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId,
- NetworkCapabilities caps) {
- CompareResult<String> interfaceDiff = new CompareResult<>(
+ private void updateInterfaces(final @Nullable LinkProperties newLp,
+ final @Nullable LinkProperties oldLp, final int netId,
+ final @Nullable NetworkCapabilities caps, final int legacyType) {
+ final CompareResult<String> interfaceDiff = new CompareResult<>(
oldLp != null ? oldLp.getAllInterfaceNames() : null,
newLp != null ? newLp.getAllInterfaceNames() : null);
- for (String iface : interfaceDiff.added) {
- try {
- if (DBG) log("Adding iface " + iface + " to network " + netId);
- mNMS.addInterfaceToNetwork(iface, netId);
- wakeupModifyInterface(iface, caps, true);
- } catch (Exception e) {
- loge("Exception adding interface: " + e);
+ if (!interfaceDiff.added.isEmpty()) {
+ final IBatteryStats bs = mDeps.getBatteryStatsService();
+ for (final String iface : interfaceDiff.added) {
+ try {
+ if (DBG) log("Adding iface " + iface + " to network " + netId);
+ mNMS.addInterfaceToNetwork(iface, netId);
+ wakeupModifyInterface(iface, caps, true);
+ bs.noteNetworkInterfaceType(iface, legacyType);
+ } catch (Exception e) {
+ loge("Exception adding interface: " + e);
+ }
}
}
- for (String iface : interfaceDiff.removed) {
+ for (final String iface : interfaceDiff.removed) {
try {
if (DBG) log("Removing iface " + iface + " from network " + netId);
wakeupModifyInterface(iface, caps, false);
@@ -6476,77 +6467,6 @@
// do this after the default net is switched, but
// before LegacyTypeTracker sends legacy broadcasts
for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri);
-
- // Linger any networks that are no longer needed. This should be done after sending the
- // available callback for newNetwork.
- for (NetworkAgentInfo nai : removedRequests) {
- updateLingerState(nai, now);
- }
- // Possibly unlinger newNetwork. Unlingering a network does not send any callbacks so it
- // does not need to be done in any particular order.
- updateLingerState(newNetwork, now);
-
- if (isNewDefault) {
- // Maintain the illusion: since the legacy API only
- // understands one network at a time, we must pretend
- // that the current default network disconnected before
- // the new one connected.
- if (oldDefaultNetwork != null) {
- mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(),
- oldDefaultNetwork, true);
- }
- mDefaultInetConditionPublished = newNetwork.lastValidated ? 100 : 0;
- mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork);
- notifyLockdownVpn(newNetwork);
- }
-
- if (reassignedRequests.containsValue(newNetwork) || newNetwork.isVPN()) {
- // Notify battery stats service about this network, both the normal
- // interface and any stacked links.
- // TODO: Avoid redoing this; this must only be done once when a network comes online.
- try {
- final IBatteryStats bs = BatteryStatsService.getService();
- final int type = newNetwork.networkInfo.getType();
-
- final String baseIface = newNetwork.linkProperties.getInterfaceName();
- bs.noteNetworkInterfaceType(baseIface, type);
- for (LinkProperties stacked : newNetwork.linkProperties.getStackedLinks()) {
- final String stackedIface = stacked.getInterfaceName();
- bs.noteNetworkInterfaceType(stackedIface, type);
- }
- } catch (RemoteException ignored) {
- }
-
- // This has to happen after the notifyNetworkCallbacks as that tickles each
- // ConnectivityManager instance so that legacy requests correctly bind dns
- // requests to this network. The legacy users are listening for this broadcast
- // and will generally do a dns request so they can ensureRouteToHost and if
- // they do that before the callbacks happen they'll use the default network.
- //
- // TODO: Is there still a race here? We send the broadcast
- // after sending the callback, but if the app can receive the
- // broadcast before the callback, it might still break.
- //
- // This *does* introduce a race where if the user uses the new api
- // (notification callbacks) and then uses the old api (getNetworkInfo(type))
- // they may get old info. Reverse this after the old startUsing api is removed.
- // This is on top of the multiple intent sequencing referenced in the todo above.
- for (int i = 0; i < newNetwork.numNetworkRequests(); i++) {
- NetworkRequest nr = newNetwork.requestAt(i);
- if (nr.legacyType != TYPE_NONE && nr.isRequest()) {
- // legacy type tracker filters out repeat adds
- mLegacyTypeTracker.add(nr.legacyType, newNetwork);
- }
- }
-
- // A VPN generally won't get added to the legacy tracker in the "for (nri)" loop above,
- // because usually there are no NetworkRequests it satisfies (e.g., mDefaultRequest
- // wants the NOT_VPN capability, so it will never be satisfied by a VPN). So, add the
- // newNetwork to the tracker explicitly (it's a no-op if it has already been added).
- if (newNetwork.isVPN()) {
- mLegacyTypeTracker.add(TYPE_VPN, newNetwork);
- }
- }
}
/**
@@ -6560,15 +6480,32 @@
// requests. Once the code has switched to a request-major iteration style, this can
// be optimized to only do the processing needed.
final long now = SystemClock.elapsedRealtime();
+ final NetworkAgentInfo oldDefaultNetwork = getDefaultNetwork();
+
final NetworkAgentInfo[] nais = mNetworkAgentInfos.values().toArray(
new NetworkAgentInfo[mNetworkAgentInfos.size()]);
// Rematch higher scoring networks first to prevent requests first matching a lower
// scoring network and then a higher scoring network, which could produce multiple
- // callbacks and inadvertently unlinger networks.
+ // callbacks.
Arrays.sort(nais);
- for (NetworkAgentInfo nai : nais) {
+ for (final NetworkAgentInfo nai : nais) {
rematchNetworkAndRequests(nai, now);
}
+
+ final NetworkAgentInfo newDefaultNetwork = getDefaultNetwork();
+
+ for (final NetworkAgentInfo nai : nais) {
+ // Rematching may have altered the linger state of some networks, so update all linger
+ // timers. updateLingerState reads the state from the network agent and does nothing
+ // if the state has not changed : the source of truth is controlled with
+ // NetworkAgentInfo#lingerRequest and NetworkAgentInfo#unlingerRequest, which have been
+ // called while rematching the individual networks above.
+ updateLingerState(nai, now);
+ }
+
+ updateLegacyTypeTrackerAndVpnLockdownForRematch(oldDefaultNetwork, newDefaultNetwork, nais);
+
+ // Tear down all unneeded networks.
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
if (unneeded(nai, UnneededFor.TEARDOWN)) {
if (nai.getLingerExpiry() > 0) {
@@ -6588,6 +6525,70 @@
}
}
+ private void updateLegacyTypeTrackerAndVpnLockdownForRematch(
+ @Nullable final NetworkAgentInfo oldDefaultNetwork,
+ @Nullable final NetworkAgentInfo newDefaultNetwork,
+ @NonNull final NetworkAgentInfo[] nais) {
+ if (oldDefaultNetwork != newDefaultNetwork) {
+ // Maintain the illusion : since the legacy API only understands one network at a time,
+ // if the default network changed, apps should see a disconnected broadcast for the
+ // old default network before they see a connected broadcast for the new one.
+ if (oldDefaultNetwork != null) {
+ mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(),
+ oldDefaultNetwork, true);
+ }
+ if (newDefaultNetwork != null) {
+ // The new default network can be newly null if and only if the old default
+ // network doesn't satisfy the default request any more because it lost a
+ // capability.
+ mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0;
+ mLegacyTypeTracker.add(newDefaultNetwork.networkInfo.getType(), newDefaultNetwork);
+ // If the legacy VPN is connected, notifyLockdownVpn may end up sending a broadcast
+ // to reflect the NetworkInfo of this new network. This broadcast has to be sent
+ // after the disconnect broadcasts above, but before the broadcasts sent by the
+ // legacy type tracker below.
+ // TODO : refactor this, it's too complex
+ notifyLockdownVpn(newDefaultNetwork);
+ }
+ }
+
+ // Now that all the callbacks have been sent, send the legacy network broadcasts
+ // as needed. This is necessary so that legacy requests correctly bind dns
+ // requests to this network. The legacy users are listening for this broadcast
+ // and will generally do a dns request so they can ensureRouteToHost and if
+ // they do that before the callbacks happen they'll use the default network.
+ //
+ // TODO: Is there still a race here? The legacy broadcast will be sent after sending
+ // callbacks, but if apps can receive the broadcast before the callback, they still might
+ // have an inconsistent view of networking.
+ //
+ // This *does* introduce a race where if the user uses the new api
+ // (notification callbacks) and then uses the old api (getNetworkInfo(type))
+ // they may get old info. Reverse this after the old startUsing api is removed.
+ // This is on top of the multiple intent sequencing referenced in the todo above.
+ for (NetworkAgentInfo nai : nais) {
+ addNetworkToLegacyTypeTracker(nai);
+ }
+ }
+
+ private void addNetworkToLegacyTypeTracker(@NonNull final NetworkAgentInfo nai) {
+ for (int i = 0; i < nai.numNetworkRequests(); i++) {
+ NetworkRequest nr = nai.requestAt(i);
+ if (nr.legacyType != TYPE_NONE && nr.isRequest()) {
+ // legacy type tracker filters out repeat adds
+ mLegacyTypeTracker.add(nr.legacyType, nai);
+ }
+ }
+
+ // A VPN generally won't get added to the legacy tracker in the "for (nri)" loop above,
+ // because usually there are no NetworkRequests it satisfies (e.g., mDefaultRequest
+ // wants the NOT_VPN capability, so it will never be satisfied by a VPN). So, add the
+ // newNetwork to the tracker explicitly (it's a no-op if it has already been added).
+ if (nai.isVPN()) {
+ mLegacyTypeTracker.add(TYPE_VPN, nai);
+ }
+ }
+
private void updateInetCondition(NetworkAgentInfo nai) {
// Don't bother updating until we've graduated to validated at least once.
if (!nai.everValidated) return;
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 24a5b7f..bb7f862 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -580,7 +580,7 @@
// semantics of WakeupMessage guarantee that if cancel is called then the alarm will
// never call its callback (handleLingerComplete), even if it has already fired.
// WakeupMessage makes no such guarantees about rescheduling a message, so if mLingerMessage
- // has already been dispatched, rescheduling to some time in the future it won't stop it
+ // has already been dispatched, rescheduling to some time in the future won't stop it
// from calling its callback immediately.
if (mLingerMessage != null) {
mLingerMessage.cancel();
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
deleted file mode 100644
index 4ad7ac4..0000000
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2017 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.server.connectivity.tethering;
-
-import android.content.Context;
-import android.net.NetworkRequest;
-import android.net.ip.IpServer;
-import android.net.util.SharedLog;
-import android.os.Handler;
-
-import com.android.internal.util.StateMachine;
-import com.android.server.connectivity.MockableSystemProperties;
-
-import java.util.ArrayList;
-
-
-/**
- * Capture tethering dependencies, for injection.
- *
- * @hide
- */
-public class TetheringDependencies {
- /**
- * Get a reference to the offload hardware interface to be used by tethering.
- */
- public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) {
- return new OffloadHardwareInterface(h, log);
- }
-
- /**
- * Get a reference to the UpstreamNetworkMonitor to be used by tethering.
- */
- public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target,
- SharedLog log, int what) {
- return new UpstreamNetworkMonitor(ctx, target, log, what);
- }
-
- /**
- * Get a reference to the IPv6TetheringCoordinator to be used by tethering.
- */
- public IPv6TetheringCoordinator getIPv6TetheringCoordinator(
- ArrayList<IpServer> notifyList, SharedLog log) {
- return new IPv6TetheringCoordinator(notifyList, log);
- }
-
- /**
- * Get dependencies to be used by IpServer.
- */
- public IpServer.Dependencies getIpServerDependencies() {
- return new IpServer.Dependencies();
- }
-
- /**
- * Indicates whether tethering is supported on the device.
- */
- public boolean isTetheringSupported() {
- return true;
- }
-
- /**
- * Get the NetworkRequest that should be fulfilled by the default network.
- */
- public NetworkRequest getDefaultNetworkRequest() {
- return null;
- }
-
- /**
- * Get a reference to the EntitlementManager to be used by tethering.
- */
- public EntitlementManager getEntitlementManager(Context ctx, StateMachine target,
- SharedLog log, int what, MockableSystemProperties systemProperties) {
- return new EntitlementManager(ctx, target, log, what, systemProperties);
- }
-
- /**
- * Generate a new TetheringConfiguration according to input sub Id.
- */
- public TetheringConfiguration generateTetheringConfiguration(Context ctx, SharedLog log,
- int subId) {
- return new TetheringConfiguration(ctx, log, subId);
- }
-}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 08c9426..e473c96 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -138,7 +138,6 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
-import com.android.server.connectivity.Tethering;
import java.io.File;
import java.io.FileDescriptor;
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index fd44cbb..12e8069 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -32,6 +32,7 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
+import android.os.Environment;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.sysprop.ApexProperties;
@@ -47,6 +48,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -64,9 +66,9 @@
static final int MATCH_FACTORY_PACKAGE = 1 << 1;
/**
- * Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerNoOp} depending
- * on whenever this device supports APEX, i.e. {@link ApexProperties#updatable()} evaluates to
- * {@code true}.
+ * Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerFlattenedApex}
+ * depending on whether this device supports APEX, i.e. {@link ApexProperties#updatable()}
+ * evaluates to {@code true}.
*/
static ApexManager create(Context systemContext) {
if (ApexProperties.updatable().orElse(false)) {
@@ -77,10 +79,28 @@
throw new IllegalStateException("Required service apexservice not available");
}
} else {
- return new ApexManagerNoOp();
+ return new ApexManagerFlattenedApex();
}
}
+ /**
+ * Minimal information about APEX mount points and the original APEX package they refer to.
+ */
+ static class ActiveApexInfo {
+ public final File apexDirectory;
+ public final File preinstalledApexPath;
+
+ private ActiveApexInfo(File apexDirectory, File preinstalledApexPath) {
+ this.apexDirectory = apexDirectory;
+ this.preinstalledApexPath = preinstalledApexPath;
+ }
+ }
+
+ /**
+ * Returns {@link ActiveApexInfo} records relative to all active APEX packages.
+ */
+ abstract List<ActiveApexInfo> getActiveApexInfos();
+
abstract void systemReady();
/**
@@ -259,6 +279,22 @@
}
@Override
+ List<ActiveApexInfo> getActiveApexInfos() {
+ try {
+ return Arrays.stream(mApexService.getActivePackages())
+ .map(apexInfo -> new ActiveApexInfo(
+ new File(
+ Environment.getApexDirectory() + File.separator
+ + apexInfo.moduleName),
+ new File(apexInfo.modulePath))).collect(
+ Collectors.toList());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
void systemReady() {
mContext.registerReceiver(new BroadcastReceiver() {
@Override
@@ -549,7 +585,40 @@
* An implementation of {@link ApexManager} that should be used in case device does not support
* updating APEX packages.
*/
- private static final class ApexManagerNoOp extends ApexManager {
+ private static final class ApexManagerFlattenedApex extends ApexManager {
+
+ @Override
+ List<ActiveApexInfo> getActiveApexInfos() {
+ // There is no apexd running in case of flattened apex
+ // We look up the /apex directory and identify the active APEX modules from there.
+ // As "preinstalled" path, we just report /system since in the case of flattened APEX
+ // the /apex directory is just a symlink to /system/apex.
+ List<ActiveApexInfo> result = new ArrayList<>();
+ File apexDir = Environment.getApexDirectory();
+ // In flattened configuration, init special-case the art directory and bind-mounts
+ // com.android.art.{release|debug} to com.android.art. At the time of writing, these
+ // directories are copied from the kArtApexDirNames variable in
+ // system/core/init/mount_namespace.cpp.
+ String[] skipDirs = {"com.android.art.release", "com.android.art.debug"};
+ if (apexDir.isDirectory()) {
+ File[] files = apexDir.listFiles();
+ // listFiles might be null if system server doesn't have permission to read
+ // a directory.
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory() && !file.getName().contains("@")) {
+ for (String skipDir : skipDirs) {
+ if (file.getName().equals(skipDir)) {
+ continue;
+ }
+ }
+ result.add(new ActiveApexInfo(file, Environment.getRootDirectory()));
+ }
+ }
+ }
+ }
+ return result;
+ }
@Override
void systemReady() {
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
index 1d3d24c..259200b 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
@@ -67,6 +67,15 @@
codeRoot = Environment.getSystemExtDirectory();
} else if (FileUtils.contains(Environment.getOdmDirectory(), codePath)) {
codeRoot = Environment.getOdmDirectory();
+ } else if (FileUtils.contains(Environment.getApexDirectory(), codePath)) {
+ String fullPath = codePath.getAbsolutePath();
+ String[] parts = fullPath.split(File.separator);
+ if (parts.length > 2) {
+ codeRoot = new File(parts[1] + File.separator + parts[2]);
+ } else {
+ Slog.w(PackageManagerService.TAG, "Can't canonicalize code path " + codePath);
+ codeRoot = Environment.getApexDirectory();
+ }
} else {
// Unrecognized code path; take its top real segment as the apk root:
// e.g. /something/app/blah.apk => /something
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8d31b34..c5a04dd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -91,6 +91,7 @@
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
@@ -374,6 +375,7 @@
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
/**
* Keep track of all those APKs everywhere.
@@ -588,16 +590,6 @@
private static final String PACKAGE_SCHEME = "package";
- private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
-
- private static final String PRODUCT_OVERLAY_DIR = "/product/overlay";
-
- private static final String SYSTEM_EXT_OVERLAY_DIR = "/system_ext/overlay";
-
- private static final String ODM_OVERLAY_DIR = "/odm/overlay";
-
- private static final String OEM_OVERLAY_DIR = "/oem/overlay";
-
/** Canonical intent used to identify what counts as a "web browser" app */
private static final Intent sBrowserIntent;
static {
@@ -757,7 +749,31 @@
private final Injector mInjector;
/**
- * Unit tests will instantiate and / or extend to mock dependencies / behaviors.
+ * The list of all system partitions that may contain packages in ascending order of
+ * specificity (the more generic, the earlier in the list a partition appears).
+ */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static final List<SystemPartition> SYSTEM_PARTITIONS = Collections.unmodifiableList(
+ Arrays.asList(
+ new SystemPartition(Environment.getRootDirectory(), 0 /* scanFlag */,
+ false /* hasOverlays */),
+ new SystemPartition(Environment.getVendorDirectory(), SCAN_AS_VENDOR,
+ true /* hasOverlays */),
+ new SystemPartition(Environment.getOdmDirectory(), SCAN_AS_ODM,
+ true /* hasOverlays */),
+ new SystemPartition(Environment.getOemDirectory(), SCAN_AS_OEM,
+ true /* hasOverlays */),
+ new SystemPartition(Environment.getProductDirectory(), SCAN_AS_PRODUCT,
+ true /* hasOverlays */),
+ new SystemPartition(Environment.getSystemExtDirectory(), SCAN_AS_SYSTEM_EXT,
+ true /* hasOverlays */)));
+
+ private final List<SystemPartition> mDirsToScanAsSystem;
+
+ /**
+ * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
+ *
+ * NOTE: All getters should return the same instance for every call.
*/
@VisibleForTesting
static class Injector {
@@ -2410,9 +2426,70 @@
}
}
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static class SystemPartition {
+ public final File folder;
+ public final int scanFlag;
+ public final File appFolder;
+ @Nullable
+ public final File privAppFolder;
+ @Nullable
+ public final File overlayFolder;
+
+
+ private static boolean shouldScanPrivApps(@ScanFlags int scanFlags) {
+ if ((scanFlags & SCAN_AS_OEM) != 0) {
+ return false;
+ }
+ if (scanFlags == 0) { // /system partition
+ return true;
+ }
+ if ((scanFlags
+ & (SCAN_AS_VENDOR | SCAN_AS_ODM | SCAN_AS_PRODUCT | SCAN_AS_SYSTEM_EXT)) != 0) {
+ return true;
+ }
+ return false;
+ }
+
+ private SystemPartition(File folder, int scanFlag, boolean hasOverlays) {
+ this.folder = folder;
+ this.scanFlag = scanFlag;
+ this.appFolder = toCanonical(new File(folder, "app"));
+ this.privAppFolder = shouldScanPrivApps(scanFlag)
+ ? toCanonical(new File(folder, "priv-app"))
+ : null;
+ this.overlayFolder = hasOverlays ? toCanonical(new File(folder, "overlay")) : null;
+ }
+
+ public boolean containsPrivApp(File scanFile) {
+ return FileUtils.contains(privAppFolder, scanFile);
+ }
+
+ public boolean containsApp(File scanFile) {
+ return FileUtils.contains(appFolder, scanFile);
+ }
+
+ public boolean containsPath(String path) {
+ return path.startsWith(folder.getPath() + "/");
+ }
+
+ public boolean containsPrivPath(String path) {
+ return privAppFolder != null && path.startsWith(privAppFolder.getPath() + "/");
+ }
+
+ private static File toCanonical(File dir) {
+ try {
+ return dir.getCanonicalFile();
+ } catch (IOException e) {
+ // failed to look up canonical path, continue with original one
+ return dir;
+ }
+ }
+ }
+
public PackageManagerService(Context context, Installer installer,
- boolean factoryTest, boolean onlyCore) {
- LockGuard.installLock(mPackages, LockGuard.INDEX_PACKAGES);
+ boolean factoryTest, boolean onlyCore) {
+ LockGuard.installLock(mPackages, LockGuard.INDEX_PACKAGES);
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "create package manager");
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
SystemClock.uptimeMillis());
@@ -2505,7 +2582,18 @@
mProtectedPackages = new ProtectedPackages(mContext);
- mApexManager = ApexManager.create(context);
+ mApexManager = ApexManager.create(mContext);
+
+ mDirsToScanAsSystem = new ArrayList<>();
+ mDirsToScanAsSystem.addAll(SYSTEM_PARTITIONS);
+ mDirsToScanAsSystem.addAll(mApexManager.getActiveApexInfos().stream()
+ .map(ai -> resolveApexToSystemPartition(ai))
+ .filter(Objects::nonNull).collect(Collectors.toList()));
+ Slog.d(TAG,
+ "Directories scanned as system partitions: [" + mDirsToScanAsSystem.stream().map(
+ d -> (d.folder.getAbsolutePath() + ":" + d.scanFlag))
+ .collect(Collectors.joining(",")) + "]");
+
// CHECKSTYLE:OFF IndentationCheck
synchronized (mInstallLock) {
// writer
@@ -2640,215 +2728,35 @@
// any apps.)
// For security and version matching reason, only consider overlay packages if they
// reside in the right directory.
- scanDirTracedLI(new File(VENDOR_OVERLAY_DIR),
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_VENDOR,
- 0);
- scanDirTracedLI(new File(PRODUCT_OVERLAY_DIR),
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_PRODUCT,
- 0);
- scanDirTracedLI(new File(SYSTEM_EXT_OVERLAY_DIR),
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_SYSTEM_EXT,
- 0);
- scanDirTracedLI(new File(ODM_OVERLAY_DIR),
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_ODM,
- 0);
- scanDirTracedLI(new File(OEM_OVERLAY_DIR),
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_OEM,
- 0);
+ final int systemParseFlags = mDefParseFlags | PackageParser.PARSE_IS_SYSTEM_DIR;
+ final int systemScanFlags = scanFlags | SCAN_AS_SYSTEM;
+ for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
+ final SystemPartition partition = mDirsToScanAsSystem.get(i);
+ if (partition.overlayFolder == null) {
+ continue;
+ }
+ scanDirTracedLI(partition.overlayFolder, systemParseFlags,
+ systemScanFlags | partition.scanFlag, 0);
+ }
mParallelPackageParserCallback.findStaticOverlayPackages();
- // Find base frameworks (resource packages without code).
- scanDirTracedLI(frameworkDir,
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_NO_DEX
- | SCAN_AS_SYSTEM
- | SCAN_AS_PRIVILEGED,
- 0);
+ scanDirTracedLI(frameworkDir, systemParseFlags,
+ systemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0);
if (!mPackages.containsKey("android")) {
throw new IllegalStateException(
"Failed to load frameworks package; check log for warnings");
}
-
- // Collect privileged system packages.
- final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
- scanDirTracedLI(privilegedAppDir,
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_PRIVILEGED,
- 0);
-
- // Collect ordinary system packages.
- final File systemAppDir = new File(Environment.getRootDirectory(), "app");
- scanDirTracedLI(systemAppDir,
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM,
- 0);
-
- // Collect privileged vendor packages.
- File privilegedVendorAppDir = new File(Environment.getVendorDirectory(), "priv-app");
- try {
- privilegedVendorAppDir = privilegedVendorAppDir.getCanonicalFile();
- } catch (IOException e) {
- // failed to look up canonical path, continue with original one
+ for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
+ final SystemPartition partition = mDirsToScanAsSystem.get(i);
+ if (partition.privAppFolder != null) {
+ scanDirTracedLI(partition.privAppFolder, systemParseFlags,
+ systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0);
+ }
+ scanDirTracedLI(partition.appFolder, systemParseFlags,
+ systemScanFlags | partition.scanFlag, 0);
}
- scanDirTracedLI(privilegedVendorAppDir,
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_VENDOR
- | SCAN_AS_PRIVILEGED,
- 0);
- // Collect ordinary vendor packages.
- File vendorAppDir = new File(Environment.getVendorDirectory(), "app");
- try {
- vendorAppDir = vendorAppDir.getCanonicalFile();
- } catch (IOException e) {
- // failed to look up canonical path, continue with original one
- }
- scanDirTracedLI(vendorAppDir,
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_VENDOR,
- 0);
-
- // Collect privileged odm packages. /odm is another vendor partition
- // other than /vendor.
- File privilegedOdmAppDir = new File(Environment.getOdmDirectory(),
- "priv-app");
- try {
- privilegedOdmAppDir = privilegedOdmAppDir.getCanonicalFile();
- } catch (IOException e) {
- // failed to look up canonical path, continue with original one
- }
- scanDirTracedLI(privilegedOdmAppDir,
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_VENDOR
- | SCAN_AS_PRIVILEGED,
- 0);
-
- // Collect ordinary odm packages. /odm is another vendor partition
- // other than /vendor.
- File odmAppDir = new File(Environment.getOdmDirectory(), "app");
- try {
- odmAppDir = odmAppDir.getCanonicalFile();
- } catch (IOException e) {
- // failed to look up canonical path, continue with original one
- }
- scanDirTracedLI(odmAppDir,
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_VENDOR,
- 0);
-
- // Collect all OEM packages.
- final File oemAppDir = new File(Environment.getOemDirectory(), "app");
- scanDirTracedLI(oemAppDir,
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_OEM,
- 0);
-
- // Collected privileged /product packages.
- File privilegedProductAppDir = new File(Environment.getProductDirectory(), "priv-app");
- try {
- privilegedProductAppDir = privilegedProductAppDir.getCanonicalFile();
- } catch (IOException e) {
- // failed to look up canonical path, continue with original one
- }
- scanDirTracedLI(privilegedProductAppDir,
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_PRODUCT
- | SCAN_AS_PRIVILEGED,
- 0);
-
- // Collect ordinary /product packages.
- File productAppDir = new File(Environment.getProductDirectory(), "app");
- try {
- productAppDir = productAppDir.getCanonicalFile();
- } catch (IOException e) {
- // failed to look up canonical path, continue with original one
- }
- scanDirTracedLI(productAppDir,
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_PRODUCT,
- 0);
-
- // Collected privileged /system_ext packages.
- File privilegedSystemExtAppDir =
- new File(Environment.getSystemExtDirectory(), "priv-app");
- try {
- privilegedSystemExtAppDir =
- privilegedSystemExtAppDir.getCanonicalFile();
- } catch (IOException e) {
- // failed to look up canonical path, continue with original one
- }
- scanDirTracedLI(privilegedSystemExtAppDir,
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_SYSTEM_EXT
- | SCAN_AS_PRIVILEGED,
- 0);
-
- // Collect ordinary /system_ext packages.
- File systemExtAppDir = new File(Environment.getSystemExtDirectory(), "app");
- try {
- systemExtAppDir = systemExtAppDir.getCanonicalFile();
- } catch (IOException e) {
- // failed to look up canonical path, continue with original one
- }
- scanDirTracedLI(systemExtAppDir,
- mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM_DIR,
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_SYSTEM_EXT,
- 0);
// Prune any system packages that no longer exist.
final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
@@ -3016,89 +2924,26 @@
logCriticalInfo(Log.WARN, "Expected better " + packageName
+ " but never showed up; reverting to system");
- final @ParseFlags int reparseFlags;
- final @ScanFlags int rescanFlags;
- if (FileUtils.contains(privilegedAppDir, scanFile)) {
- reparseFlags =
- mDefParseFlags |
- PackageParser.PARSE_IS_SYSTEM_DIR;
- rescanFlags =
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_PRIVILEGED;
- } else if (FileUtils.contains(systemAppDir, scanFile)) {
- reparseFlags =
- mDefParseFlags |
- PackageParser.PARSE_IS_SYSTEM_DIR;
- rescanFlags =
- scanFlags
- | SCAN_AS_SYSTEM;
- } else if (FileUtils.contains(privilegedVendorAppDir, scanFile)
- || FileUtils.contains(privilegedOdmAppDir, scanFile)) {
- reparseFlags =
- mDefParseFlags |
- PackageParser.PARSE_IS_SYSTEM_DIR;
- rescanFlags =
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_VENDOR
- | SCAN_AS_PRIVILEGED;
- } else if (FileUtils.contains(vendorAppDir, scanFile)
- || FileUtils.contains(odmAppDir, scanFile)) {
- reparseFlags =
- mDefParseFlags |
- PackageParser.PARSE_IS_SYSTEM_DIR;
- rescanFlags =
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_VENDOR;
- } else if (FileUtils.contains(oemAppDir, scanFile)) {
- reparseFlags =
- mDefParseFlags |
- PackageParser.PARSE_IS_SYSTEM_DIR;
- rescanFlags =
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_OEM;
- } else if (FileUtils.contains(privilegedProductAppDir, scanFile)) {
- reparseFlags =
- mDefParseFlags |
- PackageParser.PARSE_IS_SYSTEM_DIR;
- rescanFlags =
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_PRODUCT
- | SCAN_AS_PRIVILEGED;
- } else if (FileUtils.contains(productAppDir, scanFile)) {
- reparseFlags =
- mDefParseFlags |
- PackageParser.PARSE_IS_SYSTEM_DIR;
- rescanFlags =
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_PRODUCT;
- } else if (FileUtils.contains(privilegedSystemExtAppDir, scanFile)) {
- reparseFlags =
- mDefParseFlags |
- PackageParser.PARSE_IS_SYSTEM_DIR;
- rescanFlags =
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_SYSTEM_EXT
- | SCAN_AS_PRIVILEGED;
- } else if (FileUtils.contains(systemExtAppDir, scanFile)) {
- reparseFlags =
- mDefParseFlags |
- PackageParser.PARSE_IS_SYSTEM_DIR;
- rescanFlags =
- scanFlags
- | SCAN_AS_SYSTEM
- | SCAN_AS_SYSTEM_EXT;
- } else {
+ @ParseFlags int reparseFlags = 0;
+ @ScanFlags int rescanFlags = 0;
+ for (int i1 = 0, size = mDirsToScanAsSystem.size(); i1 < size; i1++) {
+ SystemPartition partition = mDirsToScanAsSystem.get(i1);
+ if (partition.containsPrivApp(scanFile)) {
+ reparseFlags = systemParseFlags;
+ rescanFlags = systemScanFlags | SCAN_AS_PRIVILEGED
+ | partition.scanFlag;
+ break;
+ }
+ if (partition.containsApp(scanFile)) {
+ reparseFlags = systemParseFlags;
+ rescanFlags = systemScanFlags | partition.scanFlag;
+ break;
+ }
+ }
+ if (rescanFlags == 0) {
Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile);
continue;
}
-
mSettings.enableSystemPackageLPw(packageName);
try {
@@ -18559,80 +18404,28 @@
}
}
- static boolean locationIsPrivileged(String path) {
- try {
- final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
- final File privilegedVendorAppDir = new File(Environment.getVendorDirectory(), "priv-app");
- final File privilegedOdmAppDir = new File(Environment.getOdmDirectory(), "priv-app");
- final File privilegedProductAppDir = new File(Environment.getProductDirectory(), "priv-app");
- final File privilegedSystemExtAppDir =
- new File(Environment.getSystemExtDirectory(), "priv-app");
- return path.startsWith(privilegedAppDir.getCanonicalPath() + "/")
- || path.startsWith(privilegedVendorAppDir.getCanonicalPath() + "/")
- || path.startsWith(privilegedOdmAppDir.getCanonicalPath() + "/")
- || path.startsWith(privilegedProductAppDir.getCanonicalPath() + "/")
- || path.startsWith(privilegedSystemExtAppDir.getCanonicalPath() + "/");
- } catch (IOException e) {
- Slog.e(TAG, "Unable to access code path " + path);
+ private static @Nullable SystemPartition resolveApexToSystemPartition(
+ ApexManager.ActiveApexInfo apexInfo) {
+ for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
+ SystemPartition sp = SYSTEM_PARTITIONS.get(i);
+ if (apexInfo.preinstalledApexPath.getAbsolutePath().startsWith(
+ sp.folder.getAbsolutePath())) {
+ return new SystemPartition(apexInfo.apexDirectory, sp.scanFlag,
+ false /* hasOverlays */);
+ }
}
- return false;
- }
-
- static boolean locationIsOem(String path) {
- try {
- return path.startsWith(Environment.getOemDirectory().getCanonicalPath() + "/");
- } catch (IOException e) {
- Slog.e(TAG, "Unable to access code path " + path);
- }
- return false;
- }
-
- static boolean locationIsVendor(String path) {
- try {
- return path.startsWith(Environment.getVendorDirectory().getCanonicalPath() + "/")
- || path.startsWith(Environment.getOdmDirectory().getCanonicalPath() + "/");
- } catch (IOException e) {
- Slog.e(TAG, "Unable to access code path " + path);
- }
- return false;
- }
-
- static boolean locationIsProduct(String path) {
- try {
- return path.startsWith(Environment.getProductDirectory().getCanonicalPath() + "/");
- } catch (IOException e) {
- Slog.e(TAG, "Unable to access code path " + path);
- }
- return false;
- }
-
- static boolean locationIsSystemExt(String path) {
- try {
- return path.startsWith(
- Environment.getSystemExtDirectory().getCanonicalPath() + "/");
- } catch (IOException e) {
- Slog.e(TAG, "Unable to access code path " + path);
- }
- return false;
- }
-
- static boolean locationIsOdm(String path) {
- try {
- return path.startsWith(Environment.getOdmDirectory().getCanonicalPath() + "/");
- } catch (IOException e) {
- Slog.e(TAG, "Unable to access code path " + path);
- }
- return false;
+ return null;
}
/*
* Tries to delete system package.
*/
private void deleteSystemPackageLIF(DeletePackageAction action, PackageSetting deletedPs,
- int[] allUserHandles, int flags, PackageRemovedInfo outInfo, boolean writeSettings)
+ int[] allUserHandles, int flags, @Nullable PackageRemovedInfo outInfo,
+ boolean writeSettings)
throws SystemDeleteException {
- final boolean applyUserRestrictions
- = (allUserHandles != null) && (outInfo.origUsers != null);
+ final boolean applyUserRestrictions =
+ (allUserHandles != null) && outInfo != null && (outInfo.origUsers != null);
final PackageParser.Package deletedPkg = deletedPs.pkg;
// Confirm if the system package has been updated
// An updated system app can be deleted. This will also have to restore
@@ -18653,19 +18446,21 @@
}
}
- // Delete the updated package
- outInfo.isRemovedPackageSystemUpdate = true;
- if (outInfo.removedChildPackages != null) {
- final int childCount = (deletedPs.childPackageNames != null)
- ? deletedPs.childPackageNames.size() : 0;
- for (int i = 0; i < childCount; i++) {
- String childPackageName = deletedPs.childPackageNames.get(i);
- if (disabledPs.childPackageNames != null && disabledPs.childPackageNames
- .contains(childPackageName)) {
- PackageRemovedInfo childInfo = outInfo.removedChildPackages.get(
- childPackageName);
- if (childInfo != null) {
- childInfo.isRemovedPackageSystemUpdate = true;
+ if (outInfo != null) {
+ // Delete the updated package
+ outInfo.isRemovedPackageSystemUpdate = true;
+ if (outInfo.removedChildPackages != null) {
+ final int childCount = (deletedPs.childPackageNames != null)
+ ? deletedPs.childPackageNames.size() : 0;
+ for (int i = 0; i < childCount; i++) {
+ String childPackageName = deletedPs.childPackageNames.get(i);
+ if (disabledPs.childPackageNames != null && disabledPs.childPackageNames
+ .contains(childPackageName)) {
+ PackageRemovedInfo childInfo = outInfo.removedChildPackages.get(
+ childPackageName);
+ if (childInfo != null) {
+ childInfo.isRemovedPackageSystemUpdate = true;
+ }
}
}
}
@@ -18698,7 +18493,8 @@
if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
try {
installPackageFromSystemLIF(disabledPs.codePathString, allUserHandles,
- outInfo.origUsers, deletedPs.getPermissionsState(), writeSettings);
+ outInfo == null ? null : outInfo.origUsers, deletedPs.getPermissionsState(),
+ writeSettings);
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to restore system package:" + deletedPkg.packageName + ": "
+ e.getMessage());
@@ -18730,23 +18526,15 @@
| PackageParser.PARSE_MUST_BE_APK
| PackageParser.PARSE_IS_SYSTEM_DIR;
@ScanFlags int scanFlags = SCAN_AS_SYSTEM;
- if (locationIsPrivileged(codePathString)) {
- scanFlags |= SCAN_AS_PRIVILEGED;
- }
- if (locationIsOem(codePathString)) {
- scanFlags |= SCAN_AS_OEM;
- }
- if (locationIsVendor(codePathString)) {
- scanFlags |= SCAN_AS_VENDOR;
- }
- if (locationIsProduct(codePathString)) {
- scanFlags |= SCAN_AS_PRODUCT;
- }
- if (locationIsSystemExt(codePathString)) {
- scanFlags |= SCAN_AS_SYSTEM_EXT;
- }
- if (locationIsOdm(codePathString)) {
- scanFlags |= SCAN_AS_ODM;
+ for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
+ SystemPartition partition = mDirsToScanAsSystem.get(i);
+ if (partition.containsPath(codePathString)) {
+ scanFlags |= partition.scanFlag;
+ if (partition.containsPrivPath(codePathString)) {
+ scanFlags |= SCAN_AS_PRIVILEGED;
+ }
+ break;
+ }
}
final File codePath = new File(codePathString);
@@ -22560,9 +22348,9 @@
mSettings.writeKernelMappingLPr(ps);
}
- final UserManager um = mContext.getSystemService(UserManager.class);
+ final UserManagerService um = sUserManager;
UserManagerInternal umInternal = getUserManagerInternal();
- for (UserInfo user : um.getUsers()) {
+ for (UserInfo user : um.getUsers(false /* excludeDying */)) {
final int flags;
if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 9091168..e991a91 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3174,7 +3174,7 @@
pw.println(" [--user USER_ID] INTENT");
pw.println(" Prints all broadcast receivers that can handle the given INTENT.");
pw.println("");
- pw.println(" install [-lrtsfdgw] [-i PACKAGE] [--user USER_ID|all|current]");
+ pw.println(" install [-lrtfdgw] [-i PACKAGE] [--user USER_ID|all|current]");
pw.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
pw.println(" [--install-reason 0/1/2/3/4] [--originating-uri URI]");
pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]");
@@ -3189,7 +3189,6 @@
pw.println(" -R: disallow replacement of existing application");
pw.println(" -t: allow test packages");
pw.println(" -i: specify package name of installer owning the app");
- pw.println(" -s: install application on sdcard");
pw.println(" -f: install application on internal flash");
pw.println(" -d: allow version code downgrade (debuggable packages only)");
pw.println(" -p: partial application install (new split on top of existing pkg)");
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4ecfbfe..d1e4537 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3506,7 +3506,7 @@
int pkgFlags = 0;
int pkgPrivateFlags = 0;
pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
- if (PackageManagerService.locationIsPrivileged(codePathStr)) {
+ if (codePathStr.contains("/priv-app/")) {
pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 8cd4f3c..34400ff 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -126,6 +126,6 @@
}
private void enforceSuggestManualTimePermission() {
- mContext.enforceCallingPermission(android.Manifest.permission.SET_TIME, "set time");
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SET_TIME, "set time");
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
new file mode 100644
index 0000000..23746ac
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2019 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.server.timezonedetector;
+
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.internal.telephony.TelephonyIntents;
+
+/**
+ * The real implementation of {@link TimeZoneDetectorStrategy.Callback}.
+ */
+public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategy.Callback {
+
+ private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
+
+ private final Context mContext;
+ private final ContentResolver mCr;
+
+ TimeZoneDetectorCallbackImpl(Context context) {
+ mContext = context;
+ mCr = context.getContentResolver();
+ }
+
+ @Override
+ public boolean isTimeZoneDetectionEnabled() {
+ return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0;
+ }
+
+ @Override
+ public boolean isDeviceTimeZoneInitialized() {
+ // timezone.equals("GMT") will be true and only true if the time zone was
+ // set to a default value by the system server (when starting, system server
+ // sets the persist.sys.timezone to "GMT" if it's not set). "GMT" is not used by
+ // any code that sets it explicitly (in case where something sets GMT explicitly,
+ // "Etc/GMT" Olson ID would be used).
+
+ String timeZoneId = getDeviceTimeZone();
+ return timeZoneId != null && timeZoneId.length() > 0 && !timeZoneId.equals("GMT");
+ }
+
+ @Override
+ @Nullable
+ public String getDeviceTimeZone() {
+ return SystemProperties.get(TIMEZONE_PROPERTY);
+ }
+
+ @Override
+ public void setDeviceTimeZone(String zoneId) {
+ AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
+ alarmManager.setTimeZone(zoneId);
+
+ // TODO Nothing in the platform appears to listen for this. Remove it.
+ Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra("time-zone", zoneId);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
new file mode 100644
index 0000000..558aa9e
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 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.server.timezonedetector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timezonedetector.ITimeZoneDetectorService;
+import android.app.timezonedetector.PhoneTimeZoneSuggestion;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.FgThread;
+import com.android.server.SystemService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * The implementation of ITimeZoneDetectorService.aidl.
+ */
+public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub {
+ private static final String TAG = "TimeZoneDetectorService";
+
+ /**
+ * Handles the lifecycle for {@link TimeZoneDetectorService}.
+ */
+ public static class Lifecycle extends SystemService {
+
+ public Lifecycle(@NonNull Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ TimeZoneDetectorService service = TimeZoneDetectorService.create(getContext());
+
+ // Publish the binder service so it can be accessed from other (appropriately
+ // permissioned) processes.
+ publishBinderService(Context.TIME_ZONE_DETECTOR_SERVICE, service);
+ }
+ }
+
+ @NonNull private final Context mContext;
+ @NonNull private final Handler mHandler;
+ @NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
+
+ private static TimeZoneDetectorService create(@NonNull Context context) {
+ final TimeZoneDetectorStrategy timeZoneDetectorStrategy =
+ TimeZoneDetectorStrategy.create(context);
+
+ Handler handler = FgThread.getHandler();
+ ContentResolver contentResolver = context.getContentResolver();
+ contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
+ new ContentObserver(handler) {
+ public void onChange(boolean selfChange) {
+ timeZoneDetectorStrategy.handleTimeZoneDetectionChange();
+ }
+ });
+
+ return new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy);
+ }
+
+ @VisibleForTesting
+ public TimeZoneDetectorService(@NonNull Context context, @NonNull Handler handler,
+ @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
+ mContext = Objects.requireNonNull(context);
+ mHandler = Objects.requireNonNull(handler);
+ mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
+ }
+
+ @Override
+ public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) {
+ enforceSetTimeZonePermission();
+ Objects.requireNonNull(timeZoneSuggestion);
+
+ mHandler.post(() -> mTimeZoneDetectorStrategy.suggestPhoneTimeZone(timeZoneSuggestion));
+ }
+
+ @Override
+ protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
+ @Nullable String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+ mTimeZoneDetectorStrategy.dumpState(pw);
+ mTimeZoneDetectorStrategy.dumpLogs(new IndentingPrintWriter(pw, " "));
+ }
+
+ private void enforceSetTimeZonePermission() {
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.SET_TIME_ZONE, "set time zone");
+ }
+}
+
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
new file mode 100644
index 0000000..e24c089
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright 2019 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.server.timezonedetector;
+
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timezonedetector.PhoneTimeZoneSuggestion;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.LocalLog;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A singleton, stateful time zone detection strategy that is aware of multiple phone devices. It
+ * keeps track of the most recent suggestion from each phone and it uses the best based on a scoring
+ * algorithm. If several phones provide the same score then the phone with the lowest numeric ID
+ * "wins". If the situation changes and it is no longer possible to be confident about the time
+ * zone, phones must submit an empty suggestion in order to "withdraw" their previous suggestion.
+ */
+public class TimeZoneDetectorStrategy {
+
+ /**
+ * Used by {@link TimeZoneDetectorStrategy} to interact with the surrounding service. It can be
+ * faked for tests.
+ */
+ @VisibleForTesting
+ public interface Callback {
+
+ /**
+ * Returns true if automatic time zone detection is enabled in settings.
+ */
+ boolean isTimeZoneDetectionEnabled();
+
+ /**
+ * Returns true if the device has had an explicit time zone set.
+ */
+ boolean isDeviceTimeZoneInitialized();
+
+ /**
+ * Returns the device's currently configured time zone.
+ */
+ String getDeviceTimeZone();
+
+ /**
+ * Sets the device's time zone.
+ */
+ void setDeviceTimeZone(@NonNull String zoneId);
+ }
+
+ static final String LOG_TAG = "TimeZoneDetectorStrategy";
+ static final boolean DBG = false;
+
+ /**
+ * The abstract score for an empty or invalid suggestion.
+ *
+ * Used to score suggestions where there is no zone.
+ */
+ @VisibleForTesting
+ public static final int SCORE_NONE = 0;
+
+ /**
+ * The abstract score for a low quality suggestion.
+ *
+ * Used to score suggestions where:
+ * The suggested zone ID is one of several possibilities, and the possibilities have different
+ * offsets.
+ *
+ * You would have to be quite desperate to want to use this choice.
+ */
+ @VisibleForTesting
+ public static final int SCORE_LOW = 1;
+
+ /**
+ * The abstract score for a medium quality suggestion.
+ *
+ * Used for:
+ * The suggested zone ID is one of several possibilities but at least the possibilities have the
+ * same offset. Users would get the correct time but for the wrong reason. i.e. their device may
+ * switch to DST at the wrong time and (for example) their calendar events.
+ */
+ @VisibleForTesting
+ public static final int SCORE_MEDIUM = 2;
+
+ /**
+ * The abstract score for a high quality suggestion.
+ *
+ * Used for:
+ * The suggestion was for one zone ID and the answer was unambiguous and likely correct given
+ * the info available.
+ */
+ @VisibleForTesting
+ public static final int SCORE_HIGH = 3;
+
+ /**
+ * The abstract score for a highest quality suggestion.
+ *
+ * Used for:
+ * Suggestions that must "win" because they constitute test or emulator zone ID.
+ */
+ @VisibleForTesting
+ public static final int SCORE_HIGHEST = 4;
+
+ /** The threshold at which suggestions are good enough to use to set the device's time zone. */
+ @VisibleForTesting
+ public static final int SCORE_USAGE_THRESHOLD = SCORE_MEDIUM;
+
+ /** The number of previous phone suggestions to keep for each ID (for use during debugging). */
+ private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30;
+
+ @NonNull
+ private final Callback mCallback;
+
+ /**
+ * A log that records the decisions / decision metadata that affected the device's time zone
+ * (for use during debugging).
+ */
+ @NonNull
+ private final LocalLog mTimeZoneChangesLog = new LocalLog(30);
+
+ /**
+ * A mapping from phoneId to a linked list of time zone suggestions (the head being the latest).
+ * We typically expect one or two entries in this Map: devices will have a small number
+ * of telephony devices and phoneIds are assumed to be stable. The LinkedList associated with
+ * the ID will not exceed {@link #KEEP_SUGGESTION_HISTORY_SIZE} in size.
+ */
+ @GuardedBy("this")
+ private ArrayMap<Integer, LinkedList<QualifiedPhoneTimeZoneSuggestion>> mSuggestionByPhoneId =
+ new ArrayMap<>();
+
+ /**
+ * The most recent best guess of time zone from all phones. Can be {@code null} to indicate
+ * there would be no current suggestion.
+ */
+ @GuardedBy("this")
+ @Nullable
+ private QualifiedPhoneTimeZoneSuggestion mCurrentSuggestion;
+
+ /**
+ * Creates a new instance of {@link TimeZoneDetectorStrategy}.
+ */
+ public static TimeZoneDetectorStrategy create(Context context) {
+ Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context);
+ return new TimeZoneDetectorStrategy(timeZoneDetectionServiceHelper);
+ }
+
+ @VisibleForTesting
+ public TimeZoneDetectorStrategy(Callback callback) {
+ mCallback = Objects.requireNonNull(callback);
+ }
+
+ /**
+ * Suggests a time zone for the device, or withdraws a previous suggestion if
+ * {@link PhoneTimeZoneSuggestion#getZoneId()} is {@code null}. The suggestion is scoped to a
+ * specific {@link PhoneTimeZoneSuggestion#getPhoneId() phone}.
+ * See {@link PhoneTimeZoneSuggestion} for an explanation of the metadata associated with a
+ * suggestion. The service uses suggestions to decide whether to modify the device's time zone
+ * setting and what to set it to.
+ */
+ public synchronized void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion newSuggestion) {
+ if (DBG) {
+ Slog.d(LOG_TAG, "suggestPhoneTimeZone: newSuggestion=" + newSuggestion);
+ }
+ Objects.requireNonNull(newSuggestion);
+
+ int score = scoreSuggestion(newSuggestion);
+ QualifiedPhoneTimeZoneSuggestion scoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(newSuggestion, score);
+
+ // Record the suggestion against the correct phoneId.
+ LinkedList<QualifiedPhoneTimeZoneSuggestion> suggestions =
+ mSuggestionByPhoneId.get(newSuggestion.getPhoneId());
+ if (suggestions == null) {
+ suggestions = new LinkedList<>();
+ mSuggestionByPhoneId.put(newSuggestion.getPhoneId(), suggestions);
+ }
+ suggestions.addFirst(scoredSuggestion);
+ if (suggestions.size() > KEEP_SUGGESTION_HISTORY_SIZE) {
+ suggestions.removeLast();
+ }
+
+ // Now run the competition between the phones' suggestions.
+ doTimeZoneDetection();
+ }
+
+ private static int scoreSuggestion(@NonNull PhoneTimeZoneSuggestion suggestion) {
+ int score;
+ if (suggestion.getZoneId() == null) {
+ score = SCORE_NONE;
+ } else if (suggestion.getMatchType() == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY
+ || suggestion.getMatchType() == MATCH_TYPE_EMULATOR_ZONE_ID) {
+ // Handle emulator / test cases : These suggestions should always just be used.
+ score = SCORE_HIGHEST;
+ } else if (suggestion.getQuality() == QUALITY_SINGLE_ZONE) {
+ score = SCORE_HIGH;
+ } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET) {
+ // The suggestion may be wrong, but at least the offset should be correct.
+ score = SCORE_MEDIUM;
+ } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS) {
+ // The suggestion has a good chance of being wrong.
+ score = SCORE_LOW;
+ } else {
+ throw new AssertionError();
+ }
+ return score;
+ }
+
+ /**
+ * Finds the best available time zone suggestion from all phones. If it is high-enough quality
+ * and automatic time zone detection is enabled then it will be set on the device. The outcome
+ * can be that this service becomes / remains un-opinionated and nothing is set.
+ */
+ @GuardedBy("this")
+ private void doTimeZoneDetection() {
+ QualifiedPhoneTimeZoneSuggestion bestSuggestion = findBestSuggestion();
+ boolean timeZoneDetectionEnabled = mCallback.isTimeZoneDetectionEnabled();
+
+ // Work out what to do with the best suggestion.
+ if (bestSuggestion == null) {
+ // There is no suggestion. Become un-opinionated.
+ if (DBG) {
+ Slog.d(LOG_TAG, "doTimeZoneDetection: No good suggestion."
+ + " bestSuggestion=null"
+ + ", timeZoneDetectionEnabled=" + timeZoneDetectionEnabled);
+ }
+ mCurrentSuggestion = null;
+ return;
+ }
+
+ // Special case handling for uninitialized devices. This should only happen once.
+ String newZoneId = bestSuggestion.suggestion.getZoneId();
+ if (newZoneId != null && !mCallback.isDeviceTimeZoneInitialized()) {
+ Slog.i(LOG_TAG, "doTimeZoneDetection: Device has no time zone set so might set the"
+ + " device to the best available suggestion."
+ + " bestSuggestion=" + bestSuggestion
+ + ", timeZoneDetectionEnabled=" + timeZoneDetectionEnabled);
+
+ mCurrentSuggestion = bestSuggestion;
+ if (timeZoneDetectionEnabled) {
+ setDeviceTimeZone(bestSuggestion.suggestion);
+ }
+ return;
+ }
+
+ boolean suggestionGoodEnough = bestSuggestion.score >= SCORE_USAGE_THRESHOLD;
+ if (!suggestionGoodEnough) {
+ if (DBG) {
+ Slog.d(LOG_TAG, "doTimeZoneDetection: Suggestion not good enough."
+ + " bestSuggestion=" + bestSuggestion);
+ }
+ mCurrentSuggestion = null;
+ return;
+ }
+
+ // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
+ // zone ID.
+ if (newZoneId == null) {
+ Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
+ + " bestSuggestion=" + bestSuggestion);
+ mCurrentSuggestion = null;
+ return;
+ }
+
+ // There is a good suggestion. Store the suggestion and set the device time zone if
+ // settings allow.
+ mCurrentSuggestion = bestSuggestion;
+
+ // Only set the device time zone if time zone detection is enabled.
+ if (!timeZoneDetectionEnabled) {
+ if (DBG) {
+ Slog.d(LOG_TAG, "doTimeZoneDetection: Not setting the time zone because time zone"
+ + " detection is disabled."
+ + " bestSuggestion=" + bestSuggestion);
+ }
+ return;
+ }
+ PhoneTimeZoneSuggestion suggestion = bestSuggestion.suggestion;
+ setDeviceTimeZone(suggestion);
+ }
+
+ private void setDeviceTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion) {
+ String currentZoneId = mCallback.getDeviceTimeZone();
+ String newZoneId = suggestion.getZoneId();
+
+ // Paranoia: This should never happen.
+ if (newZoneId == null) {
+ Slog.w(LOG_TAG, "setDeviceTimeZone: Suggested zone is null."
+ + " timeZoneSuggestion=" + suggestion);
+ return;
+ }
+
+ // Avoid unnecessary changes / intents.
+ if (newZoneId.equals(currentZoneId)) {
+ // No need to set the device time zone - the setting is already what we would be
+ // suggesting.
+ if (DBG) {
+ Slog.d(LOG_TAG, "setDeviceTimeZone: No need to change the time zone;"
+ + " device is already set to the suggested zone."
+ + " timeZoneSuggestion=" + suggestion);
+ }
+ return;
+ }
+
+ String msg = "Changing device time zone. currentZoneId=" + currentZoneId
+ + ", timeZoneSuggestion=" + suggestion;
+ if (DBG) {
+ Slog.d(LOG_TAG, msg);
+ }
+ mTimeZoneChangesLog.log(msg);
+ mCallback.setDeviceTimeZone(newZoneId);
+ }
+
+ @GuardedBy("this")
+ @Nullable
+ private QualifiedPhoneTimeZoneSuggestion findBestSuggestion() {
+ QualifiedPhoneTimeZoneSuggestion bestSuggestion = null;
+
+ // Iterate over the latest QualifiedPhoneTimeZoneSuggestion objects received for each phone
+ // and find the best. Note that we deliberately do not look at age: the caller can
+ // rate-limit so age is not a strong indicator of confidence. Instead, the callers are
+ // expected to withdraw suggestions they no longer have confidence in.
+ for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
+ LinkedList<QualifiedPhoneTimeZoneSuggestion> phoneSuggestions =
+ mSuggestionByPhoneId.valueAt(i);
+ if (phoneSuggestions == null) {
+ // Unexpected
+ continue;
+ }
+ QualifiedPhoneTimeZoneSuggestion candidateSuggestion = phoneSuggestions.getFirst();
+ if (candidateSuggestion == null) {
+ // Unexpected
+ continue;
+ }
+
+ if (bestSuggestion == null) {
+ bestSuggestion = candidateSuggestion;
+ } else if (candidateSuggestion.score > bestSuggestion.score) {
+ bestSuggestion = candidateSuggestion;
+ } else if (candidateSuggestion.score == bestSuggestion.score) {
+ // Tie! Use the suggestion with the lowest phoneId.
+ int candidatePhoneId = candidateSuggestion.suggestion.getPhoneId();
+ int bestPhoneId = bestSuggestion.suggestion.getPhoneId();
+ if (candidatePhoneId < bestPhoneId) {
+ bestSuggestion = candidateSuggestion;
+ }
+ }
+ }
+ return bestSuggestion;
+ }
+
+ /**
+ * Returns the current best suggestion. Not intended for general use: it is used during tests
+ * to check service behavior.
+ */
+ @VisibleForTesting
+ @Nullable
+ public synchronized QualifiedPhoneTimeZoneSuggestion findBestSuggestionForTests() {
+ return findBestSuggestion();
+ }
+
+ /**
+ * Called when the has been a change to the automatic time zone detection setting.
+ */
+ @VisibleForTesting
+ public synchronized void handleTimeZoneDetectionChange() {
+ if (DBG) {
+ Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called");
+ }
+ if (mCallback.isTimeZoneDetectionEnabled()) {
+ // When the user enabled time zone detection, run the time zone detection and change the
+ // device time zone if possible.
+ doTimeZoneDetection();
+ }
+ }
+
+ /**
+ * Dumps any logs held to the supplied writer.
+ */
+ public synchronized void dumpLogs(IndentingPrintWriter ipw) {
+ ipw.println("TimeZoneDetectorStrategy:");
+
+ ipw.increaseIndent(); // level 1
+
+ ipw.println("Time zone change log:");
+ ipw.increaseIndent(); // level 2
+ mTimeZoneChangesLog.dump(ipw);
+ ipw.decreaseIndent(); // level 2
+
+ ipw.println("Phone suggestion history:");
+ ipw.increaseIndent(); // level 2
+ for (Map.Entry<Integer, LinkedList<QualifiedPhoneTimeZoneSuggestion>> entry
+ : mSuggestionByPhoneId.entrySet()) {
+ ipw.println("Phone " + entry.getKey());
+
+ ipw.increaseIndent(); // level 3
+ for (QualifiedPhoneTimeZoneSuggestion suggestion : entry.getValue()) {
+ ipw.println(suggestion);
+ }
+ ipw.decreaseIndent(); // level 3
+ }
+ ipw.decreaseIndent(); // level 2
+ ipw.decreaseIndent(); // level 1
+ }
+
+ /**
+ * Dumps internal state such as field values.
+ */
+ public synchronized void dumpState(PrintWriter pw) {
+ pw.println("mCurrentSuggestion=" + mCurrentSuggestion);
+ pw.println("mCallback.isTimeZoneDetectionEnabled()="
+ + mCallback.isTimeZoneDetectionEnabled());
+ pw.println("mCallback.isDeviceTimeZoneInitialized()="
+ + mCallback.isDeviceTimeZoneInitialized());
+ pw.println("mCallback.getDeviceTimeZone()="
+ + mCallback.getDeviceTimeZone());
+ pw.flush();
+ }
+
+ /**
+ * A method used to inspect service state during tests. Not intended for general use.
+ */
+ @VisibleForTesting
+ public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int phoneId) {
+ LinkedList<QualifiedPhoneTimeZoneSuggestion> suggestions =
+ mSuggestionByPhoneId.get(phoneId);
+ if (suggestions == null) {
+ return null;
+ }
+ return suggestions.getFirst();
+ }
+
+ /**
+ * A {@link PhoneTimeZoneSuggestion} with additional qualifying metadata.
+ */
+ @VisibleForTesting
+ public static class QualifiedPhoneTimeZoneSuggestion {
+
+ @VisibleForTesting
+ public final PhoneTimeZoneSuggestion suggestion;
+
+ /**
+ * The score the suggestion has been given. This can be used to rank against other
+ * suggestions of the same type.
+ */
+ @VisibleForTesting
+ public final int score;
+
+ @VisibleForTesting
+ public QualifiedPhoneTimeZoneSuggestion(PhoneTimeZoneSuggestion suggestion, int score) {
+ this.suggestion = suggestion;
+ this.score = score;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ QualifiedPhoneTimeZoneSuggestion that = (QualifiedPhoneTimeZoneSuggestion) o;
+ return score == that.score
+ && suggestion.equals(that.suggestion);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(score, suggestion);
+ }
+
+ @Override
+ public String toString() {
+ return "QualifiedPhoneTimeZoneSuggestion{"
+ + "suggestion=" + suggestion
+ + ", score=" + score
+ + '}';
+ }
+ }
+}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 7b3fbb9..2b1c07b 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -53,7 +53,6 @@
"com_android_server_am_LowMemDetector.cpp",
"onload.cpp",
":lib_networkStatsFactory_native",
- ":tethering-jni-srcs",
],
include_dirs: [
@@ -122,7 +121,6 @@
"android.hardware.power@1.0",
"android.hardware.power@1.1",
"android.hardware.power.stats@1.0",
- "android.hardware.tetheroffload.config@1.0",
"android.hardware.thermal@1.0",
"android.hardware.tv.cec@1.0",
"android.hardware.tv.input@1.0",
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index efffa6c..692c9d2 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -40,7 +40,6 @@
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_location_GnssLocationProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
-int register_android_server_connectivity_tethering_OffloadHardwareInterface(JNIEnv*);
int register_android_server_TestNetworkService(JNIEnv* env);
int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);
int register_android_server_hdmi_HdmiCecController(JNIEnv* env);
@@ -88,7 +87,6 @@
register_android_server_SystemServer(env);
register_android_server_location_GnssLocationProvider(env);
register_android_server_connectivity_Vpn(env);
- register_android_server_connectivity_tethering_OffloadHardwareInterface(env);
register_android_server_TestNetworkService(env);
register_android_server_devicepolicy_CryptoTestHelper(env);
register_android_server_ConsumerIrService(env);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index def61bf..a5cc82a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -40,6 +40,7 @@
import android.hardware.display.DisplayManagerInternal;
import android.net.ConnectivityModuleConnector;
import android.net.NetworkStackClient;
+import android.net.TetheringManager;
import android.os.BaseBundle;
import android.os.Binder;
import android.os.Build;
@@ -271,6 +272,8 @@
"com.android.internal.car.CarServiceHelperService";
private static final String TIME_DETECTOR_SERVICE_CLASS =
"com.android.server.timedetector.TimeDetectorService$Lifecycle";
+ private static final String TIME_ZONE_DETECTOR_SERVICE_CLASS =
+ "com.android.server.timezonedetector.TimeZoneDetectorService$Lifecycle";
private static final String ACCESSIBILITY_MANAGER_SERVICE_CLASS =
"com.android.server.accessibility.AccessibilityManagerService$Lifecycle";
private static final String ADB_SERVICE_CLASS =
@@ -1463,6 +1466,14 @@
}
traceEnd();
+ traceBeginAndSlog("StartTimeZoneDetectorService");
+ try {
+ mSystemServiceManager.startService(TIME_ZONE_DETECTOR_SERVICE_CLASS);
+ } catch (Throwable e) {
+ reportWtf("starting StartTimeZoneDetectorService service", e);
+ }
+ traceEnd();
+
if (!isWatch) {
traceBeginAndSlog("StartSearchManagerService");
try {
@@ -2175,6 +2186,15 @@
}
traceEnd();
+ traceBeginAndSlog("StartTethering");
+ try {
+ // Tethering must start after ConnectivityService and NetworkStack.
+ TetheringManager.getInstance().start();
+ } catch (Throwable e) {
+ reportWtf("starting Tethering", e);
+ }
+ traceEnd();
+
traceBeginAndSlog("MakeLocationServiceReady");
try {
if (locationF != null) {
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 45430ea..2dabdb7 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -2,8 +2,8 @@
name: "services.net",
srcs: [
":net-module-utils-srcs",
- ":tethering-servicesnet-srcs",
"java/**/*.java",
+ ":tethering-manager",
],
static_libs: [
"dnsresolver_aidl_interface-V2-java",
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 1f5ebe4..842cdd5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -16,10 +16,12 @@
package com.android.server.locksettings;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -40,7 +42,9 @@
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.security.KeyStore;
-import android.test.AndroidTestCase;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
@@ -49,6 +53,9 @@
import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
import com.android.server.wm.WindowManagerInternal;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -56,8 +63,8 @@
import java.util.ArrayList;
import java.util.Arrays;
-
-public abstract class BaseLockSettingsServiceTests extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public abstract class BaseLockSettingsServiceTests {
protected static final int PRIMARY_USER_ID = 0;
protected static final int MANAGED_PROFILE_USER_ID = 12;
protected static final int TURNED_OFF_PROFILE_USER_ID = 17;
@@ -93,10 +100,8 @@
RecoverableKeyStoreManager mRecoverableKeyStoreManager;
protected boolean mHasSecureLockScreen;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
+ @Before
+ public void setUp_baseServices() throws Exception {
mGateKeeperService = new FakeGateKeeperService();
mNotificationManager = mock(NotificationManager.class);
mUserManager = mock(UserManager.class);
@@ -115,11 +120,11 @@
LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal);
LocalServices.addService(WindowManagerInternal.class, mMockWindowManager);
- mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager,
- mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class),
- mock(KeyguardManager.class));
+ mContext = new MockLockSettingsContext(InstrumentationRegistry.getContext(), mUserManager,
+ mNotificationManager, mDevicePolicyManager, mock(StorageManager.class),
+ mock(TrustManager.class), mock(KeyguardManager.class));
mStorage = new LockSettingsStorageTestable(mContext,
- new File(getContext().getFilesDir(), "locksettings"));
+ new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
File storageDir = mStorage.mStorageDir;
if (storageDir.exists()) {
FileUtils.deleteContents(storageDir);
@@ -222,11 +227,10 @@
return sm;
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
+ @After
+ public void tearDown_baseServices() throws Exception {
mStorage.closeDatabase();
- File db = getContext().getDatabasePath("locksettings.db");
+ File db = InstrumentationRegistry.getContext().getDatabasePath("locksettings.db");
assertTrue(!db.exists() || db.delete());
File storageDir = mStorage.mStorageDir;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java
index d2a9145..6616c96 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java
@@ -20,6 +20,7 @@
import static com.android.server.testutils.TestUtils.assertExpectException;
+import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;
@@ -29,10 +30,14 @@
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
@@ -45,11 +50,11 @@
*/
@SmallTest
@Presubmit
+@RunWith(AndroidJUnit4.class)
public class CachedSyntheticPasswordTests extends SyntheticPasswordTests {
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void enableSpCache() throws Exception {
enableSpCaching(true);
}
@@ -58,6 +63,7 @@
.canUserHaveUntrustedCredentialReset(anyInt())).thenReturn(enable);
}
+ @Test
public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException {
final byte[] password = "testSyntheticPasswordClearCredential-password".getBytes();
final byte[] newPassword = "testSyntheticPasswordClearCredential-newpassword".getBytes();
@@ -78,6 +84,7 @@
assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
}
+ @Test
public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException {
final byte[] password = "testSyntheticPasswordClearCredential-password".getBytes();
final byte[] newPassword = "testSyntheticPasswordClearCredential-newpassword".getBytes();
@@ -95,6 +102,7 @@
LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
}
+ @Test
public void testUntrustedCredentialChangeMaintainsAuthSecret() throws RemoteException {
final byte[] password =
"testUntrustedCredentialChangeMaintainsAuthSecret-password".getBytes();
@@ -117,6 +125,7 @@
assertEquals(1, secret.getAllValues().stream().distinct().count());
}
+ @Test
public void testUntrustedCredentialChangeBlockedIfSpNotCached() throws RemoteException {
final byte[] password =
"testUntrustedCredentialChangeBlockedIfSpNotCached-password".getBytes();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 67d6eda..c0f27ff 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -25,6 +25,12 @@
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -37,59 +43,60 @@
import android.service.gatekeeper.GateKeeperResponse;
import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.locksettings.FakeGateKeeperService.VerifyHandle;
import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
/**
- * runtest frameworks-services -c com.android.server.locksettings.LockSettingsServiceTests
+ * atest FrameworksServicesTests:LockSettingsServiceTests
*/
@SmallTest
@Presubmit
+@RunWith(AndroidJUnit4.class)
public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- }
-
+ @Test
public void testCreatePasswordPrimaryUser() throws RemoteException {
testCreateCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD,
PASSWORD_QUALITY_ALPHABETIC);
}
+ @Test
public void testCreatePasswordFailsWithoutLockScreen() throws RemoteException {
testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "password",
CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
}
+ @Test
public void testCreatePatternPrimaryUser() throws RemoteException {
testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN,
PASSWORD_QUALITY_SOMETHING);
}
+ @Test
public void testCreatePatternFailsWithoutLockScreen() throws RemoteException {
testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "123456789",
CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING);
}
+ @Test
public void testChangePasswordPrimaryUser() throws RemoteException {
testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
"asdfghjk", CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
}
+ @Test
public void testChangePatternPrimaryUser() throws RemoteException {
testChangeCredentials(PRIMARY_USER_ID, "!£$%^&*(())", CREDENTIAL_TYPE_PASSWORD,
"1596321", CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING);
}
+ @Test
public void testChangePasswordFailPrimaryUser() throws RemoteException {
final long sid = 1234;
final String FAILED_MESSAGE = "Failed to enroll password";
@@ -105,6 +112,7 @@
assertVerifyCredentials(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
}
+ @Test
public void testClearPasswordPrimaryUser() throws RemoteException {
final String PASSWORD = "password";
initializeStorageWithCredential(PRIMARY_USER_ID, PASSWORD, CREDENTIAL_TYPE_PASSWORD, 1234);
@@ -115,6 +123,7 @@
assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
}
+ @Test
public void testManagedProfileUnifiedChallenge() throws RemoteException {
final String firstUnifiedPassword = "testManagedProfileUnifiedChallenge-pwd-1";
final String secondUnifiedPassword = "testManagedProfileUnifiedChallenge-pwd-2";
@@ -170,6 +179,7 @@
assertEquals(0, mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID));
}
+ @Test
public void testManagedProfileSeparateChallenge() throws RemoteException {
final String primaryPassword = "testManagedProfileSeparateChallenge-primary";
final String profilePassword = "testManagedProfileSeparateChallenge-profile";
@@ -218,6 +228,7 @@
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
}
+ @Test
public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception {
final byte[] password = "password".getBytes();
@@ -233,6 +244,7 @@
.lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, password, PRIMARY_USER_ID);
}
+ @Test
public void testSetLockCredential_forProfileWithSeparateChallenge_sendsCredentials()
throws Exception {
final byte[] pattern = "12345".getBytes();
@@ -249,6 +261,7 @@
.lockScreenSecretChanged(CREDENTIAL_TYPE_PATTERN, pattern, MANAGED_PROFILE_USER_ID);
}
+ @Test
public void testSetLockCredential_forProfileWithSeparateChallenge_updatesCredentials()
throws Exception {
final String oldCredential = "12345";
@@ -272,6 +285,7 @@
CREDENTIAL_TYPE_PASSWORD, newCredential, MANAGED_PROFILE_USER_ID);
}
+ @Test
public void testSetLockCredential_forProfileWithUnifiedChallenge_doesNotSendRandomCredential()
throws Exception {
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
@@ -289,6 +303,7 @@
eq(CREDENTIAL_TYPE_PASSWORD), any(), eq(MANAGED_PROFILE_USER_ID));
}
+ @Test
public void
testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_updatesBothCredentials()
throws Exception {
@@ -313,6 +328,7 @@
CREDENTIAL_TYPE_PASSWORD, newCredential, MANAGED_PROFILE_USER_ID);
}
+ @Test
public void
testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_removesBothCredentials()
throws Exception {
@@ -335,6 +351,7 @@
.lockScreenSecretChanged(CREDENTIAL_TYPE_NONE, null, MANAGED_PROFILE_USER_ID);
}
+ @Test
public void testSetLockCredential_forUnifiedToSeparateChallengeProfile_sendsNewCredentials()
throws Exception {
final String parentPassword = "parentPassword";
@@ -356,6 +373,7 @@
CREDENTIAL_TYPE_PASSWORD, profilePassword, MANAGED_PROFILE_USER_ID);
}
+ @Test
public void
testSetLockCredential_forSeparateToUnifiedChallengeProfile_doesNotSendRandomCredential()
throws Exception {
@@ -379,6 +397,7 @@
.lockScreenSecretChanged(anyInt(), any(), eq(MANAGED_PROFILE_USER_ID));
}
+ @Test
public void testVerifyCredential_forPrimaryUser_sendsCredentials() throws Exception {
final String password = "password";
initializeStorageWithCredential(PRIMARY_USER_ID, password, CREDENTIAL_TYPE_PASSWORD, 1234);
@@ -392,6 +411,7 @@
CREDENTIAL_TYPE_PASSWORD, password.getBytes(), PRIMARY_USER_ID);
}
+ @Test
public void testVerifyCredential_forProfileWithSeparateChallenge_sendsCredentials()
throws Exception {
final byte[] pattern = "12345".getBytes();
@@ -411,8 +431,8 @@
CREDENTIAL_TYPE_PATTERN, pattern, MANAGED_PROFILE_USER_ID);
}
- public void
- testVerifyCredential_forPrimaryUserWithUnifiedChallengeProfile_sendsCredentialsForBoth()
+ @Test
+ public void verifyCredential_forPrimaryUserWithUnifiedChallengeProfile_sendsCredentialsForBoth()
throws Exception {
final String pattern = "12345";
initializeStorageWithCredential(PRIMARY_USER_ID, pattern, CREDENTIAL_TYPE_PATTERN, 1234);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index c00d33b..b959126 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -23,7 +23,7 @@
import static junit.framework.Assert.assertEquals;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 18453aa..a1f423e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -16,7 +16,13 @@
package com.android.server.locksettings;
-import static org.mockito.Matchers.eq;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -31,18 +37,24 @@
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
import android.util.Log;
import android.util.Log.TerribleFailure;
import android.util.Log.TerribleFailureHandler;
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
@@ -50,11 +62,12 @@
import java.util.concurrent.CountDownLatch;
/**
- * runtest frameworks-services -c com.android.server.locksettings.LockSettingsStorageTests
+ * atest FrameworksServicesTests:LockSettingsStorageTests
*/
@SmallTest
@Presubmit
-public class LockSettingsStorageTests extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class LockSettingsStorageTests {
private static final int SOME_USER_ID = 1034;
private final byte[] PASSWORD_0 = "thepassword0".getBytes();
private final byte[] PASSWORD_1 = "password1".getBytes();
@@ -68,11 +81,10 @@
private File mDb;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mStorageDir = new File(getContext().getFilesDir(), "locksettings");
- mDb = getContext().getDatabasePath("locksettings.db");
+ @Before
+ public void setUp() throws Exception {
+ mStorageDir = new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings");
+ mDb = InstrumentationRegistry.getContext().getDatabasePath("locksettings.db");
assertTrue(mStorageDir.exists() || mStorageDir.mkdirs());
assertTrue(FileUtils.deleteContents(mStorageDir));
@@ -84,11 +96,12 @@
// User 3 is a profile of user 0.
when(mockUserManager.getProfileParent(eq(3))).thenReturn(new UserInfo(0, "name", 0));
- MockLockSettingsContext context = new MockLockSettingsContext(getContext(), mockUserManager,
+ MockLockSettingsContext context = new MockLockSettingsContext(
+ InstrumentationRegistry.getContext(), mockUserManager,
mock(NotificationManager.class), mock(DevicePolicyManager.class),
mock(StorageManager.class), mock(TrustManager.class), mock(KeyguardManager.class));
mStorage = new LockSettingsStorageTestable(context,
- new File(getContext().getFilesDir(), "locksettings"));
+ new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
mStorage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
@Override
public void initialize(SQLiteDatabase db) {
@@ -97,18 +110,19 @@
});
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
+ @After
+ public void tearDown() throws Exception {
mStorage.closeDatabase();
}
+ @Test
public void testKeyValue_InitializeWorked() {
assertEquals("initialValue", mStorage.readKeyValue("initializedKey", "default", 0));
mStorage.clearCache();
assertEquals("initialValue", mStorage.readKeyValue("initializedKey", "default", 0));
}
+ @Test
public void testKeyValue_WriteThenRead() {
mStorage.writeKeyValue("key", "value", 0);
assertEquals("value", mStorage.readKeyValue("key", "default", 0));
@@ -116,11 +130,13 @@
assertEquals("value", mStorage.readKeyValue("key", "default", 0));
}
+ @Test
public void testKeyValue_DefaultValue() {
assertEquals("default", mStorage.readKeyValue("unititialized key", "default", 0));
assertEquals("default2", mStorage.readKeyValue("unititialized key", "default2", 0));
}
+ @Test
public void testKeyValue_Concurrency() {
final Object monitor = new Object();
List<Thread> threads = new ArrayList<>();
@@ -160,6 +176,7 @@
assertEquals('5', mStorage.readKeyValue("key", "default", 0).charAt(0));
}
+ @Test
public void testKeyValue_CacheStarvedWriter() {
final CountDownLatch latch = new CountDownLatch(1);
List<Thread> threads = new ArrayList<>();
@@ -195,6 +212,7 @@
assertEquals("Cached value didn't match stored value", storage, cached);
}
+ @Test
public void testRemoveUser() {
mStorage.writeKeyValue("key", "value", 0);
writePasswordBytes(PASSWORD_0, 0);
@@ -212,10 +230,12 @@
assertPatternBytes(PATTERN_1, 1);
}
+ @Test
public void testCredential_Default() {
assertEquals(mStorage.readCredentialHash(0).type, LockPatternUtils.CREDENTIAL_TYPE_NONE);
}
+ @Test
public void testPassword_Write() {
writePasswordBytes(PASSWORD_0, 0);
@@ -224,6 +244,7 @@
assertPasswordBytes(PASSWORD_0, 0);
}
+ @Test
public void testPassword_WriteProfileWritesParent() {
writePasswordBytes(PASSWORD_0, 1);
writePasswordBytes(PASSWORD_1, 2);
@@ -235,6 +256,7 @@
assertPasswordBytes(PASSWORD_1, 2);
}
+ @Test
public void testLockType_WriteProfileWritesParent() {
writePasswordBytes(PASSWORD_0, 10);
writePatternBytes(PATTERN_0, 20);
@@ -250,6 +272,7 @@
mStorage.readCredentialHash(20).type);
}
+ @Test
public void testPassword_WriteParentWritesProfile() {
writePasswordBytes(PASSWORD_0, 2);
writePasswordBytes(PASSWORD_1, 1);
@@ -261,6 +284,7 @@
assertPasswordBytes(PASSWORD_0, 2);
}
+ @Test
public void testProfileLock_ReadWriteChildProfileLock() {
assertFalse(mStorage.hasChildProfileLock(20));
mStorage.writeChildProfileLock(20, PASSWORD_0);
@@ -271,6 +295,7 @@
assertTrue(mStorage.hasChildProfileLock(20));
}
+ @Test
public void testPattern_Write() {
writePatternBytes(PATTERN_0, 0);
@@ -279,6 +304,7 @@
assertPatternBytes(PATTERN_0, 0);
}
+ @Test
public void testPattern_WriteProfileWritesParent() {
writePatternBytes(PATTERN_0, 1);
writePatternBytes(PATTERN_1, 2);
@@ -290,6 +316,7 @@
assertPatternBytes(PATTERN_1, 2);
}
+ @Test
public void testPattern_WriteParentWritesProfile() {
writePatternBytes(PATTERN_1, 2);
writePatternBytes(PATTERN_0, 1);
@@ -301,6 +328,7 @@
assertPatternBytes(PATTERN_1, 2);
}
+ @Test
public void testPrefetch() {
mStorage.writeKeyValue("key", "toBeFetched", 0);
writePatternBytes(PATTERN_0, 0);
@@ -312,8 +340,9 @@
assertPatternBytes(PATTERN_0, 0);
}
+ @Test
public void testFileLocation_Owner() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext());
+ LockSettingsStorage storage = new LockSettingsStorage(InstrumentationRegistry.getContext());
assertEquals("/data/system/gesture.key", storage.getLegacyLockPatternFilename(0));
assertEquals("/data/system/password.key", storage.getLegacyLockPasswordFilename(0));
@@ -321,27 +350,31 @@
assertEquals("/data/system/gatekeeper.password.key", storage.getLockPasswordFilename(0));
}
+ @Test
public void testFileLocation_SecondaryUser() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext());
+ LockSettingsStorage storage = new LockSettingsStorage(InstrumentationRegistry.getContext());
assertEquals("/data/system/users/1/gatekeeper.pattern.key", storage.getLockPatternFilename(1));
assertEquals("/data/system/users/1/gatekeeper.password.key", storage.getLockPasswordFilename(1));
}
+ @Test
public void testFileLocation_ProfileToSecondary() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext());
+ LockSettingsStorage storage = new LockSettingsStorage(InstrumentationRegistry.getContext());
assertEquals("/data/system/users/2/gatekeeper.pattern.key", storage.getLockPatternFilename(2));
assertEquals("/data/system/users/2/gatekeeper.password.key", storage.getLockPasswordFilename(2));
}
+ @Test
public void testFileLocation_ProfileToOwner() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext());
+ LockSettingsStorage storage = new LockSettingsStorage(InstrumentationRegistry.getContext());
assertEquals("/data/system/users/3/gatekeeper.pattern.key", storage.getLockPatternFilename(3));
assertEquals("/data/system/users/3/gatekeeper.password.key", storage.getLockPasswordFilename(3));
}
+ @Test
public void testSyntheticPasswordState() {
final byte[] data = {1,2,3,4};
mStorage.writeSyntheticPasswordState(10, 1234L, "state", data);
@@ -352,18 +385,21 @@
assertEquals(null, mStorage.readSyntheticPasswordState(10, 1234L, "state"));
}
+ @Test
public void testPersistentDataBlock_unavailable() {
mStorage.mPersistentDataBlock = null;
assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
}
+ @Test
public void testPersistentDataBlock_empty() {
mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
}
+ @Test
public void testPersistentDataBlock_withData() {
mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
when(mStorage.mPersistentDataBlock.getFrpCredentialHandle())
@@ -378,6 +414,7 @@
assertArrayEquals(PAYLOAD, data.payload);
}
+ @Test
public void testPersistentDataBlock_exception() {
mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
when(mStorage.mPersistentDataBlock.getFrpCredentialHandle())
@@ -385,6 +422,7 @@
assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
}
+ @Test
public void testPersistentData_serializeUnserialize() {
byte[] serialized = PersistentData.toBytes(PersistentData.TYPE_SP, SOME_USER_ID,
DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, PAYLOAD);
@@ -395,16 +433,19 @@
assertArrayEquals(PAYLOAD, deserialized.payload);
}
+ @Test
public void testPersistentData_unserializeNull() {
PersistentData deserialized = PersistentData.fromBytes(null);
assertSame(PersistentData.NONE, deserialized);
}
+ @Test
public void testPersistentData_unserializeEmptyArray() {
PersistentData deserialized = PersistentData.fromBytes(new byte[0]);
assertSame(PersistentData.NONE, deserialized);
}
+ @Test
public void testPersistentData_unserializeInvalid() {
assertNotNull(suppressAndReturnWtf(() -> {
PersistentData deserialized = PersistentData.fromBytes(new byte[]{5});
@@ -412,6 +453,7 @@
}));
}
+ @Test
public void testPersistentData_unserialize_version1() {
// This test ensures that we can read serialized VERSION_1 PersistentData even if we change
// the wire format in the future.
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java
index 31526b5..0f24fb2 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java
@@ -20,6 +20,12 @@
import android.test.AndroidTestCase;
import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -30,24 +36,22 @@
@SmallTest
@Presubmit
+@RunWith(AndroidJUnit4.class)
public class PasswordSlotManagerTests extends AndroidTestCase {
PasswordSlotManagerTestable mManager;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
+ @Before
+ public void setUp() throws Exception {
mManager = new PasswordSlotManagerTestable();
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
+ @After
+ public void tearDown() throws Exception {
mManager.cleanup();
}
+ @Test
public void testBasicSlotUse() throws Exception {
mManager.markSlotInUse(0);
mManager.markSlotInUse(1);
@@ -64,6 +68,7 @@
assertEquals(expected, mManager.getUsedSlots());
}
+ @Test
public void testMergeSlots() throws Exception {
// Add some slots from a different OS image.
mManager.setGsiImageNumber(1);
@@ -90,6 +95,7 @@
assertEquals(expected, mManager.getUsedSlots());
}
+ @Test
public void testSerialization() throws Exception {
mManager.markSlotInUse(0);
mManager.markSlotInUse(1);
@@ -109,6 +115,7 @@
assertEquals(expected, map);
}
+ @Test
public void testSaving() throws Exception {
mManager.markSlotInUse(0);
mManager.markSlotInUse(1);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SP800DeriveTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SP800DeriveTests.java
index 29d0fc1..89b7ec8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SP800DeriveTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SP800DeriveTests.java
@@ -16,16 +16,23 @@
package com.android.server.locksettings;
+import static org.junit.Assert.assertEquals;
+
import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.HexDump;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
@SmallTest
@Presubmit
-public class SP800DeriveTests extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SP800DeriveTests {
+ @Test
public void testFixedInput() throws Exception {
// CAVP: https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/key-derivation
byte[] keyBytes = HexDump.hexStringToByteArray(
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 1cd590c..d9b1320 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -24,6 +24,12 @@
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
@@ -36,6 +42,7 @@
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -43,31 +50,25 @@
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
/**
- * runtest frameworks-services -c com.android.server.locksettings.SyntheticPasswordTests
+ * atest FrameworksServicesTests:SyntheticPasswordTests
*/
@SmallTest
@Presubmit
+@RunWith(AndroidJUnit4.class)
public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 55};
public static final byte[] PAYLOAD2 = new byte[] {2, 3, -2, -3, 44, 1};
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- }
-
+ @Test
public void testPasswordBasedSyntheticPassword() throws RemoteException {
final int USER_ID = 10;
final byte[] password = "user-password".getBytes();
@@ -102,6 +103,7 @@
return mService.getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId) != 0;
}
+ @Test
public void testPasswordMigration() throws RemoteException {
final byte[] password = "testPasswordMigration-password".getBytes();
@@ -135,6 +137,7 @@
mService.setLockCredential(password, type, null, quality, userId, false);
}
+ @Test
public void testSyntheticPasswordChangeCredential() throws RemoteException {
final byte[] password = "testSyntheticPasswordChangeCredential-password".getBytes();
final byte[] newPassword = "testSyntheticPasswordChangeCredential-newpassword".getBytes();
@@ -149,6 +152,7 @@
assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
}
+ @Test
public void testSyntheticPasswordVerifyCredential() throws RemoteException {
final byte[] password = "testSyntheticPasswordVerifyCredential-password".getBytes();
final byte[] badPassword = "testSyntheticPasswordVerifyCredential-badpassword".getBytes();
@@ -163,6 +167,7 @@
.getResponseCode());
}
+ @Test
public void testSyntheticPasswordClearCredential() throws RemoteException {
final byte[] password = "testSyntheticPasswordClearCredential-password".getBytes();
final byte[] badPassword = "testSyntheticPasswordClearCredential-newpassword".getBytes();
@@ -183,6 +188,7 @@
assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
}
+ @Test
public void testSyntheticPasswordChangeCredentialKeepsAuthSecret() throws RemoteException {
final byte[] password =
"testSyntheticPasswordChangeCredentialKeepsAuthSecret-password".getBytes();
@@ -202,6 +208,7 @@
assertEquals(1, secret.getAllValues().stream().distinct().count());
}
+ @Test
public void testSyntheticPasswordVerifyPassesPrimaryUserAuthSecret() throws RemoteException {
final byte[] password =
"testSyntheticPasswordVerifyPassesPrimaryUserAuthSecret-password".getBytes();
@@ -216,6 +223,7 @@
verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
}
+ @Test
public void testSecondaryUserDoesNotPassAuthSecret() throws RemoteException {
final byte[] password = "testSecondaryUserDoesNotPassAuthSecret-password".getBytes();
@@ -226,6 +234,7 @@
verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
}
+ @Test
public void testNoSyntheticPasswordOrCredentialDoesNotPassAuthSecret() throws RemoteException {
// Setting null doesn't create a synthetic password
initializeCredentialUnderSP(null, PRIMARY_USER_ID);
@@ -236,6 +245,7 @@
verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
}
+ @Test
public void testSyntheticPasswordAndCredentialDoesNotPassAuthSecret() throws RemoteException {
final byte[] password = "passwordForASyntheticPassword".getBytes();
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
@@ -246,6 +256,7 @@
verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
}
+ @Test
public void testSyntheticPasswordButNoCredentialPassesAuthSecret() throws RemoteException {
final byte[] password = "getASyntheticPassword".getBytes();
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
@@ -258,6 +269,7 @@
verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
}
+ @Test
public void testManagedProfileUnifiedChallengeMigration() throws RemoteException {
final byte[] UnifiedPassword = "testManagedProfileUnifiedChallengeMigration-pwd".getBytes();
disableSyntheticPassword();
@@ -292,6 +304,7 @@
assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID));
}
+ @Test
public void testManagedProfileSeparateChallengeMigration() throws RemoteException {
final byte[] primaryPassword =
"testManagedProfileSeparateChallengeMigration-primary".getBytes();
@@ -336,6 +349,7 @@
assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID));
}
+ @Test
public void testTokenBasedResetPassword() throws RemoteException {
final byte[] password = "password".getBytes();
final byte[] pattern = "123654".getBytes();
@@ -368,6 +382,7 @@
assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
}
+ @Test
public void testTokenBasedClearPassword() throws RemoteException {
final byte[] password = "password".getBytes();
final byte[] pattern = "123654".getBytes();
@@ -394,6 +409,7 @@
assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
}
+ @Test
public void testTokenBasedResetPasswordAfterCredentialChanges() throws RemoteException {
final byte[] password = "password".getBytes();
final byte[] pattern = "123654".getBytes();
@@ -422,6 +438,7 @@
assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
}
+ @Test
public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration()
throws RemoteException {
final String token = "some-high-entropy-secure-token";
@@ -432,6 +449,7 @@
assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
}
+ @Test
public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration()
throws RemoteException {
final String token = "some-high-entropy-secure-token";
@@ -442,6 +460,7 @@
assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
}
+ @Test
public void testEscrowTokenActivatedLaterWithUserPasswordNeedsMigration()
throws RemoteException {
final byte[] token = "some-high-entropy-secure-token".getBytes();
@@ -463,6 +482,7 @@
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
}
+ @Test
public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception {
final byte[] password = "password".getBytes();
final byte[] pattern = "123654".getBytes();
@@ -494,6 +514,7 @@
assertFalse(mService.havePattern(PRIMARY_USER_ID));
}
+ @Test
public void testgetHashFactorPrimaryUser() throws RemoteException {
final byte[] password = "password".getBytes();
mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
@@ -509,6 +530,7 @@
assertArrayEquals(hashFactor, newHashFactor);
}
+ @Test
public void testgetHashFactorManagedProfileUnifiedChallenge() throws RemoteException {
final byte[] pattern = "1236".getBytes();
mService.setLockCredential(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
@@ -517,6 +539,7 @@
assertNotNull(mService.getHashFactor(null, MANAGED_PROFILE_USER_ID));
}
+ @Test
public void testgetHashFactorManagedProfileSeparateChallenge() throws RemoteException {
final byte[] primaryPassword = "primary".getBytes();
final byte[] profilePassword = "profile".getBytes();
@@ -527,6 +550,7 @@
assertNotNull(mService.getHashFactor(profilePassword, MANAGED_PROFILE_USER_ID));
}
+ @Test
public void testPasswordData_serializeDeserialize() {
PasswordData data = new PasswordData();
data.scryptN = 11;
@@ -546,6 +570,7 @@
assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
}
+ @Test
public void testPasswordData_deserialize() {
// Test that we can deserialize existing PasswordData and don't inadvertently change the
// wire format.
@@ -569,6 +594,7 @@
assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
}
+ @Test
public void testGsiDisablesAuthSecret() throws RemoteException {
mGsiService.setIsGsiRunning(true);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
index abbf016..a3ac515 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
@@ -3,15 +3,18 @@
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
@SmallTest
@Presubmit
+@RunWith(AndroidJUnit4.class)
public class WeaverBasedSyntheticPasswordTests extends SyntheticPasswordTests {
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void enableWeaver() throws Exception {
mSpManager.enableWeaver();
}
-
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index fc7cfec..0a310d1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -118,17 +118,20 @@
String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" };
String[] appdir = { "app", "priv-app" };
for (int i = 0; i < partitions.length; i++) {
+ final PackageManagerService.SystemPartition systemPartition =
+ PackageManagerService.SYSTEM_PARTITIONS.get(i);
for (int j = 0; j < appdir.length; j++) {
String canonical = new File("/" + partitions[i]).getCanonicalPath();
String path = String.format("%s/%s/A.apk", canonical, appdir[j]);
- Assert.assertEquals(j == 1 && i != 3,
- PackageManagerService.locationIsPrivileged(path));
+ Assert.assertEquals(j == 1 && i != 3, systemPartition.containsPrivPath(path));
- Assert.assertEquals(i == 1 || i == 2, PackageManagerService.locationIsVendor(path));
- Assert.assertEquals(i == 3, PackageManagerService.locationIsOem(path));
- Assert.assertEquals(i == 4, PackageManagerService.locationIsProduct(path));
- Assert.assertEquals(i == 5, PackageManagerService.locationIsSystemExt(path));
+ final int scanFlag = systemPartition.scanFlag;
+ Assert.assertEquals(i == 1, scanFlag == PackageManagerService.SCAN_AS_VENDOR);
+ Assert.assertEquals(i == 2, scanFlag == PackageManagerService.SCAN_AS_ODM);
+ Assert.assertEquals(i == 3, scanFlag == PackageManagerService.SCAN_AS_OEM);
+ Assert.assertEquals(i == 4, scanFlag == PackageManagerService.SCAN_AS_PRODUCT);
+ Assert.assertEquals(i == 5, scanFlag == PackageManagerService.SCAN_AS_SYSTEM_EXT);
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
new file mode 100644
index 0000000..f9f23c3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
@@ -0,0 +1,592 @@
+/*
+ * Copyright 2019 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.server.timezonedetector;
+
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
+
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_HIGH;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_HIGHEST;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_LOW;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_MEDIUM;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_NONE;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_USAGE_THRESHOLD;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.timezonedetector.PhoneTimeZoneSuggestion;
+import android.app.timezonedetector.PhoneTimeZoneSuggestion.MatchType;
+import android.app.timezonedetector.PhoneTimeZoneSuggestion.Quality;
+
+import com.android.server.timezonedetector.TimeZoneDetectorStrategy.QualifiedPhoneTimeZoneSuggestion;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+
+/**
+ * White-box unit tests for {@link TimeZoneDetectorStrategy}.
+ */
+public class TimeZoneDetectorStrategyTest {
+
+ /** A time zone used for initialization that does not occur elsewhere in tests. */
+ private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC";
+ private static final int PHONE1_ID = 10000;
+ private static final int PHONE2_ID = 20000;
+
+ // Suggestion test cases are ordered so that each successive one is of the same or higher score
+ // than the previous.
+ private static final SuggestionTestCase[] TEST_CASES = new SuggestionTestCase[] {
+ newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
+ QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, SCORE_LOW),
+ newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET,
+ SCORE_MEDIUM),
+ newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET,
+ QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, SCORE_MEDIUM),
+ newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_SINGLE_ZONE, SCORE_HIGH),
+ newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, SCORE_HIGH),
+ newTestCase(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY,
+ QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, SCORE_HIGHEST),
+ newTestCase(MATCH_TYPE_EMULATOR_ZONE_ID, QUALITY_SINGLE_ZONE, SCORE_HIGHEST),
+ };
+
+ private TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
+ private FakeTimeZoneDetectorStrategyCallback mFakeTimeZoneDetectorStrategyCallback;
+
+ @Before
+ public void setUp() {
+ mFakeTimeZoneDetectorStrategyCallback = new FakeTimeZoneDetectorStrategyCallback();
+ mTimeZoneDetectorStrategy =
+ new TimeZoneDetectorStrategy(mFakeTimeZoneDetectorStrategyCallback);
+ }
+
+ @Test
+ public void testEmptySuggestions() {
+ PhoneTimeZoneSuggestion phone1TimeZoneSuggestion = createEmptyPhone1Suggestion();
+ PhoneTimeZoneSuggestion phone2TimeZoneSuggestion = createEmptyPhone2Suggestion();
+ Script script = new Script()
+ .initializeTimeZoneDetectionEnabled(true)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+ script.suggestPhoneTimeZone(phone1TimeZoneSuggestion)
+ .verifyTimeZoneNotSet();
+
+ // Assert internal service state.
+ QualifiedPhoneTimeZoneSuggestion expectedPhone1ScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(phone1TimeZoneSuggestion, SCORE_NONE);
+ assertEquals(expectedPhone1ScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+ assertNull(mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
+ assertEquals(expectedPhone1ScoredSuggestion,
+ mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+
+ script.suggestPhoneTimeZone(phone2TimeZoneSuggestion)
+ .verifyTimeZoneNotSet();
+
+ // Assert internal service state.
+ QualifiedPhoneTimeZoneSuggestion expectedPhone2ScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(phone2TimeZoneSuggestion, SCORE_NONE);
+ assertEquals(expectedPhone1ScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedPhone2ScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
+ // Phone 1 should always beat phone 2, all other things being equal.
+ assertEquals(expectedPhone1ScoredSuggestion,
+ mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+ }
+
+ @Test
+ public void testFirstPlausibleSuggestionAcceptedWhenTimeZoneUninitialized() {
+ SuggestionTestCase testCase = newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
+ QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, SCORE_LOW);
+ PhoneTimeZoneSuggestion lowQualitySuggestion =
+ testCase.createSuggestion(PHONE1_ID, "America/New_York");
+
+ // The device time zone setting is left uninitialized.
+ Script script = new Script()
+ .initializeTimeZoneDetectionEnabled(true);
+
+ // The very first suggestion will be taken.
+ script.suggestPhoneTimeZone(lowQualitySuggestion)
+ .verifyTimeZoneSetAndReset(lowQualitySuggestion);
+
+ // Assert internal service state.
+ QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(lowQualitySuggestion, testCase.expectedScore);
+ assertEquals(expectedScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedScoredSuggestion,
+ mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+
+ // Another low quality suggestion will be ignored now that the setting is initialized.
+ PhoneTimeZoneSuggestion lowQualitySuggestion2 =
+ testCase.createSuggestion(PHONE1_ID, "America/Los_Angeles");
+ script.suggestPhoneTimeZone(lowQualitySuggestion2)
+ .verifyTimeZoneNotSet();
+
+ // Assert internal service state.
+ QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion2 =
+ new QualifiedPhoneTimeZoneSuggestion(lowQualitySuggestion2, testCase.expectedScore);
+ assertEquals(expectedScoredSuggestion2,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedScoredSuggestion2,
+ mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+ }
+
+ /**
+ * Confirms that toggling the auto time zone detection setting has the expected behavior when
+ * the strategy is "opinionated".
+ */
+ @Test
+ public void testTogglingTimeZoneDetection() {
+ Script script = new Script();
+
+ for (SuggestionTestCase testCase : TEST_CASES) {
+ // Start with the device in a known state.
+ script.initializeTimeZoneDetectionEnabled(false)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+ PhoneTimeZoneSuggestion suggestion =
+ testCase.createSuggestion(PHONE1_ID, "Europe/London");
+ script.suggestPhoneTimeZone(suggestion);
+
+ // When time zone detection is not enabled, the time zone suggestion will not be set
+ // regardless of the score.
+ script.verifyTimeZoneNotSet();
+
+ // Assert internal service state.
+ QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(suggestion, testCase.expectedScore);
+ assertEquals(expectedScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedScoredSuggestion,
+ mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+
+ // Toggling the time zone setting on should cause the device setting to be set.
+ script.timeZoneDetectionEnabled(true);
+
+ // When time zone detection is already enabled the suggestion (if it scores highly
+ // enough) should be set immediately.
+ if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
+ script.verifyTimeZoneSetAndReset(suggestion);
+ } else {
+ script.verifyTimeZoneNotSet();
+ }
+
+ // Assert internal service state.
+ assertEquals(expectedScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedScoredSuggestion,
+ mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+
+ // Toggling the time zone setting should off should do nothing.
+ script.timeZoneDetectionEnabled(false)
+ .verifyTimeZoneNotSet();
+
+ // Assert internal service state.
+ assertEquals(expectedScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedScoredSuggestion,
+ mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+ }
+ }
+
+ @Test
+ public void testSuggestionsSinglePhone() {
+ Script script = new Script()
+ .initializeTimeZoneDetectionEnabled(true)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+ for (SuggestionTestCase testCase : TEST_CASES) {
+ makePhone1SuggestionAndCheckState(script, testCase);
+ }
+
+ /*
+ * This is the same test as above but the test cases are in
+ * reverse order of their expected score. New suggestions always replace previous ones:
+ * there's effectively no history and so ordering shouldn't make any difference.
+ */
+
+ // Each test case will have the same or lower score than the last.
+ ArrayList<SuggestionTestCase> descendingCasesByScore =
+ new ArrayList<>(Arrays.asList(TEST_CASES));
+ Collections.reverse(descendingCasesByScore);
+
+ for (SuggestionTestCase testCase : descendingCasesByScore) {
+ makePhone1SuggestionAndCheckState(script, testCase);
+ }
+ }
+
+ private void makePhone1SuggestionAndCheckState(Script script, SuggestionTestCase testCase) {
+ // Give the next suggestion a different zone from the currently set device time zone;
+ String currentZoneId = mFakeTimeZoneDetectorStrategyCallback.getDeviceTimeZone();
+ String suggestionZoneId =
+ "Europe/London".equals(currentZoneId) ? "Europe/Paris" : "Europe/London";
+ PhoneTimeZoneSuggestion zonePhone1Suggestion =
+ testCase.createSuggestion(PHONE1_ID, suggestionZoneId);
+ QualifiedPhoneTimeZoneSuggestion expectedZonePhone1ScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(zonePhone1Suggestion, testCase.expectedScore);
+
+ script.suggestPhoneTimeZone(zonePhone1Suggestion);
+ if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
+ script.verifyTimeZoneSetAndReset(zonePhone1Suggestion);
+ } else {
+ script.verifyTimeZoneNotSet();
+ }
+
+ // Assert internal service state.
+ assertEquals(expectedZonePhone1ScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedZonePhone1ScoredSuggestion,
+ mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+ }
+
+ /**
+ * Tries a set of test cases to see if the phone with the lowest ID is given preference. This
+ * test also confirms that the time zone setting would only be set if a suggestion is of
+ * sufficient quality.
+ */
+ @Test
+ public void testMultiplePhoneSuggestionScoringAndPhoneIdBias() {
+ String[] zoneIds = { "Europe/London", "Europe/Paris" };
+ PhoneTimeZoneSuggestion emptyPhone1Suggestion = createEmptyPhone1Suggestion();
+ PhoneTimeZoneSuggestion emptyPhone2Suggestion = createEmptyPhone2Suggestion();
+ QualifiedPhoneTimeZoneSuggestion expectedEmptyPhone1ScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(emptyPhone1Suggestion, SCORE_NONE);
+ QualifiedPhoneTimeZoneSuggestion expectedEmptyPhone2ScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(emptyPhone2Suggestion, SCORE_NONE);
+
+ Script script = new Script()
+ .initializeTimeZoneDetectionEnabled(true)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ // Initialize the latest suggestions as empty so we don't need to worry about nulls
+ // below for the first loop.
+ .suggestPhoneTimeZone(emptyPhone1Suggestion)
+ .suggestPhoneTimeZone(emptyPhone2Suggestion)
+ .resetState();
+
+ for (SuggestionTestCase testCase : TEST_CASES) {
+ PhoneTimeZoneSuggestion zonePhone1Suggestion =
+ testCase.createSuggestion(PHONE1_ID, zoneIds[0]);
+ PhoneTimeZoneSuggestion zonePhone2Suggestion =
+ testCase.createSuggestion(PHONE2_ID, zoneIds[1]);
+ QualifiedPhoneTimeZoneSuggestion expectedZonePhone1ScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(zonePhone1Suggestion,
+ testCase.expectedScore);
+ QualifiedPhoneTimeZoneSuggestion expectedZonePhone2ScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(zonePhone2Suggestion,
+ testCase.expectedScore);
+
+ // Start the test by making a suggestion for phone 1.
+ script.suggestPhoneTimeZone(zonePhone1Suggestion);
+ if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
+ script.verifyTimeZoneSetAndReset(zonePhone1Suggestion);
+ } else {
+ script.verifyTimeZoneNotSet();
+ }
+
+ // Assert internal service state.
+ assertEquals(expectedZonePhone1ScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedEmptyPhone2ScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
+ assertEquals(expectedZonePhone1ScoredSuggestion,
+ mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+
+ // Phone 2 then makes an alternative suggestion with an identical score. Phone 1's
+ // suggestion should still "win" if it is above the required threshold.
+ script.suggestPhoneTimeZone(zonePhone2Suggestion);
+ script.verifyTimeZoneNotSet();
+
+ // Assert internal service state.
+ assertEquals(expectedZonePhone1ScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedZonePhone2ScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
+ // Phone 1 should always beat phone 2, all other things being equal.
+ assertEquals(expectedZonePhone1ScoredSuggestion,
+ mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+
+ // Withdrawing phone 1's suggestion should leave phone 2 as the new winner. Since the
+ // zoneId is different, the time zone setting should be updated if the score is high
+ // enough.
+ script.suggestPhoneTimeZone(emptyPhone1Suggestion);
+ if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
+ script.verifyTimeZoneSetAndReset(zonePhone2Suggestion);
+ } else {
+ script.verifyTimeZoneNotSet();
+ }
+
+ // Assert internal service state.
+ assertEquals(expectedEmptyPhone1ScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedZonePhone2ScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
+ assertEquals(expectedZonePhone2ScoredSuggestion,
+ mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+
+ // Reset the state for the next loop.
+ script.suggestPhoneTimeZone(emptyPhone2Suggestion)
+ .verifyTimeZoneNotSet();
+ assertEquals(expectedEmptyPhone1ScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedEmptyPhone2ScoredSuggestion,
+ mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
+ }
+ }
+
+ /**
+ * The {@link TimeZoneDetectorStrategy.Callback} is left to detect whether changing the time
+ * zone is actually necessary. This test proves that the service doesn't assume it knows the
+ * current setting.
+ */
+ @Test
+ public void testTimeZoneDetectorStrategyDoesNotAssumeCurrentSetting() {
+ Script script = new Script()
+ .initializeTimeZoneDetectionEnabled(true);
+
+ SuggestionTestCase testCase =
+ newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, SCORE_HIGH);
+ PhoneTimeZoneSuggestion losAngelesSuggestion =
+ testCase.createSuggestion(PHONE1_ID, "America/Los_Angeles");
+ PhoneTimeZoneSuggestion newYorkSuggestion =
+ testCase.createSuggestion(PHONE1_ID, "America/New_York");
+
+ // Initialization.
+ script.suggestPhoneTimeZone(losAngelesSuggestion)
+ .verifyTimeZoneSetAndReset(losAngelesSuggestion);
+ // Suggest it again - it should not be set because it is already set.
+ script.suggestPhoneTimeZone(losAngelesSuggestion)
+ .verifyTimeZoneNotSet();
+
+ // Toggling time zone detection should set the device time zone only if the current setting
+ // value is different from the most recent phone suggestion.
+ script.timeZoneDetectionEnabled(false)
+ .verifyTimeZoneNotSet()
+ .timeZoneDetectionEnabled(true)
+ .verifyTimeZoneNotSet();
+
+ // Simulate a user turning auto detection off, a new suggestion being made while auto
+ // detection is off, and the user turning it on again.
+ script.timeZoneDetectionEnabled(false)
+ .suggestPhoneTimeZone(newYorkSuggestion)
+ .verifyTimeZoneNotSet();
+ // Latest suggestion should be used.
+ script.timeZoneDetectionEnabled(true)
+ .verifyTimeZoneSetAndReset(newYorkSuggestion);
+ }
+
+ private static PhoneTimeZoneSuggestion createEmptyPhone1Suggestion() {
+ return new PhoneTimeZoneSuggestion.Builder(PHONE1_ID).build();
+ }
+
+ private static PhoneTimeZoneSuggestion createEmptyPhone2Suggestion() {
+ return new PhoneTimeZoneSuggestion.Builder(PHONE2_ID).build();
+ }
+
+ class FakeTimeZoneDetectorStrategyCallback implements TimeZoneDetectorStrategy.Callback {
+
+ private boolean mTimeZoneDetectionEnabled;
+ private TestState<String> mTimeZoneId = new TestState<>();
+
+ @Override
+ public boolean isTimeZoneDetectionEnabled() {
+ return mTimeZoneDetectionEnabled;
+ }
+
+ @Override
+ public boolean isDeviceTimeZoneInitialized() {
+ return mTimeZoneId.getLatest() != null;
+ }
+
+ @Override
+ public String getDeviceTimeZone() {
+ return mTimeZoneId.getLatest();
+ }
+
+ @Override
+ public void setDeviceTimeZone(String zoneId) {
+ mTimeZoneId.set(zoneId);
+ }
+
+ void initializeTimeZoneDetectionEnabled(boolean enabled) {
+ mTimeZoneDetectionEnabled = enabled;
+ }
+
+ void initializeTimeZone(String zoneId) {
+ mTimeZoneId.init(zoneId);
+ }
+
+ void setTimeZoneDetectionEnabled(boolean enabled) {
+ mTimeZoneDetectionEnabled = enabled;
+ }
+
+ void assertTimeZoneNotSet() {
+ mTimeZoneId.assertHasNotBeenSet();
+ }
+
+ void assertTimeZoneSet(String timeZoneId) {
+ mTimeZoneId.assertHasBeenSet();
+ mTimeZoneId.assertChangeCount(1);
+ mTimeZoneId.assertLatestEquals(timeZoneId);
+ }
+
+ void commitAllChanges() {
+ mTimeZoneId.commitLatest();
+ }
+ }
+
+ /** Some piece of state that tests want to track. */
+ private static class TestState<T> {
+ private T mInitialValue;
+ private LinkedList<T> mValues = new LinkedList<>();
+
+ void init(T value) {
+ mValues.clear();
+ mInitialValue = value;
+ }
+
+ void set(T value) {
+ mValues.addFirst(value);
+ }
+
+ boolean hasBeenSet() {
+ return mValues.size() > 0;
+ }
+
+ void assertHasNotBeenSet() {
+ assertFalse(hasBeenSet());
+ }
+
+ void assertHasBeenSet() {
+ assertTrue(hasBeenSet());
+ }
+
+ void commitLatest() {
+ if (hasBeenSet()) {
+ mInitialValue = mValues.getLast();
+ mValues.clear();
+ }
+ }
+
+ void assertLatestEquals(T expected) {
+ assertEquals(expected, getLatest());
+ }
+
+ void assertChangeCount(int expectedCount) {
+ assertEquals(expectedCount, mValues.size());
+ }
+
+ public T getLatest() {
+ if (hasBeenSet()) {
+ return mValues.getFirst();
+ }
+ return mInitialValue;
+ }
+ }
+
+ /**
+ * A "fluent" class allows reuse of code in tests: initialization, simulation and verification
+ * logic.
+ */
+ private class Script {
+
+ Script initializeTimeZoneDetectionEnabled(boolean enabled) {
+ mFakeTimeZoneDetectorStrategyCallback.initializeTimeZoneDetectionEnabled(enabled);
+ return this;
+ }
+
+ Script initializeTimeZoneSetting(String zoneId) {
+ mFakeTimeZoneDetectorStrategyCallback.initializeTimeZone(zoneId);
+ return this;
+ }
+
+ Script timeZoneDetectionEnabled(boolean enabled) {
+ mFakeTimeZoneDetectorStrategyCallback.setTimeZoneDetectionEnabled(enabled);
+ mTimeZoneDetectorStrategy.handleTimeZoneDetectionChange();
+ return this;
+ }
+
+ /** Simulates the time zone detection service receiving a phone-originated suggestion. */
+ Script suggestPhoneTimeZone(PhoneTimeZoneSuggestion phoneTimeZoneSuggestion) {
+ mTimeZoneDetectorStrategy.suggestPhoneTimeZone(phoneTimeZoneSuggestion);
+ return this;
+ }
+
+ /** Simulates the user manually setting the time zone. */
+ Script manuallySetTimeZone(String timeZoneId) {
+ // Assert the test code is correct to call this method.
+ assertFalse(mFakeTimeZoneDetectorStrategyCallback.isTimeZoneDetectionEnabled());
+
+ mFakeTimeZoneDetectorStrategyCallback.initializeTimeZone(timeZoneId);
+ return this;
+ }
+
+ Script verifyTimeZoneNotSet() {
+ mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneNotSet();
+ return this;
+ }
+
+ Script verifyTimeZoneSetAndReset(PhoneTimeZoneSuggestion timeZoneSuggestion) {
+ mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(timeZoneSuggestion.getZoneId());
+ mFakeTimeZoneDetectorStrategyCallback.commitAllChanges();
+ return this;
+ }
+
+ Script resetState() {
+ mFakeTimeZoneDetectorStrategyCallback.commitAllChanges();
+ return this;
+ }
+ }
+
+ private static class SuggestionTestCase {
+ public final int matchType;
+ public final int quality;
+ public final int expectedScore;
+
+ SuggestionTestCase(int matchType, int quality, int expectedScore) {
+ this.matchType = matchType;
+ this.quality = quality;
+ this.expectedScore = expectedScore;
+ }
+
+ private PhoneTimeZoneSuggestion createSuggestion(int phoneId, String zoneId) {
+ return new PhoneTimeZoneSuggestion.Builder(phoneId)
+ .setZoneId(zoneId)
+ .setMatchType(matchType)
+ .setQuality(quality)
+ .build();
+ }
+ }
+
+ private static SuggestionTestCase newTestCase(
+ @MatchType int matchType, @Quality int quality, int expectedScore) {
+ return new SuggestionTestCase(matchType, quality, expectedScore);
+ }
+}
diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java
index b428530..221f8f1 100644
--- a/telecomm/java/android/telecom/ConnectionRequest.java
+++ b/telecomm/java/android/telecom/ConnectionRequest.java
@@ -16,6 +16,10 @@
package android.telecom;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
@@ -32,6 +36,7 @@
* Builder class for {@link ConnectionRequest}
* @hide
*/
+ @TestApi // For convenience in CTS tests
public static final class Builder {
private PhoneAccountHandle mAccountHandle;
private Uri mAddress;
@@ -48,7 +53,7 @@
* Sets the phone account handle for the resulting {@link ConnectionRequest}
* @param accountHandle The accountHandle which should be used to place the call.
*/
- public Builder setAccountHandle(PhoneAccountHandle accountHandle) {
+ public @NonNull Builder setAccountHandle(@NonNull PhoneAccountHandle accountHandle) {
this.mAccountHandle = accountHandle;
return this;
}
@@ -58,7 +63,7 @@
* @param address The address(e.g., phone number) to which the {@link Connection} is to
* connect.
*/
- public Builder setAddress(Uri address) {
+ public @NonNull Builder setAddress(@NonNull Uri address) {
this.mAddress = address;
return this;
}
@@ -67,7 +72,7 @@
* Sets the extras bundle for the resulting {@link ConnectionRequest}
* @param extras Application-specific extra data.
*/
- public Builder setExtras(Bundle extras) {
+ public @NonNull Builder setExtras(@NonNull Bundle extras) {
this.mExtras = extras;
return this;
}
@@ -76,7 +81,7 @@
* Sets the video state for the resulting {@link ConnectionRequest}
* @param videoState Determines the video state for the connection.
*/
- public Builder setVideoState(int videoState) {
+ public @NonNull Builder setVideoState(int videoState) {
this.mVideoState = videoState;
return this;
}
@@ -85,7 +90,7 @@
* Sets the Telecom call ID for the resulting {@link ConnectionRequest}
* @param telecomCallId The telecom call ID.
*/
- public Builder setTelecomCallId(String telecomCallId) {
+ public @NonNull Builder setTelecomCallId(@NonNull String telecomCallId) {
this.mTelecomCallId = telecomCallId;
return this;
}
@@ -97,7 +102,7 @@
* its own incoming call UI for an incoming call. When
* {@code false}, Telecom shows the incoming call UI.
*/
- public Builder setShouldShowIncomingCallUi(boolean shouldShowIncomingCallUi) {
+ public @NonNull Builder setShouldShowIncomingCallUi(boolean shouldShowIncomingCallUi) {
this.mShouldShowIncomingCallUi = shouldShowIncomingCallUi;
return this;
}
@@ -107,7 +112,8 @@
* resulting {@link ConnectionRequest}
* @param rttPipeFromInCall The data pipe to read from.
*/
- public Builder setRttPipeFromInCall(ParcelFileDescriptor rttPipeFromInCall) {
+ public @NonNull Builder setRttPipeFromInCall(
+ @NonNull ParcelFileDescriptor rttPipeFromInCall) {
this.mRttPipeFromInCall = rttPipeFromInCall;
return this;
}
@@ -117,12 +123,16 @@
* resulting {@link ConnectionRequest}
* @param rttPipeToInCall The data pipe to write to.
*/
- public Builder setRttPipeToInCall(ParcelFileDescriptor rttPipeToInCall) {
+ public @NonNull Builder setRttPipeToInCall(@NonNull ParcelFileDescriptor rttPipeToInCall) {
this.mRttPipeToInCall = rttPipeToInCall;
return this;
}
- public ConnectionRequest build() {
+ /**
+ * Build the {@link ConnectionRequest}
+ * @return Result of the builder
+ */
+ public @NonNull ConnectionRequest build() {
return new ConnectionRequest(
mAccountHandle,
mAddress,
@@ -261,7 +271,9 @@
* @return The Telecom ID.
* @hide
*/
- public String getTelecomCallId() {
+ @SystemApi
+ @TestApi
+ public @Nullable String getTelecomCallId() {
return mTelecomCallId;
}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index cda3387..20862c5 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressAutoDoc;
@@ -1028,12 +1029,16 @@
* by the user.
*
* @param includeDisabledAccounts When {@code true}, disabled phone accounts will be included,
- * when {@code false}, only
+ * when {@code false}, only enabled phone accounts will be
+ * included.
* @return A list of {@code PhoneAccountHandle} objects.
* @hide
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 119305590)
- public List<PhoneAccountHandle> getCallCapablePhoneAccounts(boolean includeDisabledAccounts) {
+ @SystemApi
+ @TestApi
+ @RequiresPermission(READ_PRIVILEGED_PHONE_STATE)
+ public @NonNull List<PhoneAccountHandle> getCallCapablePhoneAccounts(
+ boolean includeDisabledAccounts) {
try {
if (isServiceConnected()) {
return getTelecomService().getCallCapablePhoneAccounts(
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 1a5796b..72e76ee 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -86,7 +86,6 @@
import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telecom.ITelecomService;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.INumberVerificationCallback;
import com.android.internal.telephony.IOns;
@@ -5034,13 +5033,11 @@
* @return the current call state.
*/
public @CallState int getCallState() {
- try {
- ITelecomService telecom = getTelecomService();
- if (telecom != null) {
- return telecom.getCallState();
+ if (mContext != null) {
+ TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
+ if (telecomManager != null) {
+ return telecomManager.getCallState();
}
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelecomService#getCallState", e);
}
return CALL_STATE_IDLE;
}
@@ -5210,13 +5207,6 @@
return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
}
- /**
- * @hide
- */
- private ITelecomService getTelecomService() {
- return ITelecomService.Stub.asInterface(ServiceManager.getService(TELECOM_SERVICE));
- }
-
private ITelephonyRegistry getTelephonyRegistry() {
return ITelephonyRegistry.Stub.asInterface(ServiceManager.getService("telephony.registry"));
}
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index 45b236c..0208c3a 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -470,6 +470,13 @@
}
@Override
+ public void sendOrderedBroadcast(Intent intent, String receiverPermission, String receiverAppOp,
+ Bundle options, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+ String initialData, Bundle initialExtras) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public void sendStickyBroadcast(Intent intent) {
throw new UnsupportedOperationException();
}
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index 10f27e2..b2f384a 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -45,7 +45,6 @@
name: "FrameworksNetTests",
defaults: ["FrameworksNetTests-jni-defaults"],
srcs: [
- ":tethering-tests-src",
"java/**/*.java",
"java/**/*.kt",
],
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 334b26d..25028fb 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -32,6 +32,7 @@
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkRequest
import android.net.TestNetworkStackClient
+import android.net.TetheringManager
import android.net.metrics.IpConnectivityLog
import android.os.ConditionVariable
import android.os.IBinder
@@ -48,7 +49,6 @@
import com.android.server.connectivity.IpConnectivityMetrics
import com.android.server.connectivity.MockableSystemProperties
import com.android.server.connectivity.ProxyTracker
-import com.android.server.connectivity.Tethering
import com.android.server.net.NetworkPolicyManagerInternal
import com.android.testutils.TestableNetworkCallback
import org.junit.After
@@ -169,8 +169,7 @@
val deps = spy(ConnectivityService.Dependencies())
doReturn(networkStackClient).`when`(deps).networkStack
doReturn(metricsLogger).`when`(deps).metricsLogger
- doReturn(mock(Tethering::class.java)).`when`(deps).makeTethering(
- any(), any(), any(), any(), any())
+ doReturn(mock(TetheringManager::class.java)).`when`(deps).getTetheringManager()
doReturn(mock(ProxyTracker::class.java)).`when`(deps).makeProxyTracker(any(), any())
doReturn(mock(MockableSystemProperties::class.java)).`when`(deps).systemProperties
doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager()
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 7ea9bcf..fd3ed7d 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -96,6 +96,7 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
@@ -163,6 +164,7 @@
import android.net.ResolverParamsParcel;
import android.net.RouteInfo;
import android.net.SocketKeepalive;
+import android.net.TetheringManager;
import android.net.UidRange;
import android.net.metrics.IpConnectivityLog;
import android.net.shared.NetworkMonitorUtils;
@@ -197,6 +199,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.app.IBatteryStats;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnInfo;
import com.android.internal.util.ArrayUtils;
@@ -210,7 +213,6 @@
import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import com.android.server.connectivity.ProxyTracker;
-import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
import com.android.server.net.NetworkPinner;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -305,6 +307,7 @@
@Mock DefaultNetworkMetrics mDefaultNetworkMetrics;
@Mock INetworkManagementService mNetworkManagementService;
@Mock INetworkStatsService mStatsService;
+ @Mock IBatteryStats mBatteryStatsService;
@Mock INetworkPolicyManager mNpm;
@Mock IDnsResolver mMockDnsResolver;
@Mock INetd mMockNetd;
@@ -1130,11 +1133,12 @@
doReturn(new TestNetIdManager()).when(deps).makeNetIdManager();
doReturn(mNetworkStack).when(deps).getNetworkStack();
doReturn(systemProperties).when(deps).getSystemProperties();
- doReturn(mock(Tethering.class)).when(deps).makeTethering(any(), any(), any(), any(), any());
+ doReturn(mock(TetheringManager.class)).when(deps).getTetheringManager();
doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
doReturn(mMetricsService).when(deps).getMetricsLogger();
doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt());
doReturn(mIpConnectivityMetrics).when(deps).getIpConnectivityMetrics();
+ doReturn(mBatteryStatsService).when(deps).getBatteryStatsService();
doReturn(true).when(deps).hasService(Context.ETHERNET_SERVICE);
doAnswer(inv -> {
mPolicyTracker = new WrappedMultinetworkPolicyTracker(
@@ -5640,6 +5644,37 @@
mCm.unregisterNetworkCallback(defaultCallback);
}
+ @Ignore // 40%+ flakiness : figure out why and re-enable.
+ @Test
+ public final void testBatteryStatsNetworkType() throws Exception {
+ final LinkProperties cellLp = new LinkProperties();
+ cellLp.setInterfaceName("cell0");
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+ mCellNetworkAgent.connect(true);
+ waitForIdle();
+ verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(),
+ TYPE_MOBILE);
+ reset(mBatteryStatsService);
+
+ final LinkProperties wifiLp = new LinkProperties();
+ wifiLp.setInterfaceName("wifi0");
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ verify(mBatteryStatsService).noteNetworkInterfaceType(wifiLp.getInterfaceName(),
+ TYPE_WIFI);
+ reset(mBatteryStatsService);
+
+ mCellNetworkAgent.disconnect();
+
+ cellLp.setInterfaceName("wifi0");
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+ mCellNetworkAgent.connect(true);
+ waitForIdle();
+ verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(),
+ TYPE_MOBILE);
+ }
+
/**
* Make simulated InterfaceConfig for Nat464Xlat to query clat lower layer info.
*/
@@ -5680,25 +5715,28 @@
mCm.registerNetworkCallback(networkRequest, networkCallback);
// Prepare ipv6 only link properties.
- mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
- final int cellNetId = mCellNetworkAgent.getNetwork().netId;
final LinkProperties cellLp = new LinkProperties();
cellLp.setInterfaceName(MOBILE_IFNAME);
cellLp.addLinkAddress(myIpv6);
cellLp.addRoute(new RouteInfo((IpPrefix) null, myIpv6.getAddress(), MOBILE_IFNAME));
cellLp.addRoute(new RouteInfo(myIpv6, null, MOBILE_IFNAME));
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
reset(mNetworkManagementService);
reset(mMockDnsResolver);
reset(mMockNetd);
+ reset(mBatteryStatsService);
when(mNetworkManagementService.getInterfaceConfig(CLAT_PREFIX + MOBILE_IFNAME))
.thenReturn(getClatInterfaceConfig(myIpv4));
// Connect with ipv6 link properties. Expect prefix discovery to be started.
- mCellNetworkAgent.sendLinkProperties(cellLp);
mCellNetworkAgent.connect(true);
+ final int cellNetId = mCellNetworkAgent.getNetwork().netId;
+ waitForIdle();
verify(mMockNetd, times(1)).networkCreatePhysical(eq(cellNetId), anyInt());
verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId));
+ verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(),
+ TYPE_MOBILE);
networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
@@ -5714,6 +5752,11 @@
verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId);
verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any());
+ // Make sure BatteryStats was not told about any v4- interfaces, as none should have
+ // come online yet.
+ waitForIdle();
+ verify(mBatteryStatsService, never()).noteNetworkInterfaceType(startsWith("v4-"), anyInt());
+
verifyNoMoreInteractions(mMockNetd);
verifyNoMoreInteractions(mMockDnsResolver);
reset(mMockNetd);
@@ -5760,6 +5803,11 @@
assertEquals(1, resolvrParams.servers.length);
assertTrue(ArrayUtils.contains(resolvrParams.servers, "8.8.8.8"));
+ for (final LinkProperties stackedLp : stackedLpsAfterChange) {
+ verify(mBatteryStatsService).noteNetworkInterfaceType(stackedLp.getInterfaceName(),
+ TYPE_MOBILE);
+ }
+
// Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked
// linkproperties are cleaned up.
cellLp.addLinkAddress(myIpv4);