Merge "Make WifiScanner#getAvailableChannels() @SystemApi"
diff --git a/Android.bp b/Android.bp
index 6b2fb0c..3b6eaa7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -194,13 +194,14 @@
":framework-core-sources",
":framework-drm-sources",
":framework-graphics-sources",
+ ":framework-jobscheduler-sources", // jobscheduler is not a module for R
":framework-keystore-sources",
":framework-location-sources",
":framework-lowpan-sources",
- ":framework-media-sources",
":framework-mca-effect-sources",
":framework-mca-filterfw-sources",
":framework-mca-filterpacks-sources",
+ ":framework-media-sources",
":framework-mime-sources",
":framework-mms-sources",
":framework-opengl-sources",
@@ -410,7 +411,6 @@
installable: true,
static_libs: [
"framework-minus-apex",
- "jobscheduler-framework",
],
required: [
"framework-platform-compat-config",
@@ -965,7 +965,6 @@
":updatable-media-srcs",
"test-mock/src/**/*.java",
"test-runner/src/**/*.java",
- ":jobscheduler-framework-source",
],
libs: framework_docs_only_libs,
local_sourcepaths: frameworks_base_subdirs,
@@ -1028,7 +1027,6 @@
":core-current-stubs-source",
":core_public_api_files",
":updatable-media-srcs",
- ":jobscheduler-framework-source",
],
libs: ["framework-internal-utils"],
local_sourcepaths: frameworks_base_subdirs,
diff --git a/apex/jobscheduler/framework/Android.bp b/apex/jobscheduler/framework/Android.bp
index 621ff9a..3902aa2 100644
--- a/apex/jobscheduler/framework/Android.bp
+++ b/apex/jobscheduler/framework/Android.bp
@@ -1,5 +1,5 @@
filegroup {
- name: "jobscheduler-framework-source",
+ name: "framework-jobscheduler-sources",
srcs: [
"java/**/*.java",
"java/android/app/job/IJobCallback.aidl",
@@ -12,13 +12,12 @@
java_library {
name: "jobscheduler-framework",
- installable: true,
+ installable: false,
+ compile_dex: true,
sdk_version: "core_platform",
-
srcs: [
- ":jobscheduler-framework-source",
+ ":framework-jobscheduler-sources",
],
-
aidl: {
export_include_dirs: [
"java",
diff --git a/apex/permission/Android.bp b/apex/permission/Android.bp
new file mode 100644
index 0000000..0274814
--- /dev/null
+++ b/apex/permission/Android.bp
@@ -0,0 +1,33 @@
+// 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.permission",
+
+ manifest: "apex_manifest.json",
+
+ key: "com.android.permission.key",
+ certificate: ":com.android.permission.certificate",
+}
+
+apex_key {
+ name: "com.android.permission.key",
+ public_key: "com.android.permission.avbpubkey",
+ private_key: "com.android.permission.pem",
+}
+
+android_app_certificate {
+ name: "com.android.permission.certificate",
+ certificate: "com.android.permission",
+}
diff --git a/apex/permission/OWNERS b/apex/permission/OWNERS
new file mode 100644
index 0000000..957e10a
--- /dev/null
+++ b/apex/permission/OWNERS
@@ -0,0 +1,6 @@
+svetoslavganov@google.com
+moltmann@google.com
+eugenesusla@google.com
+zhanghai@google.com
+evanseverson@google.com
+ntmyren@google.com
diff --git a/apex/permission/apex_manifest.json b/apex/permission/apex_manifest.json
new file mode 100644
index 0000000..2a8c4f7
--- /dev/null
+++ b/apex/permission/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.permission",
+ "version": 1
+}
diff --git a/apex/permission/com.android.permission.avbpubkey b/apex/permission/com.android.permission.avbpubkey
new file mode 100644
index 0000000..9eaf852
--- /dev/null
+++ b/apex/permission/com.android.permission.avbpubkey
Binary files differ
diff --git a/apex/permission/com.android.permission.pem b/apex/permission/com.android.permission.pem
new file mode 100644
index 0000000..3d584be
--- /dev/null
+++ b/apex/permission/com.android.permission.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKgIBAAKCAgEA6snt4eqoz85xiL9Sf6w1S1b9FgSHK05zYTh2JYPvQKQ3yeZp
+E6avJ6FN6XcbmkDzSd658BvUGDBSPhOlzuUO4BsoKBuLMxP6TxIQXFKidzDqY0vQ
+4qkS++bdIhUjwBP3OSZ3Czu0BiihK8GC75Abr//EyCyObGIGGfHEGANiOgrpP4X5
++OmLzQLCjk4iE1kg+U6cRSRI/XLaoWC0TvIIuzxznrQ6r5GmzgTOwyBWyIB+bj73
+bmsweHTU+w9Y7kGOx4hO3XCLIhoBWEw0EbuW9nZmQ4sZls5Jo/CbyJlCclF11yVo
+SCf2LG/T+9pah5NOmDQ1kPbU+0iKZIV4YFHGTIhyGDE/aPOuUT05ziCGDifgHr0u
+SG1x/RLqsVh/POvNxnvP9cQFMQ08BvbEJaTTgB785iwKsvdqCfmng/SAyxSetmzP
+StXVB3fh1OoZ8vunRbQYxnmUxycVqaA96zmBx2wLvbvzKo7pZFDE6nbhnT5+MRAM
+z/VIK89W26uB4gj8sBFslqZjT0jPqsAZuvDm7swOtMwIcEolyGJuFLqlhN7UwMz2
+9y8+IpYixR+HvD1TZI9NtmuCmv3kPrWgoMZg6yvaBayTIr8RdYzi6FO/C1lLiraz
+48dH3sXWRa8cgw6VcSUwYrEBIc3sotdsupO1iOjcFybIwaee0YTZJfjvbqkCAwEA
+AQKCAgEArRnfdpaJi1xLPGTCMDsIt9kUku0XswgN7PmxsYsKFAB+2S40/jYAIRm9
+1YjpItsMA8RgFfSOdJ77o6TctCMQyo17F8bm4+uwuic5RLfv7Cx2QmsdQF8jDfFx
+y7UGPJD7znjbf76uxXOjEB2FqZX3s9TAgkzHXIUQtoQW7RVhkCWHPjxKxgd5+NY2
+FrDoUpd9xhD9CcTsw1+wbRZdGW88nL6/B50dP2AFORM2VYo8MWr6y9FEn3YLsGOC
+uu7fxBk1aUrHyl81VRkTMMROB1zkuiUk1FtzrEm+5U15rXXBFYOVe9+qeLhtuOlh
+wueDoz0pzvF/JLe24uTik6YL0Ae6SD0pFXQ2KDrdH3cUHLok3r76/yGzaDNTFjS2
+2WbQ8dEJV8veNHk8gjGpFTJIsBUlcZpmUCDHlfvVMb3+2ahQ+28piQUt5t3zqJdZ
+NDqsOHzY6BRPc+Wm85Xii/lWiQceZSee/b1Enu+nchsyXhSenBfC6bIGZReyMI0K
+KKKuVhyR6OSOiR5ZdZ/NyXGqsWy05fn/h0X9hnpETsNaNYNKWvpHLfKll+STJpf7
+AZquJPIclQyiq5NONx6kfPztoCLkKV/zOgIj3Sx5oSZq+5gpO91nXWVwkTbqK1d1
+004q2Mah6UQyAk1XGQc2pHx7ouVcWawjU30vZ4C015Hv2lm/gVkCggEBAPltATYS
+OqOSL1YAtIHPiHxMjNAgUdglq8JiJFXVfkocGU9eNub3Ed3sSWu6GB9Myu/sSKje
+bJ5DZqxJnvB2Fqmu9I9OunLGFSD0aXs4prwsQ1Rm5FcbImtrxcciASdkoo8Pj0z4
+vk2r2NZD3VtER5Uh+YjSDkxcS9gBStXUpCL6gj69UpOxMmWqZVjyHatVB4lEvYJl
+N82uT7N7QVNL1DzcZ9z4C4r7ks1Pm7ka12s5m/oaAlAMdVeofiPJe1xA9zRToSr4
+tIbMkOeXFLVRLuji/7XsOgal5Rl59p+OwLshX5cswPVOMrH6zt+hbsJ5q8M5dqnX
+VAOBK7KNQ/EKZwcCggEBAPD6KVvyCim46n5EbcEqCkO7gevwZkw/9vLwmM5YsxTh
+z9FQkPO0iB7mwbX8w04I91Pre4NdfcgMG0pP1b13Sb4KHBchqW1a+TCs3kSGC6gn
+1SxmXHnA9jRxAkrWlGkoAQEz+aP61cXiiy2tXpQwJ8xQCKprfoqWZwhkCtEVU6CE
+S7v9cscOHIqgNxx4WoceMmq4EoihHAZzHxTcNVbByckMjb2XQJ0iNw3lDP4ddvc+
+a4HzHfHkhzeQ5ZNc8SvWU8z80aSCOKRsSD3aUTZzxhZ4O2tZSW7v7p+FpvVee7bC
+g8YCfszTdpVUMlLRLjScimAcovcFLSvtyupinxWg4M8CggEAN9YGEmOsSte7zwXj
+YrfhtumwEBtcFwX/2Ej+F1Tuq4p0xAa0RaoDjumJWhtTsRYQy/raHSuFpzwxbNoi
+QXQ+CIhI6RfXtz/OlQ0B2/rHoJJMFEXgUfuaDfAXW0eqeHYXyezSyIlamKqipPyW
+Pgsf9yue39keKEv1EorfhNTQVaA8rezV4oglXwrxGyNALw2e3UTNI7ai8mFWKDis
+XAg6n9E7UwUYGGnO6DUtCBgRJ0jDOQ6/e8n+LrxiWIKPIgzNCiK6jpMUXqTGv4Fb
+umdNGAdQ9RnHt5tFmRlrczaSwJFtA7uaCpAR2zPpQbiywchZAiAIB2dTwGEXNiZX
+kksg2wKCAQEA6pNad3qhkgPDoK6T+Jkn7M82paoaqtcJWWwEE7oceZNnbWZz9Agl
+CY+vuawXonrv5+0vCq2Tp4zBdBFLC2h3jFrjBVFrUFxifpOIukOSTVqZFON/2bWQ
+9XOcu6UuSz7522Xw+UNPnZXtzcUacD6AP08ZYGvLfrTyDyTzspyED5k48ALEHCkM
+d5WGkFxII4etpF0TDZVnZo/iDbhe49k4yFFEGO6Ho26PESOLBkNAb2V/2bwDxlij
+l9+g21Z6HiZA5SamHPH2mXgeyrcen1cL2QupK9J6vVcqfnboE6qp2zp2c+Yx8MlY
+gfy4EA44YFaSDQVTTgrn8f9Eq+zc130H2QKCAQEAqOKgv68nIPdDSngNyCVyWego
+boFiDaEJoBBg8FrBjTJ6wFLrNAnXmbvfTtgNmNAzF1cUPJZlIIsHgGrMCfpehbXq
+WQQIw+E+yFbTGLxseGRfsLrV0CsgnAoOVeod+yIHmqc3livaUbrWhL1V2f6Ue+sE
+7YLp/iP43NaMfA4kYk2ep7+ZJoEVkCjHJJaHWgAG3RynPJHkTJlSgu7wLYvGc9uE
+ZsEFUM46lX02t7rrtMfasVGrUy1c2xOxFb4v1vG6iEZ7+YWeq5o3AkxUwEGn+mG4
+/3p+k4AaTXJDXgyZ0Sv6CkGuPHenAYG4cswcUUEf/G4Ag77x6LBNMgycJBxUJA==
+-----END RSA PRIVATE KEY-----
diff --git a/apex/permission/com.android.permission.pk8 b/apex/permission/com.android.permission.pk8
new file mode 100644
index 0000000..d51673d
--- /dev/null
+++ b/apex/permission/com.android.permission.pk8
Binary files differ
diff --git a/apex/permission/com.android.permission.x509.pem b/apex/permission/com.android.permission.x509.pem
new file mode 100644
index 0000000..4b146c9
--- /dev/null
+++ b/apex/permission/com.android.permission.x509.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGKzCCBBOgAwIBAgIUezo3fQeVZsmLpm/dkpGWJ/G/MN8wDQYJKoZIhvcNAQEL
+BQAwgaMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMR8wHQYDVQQDDBZjb20uYW5kcm9pZC5wZXJtaXNzaW9uMSIwIAYJKoZIhvcN
+AQkBFhNhbmRyb2lkQGFuZHJvaWQuY29tMCAXDTE5MTAwOTIxMzExOVoYDzQ3NTcw
+OTA0MjEzMTE5WjCBozELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx
+FjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB0FuZHJvaWQxEDAOBgNV
+BAsMB0FuZHJvaWQxHzAdBgNVBAMMFmNvbS5hbmRyb2lkLnBlcm1pc3Npb24xIjAg
+BgkqhkiG9w0BCQEWE2FuZHJvaWRAYW5kcm9pZC5jb20wggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCxefguRJ7E6tBCTEOeU2HJEGs6AQQapLz9hMed0aaJ
+Qr7aTQiYJEk+sG4+jPYbjpxa8JDDzJHp+4g7DjfSb+dvT9n84A8lWaI/yRXTZTQN
+Hu5m/bgHhi0LbySpiaFyodXBKUAnOhZyGPtYjtBFywFylueub8ryc1Z6UxxU7udH
+1mkIr7sE48Qkq5SyjFROE96iFmYA+vS/JXOfS0NBHiMB4GBxx4V7kXpvrTI7hhZG
+HiyhKvNh7wyHIhO9nDEw1rwtAH6CsL3YkQEVBeAU98m+0Au+qStLYkKHh2l8zT4W
+7sVK1VSqfB+VqOUmeIGdzlBfqMsoXD+FJz6KnIdUHIwjFDjL7Xr+hd+7xve+Q3S+
+U3Blk/U6atY8PM09wNfilG+SvwcKk5IgriDcu3rWKgIFxbUUaxLrDW7pLlu6wt/d
+GGtKK+Bc0jF+9Z901Tl33i5xhc5yOktT0btkKs7lSeE6VzP/Nk5g0SuzixmuRoh9
+f5Ge41N2ZCEHNXx3wZeVZwHIIPfYrL7Yql1Xoxbfs4ETFk6ChzVQcvjfDQQuK58J
+uNc+TOCoI/qflXwGCwpuHl0ier8V5Z4tpMUl5rWyVR/QGRtLPvs2lLuxczDw1OXq
+wEVtCMn9aNnd4y7R9PZ52hi53HAvDjpWefrLYi+Q04J6iGFQ1qAFBClK9DquBvmR
+swIDAQABo1MwUTAdBgNVHQ4EFgQULpfus5s5SrqLkoUKyPXA0D1iHPMwHwYDVR0j
+BBgwFoAULpfus5s5SrqLkoUKyPXA0D1iHPMwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAgEAjxQG5EFv8V/9yV2glI53VOmlWMjfEgvUjd39s/XLyPlr
+OzPOKSB0NFo8To3l4l+MsManxPK8y0OyfEVKbWVz9onv0ovo5MVokBmV/2G0jmsV
+B4e9yjOq+DmqIvY/Qh63Ywb97sTgcFI8620MhQDbh2IpEGv4ZNV0H6rgXmgdSCBw
+1EjBoYfFpN5aMgZjeyzZcq+d1IapdWqdhuEJQkMvoYS4WIumNIJlEXPQRoq/F5Ih
+nszdbKI/jVyiGFa2oeZ3rja1Y6GCRU8TYEoKx1pjS8uQDOEDTwsG/QnUe9peEj0V
+SsCkIidJWTomAmq9Tub9vpBe1zuTpuRAwxwR0qwgSxozV1Mvow1dJ19oFtHX0yD6
+ZjCpRn5PW9kMvSWSlrcrFs1NJf0j1Cvf7bHpkEDqLqpMnnh9jaFQq3nzDY+MWcIR
+jDcgQpI+AiE2/qtauZnFEVhbce49nCnk9+5bpTTIZJdzqeaExe5KXHwEtZLaEDh4
+atLY9LuEvPsjmDIMOR6hycD9FvwGXhJOQBjESIWFwigtSb1Yud9n6201jw3MLJ4k
++WhkbmZgWy+xc+Mdm5H3XyB1lvHaHGkxu+QB9KyQuVQKwbUVcbwZIfTFPN6Zr/dS
+ZXJqAbBhG/dBgF0LazuLaPVpibi+a3Y+tb9b8eXGkz4F97PWZIEDkELQ+9KOvhc=
+-----END CERTIFICATE-----
diff --git a/apex/statsd/.clang-format b/apex/statsd/.clang-format
new file mode 100644
index 0000000..cead3a0
--- /dev/null
+++ b/apex/statsd/.clang-format
@@ -0,0 +1,17 @@
+BasedOnStyle: Google
+AllowShortIfStatementsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: false
+AllowShortLoopsOnASingleLine: true
+BinPackArguments: true
+BinPackParameters: true
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ContinuationIndentWidth: 8
+DerivePointerAlignment: false
+IndentWidth: 4
+PointerAlignment: Left
+TabWidth: 4
+AccessModifierOffset: -4
+IncludeCategories:
+ - Regex: '^"Log\.h"'
+ Priority: -1
diff --git a/api/current.txt b/api/current.txt
index 71dc58b..6d2221e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6851,6 +6851,7 @@
method public boolean setKeyPairCertificate(@Nullable android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.security.cert.Certificate>, boolean);
method public boolean setKeyguardDisabled(@NonNull android.content.ComponentName, boolean);
method public void setKeyguardDisabledFeatures(@NonNull android.content.ComponentName, int);
+ method public void setLocationEnabled(@NonNull android.content.ComponentName, boolean);
method public void setLockTaskFeatures(@NonNull android.content.ComponentName, int);
method public void setLockTaskPackages(@NonNull android.content.ComponentName, @NonNull String[]) throws java.lang.SecurityException;
method public void setLogoutEnabled(@NonNull android.content.ComponentName, boolean);
@@ -9489,6 +9490,7 @@
method @Nullable public android.database.Cursor query(@NonNull android.net.Uri, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String, @Nullable android.os.CancellationSignal);
method @Nullable public android.database.Cursor query(@NonNull android.net.Uri, @Nullable String[], @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal);
method public boolean refresh(android.net.Uri, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal);
+ method @NonNull public final android.content.Context requireContext();
method public final void restoreCallingIdentity(@NonNull android.content.ContentProvider.CallingIdentity);
method protected final void setPathPermissions(@Nullable android.content.pm.PathPermission[]);
method protected final void setReadPermission(@Nullable String);
@@ -29975,10 +29977,16 @@
method public String getSSID();
method public android.net.wifi.SupplicantState getSupplicantState();
method @IntRange(from=0xffffffff) public int getTxLinkSpeedMbps();
+ method public int getWifiTechnology();
method public void writeToParcel(android.os.Parcel, int);
field public static final String FREQUENCY_UNITS = "MHz";
field public static final String LINK_SPEED_UNITS = "Mbps";
field public static final int LINK_SPEED_UNKNOWN = -1; // 0xffffffff
+ field public static final int WIFI_TECHNOLOGY_11AC = 5; // 0x5
+ field public static final int WIFI_TECHNOLOGY_11AX = 6; // 0x6
+ field public static final int WIFI_TECHNOLOGY_11N = 4; // 0x4
+ field public static final int WIFI_TECHNOLOGY_LEGACY = 1; // 0x1
+ field public static final int WIFI_TECHNOLOGY_UNKNOWN = 0; // 0x0
}
public class WifiManager {
@@ -30336,6 +30344,8 @@
method public int describeContents();
method public android.net.wifi.hotspot2.pps.Credential getCredential();
method public android.net.wifi.hotspot2.pps.HomeSp getHomeSp();
+ method public long getSubscriptionExpirationTimeInMillis();
+ method public boolean isOsuProvisioned();
method public void setCredential(android.net.wifi.hotspot2.pps.Credential);
method public void setHomeSp(android.net.wifi.hotspot2.pps.HomeSp);
method public void writeToParcel(android.os.Parcel, int);
@@ -41368,11 +41378,16 @@
field public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 1; // 0x1
field public static final int NEGATIVE_BUTTON_STYLE_CANCEL = 0; // 0x0
field public static final int NEGATIVE_BUTTON_STYLE_REJECT = 1; // 0x1
+ field public static final int POSITIVE_BUTTON_STYLE_CONTINUE = 1; // 0x1
+ field public static final int POSITIVE_BUTTON_STYLE_SAVE = 0; // 0x0
field public static final int SAVE_DATA_TYPE_ADDRESS = 2; // 0x2
field public static final int SAVE_DATA_TYPE_CREDIT_CARD = 4; // 0x4
+ field public static final int SAVE_DATA_TYPE_DEBIT_CARD = 32; // 0x20
field public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 16; // 0x10
field public static final int SAVE_DATA_TYPE_GENERIC = 0; // 0x0
+ field public static final int SAVE_DATA_TYPE_GENERIC_CARD = 128; // 0x80
field public static final int SAVE_DATA_TYPE_PASSWORD = 1; // 0x1
+ field public static final int SAVE_DATA_TYPE_PAYMENT_CARD = 64; // 0x40
field public static final int SAVE_DATA_TYPE_USERNAME = 8; // 0x8
}
@@ -41386,6 +41401,7 @@
method @NonNull public android.service.autofill.SaveInfo.Builder setFlags(int);
method @NonNull public android.service.autofill.SaveInfo.Builder setNegativeAction(int, @Nullable android.content.IntentSender);
method @NonNull public android.service.autofill.SaveInfo.Builder setOptionalIds(@NonNull android.view.autofill.AutofillId[]);
+ method @NonNull public android.service.autofill.SaveInfo.Builder setPositiveAction(int);
method @NonNull public android.service.autofill.SaveInfo.Builder setTriggerId(@NonNull android.view.autofill.AutofillId);
method @NonNull public android.service.autofill.SaveInfo.Builder setValidator(@NonNull android.service.autofill.Validator);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 1f7337ca..9573478 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4780,8 +4780,11 @@
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsConfiguratorInitiator(@NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsEnrolleeInitiator(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public boolean startScan(android.os.WorkSource);
+ method @RequiresPermission(anyOf={"android.permission.NETWORK_STACK", android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public boolean startSoftAp(@Nullable android.net.wifi.WifiConfiguration);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(@NonNull android.net.wifi.hotspot2.OsuProvider, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.hotspot2.ProvisioningCallback);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession();
+ method @RequiresPermission(anyOf={"android.permission.NETWORK_STACK", android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public boolean stopSoftAp();
+ method @RequiresPermission(anyOf={"android.permission.NETWORK_STACK", android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void updateInterfaceIpState(@Nullable String, int);
method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void updateWifiUsabilityScore(int, int, int);
field public static final int CHANGE_REASON_ADDED = 0; // 0x0
field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2
@@ -4796,10 +4799,16 @@
field public static final String EXTRA_CHANGE_REASON = "changeReason";
field public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
field public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
+ field public static final String EXTRA_WIFI_AP_INTERFACE_NAME = "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME";
+ field public static final String EXTRA_WIFI_AP_MODE = "android.net.wifi.extra.WIFI_AP_MODE";
field public static final String EXTRA_WIFI_AP_STATE = "wifi_state";
field public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
field public static final String EXTRA_WIFI_CREDENTIAL_EVENT_TYPE = "et";
field public static final String EXTRA_WIFI_CREDENTIAL_SSID = "ssid";
+ field public static final int IFACE_IP_MODE_CONFIGURATION_ERROR = 0; // 0x0
+ field public static final int IFACE_IP_MODE_LOCAL_ONLY = 2; // 0x2
+ field public static final int IFACE_IP_MODE_TETHERED = 1; // 0x1
+ field public static final int IFACE_IP_MODE_UNSPECIFIED = -1; // 0xffffffff
field public static final int PASSPOINT_HOME_NETWORK = 0; // 0x0
field public static final int PASSPOINT_ROAMING_NETWORK = 1; // 0x1
field public static final String WIFI_AP_STATE_CHANGED_ACTION = "android.net.wifi.WIFI_AP_STATE_CHANGED";
@@ -5637,6 +5646,8 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isPrimaryUser();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile(@NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isSameProfileGroup(@NonNull android.os.UserHandle, @NonNull android.os.UserHandle);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean removeUser(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String);
@@ -8286,6 +8297,9 @@
public class TelephonyManager {
method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void carrierActionReportDefaultNetworkStatus(int, boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void carrierActionResetAll(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void carrierActionSetRadioEnabled(int, boolean);
method public int checkCarrierPrivilegesForPackage(String);
method public int checkCarrierPrivilegesForPackageAnyPhone(String);
method public void dial(String);
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 26c2c0c..f4e465a 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -563,6 +563,21 @@
* account, or the AbstractAcccountAuthenticator managing the account did so or because the
* client shares a signature with the managing AbstractAccountAuthenticator.
*
+ * <div class="caution"><p><b>Caution: </b>This method returns personal and sensitive user data.
+ * If your app accesses, collects, uses, or shares personal and sensitive data, you must clearly
+ * disclose that fact to users. For apps published on Google Play, policies protecting user data
+ * require that you do the following:</p>
+ * <ul>
+ * <li>Disclose to the user how your app accesses, collects, uses, or shares personal and
+ * sensitive data. Learn more about
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data/#!#personal-sensitive">acceptable
+ * disclosure and consent</a>.</li>
+ * <li>Provide a privacy policy that describes your use of this data on- and off-device.</li>
+ * </ul>
+ * <p>To learn more, visit the
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data">Google Play
+ * Policy regarding user data</a>.</p></div>
+ *
* <p>
* It is safe to call this method from the main thread.
*
@@ -649,6 +664,22 @@
* the account. For example, there are types corresponding to Google and Facebook. The exact
* string token to use will be published somewhere associated with the authenticator in
* question.
+ * </p>
+ *
+ * <div class="caution"><p><b>Caution: </b>This method returns personal and sensitive user data.
+ * If your app accesses, collects, uses, or shares personal and sensitive data, you must clearly
+ * disclose that fact to users. For apps published on Google Play, policies protecting user data
+ * require that you do the following:</p>
+ * <ul>
+ * <li>Disclose to the user how your app accesses, collects, uses, or shares personal and
+ * sensitive data. Learn more about
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data/#!#personal-sensitive">acceptable
+ * disclosure and consent</a>.</li>
+ * <li>Provide a privacy policy that describes your use of this data on- and off-device.</li>
+ * </ul>
+ * <p>To learn more, visit the
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data">Google Play
+ * Policy regarding user data</a>.</p></div>
*
* <p>
* It is safe to call this method from the main thread.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 375a06e..ad671df 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8351,6 +8351,24 @@
}
/**
+ * Called by device owners to set the user's master location setting.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with
+ * @param locationEnabled whether location should be enabled or disabled
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public void setLocationEnabled(@NonNull ComponentName admin, boolean locationEnabled) {
+ throwIfParentInstance("setLocationEnabled");
+ if (mService != null) {
+ try {
+ mService.setLocationEnabled(admin, locationEnabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Called by profile or device owners to update {@link android.provider.Settings.Secure}
* settings. Validation that the value of the setting is in the correct form for the setting
* type should be performed by the caller.
@@ -8379,6 +8397,11 @@
* all users.
* </strong>
*
+ * <strong>Note: Starting from Android R, apps should no longer call this method with the
+ * setting {@link android.provider.Settings.Secure#LOCATION_MODE}, which is deprecated. Instead,
+ * device owners should call {@link #setLocationEnabled(ComponentName, boolean)}.
+ * </strong>
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param setting The name of the setting to update.
* @param value The value to update the setting to.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 7d2c54e..6b50522 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -258,6 +258,8 @@
void setSystemSetting(in ComponentName who, in String setting, in String value);
void setSecureSetting(in ComponentName who, in String setting, in String value);
+ void setLocationEnabled(in ComponentName who, boolean locationEnabled);
+
boolean setTime(in ComponentName who, long millis);
boolean setTimeZone(in ComponentName who, String timeZone);
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index d9bfde5..0ecfca7 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -73,6 +73,11 @@
// consider it a complex PIN/password.
public static final int MAX_ALLOWED_SEQUENCE = 3;
+ // One of CREDENTIAL_TYPE_NONE, CREDENTIAL_TYPE_PATTERN or CREDENTIAL_TYPE_PASSWORD.
+ // Note that this class still uses CREDENTIAL_TYPE_PASSWORD to represent both numeric PIN
+ // and alphabetic password. This is OK as long as this definition is only used internally,
+ // and the value never gets mixed up with credential types from other parts of the framework.
+ // TODO: fix this (ideally after we move logic to PasswordPolicy)
public @CredentialType int credType;
// Fields below only make sense when credType is PASSWORD.
public int length = 0;
@@ -161,24 +166,26 @@
public static final @NonNull Parcelable.Creator<PasswordMetrics> CREATOR
= new Parcelable.Creator<PasswordMetrics>() {
- public PasswordMetrics createFromParcel(Parcel in) {
- int credType = in.readInt();
- int length = in.readInt();
- int letters = in.readInt();
- int upperCase = in.readInt();
- int lowerCase = in.readInt();
- int numeric = in.readInt();
- int symbols = in.readInt();
- int nonLetter = in.readInt();
- int nonNumeric = in.readInt();
- int seqLength = in.readInt();
- return new PasswordMetrics(credType, length, letters, upperCase, lowerCase, numeric,
- symbols, nonLetter, nonNumeric, seqLength);
- }
+ @Override
+ public PasswordMetrics createFromParcel(Parcel in) {
+ int credType = in.readInt();
+ int length = in.readInt();
+ int letters = in.readInt();
+ int upperCase = in.readInt();
+ int lowerCase = in.readInt();
+ int numeric = in.readInt();
+ int symbols = in.readInt();
+ int nonLetter = in.readInt();
+ int nonNumeric = in.readInt();
+ int seqLength = in.readInt();
+ return new PasswordMetrics(credType, length, letters, upperCase, lowerCase,
+ numeric, symbols, nonLetter, nonNumeric, seqLength);
+ }
- public PasswordMetrics[] newArray(int size) {
- return new PasswordMetrics[size];
- }
+ @Override
+ public PasswordMetrics[] newArray(int size) {
+ return new PasswordMetrics[size];
+ }
};
/**
@@ -189,7 +196,7 @@
* {@link com.android.internal.widget.LockPatternUtils#CREDENTIAL_TYPE_PASSWORD}.
*/
public static PasswordMetrics computeForCredential(LockscreenCredential credential) {
- if (credential.isPassword()) {
+ if (credential.isPassword() || credential.isPin()) {
return PasswordMetrics.computeForPassword(credential.getCredential());
} else if (credential.isPattern()) {
return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index f624446..ddc4932 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -16,10 +16,10 @@
package android.app.timedetector;
-import android.app.timedetector.TimeSignal;
+import android.app.timedetector.PhoneTimeSuggestion;
/**
- * System private API to comunicate with time detector service.
+ * System private API to communicate with time detector service.
*
* <p>Used by parts of the Android system with signals associated with the device's time to provide
* information to the Time Detector Service.
@@ -32,5 +32,5 @@
* {@hide}
*/
interface ITimeDetectorService {
- void suggestTime(in TimeSignal timeSignal);
+ void suggestPhoneTime(in PhoneTimeSuggestion timeSuggestion);
}
diff --git a/core/java/android/app/timedetector/TimeSignal.aidl b/core/java/android/app/timedetector/PhoneTimeSuggestion.aidl
similarity index 94%
rename from core/java/android/app/timedetector/TimeSignal.aidl
rename to core/java/android/app/timedetector/PhoneTimeSuggestion.aidl
index d2ec357..f5e2405 100644
--- a/core/java/android/app/timedetector/TimeSignal.aidl
+++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.aidl
@@ -16,4 +16,4 @@
package android.app.timedetector;
-parcelable TimeSignal;
\ No newline at end of file
+parcelable PhoneTimeSuggestion;
diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
new file mode 100644
index 0000000..475a4aa
--- /dev/null
+++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
@@ -0,0 +1,137 @@
+/*
+ * 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.timedetector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.TimestampedValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A time signal from a telephony source. The value consists of the number of milliseconds elapsed
+ * since 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime clock when that number
+ * was established. The elapsed realtime clock is considered accurate but volatile, so time signals
+ * must not be persisted across device resets.
+ *
+ * @hide
+ */
+public final class PhoneTimeSuggestion implements Parcelable {
+
+ public static final @NonNull Parcelable.Creator<PhoneTimeSuggestion> CREATOR =
+ new Parcelable.Creator<PhoneTimeSuggestion>() {
+ public PhoneTimeSuggestion createFromParcel(Parcel in) {
+ return PhoneTimeSuggestion.createFromParcel(in);
+ }
+
+ public PhoneTimeSuggestion[] newArray(int size) {
+ return new PhoneTimeSuggestion[size];
+ }
+ };
+
+ private final int mPhoneId;
+ @NonNull
+ private final TimestampedValue<Long> mUtcTime;
+ @Nullable
+ private ArrayList<String> mDebugInfo;
+
+ public PhoneTimeSuggestion(int phoneId, @NonNull TimestampedValue<Long> utcTime) {
+ mPhoneId = phoneId;
+ mUtcTime = Objects.requireNonNull(utcTime);
+ }
+
+ private static PhoneTimeSuggestion createFromParcel(Parcel in) {
+ int phoneId = in.readInt();
+ TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
+ PhoneTimeSuggestion suggestion = new PhoneTimeSuggestion(phoneId, utcTime);
+ @SuppressWarnings("unchecked")
+ ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+ suggestion.mDebugInfo = debugInfo;
+ return suggestion;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mPhoneId);
+ dest.writeParcelable(mUtcTime, 0);
+ dest.writeList(mDebugInfo);
+ }
+
+ public int getPhoneId() {
+ return mPhoneId;
+ }
+
+ @NonNull
+ public TimestampedValue<Long> getUtcTime() {
+ return mUtcTime;
+ }
+
+ @NonNull
+ public List<String> getDebugInfo() {
+ return 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(String... debugInfos) {
+ if (mDebugInfo == null) {
+ mDebugInfo = new ArrayList<>();
+ }
+ mDebugInfo.addAll(Arrays.asList(debugInfos));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PhoneTimeSuggestion that = (PhoneTimeSuggestion) o;
+ return mPhoneId == that.mPhoneId
+ && Objects.equals(mUtcTime, that.mUtcTime);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPhoneId, mUtcTime);
+ }
+
+ @Override
+ public String toString() {
+ return "PhoneTimeSuggestion{"
+ + "mPhoneId='" + mPhoneId + '\''
+ + ", mUtcTime=" + mUtcTime
+ + ", mDebugInfo=" + mDebugInfo
+ + '}';
+ }
+}
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index 052050d..334e958 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -45,12 +45,12 @@
* signals are available such as those that come from more reliable sources or were
* determined more recently.
*/
- public void suggestTime(@NonNull TimeSignal timeSignal) {
+ public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
if (DEBUG) {
- Log.d(TAG, "suggestTime called: " + timeSignal);
+ Log.d(TAG, "suggestPhoneTime called: " + timeSuggestion);
}
try {
- mITimeDetectorService.suggestTime(timeSignal);
+ mITimeDetectorService.suggestPhoneTime(timeSuggestion);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/timedetector/TimeSignal.java b/core/java/android/app/timedetector/TimeSignal.java
deleted file mode 100644
index b494260..0000000
--- a/core/java/android/app/timedetector/TimeSignal.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.timedetector;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.TimestampedValue;
-
-import java.util.Objects;
-
-/**
- * A time signal from a named source. The value consists of the number of milliseconds elapsed since
- * 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime clock when that number was
- * established. The elapsed realtime clock is considered accurate but volatile, so time signals
- * must not be persisted across device resets.
- *
- * @hide
- */
-public final class TimeSignal implements Parcelable {
-
- public static final @android.annotation.NonNull Parcelable.Creator<TimeSignal> CREATOR =
- new Parcelable.Creator<TimeSignal>() {
- public TimeSignal createFromParcel(Parcel in) {
- return TimeSignal.createFromParcel(in);
- }
-
- public TimeSignal[] newArray(int size) {
- return new TimeSignal[size];
- }
- };
-
- public static final String SOURCE_ID_NITZ = "nitz";
-
- private final String mSourceId;
- private final TimestampedValue<Long> mUtcTime;
-
- public TimeSignal(String sourceId, TimestampedValue<Long> utcTime) {
- mSourceId = Objects.requireNonNull(sourceId);
- mUtcTime = Objects.requireNonNull(utcTime);
- }
-
- private static TimeSignal createFromParcel(Parcel in) {
- String sourceId = in.readString();
- TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
- return new TimeSignal(sourceId, utcTime);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(mSourceId);
- dest.writeParcelable(mUtcTime, 0);
- }
-
- @NonNull
- public String getSourceId() {
- return mSourceId;
- }
-
- @NonNull
- public TimestampedValue<Long> getUtcTime() {
- return mUtcTime;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- TimeSignal that = (TimeSignal) o;
- return Objects.equals(mSourceId, that.mSourceId)
- && Objects.equals(mUtcTime, that.mUtcTime);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mSourceId, mUtcTime);
- }
-
- @Override
- public String toString() {
- return "TimeSignal{"
- + "mSourceId='" + mSourceId + '\''
- + ", mUtcTime=" + mUtcTime
- + '}';
- }
-}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 02b6b3e..7de8793 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -834,6 +834,23 @@
}
/**
+ * Retrieves a Non-Nullable Context this provider is running in, this is intended to be called
+ * after {@link #onCreate}. When called before context was created, an IllegalStateException
+ * will be thrown.
+ * <p>
+ * Note A provider must be declared in the manifest and created automatically by the system,
+ * and context is only available after {@link #onCreate} is called.
+ */
+ @NonNull
+ public final Context requireContext() {
+ final Context ctx = getContext();
+ if (ctx == null) {
+ throw new IllegalStateException("Cannot find context from the provider.");
+ }
+ return ctx;
+ }
+
+ /**
* Set the calling package, returning the current value (or {@code null})
* which can be used later to restore the previous state.
*/
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2ecfc8e..4e7e713 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2678,6 +2678,9 @@
* that application is first launched (that is the first time it is moved
* out of the stopped state). The data contains the name of the package.
*
+ * <p>When the application is first launched, the application itself doesn't receive this
+ * broadcast.</p>
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
index 0ec812f..698876b 100644
--- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java
+++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
@@ -36,23 +36,30 @@
* @hide
*/
int TYPE_NONE = 0;
+
+ /**
+ * Constant representing credential (PIN, pattern, or password).
+ * @hide
+ */
+ int TYPE_CREDENTIAL = 1 << 0;
+
/**
* Constant representing fingerprint.
* @hide
*/
- int TYPE_FINGERPRINT = 1 << 0;
+ int TYPE_FINGERPRINT = 1 << 1;
/**
* Constant representing iris.
* @hide
*/
- int TYPE_IRIS = 1 << 1;
+ int TYPE_IRIS = 1 << 2;
/**
* Constant representing face.
* @hide
*/
- int TYPE_FACE = 1 << 2;
+ int TYPE_FACE = 1 << 3;
/**
* Container for biometric data
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 27c04b4..c8bf570 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -134,6 +134,13 @@
int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
/**
+ * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
+ * because the authentication attempt was unsuccessful.
+ * @hide
+ */
+ int BIOMETRIC_PAUSED_REJECTED = 100;
+
+ /**
* @hide
*/
@UnsupportedAppUsage
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index cbe8a05..9d427c8 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -137,7 +137,7 @@
public boolean hasEnrolledBiometrics(int userId) {
if (mService != null) {
try {
- return mService.hasEnrolledBiometrics(userId);
+ return mService.hasEnrolledBiometrics(userId, mContext.getOpPackageName());
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception in hasEnrolledBiometrics(): " + e);
return false;
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index cf86e251..9c51b52 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -26,6 +26,8 @@
import android.app.KeyguardManager;
import android.content.Context;
import android.content.DialogInterface;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -339,9 +341,23 @@
}
@Override
- public void onError(int error, String message) throws RemoteException {
+ public void onError(int modality, int error, int vendorCode) throws RemoteException {
mExecutor.execute(() -> {
- mAuthenticationCallback.onAuthenticationError(error, message);
+ String errorMessage;
+ switch (modality) {
+ case TYPE_FACE:
+ errorMessage = FaceManager.getErrorString(mContext, error, vendorCode);
+ break;
+
+ case TYPE_FINGERPRINT:
+ errorMessage = FingerprintManager.getErrorString(mContext, error,
+ vendorCode);
+ break;
+
+ default:
+ errorMessage = "";
+ }
+ mAuthenticationCallback.onAuthenticationError(error, errorMessage);
});
}
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
new file mode 100644
index 0000000..987d197
--- /dev/null
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -0,0 +1,58 @@
+/*
+ * 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.hardware.biometrics;
+
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.face.IFaceServiceReceiver;
+import android.hardware.face.Face;
+
+/**
+ * This interface encapsulates fingerprint, face, iris, etc. authenticators.
+ * Implementations of this interface are meant to be registered with BiometricService.
+ * @hide
+ */
+interface IBiometricAuthenticator {
+
+ // This method prepares the service to start authenticating, but doesn't start authentication.
+ // This is protected by the MANAGE_BIOMETRIC signature permission. This method should only be
+ // called from BiometricService. The additional uid, pid, userId arguments should be determined
+ // by BiometricService. To start authentication after the clients are ready, use
+ // startPreparedClient().
+ void prepareForAuthentication(boolean requireConfirmation, IBinder token, long sessionId,
+ int userId, IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
+ int cookie, int callingUid, int callingPid, int callingUserId);
+
+ // Starts authentication with the previously prepared client.
+ void startPreparedClient(int cookie);
+
+ // Same as above, with extra arguments.
+ void cancelAuthenticationFromService(IBinder token, String opPackageName,
+ int callingUid, int callingPid, int callingUserId, boolean fromClient);
+
+ // Determine if HAL is loaded and ready
+ boolean isHardwareDetected(String opPackageName);
+
+ // Determine if a user has at least one enrolled face
+ boolean hasEnrolledTemplates(int userId, String opPackageName);
+
+ // Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
+ void resetLockout(in byte [] token);
+
+ // Explicitly set the active user (for enrolling work profile)
+ void setActiveUser(int uid);
+}
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 6a3bf38..06336a5 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -19,6 +19,7 @@
import android.os.Bundle;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricServiceReceiver;
+import android.hardware.biometrics.IBiometricAuthenticator;
/**
* Communication channel from BiometricPrompt and BiometricManager to BiometricService. The
@@ -40,7 +41,12 @@
int canAuthenticate(String opPackageName, int userId);
// Checks if any biometrics are enrolled.
- boolean hasEnrolledBiometrics(int userId);
+ boolean hasEnrolledBiometrics(int userId, String opPackageName);
+
+ // Registers an authenticator (e.g. face, fingerprint, iris).
+ // Id must be unique, whereas strength and modality don't need to be.
+ // TODO(b/123321528): Turn strength and modality into enums.
+ void registerAuthenticator(int id, int strength, int modality, IBiometricAuthenticator authenticator);
// Register callback for when keyguard biometric eligibility changes.
void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
index 22ef33e..c960049 100644
--- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
@@ -25,7 +25,7 @@
// Noties that authentication failed.
void onAuthenticationFailed();
// Notify BiometricPrompt that an error has occurred.
- void onError(int error, String message);
+ void onError(int modality, int error, int vendorCode);
// Notifies that a biometric has been acquired.
void onAcquired(int acquiredInfo, String message);
// Notifies that the SystemUI dialog has been dismissed.
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl
index 66b6e89..61310f3 100644
--- a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl
@@ -31,7 +31,7 @@
void onAuthenticationFailed();
// Notify BiometricService than an error has occured. Forward to the correct receiver depending
// on the cookie.
- void onError(int cookie, int error, String message);
+ void onError(int cookie, int modality, int error, int vendorCode);
// Notifies that a biometric has been acquired.
void onAcquired(int acquiredInfo, String message);
// Notifies that the SystemUI dialog has been dismissed.
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 12b285a..55ebe28 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -504,8 +504,7 @@
public boolean isHardwareDetected() {
if (mService != null) {
try {
- long deviceId = 0; /* TODO: plumb hardware id to FPMS */
- return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
+ return mService.isHardwareDetected(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index b6a0afb..68a4aef 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -67,7 +67,7 @@
List<Face> getEnrolledFaces(int userId, String opPackageName);
// Determine if HAL is loaded and ready
- boolean isHardwareDetected(long deviceId, String opPackageName);
+ boolean isHardwareDetected(String opPackageName);
// Get a pre-enrollment authentication token
long generateChallenge(IBinder token);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index d0622c8..22f8590 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -691,8 +691,7 @@
public boolean isHardwareDetected() {
if (mService != null) {
try {
- long deviceId = 0; /* TODO: plumb hardware id to FPMS */
- return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
+ return mService.isHardwareDetected(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index dd6b29d..1a7e128 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -71,7 +71,7 @@
List<Fingerprint> getEnrolledFingerprints(int groupId, String opPackageName);
// Determine if HAL is loaded and ready
- boolean isHardwareDetected(long deviceId, String opPackageName);
+ boolean isHardwareDetected(String opPackageName);
// Get a pre-enrollment authentication token
long preEnroll(IBinder token);
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 83391f3..43842c5 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -94,6 +94,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
/**
* InputMethodService provides a standard implementation of an InputMethod,
@@ -434,6 +435,7 @@
final int[] mTmpLocation = new int[2];
final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
+ onComputeInsets(mTmpInsets);
if (isExtractViewShown()) {
// In true fullscreen mode, we just say the window isn't covering
// any content so we don't impact whatever is behind.
@@ -442,12 +444,15 @@
info.touchableRegion.setEmpty();
info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
} else {
- onComputeInsets(mTmpInsets);
info.contentInsets.top = mTmpInsets.contentTopInsets;
info.visibleInsets.top = mTmpInsets.visibleTopInsets;
info.touchableRegion.set(mTmpInsets.touchableRegion);
info.setTouchableInsets(mTmpInsets.touchableInsets);
}
+
+ if (mInputFrame != null) {
+ setImeExclusionRect(mTmpInsets.visibleTopInsets);
+ }
};
final View.OnClickListener mActionClickListener = v -> {
@@ -672,6 +677,14 @@
mPrivOps.setImeWindowStatus(visibilityFlags, backDisposition);
}
+ /** Set region of the keyboard to be avoided from back gesture */
+ private void setImeExclusionRect(int visibleTopInsets) {
+ View inputFrameRootView = mInputFrame.getRootView();
+ Rect r = new Rect(0, visibleTopInsets, inputFrameRootView.getWidth(),
+ inputFrameRootView.getHeight());
+ inputFrameRootView.setSystemGestureExclusionRects(Collections.singletonList(r));
+ }
+
/**
* Concrete implementation of
* {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 3e325b7..e3259ff 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -57,6 +57,9 @@
private static final String TAG = "NetworkCapabilities";
private static final int INVALID_UID = -1;
+ // Set to true when private DNS is broken.
+ private boolean mPrivateDnsBroken;
+
/**
* @hide
*/
@@ -86,6 +89,7 @@
mUids = null;
mEstablishingVpnAppUid = INVALID_UID;
mSSID = null;
+ mPrivateDnsBroken = false;
}
/**
@@ -104,6 +108,7 @@
mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid;
mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities;
mSSID = nc.mSSID;
+ mPrivateDnsBroken = nc.mPrivateDnsBroken;
}
/**
@@ -557,6 +562,9 @@
}
if (mLinkUpBandwidthKbps != 0 || mLinkDownBandwidthKbps != 0) return "link bandwidth";
if (hasSignalStrength()) return "signalStrength";
+ if (isPrivateDnsBroken()) {
+ return "privateDnsBroken";
+ }
return null;
}
@@ -1443,7 +1451,8 @@
&& equalsSpecifier(that)
&& equalsTransportInfo(that)
&& equalsUids(that)
- && equalsSSID(that));
+ && equalsSSID(that)
+ && equalsPrivateDnsBroken(that));
}
@Override
@@ -1460,7 +1469,8 @@
+ (mSignalStrength * 29)
+ Objects.hashCode(mUids) * 31
+ Objects.hashCode(mSSID) * 37
- + Objects.hashCode(mTransportInfo) * 41;
+ + Objects.hashCode(mTransportInfo) * 41
+ + Objects.hashCode(mPrivateDnsBroken) * 43;
}
@Override
@@ -1479,6 +1489,7 @@
dest.writeInt(mSignalStrength);
dest.writeArraySet(mUids);
dest.writeString(mSSID);
+ dest.writeBoolean(mPrivateDnsBroken);
}
public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
@@ -1498,6 +1509,7 @@
netCap.mUids = (ArraySet<UidRange>) in.readArraySet(
null /* ClassLoader, null for default */);
netCap.mSSID = in.readString();
+ netCap.mPrivateDnsBroken = in.readBoolean();
return netCap;
}
@Override
@@ -1555,6 +1567,10 @@
sb.append(" SSID: ").append(mSSID);
}
+ if (mPrivateDnsBroken) {
+ sb.append(" Private DNS is broken");
+ }
+
sb.append("]");
return sb.toString();
}
@@ -1706,4 +1722,28 @@
public boolean isMetered() {
return !hasCapability(NET_CAPABILITY_NOT_METERED);
}
+
+ /**
+ * Check if private dns is broken.
+ *
+ * @return {@code true} if {@code mPrivateDnsBroken} is set when private DNS is broken.
+ * @hide
+ */
+ public boolean isPrivateDnsBroken() {
+ return mPrivateDnsBroken;
+ }
+
+ /**
+ * Set mPrivateDnsBroken to true when private dns is broken.
+ *
+ * @param broken the status of private DNS to be set.
+ * @hide
+ */
+ public void setPrivateDnsBroken(boolean broken) {
+ mPrivateDnsBroken = broken;
+ }
+
+ private boolean equalsPrivateDnsBroken(NetworkCapabilities nc) {
+ return mPrivateDnsBroken == nc.mPrivateDnsBroken;
+ }
}
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
index 9ba3bd9..4ad52d5 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkMisc.java
@@ -77,6 +77,12 @@
*/
public boolean skip464xlat;
+ /**
+ * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
+ * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
+ */
+ public boolean hasShownBroken;
+
public NetworkMisc() {
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c6b63ca..71b94ed 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1748,8 +1748,30 @@
}
}
- /** {@hide} */
- public boolean isUserUnlockingOrUnlocked(UserHandle user) {
+ /**
+ * Return whether the provided user is already running in an
+ * "unlocked" state or in the process of unlocking.
+ * <p>
+ * On devices with direct boot, a user is unlocked only after they've
+ * entered their credentials (such as a lock pattern or PIN). On devices
+ * without direct boot, a user is unlocked as soon as it starts.
+ * <p>
+ * When a user is locked, only device-protected data storage is available.
+ * When a user is unlocked, both device-protected and credential-protected
+ * private app data storage is available.
+ *
+ * <p>Requires {@code android.permission.MANAGE_USERS} or
+ * {@code android.permission.INTERACT_ACROSS_USERS}, otherwise specified {@link UserHandle user}
+ * must be the calling user or a profile associated with it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS
+ })
+ public boolean isUserUnlockingOrUnlocked(@NonNull UserHandle user) {
return isUserUnlockingOrUnlocked(user.getIdentifier());
}
@@ -2568,6 +2590,22 @@
}
/**
+ * Checks if the 2 provided user handles belong to the same profile group.
+ *
+ * @param user one of the two user handles to check.
+ * @param otherUser one of the two user handles to check.
+ * @return true if the two users are in the same profile group.
+ *
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public boolean isSameProfileGroup(@NonNull UserHandle user, @NonNull UserHandle otherUser) {
+ return isSameProfileGroup(user.getIdentifier(), otherUser.getIdentifier());
+ }
+
+ /**
* Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
* @param userId one of the two user ids to check.
* @param otherUserId one of the two user ids to check.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bdc7834..cff99f3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8594,13 +8594,19 @@
public static final String POWER_SOUNDS_ENABLED = "power_sounds_enabled";
/**
- * URI for the "wireless charging started" and "wired charging started" sound.
+ * URI for the "wireless charging started" sound.
* @hide
*/
- public static final String CHARGING_STARTED_SOUND =
+ public static final String WIRELESS_CHARGING_STARTED_SOUND =
"wireless_charging_started_sound";
/**
+ * URI for "wired charging started" sound.
+ * @hide
+ */
+ public static final String CHARGING_STARTED_SOUND = "charging_started_sound";
+
+ /**
* Whether to play a sound for charging events.
* @deprecated Use {@link android.provider.Settings.Secure#CHARGING_SOUNDS_ENABLED} instead
* @hide
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 94b9d05..48ba429 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -181,6 +181,23 @@
public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 0x10;
/**
+ * Type used when the {@link FillResponse} represents a debit card.
+ */
+ public static final int SAVE_DATA_TYPE_DEBIT_CARD = 0x20;
+
+ /**
+ * Type used when the {@link FillResponse} represents a payment card except for credit and
+ * debit cards.
+ */
+ public static final int SAVE_DATA_TYPE_PAYMENT_CARD = 0x40;
+
+ /**
+ * Type used when the {@link FillResponse} represents a card that does not a specified card or
+ * cannot identify what the card is for.
+ */
+ public static final int SAVE_DATA_TYPE_GENERIC_CARD = 0x80;
+
+ /**
* Style for the negative button of the save UI to cancel the
* save operation. In this case, the user tapping the negative
* button signals that they would prefer to not save the filled
@@ -207,6 +224,30 @@
@Retention(RetentionPolicy.SOURCE)
@interface NegativeButtonStyle{}
+ /**
+ * Style for the positive button of save UI to request the save operation.
+ * In this case, the user tapping the positive button signals that they
+ * agrees to save the filled content.
+ */
+ public static final int POSITIVE_BUTTON_STYLE_SAVE = 0;
+
+ /**
+ * Style for the positive button of save UI to have next action before the save operation.
+ * This could be useful if the filled content contains sensitive personally identifiable
+ * information and then requires user confirmation or verification. In this case, the user
+ * tapping the positive button signals that they would complete the next required action
+ * to save the filled content.
+ */
+ public static final int POSITIVE_BUTTON_STYLE_CONTINUE = 1;
+
+ /** @hide */
+ @IntDef(prefix = { "POSITIVE_BUTTON_STYLE_" }, value = {
+ POSITIVE_BUTTON_STYLE_SAVE,
+ POSITIVE_BUTTON_STYLE_CONTINUE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface PositiveButtonStyle{}
+
/** @hide */
@IntDef(flag = true, prefix = { "SAVE_DATA_TYPE_" }, value = {
SAVE_DATA_TYPE_GENERIC,
@@ -214,7 +255,10 @@
SAVE_DATA_TYPE_ADDRESS,
SAVE_DATA_TYPE_CREDIT_CARD,
SAVE_DATA_TYPE_USERNAME,
- SAVE_DATA_TYPE_EMAIL_ADDRESS
+ SAVE_DATA_TYPE_EMAIL_ADDRESS,
+ SAVE_DATA_TYPE_DEBIT_CARD,
+ SAVE_DATA_TYPE_PAYMENT_CARD,
+ SAVE_DATA_TYPE_GENERIC_CARD
})
@Retention(RetentionPolicy.SOURCE)
@interface SaveDataType{}
@@ -266,6 +310,7 @@
private final @SaveDataType int mType;
private final @NegativeButtonStyle int mNegativeButtonStyle;
+ private final @PositiveButtonStyle int mPositiveButtonStyle;
private final IntentSender mNegativeActionListener;
private final AutofillId[] mRequiredIds;
private final AutofillId[] mOptionalIds;
@@ -281,6 +326,7 @@
mType = builder.mType;
mNegativeButtonStyle = builder.mNegativeButtonStyle;
mNegativeActionListener = builder.mNegativeActionListener;
+ mPositiveButtonStyle = builder.mPositiveButtonStyle;
mRequiredIds = builder.mRequiredIds;
mOptionalIds = builder.mOptionalIds;
mDescription = builder.mDescription;
@@ -313,6 +359,11 @@
}
/** @hide */
+ public @PositiveButtonStyle int getPositiveActionStyle() {
+ return mPositiveButtonStyle;
+ }
+
+ /** @hide */
public @Nullable AutofillId[] getRequiredIds() {
return mRequiredIds;
}
@@ -374,6 +425,7 @@
private final @SaveDataType int mType;
private @NegativeButtonStyle int mNegativeButtonStyle = NEGATIVE_BUTTON_STYLE_CANCEL;
+ private @PositiveButtonStyle int mPositiveButtonStyle = POSITIVE_BUTTON_STYLE_SAVE;
private IntentSender mNegativeActionListener;
private final AutofillId[] mRequiredIds;
private AutofillId[] mOptionalIds;
@@ -394,8 +446,9 @@
* can be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
* {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
* {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD},
- * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or
- * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
+ * {@link SaveInfo#SAVE_DATA_TYPE_DEBIT_CARD}, {@link SaveInfo#SAVE_DATA_TYPE_PAYMENT_CARD},
+ * {@link SaveInfo#SAVE_DATA_TYPE_GENERIC_CARD}, {@link SaveInfo#SAVE_DATA_TYPE_USERNAME},
+ * or {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
* @param requiredIds ids of all required views that will trigger a save request.
*
* <p>See {@link SaveInfo} for more info.
@@ -418,8 +471,9 @@
* can be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
* {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
* {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD},
- * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or
- * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
+ * {@link SaveInfo#SAVE_DATA_TYPE_DEBIT_CARD}, {@link SaveInfo#SAVE_DATA_TYPE_PAYMENT_CARD},
+ * {@link SaveInfo#SAVE_DATA_TYPE_GENERIC_CARD}, {@link SaveInfo#SAVE_DATA_TYPE_USERNAME},
+ * or {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
*
* <p>See {@link SaveInfo} for more info.
*/
@@ -523,6 +577,8 @@
public @NonNull Builder setNegativeAction(@NegativeButtonStyle int style,
@Nullable IntentSender listener) {
throwIfDestroyed();
+ Preconditions.checkArgumentInRange(style, NEGATIVE_BUTTON_STYLE_CANCEL,
+ NEGATIVE_BUTTON_STYLE_REJECT, "style");
if (style != NEGATIVE_BUTTON_STYLE_CANCEL
&& style != NEGATIVE_BUTTON_STYLE_REJECT) {
throw new IllegalArgumentException("Invalid style: " + style);
@@ -533,6 +589,33 @@
}
/**
+ * Sets the style for the positive save action.
+ *
+ * <p>This allows an autofill service to customize the style of the
+ * positive action in the save UI. Note that selecting the positive
+ * action regardless of its style would dismiss the save UI and calling
+ * into the {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback) save request}.
+ * The service should take the next action if selecting style
+ * {@link #POSITIVE_BUTTON_STYLE_CONTINUE}. The default style is
+ * {@link #POSITIVE_BUTTON_STYLE_SAVE}
+ *
+ * @param style The action style.
+ * @return This builder.
+ *
+ * @see #POSITIVE_BUTTON_STYLE_SAVE
+ * @see #POSITIVE_BUTTON_STYLE_CONTINUE
+ *
+ * @throws IllegalArgumentException If the style is invalid
+ */
+ public @NonNull Builder setPositiveAction(@PositiveButtonStyle int style) {
+ throwIfDestroyed();
+ Preconditions.checkArgumentInRange(style, POSITIVE_BUTTON_STYLE_SAVE,
+ POSITIVE_BUTTON_STYLE_CONTINUE, "style");
+ mPositiveButtonStyle = style;
+ return this;
+ }
+
+ /**
* Sets an object used to validate the user input - if the input is not valid, the
* autofill save UI is not shown.
*
@@ -717,8 +800,10 @@
final StringBuilder builder = new StringBuilder("SaveInfo: [type=")
.append(DebugUtils.flagsToString(SaveInfo.class, "SAVE_DATA_TYPE_", mType))
.append(", requiredIds=").append(Arrays.toString(mRequiredIds))
- .append(", style=").append(DebugUtils.flagsToString(SaveInfo.class,
- "NEGATIVE_BUTTON_STYLE_", mNegativeButtonStyle));
+ .append(", negative style=").append(DebugUtils.flagsToString(SaveInfo.class,
+ "NEGATIVE_BUTTON_STYLE_", mNegativeButtonStyle))
+ .append(", positive style=").append(DebugUtils.flagsToString(SaveInfo.class,
+ "POSITIVE_BUTTON_STYLE_", mPositiveButtonStyle));
if (mOptionalIds != null) {
builder.append(", optionalIds=").append(Arrays.toString(mOptionalIds));
}
@@ -763,6 +848,7 @@
parcel.writeParcelableArray(mOptionalIds, flags);
parcel.writeInt(mNegativeButtonStyle);
parcel.writeParcelable(mNegativeActionListener, flags);
+ parcel.writeInt(mPositiveButtonStyle);
parcel.writeCharSequence(mDescription);
parcel.writeParcelable(mCustomDescription, flags);
parcel.writeParcelable(mValidator, flags);
@@ -794,6 +880,7 @@
}
builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null));
+ builder.setPositiveAction(parcel.readInt());
builder.setDescription(parcel.readCharSequence());
final CustomDescription customDescripton = parcel.readParcelable(null);
if (customDescripton != null) {
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index 8a9f689..12c2580 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -516,7 +516,7 @@
* @see android.telephony.euicc.EuiccManager#eraseSubscriptions
*
* @deprecated From R, callers should specify a flag for specific set of subscriptions to erase
- * and use @link{onEraseSubscriptionsWithOptions} instead
+ * and use {@link #onEraseSubscriptionsWithOptions(int, int)} instead
*/
@Deprecated
public abstract int onEraseSubscriptions(int slotId);
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index b9a92b5..28a8a86 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -274,6 +274,7 @@
// Fall into normal sort when number of ranked elements
// needed is not smaller than size of input list.
sort(inputList);
+ return;
}
try {
long beforeRank = System.currentTimeMillis();
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 4099cfa..8391ad2 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -49,9 +49,10 @@
* you do not need to call this API directly. The change will be reported for you.
*
* @param changeId The ID of the compatibility change taking effect.
+ * @param userId The ID of the user that the operation is done for.
* @param packageName The package name of the app in question.
*/
- void reportChangeByPackageName(long changeId, in String packageName);
+ void reportChangeByPackageName(long changeId, in String packageName, int userId);
/**
* Reports that a compatibility change is affecting an app process now.
@@ -86,7 +87,7 @@
* be called when implementing functionality on behalf of the affected app.
*
* <p>Same as {@link #isChangeEnabled(long, ApplicationInfo)}, except it receives a package name
- * instead of an {@link ApplicationInfo}
+ * and userId instead of an {@link ApplicationInfo}
* object, and finds an app info object based on the package name. Returns {@code true} if
* there is no installed package by that name.
*
@@ -100,9 +101,10 @@
*
* @param changeId The ID of the compatibility change in question.
* @param packageName The package name of the app in question.
+ * @param userId The ID of the user that the operation is done for.
* @return {@code true} if the change is enabled for the current app.
*/
- boolean isChangeEnabledByPackageName(long changeId, in String packageName);
+ boolean isChangeEnabledByPackageName(long changeId, in String packageName, int userId);
/**
* Query if a given compatibility change is enabled for an app process. This method should
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index d9e2ba3..317469e 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -155,12 +155,12 @@
// Used to show the authentication dialog (Biometrics, Device Credential)
void showAuthenticationDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver,
int biometricModality, boolean requireConfirmation, int userId, String opPackageName);
- // Used to notify the authentication dialog that a biometric has been authenticated or rejected
- void onBiometricAuthenticated(boolean authenticated, String failureReason);
+ // Used to notify the authentication dialog that a biometric has been authenticated
+ void onBiometricAuthenticated();
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
void onBiometricHelp(String message);
- // Used to set a message - the dialog will dismiss after a certain amount of time
- void onBiometricError(int errorCode, String error);
+ // Used to show an error - the dialog will dismiss after a certain amount of time
+ void onBiometricError(int modality, int error, int vendorCode);
// Used to hide the authentication dialog, e.g. when the application cancels authentication
void hideAuthenticationDialog();
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 659134a..499a4d2 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -104,12 +104,12 @@
// Used to show the authentication dialog (Biometrics, Device Credential)
void showAuthenticationDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver,
int biometricModality, boolean requireConfirmation, int userId, String opPackageName);
- // Used to notify the authentication dialog that a biometric has been authenticated or rejected
- void onBiometricAuthenticated(boolean authenticated, String failureReason);
+ // Used to notify the authentication dialog that a biometric has been authenticated
+ void onBiometricAuthenticated();
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
void onBiometricHelp(String message);
- // Used to set a message - the dialog will dismiss after a certain amount of time
- void onBiometricError(int errorCode, String error);
+ // Used to show an error - the dialog will dismiss after a certain amount of time
+ void onBiometricError(int modality, int error, int vendorCode);
// Used to hide the authentication dialog, e.g. when the application cancels authentication
void hideAuthenticationDialog();
}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index b626fc6..897b982 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -24,6 +24,7 @@
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.RecoveryCertPath;
import com.android.internal.widget.ICheckCredentialProgressCallback;
+import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import java.util.Map;
@@ -42,19 +43,16 @@
long getLong(in String key, in long defaultValue, in int userId);
@UnsupportedAppUsage
String getString(in String key, in String defaultValue, in int userId);
- boolean setLockCredential(in byte[] credential, int type, in byte[] savedCredential, int requestedQuality, int userId, boolean allowUntrustedChange);
+ boolean setLockCredential(in LockscreenCredential credential, in LockscreenCredential savedCredential, int userId, boolean allowUntrustedChange);
void resetKeyStore(int userId);
- VerifyCredentialResponse checkCredential(in byte[] credential, int type, int userId,
+ VerifyCredentialResponse checkCredential(in LockscreenCredential credential, int userId,
in ICheckCredentialProgressCallback progressCallback);
- VerifyCredentialResponse verifyCredential(in byte[] credential, int type, long challenge, int userId);
- VerifyCredentialResponse verifyTiedProfileChallenge(in byte[] credential, int type, long challenge, int userId);
+ VerifyCredentialResponse verifyCredential(in LockscreenCredential credential, long challenge, int userId);
+ VerifyCredentialResponse verifyTiedProfileChallenge(in LockscreenCredential credential, long challenge, int userId);
boolean checkVoldPassword(int userId);
- @UnsupportedAppUsage
- boolean havePattern(int userId);
- @UnsupportedAppUsage
- boolean havePassword(int userId);
- byte[] getHashFactor(in byte[] currentCredential, int userId);
- void setSeparateProfileChallengeEnabled(int userId, boolean enabled, in byte[] managedUserPassword);
+ int getCredentialType(int userId);
+ byte[] getHashFactor(in LockscreenCredential currentCredential, int userId);
+ void setSeparateProfileChallengeEnabled(int userId, boolean enabled, in LockscreenCredential managedUserPassword);
boolean getSeparateProfileChallengeEnabled(int userId);
void registerStrongAuthTracker(in IStrongAuthTracker tracker);
void unregisterStrongAuthTracker(in IStrongAuthTracker tracker);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 7a42985..b534213 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -17,9 +17,6 @@
package com.android.internal.widget;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
@@ -59,10 +56,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
-import libcore.util.HexEncoding;
-
import com.google.android.collect.Lists;
+import libcore.util.HexEncoding;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.MessageDigest;
@@ -117,13 +114,19 @@
// NOTE: When modifying this, make sure credential sufficiency validation logic is intact.
public static final int CREDENTIAL_TYPE_NONE = -1;
public static final int CREDENTIAL_TYPE_PATTERN = 1;
- public static final int CREDENTIAL_TYPE_PASSWORD = 2;
+ // This is the legacy value persisted on disk. Never return it to clients, but internally
+ // we still need it to handle upgrade cases.
+ public static final int CREDENTIAL_TYPE_PASSWORD_OR_PIN = 2;
+ public static final int CREDENTIAL_TYPE_PIN = 3;
+ public static final int CREDENTIAL_TYPE_PASSWORD = 4;
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"CREDENTIAL_TYPE_"}, value = {
CREDENTIAL_TYPE_NONE,
CREDENTIAL_TYPE_PATTERN,
- CREDENTIAL_TYPE_PASSWORD, // Either pin or password.
+ CREDENTIAL_TYPE_PASSWORD,
+ CREDENTIAL_TYPE_PIN,
+ // CREDENTIAL_TYPE_PASSWORD_OR_PIN is missing on purpose.
})
public @interface CredentialType {}
@@ -169,6 +172,7 @@
public static final String SYNTHETIC_PASSWORD_HANDLE_KEY = "sp-handle";
public static final String SYNTHETIC_PASSWORD_ENABLED_KEY = "enable-sp";
+ public static final int SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT = 1;
private static final String HISTORY_DELIMITER = ",";
@UnsupportedAppUsage
@@ -372,8 +376,8 @@
*
* @param credential The credential to check.
* @param challenge The challenge to verify against the credential
- * @return the attestation that the challenge was verified, or null
* @param userId The user whose credential is being verified
+ * @return the attestation that the challenge was verified, or null
* @throws RequestThrottledException if credential verification is being throttled due to
* to many incorrect attempts.
* @throws IllegalStateException if called on the main thread.
@@ -383,7 +387,7 @@
throwIfCalledOnMainThread();
try {
VerifyCredentialResponse response = getLockSettings().verifyCredential(
- credential.getCredential(), credential.getType(), challenge, userId);
+ credential, challenge, userId);
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
return response.getPayload();
} else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
@@ -392,6 +396,7 @@
return null;
}
} catch (RemoteException re) {
+ Log.e(TAG, "failed to verify credential", re);
return null;
}
}
@@ -413,8 +418,7 @@
throwIfCalledOnMainThread();
try {
VerifyCredentialResponse response = getLockSettings().checkCredential(
- credential.getCredential(), credential.getType(),
- userId, wrapCallback(progressCallback));
+ credential, userId, wrapCallback(progressCallback));
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
return true;
@@ -424,6 +428,7 @@
return false;
}
} catch (RemoteException re) {
+ Log.e(TAG, "failed to check credential", re);
return false;
}
}
@@ -447,8 +452,7 @@
throwIfCalledOnMainThread();
try {
VerifyCredentialResponse response =
- getLockSettings().verifyTiedProfileChallenge(
- credential.getCredential(), credential.getType(), challenge, userId);
+ getLockSettings().verifyTiedProfileChallenge(credential, challenge, userId);
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
return response.getPayload();
@@ -458,6 +462,7 @@
return null;
}
} catch (RemoteException re) {
+ Log.e(TAG, "failed to verify tied profile credential", re);
return null;
}
}
@@ -471,6 +476,7 @@
try {
return getLockSettings().checkVoldPassword(userId);
} catch (RemoteException re) {
+ Log.e(TAG, "failed to check vold password", re);
return false;
}
}
@@ -482,7 +488,7 @@
public byte[] getPasswordHistoryHashFactor(@NonNull LockscreenCredential currentPassword,
int userId) {
try {
- return getLockSettings().getHashFactor(currentPassword.getCredential(), userId);
+ return getLockSettings().getHashFactor(currentPassword, userId);
} catch (RemoteException e) {
Log.e(TAG, "failed to get hash factor", e);
return null;
@@ -524,30 +530,6 @@
}
/**
- * Check to see if the user has stored a lock pattern.
- * @return Whether a saved pattern exists.
- */
- private boolean savedPatternExists(int userId) {
- try {
- return getLockSettings().havePattern(userId);
- } catch (RemoteException re) {
- return false;
- }
- }
-
- /**
- * Check to see if the user has stored a lock pattern.
- * @return Whether a saved pattern exists.
- */
- private boolean savedPasswordExists(int userId) {
- try {
- return getLockSettings().havePassword(userId);
- } catch (RemoteException re) {
- return false;
- }
- }
-
- /**
* Return true if the user has ever chosen a pattern. This is true even if the pattern is
* currently cleared.
*
@@ -568,22 +550,11 @@
/**
* Used by device policy manager to validate the current password
* information it has.
+ * @Deprecated use {@link #getKeyguardStoredPasswordQuality}
*/
@UnsupportedAppUsage
public int getActivePasswordQuality(int userId) {
- int quality = getKeyguardStoredPasswordQuality(userId);
-
- if (isLockPasswordEnabled(quality, userId)) {
- // Quality is a password and a password exists. Return the quality.
- return quality;
- }
-
- if (isLockPatternEnabled(quality, userId)) {
- // Quality is a pattern and a pattern exists. Return the quality.
- return quality;
- }
-
- return PASSWORD_QUALITY_UNSPECIFIED;
+ return getKeyguardStoredPasswordQuality(userId);
}
/**
@@ -641,6 +612,22 @@
return quality == PASSWORD_QUALITY_NUMERIC || quality == PASSWORD_QUALITY_NUMERIC_COMPLEX;
}
+ /** Returns the canonical password quality corresponding to the given credential type. */
+ public static int credentialTypeToPasswordQuality(int credentialType) {
+ switch (credentialType) {
+ case CREDENTIAL_TYPE_NONE:
+ return PASSWORD_QUALITY_UNSPECIFIED;
+ case CREDENTIAL_TYPE_PATTERN:
+ return PASSWORD_QUALITY_SOMETHING;
+ case CREDENTIAL_TYPE_PIN:
+ return PASSWORD_QUALITY_NUMERIC;
+ case CREDENTIAL_TYPE_PASSWORD:
+ return PASSWORD_QUALITY_ALPHABETIC;
+ default:
+ throw new IllegalStateException("Unknown type: " + credentialType);
+ }
+ }
+
/**
* Save a new lockscreen credential.
*
@@ -684,19 +671,12 @@
}
newCredential.checkLength();
- final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
- setKeyguardStoredPasswordQuality(newCredential.getQuality(), userHandle);
-
try {
if (!getLockSettings().setLockCredential(
- newCredential.getCredential(), newCredential.getType(),
- savedCredential.getCredential(),
- newCredential.getQuality(), userHandle, allowUntrustedChange)) {
- setKeyguardStoredPasswordQuality(currentQuality, userHandle);
+ newCredential, savedCredential, userHandle, allowUntrustedChange)) {
return false;
}
- } catch (RemoteException | RuntimeException e) {
- setKeyguardStoredPasswordQuality(currentQuality, userHandle);
+ } catch (RemoteException e) {
throw new RuntimeException("Unable to save lock password", e);
}
@@ -904,14 +884,12 @@
* @see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)
*
* @return stored password quality
+ * @deprecated use {@link #getCredentialTypeForUser(int)} instead
*/
@UnsupportedAppUsage
+ @Deprecated
public int getKeyguardStoredPasswordQuality(int userHandle) {
- return (int) getLong(PASSWORD_TYPE_KEY, PASSWORD_QUALITY_UNSPECIFIED, userHandle);
- }
-
- private void setKeyguardStoredPasswordQuality(int quality, int userHandle) {
- setLong(PASSWORD_TYPE_KEY, quality, userHandle);
+ return credentialTypeToPasswordQuality(getCredentialTypeForUser(userHandle));
}
/**
@@ -920,17 +898,17 @@
*
* @param userHandle Managed profile user id
* @param enabled True if separate challenge is enabled
- * @param managedUserPassword Managed profile previous password. Null when {@code enabled} is
+ * @param profilePassword Managed profile previous password. Null when {@code enabled} is
* true
*/
public void setSeparateProfileChallengeEnabled(int userHandle, boolean enabled,
- LockscreenCredential managedUserPassword) {
+ LockscreenCredential profilePassword) {
if (!isManagedProfile(userHandle)) {
return;
}
try {
getLockSettings().setSeparateProfileChallengeEnabled(userHandle, enabled,
- managedUserPassword.getCredential());
+ profilePassword);
reportEnabledTrustAgentsChanged(userHandle);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't update work profile challenge enabled");
@@ -1098,28 +1076,33 @@
}
/**
+ * Returns the credential type of the user, can be one of {@link #CREDENTIAL_TYPE_NONE},
+ * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} and
+ * {@link #CREDENTIAL_TYPE_PASSWORD}
+ */
+ public @CredentialType int getCredentialTypeForUser(int userHandle) {
+ try {
+ return getLockSettings().getCredentialType(userHandle);
+ } catch (RemoteException re) {
+ Log.e(TAG, "failed to get credential type", re);
+ return CREDENTIAL_TYPE_NONE;
+ }
+ }
+
+ /**
* @param userId the user for which to report the value
* @return Whether the lock screen is secured.
*/
@UnsupportedAppUsage
public boolean isSecure(int userId) {
- int mode = getKeyguardStoredPasswordQuality(userId);
- return isLockPatternEnabled(mode, userId) || isLockPasswordEnabled(mode, userId);
+ int type = getCredentialTypeForUser(userId);
+ return type != CREDENTIAL_TYPE_NONE;
}
@UnsupportedAppUsage
public boolean isLockPasswordEnabled(int userId) {
- return isLockPasswordEnabled(getKeyguardStoredPasswordQuality(userId), userId);
- }
-
- private boolean isLockPasswordEnabled(int mode, int userId) {
- final boolean passwordEnabled = mode == PASSWORD_QUALITY_ALPHABETIC
- || mode == PASSWORD_QUALITY_NUMERIC
- || mode == PASSWORD_QUALITY_NUMERIC_COMPLEX
- || mode == PASSWORD_QUALITY_ALPHANUMERIC
- || mode == PASSWORD_QUALITY_COMPLEX
- || mode == PASSWORD_QUALITY_MANAGED;
- return passwordEnabled && savedPasswordExists(userId);
+ int type = getCredentialTypeForUser(userId);
+ return type == CREDENTIAL_TYPE_PASSWORD || type == CREDENTIAL_TYPE_PIN;
}
/**
@@ -1127,7 +1110,8 @@
*/
@UnsupportedAppUsage
public boolean isLockPatternEnabled(int userId) {
- return isLockPatternEnabled(getKeyguardStoredPasswordQuality(userId), userId);
+ int type = getCredentialTypeForUser(userId);
+ return type == CREDENTIAL_TYPE_PATTERN;
}
@Deprecated
@@ -1143,10 +1127,6 @@
setBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, true, userId);
}
- private boolean isLockPatternEnabled(int mode, int userId) {
- return mode == PASSWORD_QUALITY_SOMETHING && savedPatternExists(userId);
- }
-
/**
* @return Whether the visible pattern is enabled.
*/
@@ -1543,8 +1523,8 @@
* @param userHandle The user who's lock credential to be changed
* @return {@code true} if the operation is successful.
*/
- public boolean setLockCredentialWithToken(LockscreenCredential credential, long tokenHandle,
- byte[] token, int userHandle) {
+ public boolean setLockCredentialWithToken(@NonNull LockscreenCredential credential,
+ long tokenHandle, byte[] token, int userHandle) {
if (!hasSecureLockScreen()) {
throw new UnsupportedOperationException(
"This operation requires the lock screen feature.");
@@ -1552,19 +1532,8 @@
credential.checkLength();
LockSettingsInternal localService = getLockSettingsInternal();
- final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
- setKeyguardStoredPasswordQuality(credential.getQuality(), userHandle);
-
- try {
- if (!localService.setLockCredentialWithToken(credential.getCredential(),
- credential.getType(),
- tokenHandle, token, credential.getQuality(), userHandle)) {
- setKeyguardStoredPasswordQuality(currentQuality, userHandle);
- return false;
- }
- } catch (RuntimeException e) {
- setKeyguardStoredPasswordQuality(currentQuality, userHandle);
- throw new RuntimeException("Unable to save lock credential", e);
+ if (!localService.setLockCredentialWithToken(credential, tokenHandle, token, userHandle)) {
+ return false;
}
onPostPasswordChanged(credential, userHandle);
@@ -1765,7 +1734,8 @@
}
public boolean isSyntheticPasswordEnabled() {
- return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0;
+ return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT,
+ UserHandle.USER_SYSTEM) != 0;
}
/**
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index aab2f4b..dd05576 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -59,8 +59,8 @@
*
* @return true if password is set.
*/
- public abstract boolean setLockCredentialWithToken(byte[] credential, int type,
- long tokenHandle, byte[] token, int requestedQuality, int userId);
+ public abstract boolean setLockCredentialWithToken(LockscreenCredential credential,
+ long tokenHandle, byte[] token, int userId);
public abstract boolean unlockUserWithToken(long tokenHandle, byte[] token, int userId);
diff --git a/core/java/android/app/timedetector/TimeSignal.aidl b/core/java/com/android/internal/widget/LockscreenCredential.aidl
similarity index 75%
copy from core/java/android/app/timedetector/TimeSignal.aidl
copy to core/java/com/android/internal/widget/LockscreenCredential.aidl
index d2ec357..22501ff 100644
--- a/core/java/android/app/timedetector/TimeSignal.aidl
+++ b/core/java/com/android/internal/widget/LockscreenCredential.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,6 +14,10 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package com.android.internal.widget;
-parcelable TimeSignal;
\ No newline at end of file
+/**
+ * A class representing a lockscreen credential.
+ */
+parcelable LockscreenCredential;
+
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index 19e6d97..f456349 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -16,14 +16,11 @@
package com.android.internal.widget;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -61,9 +58,6 @@
// Stores raw credential bytes, or null if credential has been zeroized. An empty password
// is represented as a byte array of length 0.
private byte[] mCredential;
- // Store the quality of the password, this is used to distinguish between pin
- // (PASSWORD_QUALITY_NUMERIC) and password (PASSWORD_QUALITY_ALPHABETIC).
- private final int mQuality;
/**
* Private constructor, use static builder methods instead.
@@ -72,15 +66,18 @@
* LockscreenCredential will only store the reference internally without copying. This is to
* minimize the number of extra copies introduced.
*/
- private LockscreenCredential(int type, int quality, byte[] credential) {
+ private LockscreenCredential(int type, byte[] credential) {
Preconditions.checkNotNull(credential);
if (type == CREDENTIAL_TYPE_NONE) {
Preconditions.checkArgument(credential.length == 0);
} else {
+ // Do not allow constructing a CREDENTIAL_TYPE_PASSWORD_OR_PIN object.
+ Preconditions.checkArgument(type == CREDENTIAL_TYPE_PIN
+ || type == CREDENTIAL_TYPE_PASSWORD
+ || type == CREDENTIAL_TYPE_PATTERN);
Preconditions.checkArgument(credential.length > 0);
}
mType = type;
- mQuality = quality;
mCredential = credential;
}
@@ -88,8 +85,7 @@
* Creates a LockscreenCredential object representing empty password.
*/
public static LockscreenCredential createNone() {
- return new LockscreenCredential(CREDENTIAL_TYPE_NONE, PASSWORD_QUALITY_UNSPECIFIED,
- new byte[0]);
+ return new LockscreenCredential(CREDENTIAL_TYPE_NONE, new byte[0]);
}
/**
@@ -97,7 +93,6 @@
*/
public static LockscreenCredential createPattern(@NonNull List<LockPatternView.Cell> pattern) {
return new LockscreenCredential(CREDENTIAL_TYPE_PATTERN,
- PASSWORD_QUALITY_SOMETHING,
LockPatternUtils.patternToByteArray(pattern));
}
@@ -106,16 +101,25 @@
*/
public static LockscreenCredential createPassword(@NonNull CharSequence password) {
return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
- PASSWORD_QUALITY_ALPHABETIC,
charSequenceToByteArray(password));
}
/**
+ * Creates a LockscreenCredential object representing a managed password for profile with
+ * unified challenge. This credentiall will have type {@code CREDENTIAL_TYPE_PASSWORD} for now.
+ * TODO: consider add a new credential type for this. This can then supersede the
+ * isLockTiedToParent argument in various places in LSS.
+ */
+ public static LockscreenCredential createManagedPassword(@NonNull byte[] password) {
+ return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
+ Arrays.copyOf(password, password.length));
+ }
+
+ /**
* Creates a LockscreenCredential object representing the given numeric PIN.
*/
public static LockscreenCredential createPin(@NonNull CharSequence pin) {
- return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
- PASSWORD_QUALITY_NUMERIC,
+ return new LockscreenCredential(CREDENTIAL_TYPE_PIN,
charSequenceToByteArray(pin));
}
@@ -143,27 +147,13 @@
}
}
- /**
- * Create a LockscreenCredential object based on raw credential and type
- * TODO: Remove once LSS.setUserPasswordMetrics accepts a LockscreenCredential
- */
- public static LockscreenCredential createRaw(int type, byte[] credential) {
- if (type == CREDENTIAL_TYPE_NONE) {
- return createNone();
- } else {
- return new LockscreenCredential(type, PASSWORD_QUALITY_UNSPECIFIED, credential);
- }
- }
-
private void ensureNotZeroized() {
Preconditions.checkState(mCredential != null, "Credential is already zeroized");
}
/**
* Returns the type of this credential. Can be one of {@link #CREDENTIAL_TYPE_NONE},
- * {@link #CREDENTIAL_TYPE_PATTERN} or {@link #CREDENTIAL_TYPE_PASSWORD}.
- *
- * TODO: Remove once credential type is internal. Callers should use {@link #isNone},
- * {@link #isPattern} and {@link #isPassword} instead.
+ * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} or
+ * {@link #CREDENTIAL_TYPE_PASSWORD}.
*/
public int getType() {
ensureNotZeroized();
@@ -171,14 +161,6 @@
}
/**
- * Returns the quality type of the credential
- */
- public int getQuality() {
- ensureNotZeroized();
- return mQuality;
- }
-
- /**
* Returns the credential bytes. This is a direct reference of the internal field so
* callers should not modify it.
*
@@ -200,9 +182,11 @@
if (isPattern()) {
return StorageManager.CRYPT_TYPE_PATTERN;
}
+ if (isPin()) {
+ return StorageManager.CRYPT_TYPE_PIN;
+ }
if (isPassword()) {
- return mQuality == PASSWORD_QUALITY_NUMERIC
- ? StorageManager.CRYPT_TYPE_PIN : StorageManager.CRYPT_TYPE_PASSWORD;
+ return StorageManager.CRYPT_TYPE_PASSWORD;
}
throw new IllegalStateException("Unhandled credential type");
}
@@ -219,7 +203,13 @@
return mType == CREDENTIAL_TYPE_PATTERN;
}
- /** Returns whether this is a password credential */
+ /** Returns whether this is a numeric pin credential */
+ public boolean isPin() {
+ ensureNotZeroized();
+ return mType == CREDENTIAL_TYPE_PIN;
+ }
+
+ /** Returns whether this is an alphabetic password credential */
public boolean isPassword() {
ensureNotZeroized();
return mType == CREDENTIAL_TYPE_PASSWORD;
@@ -233,7 +223,7 @@
/** Create a copy of the credential */
public LockscreenCredential duplicate() {
- return new LockscreenCredential(mType, mQuality,
+ return new LockscreenCredential(mType,
mCredential != null ? Arrays.copyOf(mCredential, mCredential.length) : null);
}
@@ -263,7 +253,7 @@
}
return;
}
- if (isPassword()) {
+ if (isPassword() || isPin()) {
if (size() < LockPatternUtils.MIN_LOCK_PASSWORD_SIZE) {
throw new IllegalArgumentException("password must not be null and at least "
+ "of length " + LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
@@ -272,10 +262,22 @@
}
}
+ /**
+ * Check if this credential's type matches one that's retrieved from disk. The nuance here is
+ * that the framework used to not distinguish between PIN and password, so this method will
+ * allow a PIN/Password LockscreenCredential to match against the legacy
+ * {@link #CREDENTIAL_TYPE_PASSWORD_OR_PIN} stored on disk.
+ */
+ public boolean checkAgainstStoredType(int storedCredentialType) {
+ if (storedCredentialType == CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
+ return getType() == CREDENTIAL_TYPE_PASSWORD || getType() == CREDENTIAL_TYPE_PIN;
+ }
+ return getType() == storedCredentialType;
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
- dest.writeInt(mQuality);
dest.writeByteArray(mCredential);
}
@@ -284,8 +286,7 @@
@Override
public LockscreenCredential createFromParcel(Parcel source) {
- return new LockscreenCredential(source.readInt(), source.readInt(),
- source.createByteArray());
+ return new LockscreenCredential(source.readInt(), source.createByteArray());
}
@Override
@@ -307,7 +308,7 @@
@Override
public int hashCode() {
// Effective Java — Item 9
- return ((17 + mType) * 31 + mQuality) * 31 + mCredential.hashCode();
+ return (17 + mType) * 31 + mCredential.hashCode();
}
@Override
@@ -315,8 +316,7 @@
if (o == this) return true;
if (!(o instanceof LockscreenCredential)) return false;
final LockscreenCredential other = (LockscreenCredential) o;
- return mType == other.mType && mQuality == other.mQuality
- && Arrays.equals(mCredential, other.mCredential);
+ return mType == other.mType && Arrays.equals(mCredential, other.mCredential);
}
/**
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index ea0389f..b752396 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -26,6 +26,7 @@
import android.os.Environment;
import android.os.Process;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.storage.StorageManager;
import android.permission.PermissionManager.SplitPermissionInfo;
import android.text.TextUtils;
@@ -33,6 +34,7 @@
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.TimingsTraceLog;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
@@ -411,7 +413,13 @@
}
SystemConfig() {
- readAllPermissions();
+ TimingsTraceLog log = new TimingsTraceLog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
+ log.traceBegin("readAllPermissions");
+ try {
+ readAllPermissions();
+ } finally {
+ log.traceEnd();
+ }
}
private void readAllPermissions() {
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index a568c13..f7d4b3f 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -853,6 +853,7 @@
optional SettingProto low_battery_sounds_enabled = 12 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto trusted = 13;
optional SettingProto unlock = 14;
+ optional SettingProto wireless_charging_started = 15;
}
optional Sounds sounds = 110;
diff --git a/core/proto/android/server/notificationhistory.proto b/core/proto/android/server/notificationhistory.proto
new file mode 100644
index 0000000..148bd7e
--- /dev/null
+++ b/core/proto/android/server/notificationhistory.proto
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package com.android.server.notification;
+
+import "frameworks/base/core/proto/android/server/enums.proto";
+
+option java_multiple_files = true;
+
+// On disk data store for historical notifications
+message NotificationHistoryProto {
+ message StringPool {
+ optional int32 size = 1;
+ repeated string strings = 2;
+ }
+
+ message Notification {
+ // The package that posted the notification
+ optional string package = 1;
+ // package_index contains the index + 1 of the package name in the string pool
+ optional int32 package_index = 2;
+
+ // The name of the NotificationChannel this notification was posted to
+ optional string channel_name = 3;
+ // channel_name_index contains the index + 1 of the channel name in the string pool
+ optional int32 channel_name_index = 4;
+
+ // The id of the NotificationChannel this notification was posted to
+ optional string channel_id = 5;
+ // channel_id_index contains the index + 1 of the channel id in the string pool
+ optional int32 channel_id_index = 6;
+
+ // The uid of the package that posted the notification
+ optional int32 uid = 7;
+ // The user id of the package that posted the notification
+ optional int32 user_id = 8;
+ // The time at which the notification was posted
+ optional int64 posted_time_ms = 9;
+ // The title of the notification
+ optional string title = 10;
+ // The text of the notification
+ optional string text = 11;
+ // The small icon of the notification
+ optional Icon icon = 12;
+
+ // Matches the constants of android.graphics.drawable.Icon
+ enum ImageTypeEnum {
+ TYPE_UNKNOWN = 0;
+ TYPE_BITMAP = 1;
+ TYPE_RESOURCE = 2;
+ TYPE_DATA = 3;
+ TYPE_URI = 4;
+ TYPE_ADAPTIVE_BITMAP = 5;
+ }
+
+ message Icon {
+ optional ImageTypeEnum image_type = 1;
+ optional string image_bitmap_filename = 2;
+ optional int32 image_resource_id = 3;
+ optional bytes image_data = 4;
+ optional string image_uri = 5;
+ }
+ }
+
+ // The time the last entry was written
+ optional int64 end_time_ms = 1;
+ // Pool of strings to save space
+ optional StringPool stringpool = 2;
+ // Versioning fields
+ optional int32 major_version = 3;
+ optional int32 minor_version = 4;
+
+ // List of historical notifications
+ repeated Notification notification = 5;
+}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 75cdb42..eaf93eb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4357,4 +4357,8 @@
create additional screen real estate outside beyond the keyboard. Note that the user needs
to have a confirmed way to dismiss the keyboard when desired. -->
<bool name="config_automotiveHideNavBarForKeyboard">false</bool>
+
+ <!-- Whether or not to show the built-in charging animation when the device begins charging
+ wirelessly. -->
+ <bool name="config_showBuiltinWirelessChargingAnim">true</bool>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index a01bbe3..9791241 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -41,6 +41,8 @@
<dimen name="quick_qs_offset_height">48dp</dimen>
<!-- Total height of QQS (quick_qs_offset_height + 128) -->
<dimen name="quick_qs_total_height">176dp</dimen>
+ <!-- Total height of QQS with two rows to fit media player (quick_qs_offset_height + 176) -->
+ <dimen name="quick_qs_total_height_with_media">224dp</dimen>
<!-- Height of the bottom navigation / system bar. -->
<dimen name="navigation_bar_height">48dp</dimen>
<!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index bf5f706..d39d507 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3440,6 +3440,15 @@
<!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. -->
<string name="wifi_no_internet_detailed">Tap for options</string>
+ <!-- A notification is shown when the user connects to a mobile network without internet access. This is the notification's title. -->
+ <string name="mobile_no_internet">Mobile network has no internet access</string>
+
+ <!-- A notification is shown when the user connects to a non-mobile and non-wifi network without internet access. This is the notification's title. -->
+ <string name="other_networks_no_internet">Network has no internet access</string>
+
+ <!-- A notification is shown when connected network without internet due to private dns validation failed. This is the notification's message. [CHAR LIMIT=NONE] -->
+ <string name="private_dns_broken_detailed">Private DNS server cannot be accessed</string>
+
<!-- A notification is shown after the user logs in to a captive portal network, to indicate that the network should now have internet connectivity. This is the message of notification. [CHAR LIMIT=50] -->
<string name="captive_portal_logged_in_detailed">Connected</string>
<!-- A notification is shown when the user connects to a network that doesn't have access to some services (e.g. Push notifications may not work). This is the notification's title. [CHAR LIMIT=50] -->
@@ -5205,6 +5214,8 @@
<string name="autofill_save_no">No thanks</string>
<!-- Label for the autofill update button [CHAR LIMIT=NONE] -->
<string name="autofill_update_yes">Update</string>
+ <!-- Label for the autofill continue button [CHAR LIMIT=NONE] -->
+ <string name="autofill_continue_yes">Continue</string>
<!-- Label for the type of data being saved for autofill when it represent user credentials with a password [CHAR LIMIT=NONE] -->
<string name="autofill_save_type_password">password</string>
@@ -5212,6 +5223,12 @@
<string name="autofill_save_type_address">address</string>
<!-- Label for the type of data being saved for autofill when it represents a credit card [CHAR LIMIT=NONE] -->
<string name="autofill_save_type_credit_card">credit card</string>
+ <!-- Label for the type of data being saved for autofill when it represents a debit card [CHAR LIMIT=NONE] -->
+ <string name="autofill_save_type_debit_card">debit card</string>
+ <!-- Label for the type of data being saved for autofill when it represents a payment card [CHAR LIMIT=NONE] -->
+ <string name="autofill_save_type_payment_card">payment card</string>
+ <!-- Label for the type of data being saved for autofill when it represents a card that does not a specified type or cannot identify what the type is for [CHAR LIMIT=NONE] -->
+ <string name="autofill_save_type_generic_card">card</string>
<!-- Label for the type of data being saved for autofill when it represents an username [CHAR LIMIT=NONE] -->
<string name="autofill_save_type_username">username</string>
<!-- Label for the type of data being saved for autofill when it represents an email address [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6bb4719..8d96c79 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -593,9 +593,12 @@
<java-symbol type="string" name="menu_space_shortcut_label" />
<java-symbol type="string" name="menu_shift_shortcut_label" />
<java-symbol type="string" name="menu_sym_shortcut_label" />
+ <java-symbol type="string" name="mobile_no_internet" />
<java-symbol type="string" name="notification_title" />
+ <java-symbol type="string" name="other_networks_no_internet" />
<java-symbol type="string" name="permission_request_notification_with_subtitle" />
<java-symbol type="string" name="prepend_shortcut_label" />
+ <java-symbol type="string" name="private_dns_broken_detailed" />
<java-symbol type="string" name="paste_as_plain_text" />
<java-symbol type="string" name="replace" />
<java-symbol type="string" name="undo" />
@@ -1774,6 +1777,7 @@
<java-symbol type="dimen" name="display_cutout_touchable_region_size" />
<java-symbol type="dimen" name="quick_qs_offset_height" />
<java-symbol type="dimen" name="quick_qs_total_height" />
+ <java-symbol type="dimen" name="quick_qs_total_height_with_media" />
<java-symbol type="drawable" name="ic_jog_dial_sound_off" />
<java-symbol type="drawable" name="ic_jog_dial_sound_on" />
<java-symbol type="drawable" name="ic_jog_dial_unlock" />
@@ -3355,6 +3359,7 @@
<java-symbol type="string" name="autofill_update_title_with_2types" />
<java-symbol type="string" name="autofill_update_title_with_3types" />
<java-symbol type="string" name="autofill_update_yes" />
+ <java-symbol type="string" name="autofill_continue_yes" />
<java-symbol type="string" name="autofill_save_accessibility_title " />
<java-symbol type="string" name="autofill_save_title" />
<java-symbol type="string" name="autofill_save_title_with_type" />
@@ -3365,6 +3370,9 @@
<java-symbol type="string" name="autofill_save_type_password" />
<java-symbol type="string" name="autofill_save_type_address" />
<java-symbol type="string" name="autofill_save_type_credit_card" />
+ <java-symbol type="string" name="autofill_save_type_debit_card" />
+ <java-symbol type="string" name="autofill_save_type_payment_card" />
+ <java-symbol type="string" name="autofill_save_type_generic_card" />
<java-symbol type="string" name="autofill_save_type_username" />
<java-symbol type="string" name="autofill_save_type_email_address" />
<java-symbol type="drawable" name="autofill_dataset_picker_background" />
@@ -3866,4 +3874,6 @@
<java-symbol type="bool" name="config_automotiveHideNavBarForKeyboard" />
+ <java-symbol type="bool" name="config_showBuiltinWirelessChargingAnim" />
+
</resources>
diff --git a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
new file mode 100644
index 0000000..1b5ad88
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.timedetector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.TimestampedValue;
+
+import org.junit.Test;
+
+public class PhoneTimeSuggestionTest {
+ private static final int PHONE_ID = 99999;
+
+ @Test
+ public void testEquals() {
+ PhoneTimeSuggestion one =
+ new PhoneTimeSuggestion(PHONE_ID, new TimestampedValue<>(1111L, 2222L));
+ assertEquals(one, one);
+
+ PhoneTimeSuggestion two =
+ new PhoneTimeSuggestion(PHONE_ID, new TimestampedValue<>(1111L, 2222L));
+ assertEquals(one, two);
+ assertEquals(two, one);
+
+ PhoneTimeSuggestion three =
+ new PhoneTimeSuggestion(PHONE_ID + 1, new TimestampedValue<>(1111L, 2222L));
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+
+ // DebugInfo must not be considered in equals().
+ one.addDebugInfo("Debug info 1");
+ two.addDebugInfo("Debug info 2");
+ assertEquals(one, two);
+ }
+
+ @Test
+ public void testParcelable() {
+ PhoneTimeSuggestion one =
+ new PhoneTimeSuggestion(PHONE_ID, new TimestampedValue<>(1111L, 2222L));
+ assertEquals(one, roundTripParcelable(one));
+
+ // DebugInfo should also be stored (but is not checked by equals()
+ one.addDebugInfo("This is debug info");
+ PhoneTimeSuggestion two = roundTripParcelable(one);
+ assertEquals(one.getDebugInfo(), two.getDebugInfo());
+ }
+
+ @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(PhoneTimeSuggestion.CREATOR);
+ parcel.recycle();
+ return toReturn;
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
index 5eec91c..05bab1c 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
@@ -31,10 +31,23 @@
assertEquals(0, empty.size());
assertNotNull(empty.getCredential());
+ assertFalse(empty.isPin());
assertFalse(empty.isPassword());
assertFalse(empty.isPattern());
}
+ public void testPinCredential() {
+ LockscreenCredential pin = LockscreenCredential.createPin("3456");
+
+ assertTrue(pin.isPin());
+ assertEquals(4, pin.size());
+ assertTrue(Arrays.equals("3456".getBytes(), pin.getCredential()));
+
+ assertFalse(pin.isNone());
+ assertFalse(pin.isPassword());
+ assertFalse(pin.isPattern());
+ }
+
public void testPasswordCredential() {
LockscreenCredential password = LockscreenCredential.createPassword("password");
@@ -43,6 +56,7 @@
assertTrue(Arrays.equals("password".getBytes(), password.getCredential()));
assertFalse(password.isNone());
+ assertFalse(password.isPin());
assertFalse(password.isPattern());
}
@@ -60,6 +74,7 @@
assertTrue(Arrays.equals("12369".getBytes(), pattern.getCredential()));
assertFalse(pattern.isNone());
+ assertFalse(pattern.isPin());
assertFalse(pattern.isPassword());
}
@@ -72,6 +87,15 @@
LockscreenCredential.createPasswordOrNone("abcd"));
}
+ public void testPinOrNoneCredential() {
+ assertEquals(LockscreenCredential.createNone(),
+ LockscreenCredential.createPinOrNone(null));
+ assertEquals(LockscreenCredential.createNone(),
+ LockscreenCredential.createPinOrNone(""));
+ assertEquals(LockscreenCredential.createPin("1357"),
+ LockscreenCredential.createPinOrNone("1357"));
+ }
+
public void testSanitize() {
LockscreenCredential password = LockscreenCredential.createPassword("password");
password.zeroize();
@@ -80,12 +104,15 @@
password.isNone();
fail("Sanitized credential still accessible");
} catch (IllegalStateException expected) { }
-
try {
password.isPattern();
fail("Sanitized credential still accessible");
} catch (IllegalStateException expected) { }
try {
+ password.isPin();
+ fail("Sanitized credential still accessible");
+ } catch (IllegalStateException expected) { }
+ try {
password.isPassword();
fail("Sanitized credential still accessible");
} catch (IllegalStateException expected) { }
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 9913531..50e8474 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -62,7 +62,9 @@
Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
final ILockSettings ils = Mockito.mock(ILockSettings.class);
- when(ils.havePassword(DEMO_USER_ID)).thenReturn(isSecure);
+ when(ils.getCredentialType(DEMO_USER_ID)).thenReturn(
+ isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+ : LockPatternUtils.CREDENTIAL_TYPE_NONE);
when(ils.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED, DEMO_USER_ID))
.thenReturn((long) PASSWORD_QUALITY_MANAGED);
// TODO(b/63758238): stop spying the class under test
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 8a76d6b..6e3e43a 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -242,7 +242,8 @@
nsecs_t queueDuration;
};
- RingBuffer<SwapHistory, 3> mSwapHistory;
+ // Need at least 4 because we do quad buffer. Add a 5th for good measure.
+ RingBuffer<SwapHistory, 5> mSwapHistory;
int64_t mFrameNumber = -1;
// last vsync for a dropped frame due to stuffed queue
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 91f3a20fc..79bec92 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -42,20 +42,23 @@
*/
interface ILocationManager
{
- Location getLastLocation(in LocationRequest request, String packageName);
+ Location getLastLocation(in LocationRequest request, String packageName, String featureId);
boolean getCurrentLocation(in LocationRequest request,
in ICancellationSignal cancellationSignal, in ILocationListener listener,
- String packageName, String listenerIdentifier);
+ String packageName, String featureId, String listenerIdentifier);
void requestLocationUpdates(in LocationRequest request, in ILocationListener listener,
- in PendingIntent intent, String packageName, String listenerIdentifier);
+ in PendingIntent intent, String packageName, String featureId,
+ String listenerIdentifier);
void removeUpdates(in ILocationListener listener, in PendingIntent intent, String packageName);
void requestGeofence(in LocationRequest request, in Geofence geofence,
- in PendingIntent intent, String packageName, String listenerIdentifier);
+ in PendingIntent intent, String packageName, String featureId,
+ String listenerIdentifier);
void removeGeofence(in Geofence fence, in PendingIntent intent, String packageName);
- boolean registerGnssStatusCallback(IGnssStatusListener callback, String packageName);
+ boolean registerGnssStatusCallback(IGnssStatusListener callback, String packageName,
+ String featureId);
void unregisterGnssStatusCallback(IGnssStatusListener callback);
boolean geocoderIsPresent();
@@ -69,14 +72,14 @@
boolean sendNiResponse(int notifId, int userResponse);
boolean addGnssMeasurementsListener(in IGnssMeasurementsListener listener,
- String packageName, String listenerIdentifier);
+ String packageName, String featureId, String listenerIdentifier);
void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections,
in String packageName);
long getGnssCapabilities(in String packageName);
void removeGnssMeasurementsListener(in IGnssMeasurementsListener listener);
boolean addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener,
- String packageName, String listenerIdentifier);
+ String packageName, String featureId, String listenerIdentifier);
void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener);
int getGnssYearOfHardware();
@@ -84,7 +87,7 @@
int getGnssBatchSize(String packageName);
boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName,
- String listenerIdentifier);
+ String featureId, String listenerIdentifier);
void removeGnssBatchingCallback();
boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName);
void flushGnssBatch(String packageName);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 9e17e95..010f92f 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -515,7 +515,8 @@
@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
public Location getLastLocation() {
try {
- return mService.getLastLocation(null, mContext.getPackageName());
+ return mService.getLastLocation(null, mContext.getPackageName(),
+ mContext.getFeatureId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -547,7 +548,8 @@
provider, 0, 0, true);
try {
- return mService.getLastLocation(request, mContext.getPackageName());
+ return mService.getLastLocation(request, mContext.getPackageName(),
+ mContext.getFeatureId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -641,7 +643,7 @@
try {
if (mService.getCurrentLocation(currentLocationRequest, remoteCancellationSignal,
- listenerTransport, mContext.getPackageName(),
+ listenerTransport, mContext.getPackageName(), mContext.getFeatureId(),
getListenerIdentifier(consumer))) {
listenerTransport.register(mContext.getSystemService(AlarmManager.class),
remoteCancellationSignal);
@@ -1085,7 +1087,8 @@
boolean registered = false;
try {
mService.requestLocationUpdates(locationRequest, transport, null,
- mContext.getPackageName(), getListenerIdentifier(listener));
+ mContext.getPackageName(), mContext.getFeatureId(),
+ getListenerIdentifier(listener));
registered = true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1130,7 +1133,8 @@
try {
mService.requestLocationUpdates(locationRequest, null, pendingIntent,
- mContext.getPackageName(), getListenerIdentifier(pendingIntent));
+ mContext.getPackageName(), mContext.getFeatureId(),
+ getListenerIdentifier(pendingIntent));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1594,7 +1598,7 @@
LocationRequest request = new LocationRequest().setExpireIn(expiration);
try {
mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
- getListenerIdentifier(intent));
+ mContext.getFeatureId(), getListenerIdentifier(intent));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1672,7 +1676,7 @@
try {
mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
- getListenerIdentifier(intent));
+ mContext.getFeatureId(), getListenerIdentifier(intent));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2748,7 +2752,7 @@
mListenerTransport = new GnssStatusListener();
return mService.registerGnssStatusCallback(mListenerTransport,
- mContext.getPackageName());
+ mContext.getPackageName(), mContext.getFeatureId());
}
@Override
@@ -2808,7 +2812,8 @@
mListenerTransport = new GnssMeasurementsListener();
return mService.addGnssMeasurementsListener(mListenerTransport,
- mContext.getPackageName(), "gnss measurement callback");
+ mContext.getPackageName(), mContext.getFeatureId(),
+ "gnss measurement callback");
}
@Override
@@ -2844,7 +2849,8 @@
mListenerTransport = new GnssNavigationMessageListener();
return mService.addGnssNavigationMessageListener(mListenerTransport,
- mContext.getPackageName(), "gnss navigation callback");
+ mContext.getPackageName(), mContext.getFeatureId(),
+ "gnss navigation callback");
}
@Override
@@ -2880,7 +2886,7 @@
mListenerTransport = new BatchedLocationCallback();
return mService.addGnssBatchingCallback(mListenerTransport, mContext.getPackageName(),
- "batched location callback");
+ mContext.getFeatureId(), "batched location callback");
}
@Override
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 53bc65d..bb731a8 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -16,6 +16,7 @@
package android.media;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
@@ -26,6 +27,8 @@
import android.media.audiopolicy.AudioMix;
import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Map;
@@ -431,6 +434,50 @@
public static final int DEAD_OBJECT = -6;
public static final int WOULD_BLOCK = -7;
+ /** @hide */
+ @IntDef({
+ SUCCESS,
+ ERROR,
+ BAD_VALUE,
+ INVALID_OPERATION,
+ PERMISSION_DENIED,
+ NO_INIT,
+ DEAD_OBJECT,
+ WOULD_BLOCK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AudioSystemError {}
+
+ /**
+ * Convert an int error value to its String value for readability.
+ * Accepted error values are the java AudioSystem errors, matching android_media_AudioErrors.h,
+ * which map onto the native status_t type.
+ * @param error one of the java AudioSystem errors
+ * @return a human-readable string
+ */
+ public static String audioSystemErrorToString(@AudioSystemError int error) {
+ switch(error) {
+ case SUCCESS:
+ return "SUCCESS";
+ case ERROR:
+ return "ERROR";
+ case BAD_VALUE:
+ return "BAD_VALUE";
+ case INVALID_OPERATION:
+ return "INVALID_OPERATION";
+ case PERMISSION_DENIED:
+ return "PERMISSION_DENIED";
+ case NO_INIT:
+ return "NO_INIT";
+ case DEAD_OBJECT:
+ return "DEAD_OBJECT";
+ case WOULD_BLOCK:
+ return "WOULD_BLOCK";
+ default:
+ return ("unknown error:" + error);
+ }
+ }
+
/*
* AudioPolicyService methods
*/
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 7906fa3..7d107dd 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -89,6 +89,10 @@
* of audio/video files and streams. An example on how to use the methods in
* this class can be found in {@link android.widget.VideoView}.
*
+ * <p>MediaPlayer is not thread-safe. Creation of and all access to player instances
+ * should be on the same thread. If registering <a href="#Callbacks">callbacks</a>,
+ * the thread must have a Looper.
+ *
* <p>Topics covered here are:
* <ol>
* <li><a href="#StateDiagram">State Diagram</a>
@@ -305,7 +309,7 @@
* </li>
* <li>When the playback reaches the end of stream, the playback completes.
* <ul>
- * <li>If the looping mode was being set to <var>true</var>with
+ * <li>If the looping mode was being set to <var>true</var> with
* {@link #setLooping(boolean)}, the MediaPlayer object shall remain in
* the <em>Started</em> state.</li>
* <li>If the looping mode was set to <var>false
@@ -554,13 +558,13 @@
* possible runtime errors during playback or streaming. Registration for
* these events is done by properly setting the appropriate listeners (via calls
* to
- * {@link #setOnPreparedListener(OnPreparedListener)}setOnPreparedListener,
- * {@link #setOnVideoSizeChangedListener(OnVideoSizeChangedListener)}setOnVideoSizeChangedListener,
- * {@link #setOnSeekCompleteListener(OnSeekCompleteListener)}setOnSeekCompleteListener,
- * {@link #setOnCompletionListener(OnCompletionListener)}setOnCompletionListener,
- * {@link #setOnBufferingUpdateListener(OnBufferingUpdateListener)}setOnBufferingUpdateListener,
- * {@link #setOnInfoListener(OnInfoListener)}setOnInfoListener,
- * {@link #setOnErrorListener(OnErrorListener)}setOnErrorListener, etc).
+ * {@link #setOnPreparedListener(OnPreparedListener) setOnPreparedListener},
+ * {@link #setOnVideoSizeChangedListener(OnVideoSizeChangedListener) setOnVideoSizeChangedListener},
+ * {@link #setOnSeekCompleteListener(OnSeekCompleteListener) setOnSeekCompleteListener},
+ * {@link #setOnCompletionListener(OnCompletionListener) setOnCompletionListener},
+ * {@link #setOnBufferingUpdateListener(OnBufferingUpdateListener) setOnBufferingUpdateListener},
+ * {@link #setOnInfoListener(OnInfoListener) setOnInfoListener},
+ * {@link #setOnErrorListener(OnErrorListener) setOnErrorListener}, etc).
* In order to receive the respective callback
* associated with these listeners, applications are required to create
* MediaPlayer objects on a thread with its own Looper running (main UI
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 39474e1..01f1250 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -853,6 +853,10 @@
Log.v(TAG, "notifyVolumeAdjust: " + adjustment);
}
}
+
+ public void notifyUnregistration() {
+ setRegistration(null);
+ }
};
//==================================================
diff --git a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
index 107e7cd..1d58151 100644
--- a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
+++ b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
@@ -34,4 +34,8 @@
// callback for volume events
void notifyVolumeAdjust(int adjustment);
+
+ // callback for unregistration (e.g. if policy couldn't automatically be re-registered after
+ // an audioserver crash)
+ void notifyUnregistration();
}
diff --git a/media/java/android/media/midi/MidiDeviceInfo.aidl b/media/java/android/media/midi/MidiDeviceInfo.aidl
index 5b2ac9b..a248204 100644
--- a/media/java/android/media/midi/MidiDeviceInfo.aidl
+++ b/media/java/android/media/midi/MidiDeviceInfo.aidl
@@ -16,4 +16,4 @@
package android.media.midi;
-parcelable MidiDeviceInfo cpp_header "media/MidiDeviceInfo.h";
+parcelable MidiDeviceInfo cpp_header "MidiDeviceInfo.h";
diff --git a/media/jni/soundpool/Android.bp b/media/jni/soundpool/Android.bp
index 35b7b01..e1945dd 100644
--- a/media/jni/soundpool/Android.bp
+++ b/media/jni/soundpool/Android.bp
@@ -3,11 +3,16 @@
srcs: [
"android_media_SoundPool.cpp",
+ "Sound.cpp",
+ "SoundDecoder.cpp",
+ "SoundManager.cpp",
"SoundPool.cpp",
- "SoundPoolThread.cpp",
+ "Stream.cpp",
+ "StreamManager.cpp",
],
shared_libs: [
+ "libaudioutils",
"liblog",
"libcutils",
"libutils",
diff --git a/media/jni/soundpool/Sound.cpp b/media/jni/soundpool/Sound.cpp
new file mode 100644
index 0000000..0bbc3e4
--- /dev/null
+++ b/media/jni/soundpool/Sound.cpp
@@ -0,0 +1,241 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::Sound"
+#include <utils/Log.h>
+
+#include "Sound.h"
+
+#include <media/NdkMediaCodec.h>
+#include <media/NdkMediaExtractor.h>
+#include <media/NdkMediaFormat.h>
+
+namespace android::soundpool {
+
+constexpr uint32_t kMaxSampleRate = 192000;
+constexpr size_t kDefaultHeapSize = 1024 * 1024; // 1MB (compatible with low mem devices)
+
+Sound::Sound(int32_t soundID, int fd, int64_t offset, int64_t length)
+ : mSoundID(soundID)
+ , mFd(dup(fd))
+ , mOffset(offset)
+ , mLength(length)
+{
+ ALOGV("%s(soundID=%d, fd=%d, offset=%lld, length=%lld)",
+ __func__, soundID, fd, (long long)offset, (long long)length);
+ ALOGW_IF(mFd == -1, "Unable to dup descriptor %d", fd);
+}
+
+Sound::~Sound()
+{
+ ALOGV("%s(soundID=%d, fd=%d)", __func__, mSoundID, mFd.get());
+}
+
+static status_t decode(int fd, int64_t offset, int64_t length,
+ uint32_t *rate, int32_t *channelCount, audio_format_t *audioFormat,
+ audio_channel_mask_t *channelMask, sp<MemoryHeapBase> heap,
+ size_t *sizeInBytes) {
+ ALOGV("%s(fd=%d, offset=%lld, length=%lld, ...)",
+ __func__, fd, (long long)offset, (long long)length);
+ std::unique_ptr<AMediaExtractor, decltype(&AMediaExtractor_delete)> ex{
+ AMediaExtractor_new(), &AMediaExtractor_delete};
+ status_t err = AMediaExtractor_setDataSourceFd(ex.get(), fd, offset, length);
+
+ if (err != AMEDIA_OK) {
+ return err;
+ }
+
+ *audioFormat = AUDIO_FORMAT_PCM_16_BIT; // default format for audio codecs.
+ const size_t numTracks = AMediaExtractor_getTrackCount(ex.get());
+ for (size_t i = 0; i < numTracks; i++) {
+ std::unique_ptr<AMediaFormat, decltype(&AMediaFormat_delete)> format{
+ AMediaExtractor_getTrackFormat(ex.get(), i), &AMediaFormat_delete};
+ const char *mime;
+ if (!AMediaFormat_getString(format.get(), AMEDIAFORMAT_KEY_MIME, &mime)) {
+ return UNKNOWN_ERROR;
+ }
+ if (strncmp(mime, "audio/", 6) == 0) {
+ std::unique_ptr<AMediaCodec, decltype(&AMediaCodec_delete)> codec{
+ AMediaCodec_createDecoderByType(mime), &AMediaCodec_delete};
+ if (codec == nullptr
+ || AMediaCodec_configure(codec.get(), format.get(),
+ nullptr /* window */, nullptr /* drm */, 0 /* flags */) != AMEDIA_OK
+ || AMediaCodec_start(codec.get()) != AMEDIA_OK
+ || AMediaExtractor_selectTrack(ex.get(), i) != AMEDIA_OK) {
+ return UNKNOWN_ERROR;
+ }
+
+ bool sawInputEOS = false;
+ bool sawOutputEOS = false;
+ uint8_t* writePos = static_cast<uint8_t*>(heap->getBase());
+ size_t available = heap->getSize();
+ size_t written = 0;
+ format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format.
+
+ while (!sawOutputEOS) {
+ if (!sawInputEOS) {
+ ssize_t bufidx = AMediaCodec_dequeueInputBuffer(codec.get(), 5000);
+ ALOGV("%s: input buffer %zd", __func__, bufidx);
+ if (bufidx >= 0) {
+ size_t bufsize;
+ uint8_t * const buf = AMediaCodec_getInputBuffer(
+ codec.get(), bufidx, &bufsize);
+ if (buf == nullptr) {
+ ALOGE("%s: AMediaCodec_getInputBuffer returned nullptr, short decode",
+ __func__);
+ break;
+ }
+ int sampleSize = AMediaExtractor_readSampleData(ex.get(), buf, bufsize);
+ ALOGV("%s: read %d", __func__, sampleSize);
+ if (sampleSize < 0) {
+ sampleSize = 0;
+ sawInputEOS = true;
+ ALOGV("%s: EOS", __func__);
+ }
+ const int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex.get());
+
+ const media_status_t mstatus = AMediaCodec_queueInputBuffer(
+ codec.get(), bufidx,
+ 0 /* offset */, sampleSize, presentationTimeUs,
+ sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
+ if (mstatus != AMEDIA_OK) {
+ // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
+ ALOGE("%s: AMediaCodec_queueInputBuffer returned status %d,"
+ "short decode",
+ __func__, (int)mstatus);
+ break;
+ }
+ (void)AMediaExtractor_advance(ex.get());
+ }
+ }
+
+ AMediaCodecBufferInfo info;
+ const int status = AMediaCodec_dequeueOutputBuffer(codec.get(), &info, 1);
+ ALOGV("%s: dequeueoutput returned: %d", __func__, status);
+ if (status >= 0) {
+ if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+ ALOGV("%s: output EOS", __func__);
+ sawOutputEOS = true;
+ }
+ ALOGV("%s: got decoded buffer size %d", __func__, info.size);
+
+ const uint8_t * const buf = AMediaCodec_getOutputBuffer(
+ codec.get(), status, nullptr /* out_size */);
+ if (buf == nullptr) {
+ ALOGE("%s: AMediaCodec_getOutputBuffer returned nullptr, short decode",
+ __func__);
+ break;
+ }
+ const size_t dataSize = std::min((size_t)info.size, available);
+ memcpy(writePos, buf + info.offset, dataSize);
+ writePos += dataSize;
+ written += dataSize;
+ available -= dataSize;
+ const media_status_t mstatus = AMediaCodec_releaseOutputBuffer(
+ codec.get(), status, false /* render */);
+ if (mstatus != AMEDIA_OK) {
+ // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
+ ALOGE("%s: AMediaCodec_releaseOutputBuffer"
+ " returned status %d, short decode",
+ __func__, (int)mstatus);
+ break;
+ }
+ if (available == 0) {
+ // there might be more data, but there's no space for it
+ sawOutputEOS = true;
+ }
+ } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+ ALOGV("%s: output buffers changed", __func__);
+ } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+ format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format
+ ALOGV("%s: format changed to: %s",
+ __func__, AMediaFormat_toString(format.get()));
+ } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+ ALOGV("%s: no output buffer right now", __func__);
+ } else if (status <= AMEDIA_ERROR_BASE) {
+ ALOGE("%s: decode error: %d", __func__, status);
+ break;
+ } else {
+ ALOGV("%s: unexpected info code: %d", __func__, status);
+ }
+ }
+
+ (void)AMediaCodec_stop(codec.get());
+ if (!AMediaFormat_getInt32(
+ format.get(), AMEDIAFORMAT_KEY_SAMPLE_RATE, (int32_t*) rate) ||
+ !AMediaFormat_getInt32(
+ format.get(), AMEDIAFORMAT_KEY_CHANNEL_COUNT, channelCount)) {
+ return UNKNOWN_ERROR;
+ }
+ if (!AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_CHANNEL_MASK,
+ (int32_t*) channelMask)) {
+ *channelMask = AUDIO_CHANNEL_NONE;
+ }
+ *sizeInBytes = written;
+ return OK;
+ }
+ }
+ return UNKNOWN_ERROR;
+}
+
+status_t Sound::doLoad()
+{
+ ALOGV("%s()", __func__);
+ status_t status = NO_INIT;
+ if (mFd.get() != -1) {
+ mHeap = new MemoryHeapBase(kDefaultHeapSize);
+
+ ALOGV("%s: start decode", __func__);
+ uint32_t sampleRate;
+ int32_t channelCount;
+ audio_format_t format;
+ audio_channel_mask_t channelMask;
+ status_t status = decode(mFd.get(), mOffset, mLength, &sampleRate, &channelCount, &format,
+ &channelMask, mHeap, &mSizeInBytes);
+ ALOGV("%s: close(%d)", __func__, mFd.get());
+ mFd.reset(); // close
+
+ if (status != NO_ERROR) {
+ ALOGE("%s: unable to load sound", __func__);
+ } else if (sampleRate > kMaxSampleRate) {
+ ALOGE("%s: sample rate (%u) out of range", __func__, sampleRate);
+ status = BAD_VALUE;
+ } else if (channelCount < 1 || channelCount > FCC_8) {
+ ALOGE("%s: sample channel count (%d) out of range", __func__, channelCount);
+ status = BAD_VALUE;
+ } else {
+ // Correctly loaded, proper parameters
+ ALOGV("%s: pointer = %p, sizeInBytes = %zu, sampleRate = %u, channelCount = %d",
+ __func__, mHeap->getBase(), mSizeInBytes, sampleRate, channelCount);
+ mData = new MemoryBase(mHeap, 0, mSizeInBytes);
+ mSampleRate = sampleRate;
+ mChannelCount = channelCount;
+ mFormat = format;
+ mChannelMask = channelMask;
+ mState = READY; // this should be last, as it is an atomic sync point
+ return NO_ERROR;
+ }
+ } else {
+ ALOGE("%s: uninitialized fd, dup failed", __func__);
+ }
+ // ERROR handling
+ mHeap.clear();
+ mState = DECODE_ERROR; // this should be last, as it is an atomic sync point
+ return status;
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/Sound.h b/media/jni/soundpool/Sound.h
new file mode 100644
index 0000000..efe940a
--- /dev/null
+++ b/media/jni/soundpool/Sound.h
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/unique_fd.h>
+#include <binder/MemoryBase.h>
+#include <binder/MemoryHeapBase.h>
+#include <system/audio.h>
+
+namespace android::soundpool {
+
+class SoundDecoder;
+
+/**
+ * Sound is a resource used by SoundPool, referenced by soundID.
+ *
+ * After loading, it is effectively const so no locking required.
+ * However, in order to guarantee that all the values have been
+ * written properly and read properly, we use the mState as an atomic synchronization
+ * point. So if getState() shows READY, then all the other getters may
+ * be safely read.
+ *
+ * Technical details:
+ * We access the mState atomic value through memory_order_seq_cst
+ *
+ * https://en.cppreference.com/w/cpp/atomic/memory_order
+ *
+ * which provides memory barriers. So if the last value written by the SoundDecoder
+ * is mState, then the compiler ensures no other prior writes by SoundDecoder will be
+ * reordered afterwards, and memory barrier is placed (as necessary) to ensure the
+ * cache is visible to other processors.
+ *
+ * Likewise, if the first value read by SoundPool is mState,
+ * the compiler ensures no reads for that thread will be reordered before mState is read,
+ * and a memory barrier is placed (as necessary) to ensure that the cache is properly
+ * updated with other processor's writes before reading.
+ *
+ * See https://developer.android.com/training/articles/smp for discussions about
+ * the variant load-acquire, store-release semantics.
+ */
+class Sound {
+ friend SoundDecoder; // calls doLoad().
+
+public:
+ enum sound_state : int32_t { LOADING, READY, DECODE_ERROR };
+ // A sound starts in the LOADING state and transitions only once
+ // to either READY or DECODE_ERROR when doLoad() is called.
+
+ Sound(int soundID, int fd, int64_t offset, int64_t length);
+ ~Sound();
+
+ int32_t getSoundID() const { return mSoundID; }
+ int32_t getChannelCount() const { return mChannelCount; }
+ uint32_t getSampleRate() const { return mSampleRate; }
+ audio_format_t getFormat() const { return mFormat; }
+ audio_channel_mask_t getChannelMask() const { return mChannelMask; }
+ size_t getSizeInBytes() const { return mSizeInBytes; }
+ sound_state getState() const { return mState; }
+ uint8_t* getData() const { return static_cast<uint8_t*>(mData->unsecurePointer()); }
+ sp<IMemory> getIMemory() const { return mData; }
+
+private:
+ status_t doLoad(); // only SoundDecoder accesses this.
+
+ size_t mSizeInBytes = 0;
+ const int32_t mSoundID;
+ uint32_t mSampleRate = 0;
+ std::atomic<sound_state> mState = LOADING; // used as synchronization point
+ int32_t mChannelCount = 0;
+ audio_format_t mFormat = AUDIO_FORMAT_INVALID;
+ audio_channel_mask_t mChannelMask = AUDIO_CHANNEL_NONE;
+ base::unique_fd mFd; // initialized in constructor, reset to -1 after loading
+ const int64_t mOffset; // int64_t to match java long, see off64_t
+ const int64_t mLength; // int64_t to match java long, see off64_t
+ sp<IMemory> mData;
+ sp<MemoryHeapBase> mHeap;
+};
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/SoundDecoder.cpp b/media/jni/soundpool/SoundDecoder.cpp
new file mode 100644
index 0000000..12200ef
--- /dev/null
+++ b/media/jni/soundpool/SoundDecoder.cpp
@@ -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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::SoundDecoder"
+#include "utils/Log.h"
+
+#include "SoundDecoder.h"
+
+namespace android::soundpool {
+
+// Maximum Samples that can be background decoded before we block the caller.
+static constexpr size_t kMaxQueueSize = 128;
+
+// The amount of time we wait for a new Sound decode request
+// before the SoundDecoder thread closes.
+static constexpr int32_t kWaitTimeBeforeCloseMs = 1000;
+
+SoundDecoder::SoundDecoder(SoundManager* soundManager, size_t threads)
+ : mSoundManager(soundManager)
+{
+ ALOGV("%s(%p, %zu)", __func__, soundManager, threads);
+ // ThreadPool is created, but we don't launch any threads.
+ mThreadPool = std::make_unique<ThreadPool>(
+ std::min(threads, (size_t)std::thread::hardware_concurrency()),
+ "SoundDecoder_");
+}
+
+SoundDecoder::~SoundDecoder()
+{
+ ALOGV("%s()", __func__);
+ quit();
+}
+
+void SoundDecoder::quit()
+{
+ ALOGV("%s()", __func__);
+ {
+ std::lock_guard lock(mLock);
+ mQuit = true;
+ mQueueSpaceAvailable.notify_all(); // notify all load waiters
+ mQueueDataAvailable.notify_all(); // notify all worker threads
+ }
+ mThreadPool->quit();
+}
+
+void SoundDecoder::run(int32_t id __unused /* ALOGV only */)
+{
+ ALOGV("%s(%d): entering", __func__, id);
+ std::unique_lock lock(mLock);
+ while (!mQuit) {
+ if (mSoundIDs.size() == 0) {
+ ALOGV("%s(%d): waiting", __func__, id);
+ mQueueDataAvailable.wait_for(
+ lock, std::chrono::duration<int32_t, std::milli>(kWaitTimeBeforeCloseMs));
+ if (mSoundIDs.size() == 0) {
+ break; // no new sound, exit this thread.
+ }
+ continue;
+ }
+ const int32_t soundID = mSoundIDs.front();
+ mSoundIDs.pop_front();
+ mQueueSpaceAvailable.notify_one();
+ ALOGV("%s(%d): processing soundID: %d size: %zu", __func__, id, soundID, mSoundIDs.size());
+ lock.unlock();
+ std::shared_ptr<Sound> sound = mSoundManager->findSound(soundID);
+ status_t status = NO_INIT;
+ if (sound.get() != nullptr) {
+ status = sound->doLoad();
+ }
+ ALOGV("%s(%d): notifying loaded soundID:%d status:%d", __func__, id, soundID, status);
+ mSoundManager->notify(SoundPoolEvent(SoundPoolEvent::SOUND_LOADED, soundID, status));
+ lock.lock();
+ }
+ ALOGV("%s(%d): exiting", __func__, id);
+}
+
+void SoundDecoder::loadSound(int32_t soundID)
+{
+ ALOGV("%s(%d)", __func__, soundID);
+ size_t pendingSounds;
+ {
+ std::unique_lock lock(mLock);
+ while (mSoundIDs.size() == kMaxQueueSize) {
+ if (mQuit) return;
+ ALOGV("%s: waiting soundID: %d size: %zu", __func__, soundID, mSoundIDs.size());
+ mQueueSpaceAvailable.wait(lock);
+ }
+ if (mQuit) return;
+ mSoundIDs.push_back(soundID);
+ mQueueDataAvailable.notify_one();
+ ALOGV("%s: adding soundID: %d size: %zu", __func__, soundID, mSoundIDs.size());
+ pendingSounds = mSoundIDs.size();
+ }
+ // Launch threads as needed. The "as needed" is weakly consistent as we release mLock.
+ if (pendingSounds > mThreadPool->getActiveThreadCount()) {
+ const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
+ ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
+ }
+}
+
+} // end namespace android::soundpool
diff --git a/media/jni/soundpool/SoundDecoder.h b/media/jni/soundpool/SoundDecoder.h
new file mode 100644
index 0000000..1288943
--- /dev/null
+++ b/media/jni/soundpool/SoundDecoder.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "SoundPool.h"
+
+#include <deque>
+#include <mutex>
+
+namespace android::soundpool {
+
+/**
+ * SoundDecoder handles background decoding tasks.
+ */
+class SoundDecoder {
+public:
+ SoundDecoder(SoundManager* soundManager, size_t threads);
+ ~SoundDecoder();
+ void loadSound(int32_t soundID);
+ void quit();
+
+private:
+ void run(int32_t id); // The decode thread function.
+
+ SoundManager* const mSoundManager; // set in constructor, has own lock
+ std::unique_ptr<ThreadPool> mThreadPool; // set in constructor, has own lock
+
+ std::mutex mLock;
+ std::condition_variable mQueueSpaceAvailable;
+ std::condition_variable mQueueDataAvailable;
+
+ std::deque<int32_t> mSoundIDs; // GUARDED_BY(mLock);
+ bool mQuit = false; // GUARDED_BY(mLock);
+};
+
+} // end namespace android::soundpool
+
diff --git a/media/jni/soundpool/SoundManager.cpp b/media/jni/soundpool/SoundManager.cpp
new file mode 100644
index 0000000..3c625bf
--- /dev/null
+++ b/media/jni/soundpool/SoundManager.cpp
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::SoundManager"
+#include <utils/Log.h>
+
+#include "SoundManager.h"
+
+#include <thread>
+
+#include "SoundDecoder.h"
+
+namespace android::soundpool {
+
+static const size_t kDecoderThreads = std::thread::hardware_concurrency() >= 4 ? 2 : 1;
+
+SoundManager::SoundManager()
+ : mDecoder{std::make_unique<SoundDecoder>(this, kDecoderThreads)}
+{
+ ALOGV("%s()", __func__);
+}
+
+SoundManager::~SoundManager()
+{
+ ALOGV("%s()", __func__);
+ mDecoder->quit();
+
+ std::lock_guard lock(mSoundManagerLock);
+ mSounds.clear();
+}
+
+int32_t SoundManager::load(int fd, int64_t offset, int64_t length, int32_t priority __unused)
+{
+ ALOGV("%s(fd=%d, offset=%lld, length=%lld, priority=%d)",
+ __func__, fd, (long long)offset, (long long)length, priority);
+ int32_t soundID;
+ {
+ std::lock_guard lock(mSoundManagerLock);
+ // mNextSoundID is always positive and does not "integer overflow"
+ do {
+ mNextSoundID = mNextSoundID == INT32_MAX ? 1 : mNextSoundID + 1;
+ } while (findSound_l(mNextSoundID) != nullptr);
+ soundID = mNextSoundID;
+ auto sound = std::make_shared<Sound>(soundID, fd, offset, length);
+ mSounds.emplace(soundID, sound);
+ }
+ // mDecoder->loadSound() must be called outside of mSoundManagerLock.
+ // mDecoder->loadSound() may block on mDecoder message queue space;
+ // the message queue emptying may block on SoundManager::findSound().
+ //
+ // It is theoretically possible that sound loads might decode out-of-order.
+ mDecoder->loadSound(soundID);
+ return soundID;
+}
+
+bool SoundManager::unload(int32_t soundID)
+{
+ ALOGV("%s(soundID=%d)", __func__, soundID);
+ std::lock_guard lock(mSoundManagerLock);
+ return mSounds.erase(soundID) > 0; // erase() returns number of sounds removed.
+}
+
+std::shared_ptr<Sound> SoundManager::findSound(int32_t soundID) const
+{
+ std::lock_guard lock(mSoundManagerLock);
+ return findSound_l(soundID);
+}
+
+std::shared_ptr<Sound> SoundManager::findSound_l(int32_t soundID) const
+{
+ auto it = mSounds.find(soundID);
+ return it != mSounds.end() ? it->second : nullptr;
+}
+
+void SoundManager::setCallback(SoundPool *soundPool, SoundPoolCallback* callback, void* user)
+{
+ mCallbackHandler.setCallback(soundPool, callback, user);
+}
+
+void SoundManager::notify(SoundPoolEvent event)
+{
+ mCallbackHandler.notify(event);
+}
+
+void* SoundManager::getUserData() const
+{
+ return mCallbackHandler.getUserData();
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/SoundManager.h b/media/jni/soundpool/SoundManager.h
new file mode 100644
index 0000000..9201e78
--- /dev/null
+++ b/media/jni/soundpool/SoundManager.h
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Sound.h"
+
+#include <mutex>
+#include <unordered_map>
+
+namespace android {
+
+class SoundPool;
+
+// for queued events
+class SoundPoolEvent {
+public:
+ explicit SoundPoolEvent(int msg, int arg1 = 0, int arg2 = 0) :
+ mMsg(msg), mArg1(arg1), mArg2(arg2) {}
+ const int mMsg; // MessageType
+ const int mArg1; // soundID
+ const int mArg2; // status
+ enum MessageType { INVALID, SOUND_LOADED };
+};
+
+// callback function prototype
+typedef void SoundPoolCallback(SoundPoolEvent event, SoundPool* soundPool, void* user);
+
+} // namespace android
+
+namespace android::soundpool {
+
+// This class manages Sounds for the SoundPool.
+class SoundManager {
+public:
+ SoundManager();
+ ~SoundManager();
+
+ // Matches corresponding SoundPool API functions
+ int32_t load(int fd, int64_t offset, int64_t length, int32_t priority);
+ bool unload(int32_t soundID);
+ void setCallback(SoundPool* soundPool, SoundPoolCallback* callback, void* user);
+ void* getUserData() const;
+
+ // SoundPool and SoundDecoder access
+ std::shared_ptr<Sound> findSound(int32_t soundID) const;
+
+ // from the SoundDecoder
+ void notify(SoundPoolEvent event);
+
+private:
+
+ // CallbackHandler is used to manage notifications back to the app when a sound
+ // has been loaded. It uses a recursive lock to allow setting the callback
+ // during the callback.
+ class CallbackHandler {
+ public:
+ void setCallback(SoundPool *soundPool, SoundPoolCallback* callback, void* userData)
+ {
+ std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
+ mSoundPool = soundPool;
+ mCallback = callback;
+ mUserData = userData;
+ }
+ void notify(SoundPoolEvent event) const
+ {
+ std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
+ if (mCallback != nullptr) {
+ mCallback(event, mSoundPool, mUserData);
+ // Note: mCallback may call setCallback().
+ // so mCallback, mUserData may have changed.
+ }
+ }
+ void* getUserData() const
+ {
+ std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
+ return mUserData;
+ }
+ private:
+ mutable std::recursive_mutex mCallbackLock; // allow mCallback to setCallback().
+ SoundPool* mSoundPool = nullptr; // GUARDED_BY(mCallbackLock)
+ SoundPoolCallback* mCallback = nullptr; // GUARDED_BY(mCallbackLock)
+ void* mUserData = nullptr; // GUARDED_BY(mCallbackLock)
+ };
+
+ std::shared_ptr<Sound> findSound_l(int32_t soundID) const;
+
+ // The following variables are initialized in constructor and can be accessed anytime.
+ CallbackHandler mCallbackHandler; // has its own lock
+ const std::unique_ptr<SoundDecoder> mDecoder; // has its own lock
+
+ mutable std::mutex mSoundManagerLock;
+ std::unordered_map<int, std::shared_ptr<Sound>> mSounds; // GUARDED_BY(mSoundManagerLock)
+ int32_t mNextSoundID = 0; // GUARDED_BY(mSoundManagerLock)
+};
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 102bbf0..ac44843 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -16,1124 +16,230 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "SoundPool"
-
-#include <chrono>
-#include <inttypes.h>
-#include <thread>
#include <utils/Log.h>
-#define USE_SHARED_MEM_BUFFER
+#include <algorithm>
+#include <thread>
-#include <media/AudioTrack.h>
#include "SoundPool.h"
-#include "SoundPoolThread.h"
-#include <media/NdkMediaCodec.h>
-#include <media/NdkMediaExtractor.h>
-#include <media/NdkMediaFormat.h>
namespace android
{
-int kDefaultBufferCount = 4;
-uint32_t kMaxSampleRate = 48000;
-uint32_t kDefaultSampleRate = 44100;
-uint32_t kDefaultFrameCount = 1200;
-size_t kDefaultHeapSize = 1024 * 1024; // 1MB
+// kManagerThreads = 1 historically.
+// Not really necessary to have more than one, but it does speed things up by about
+// 25% having 2 threads instead of 1 when playing many sounds. Having many threads
+// could starve other AudioFlinger clients with SoundPool activity. It may also cause
+// issues with app loading, e.g. Camera.
+static const size_t kStreamManagerThreads = std::thread::hardware_concurrency() >= 4 ? 2 : 1;
+// kUseApiLock = true prior to R.
+// Set to true to prevent multiple users access internal to the SoundPool API.
+// Set to false to make the SoundPool methods weakly consistent. When set to false,
+// only AutoPause and AutoResume are locked, which are the only two methods that
+// require API level locking for consistency.
+static constexpr bool kUseApiLock = false;
-SoundPool::SoundPool(int maxChannels, const audio_attributes_t* pAttributes)
+namespace {
+// Check input arguments to SoundPool - return "true" to reject request.
+
+bool checkVolume(float *leftVolume, float *rightVolume)
{
- ALOGV("SoundPool constructor: maxChannels=%d, attr.usage=%d, attr.flags=0x%x, attr.tags=%s",
- maxChannels, pAttributes->usage, pAttributes->flags, pAttributes->tags);
-
- // check limits
- mMaxChannels = maxChannels;
- if (mMaxChannels < 1) {
- mMaxChannels = 1;
+ if (*leftVolume != std::clamp(*leftVolume, 0.f, 1.f) ||
+ *rightVolume != std::clamp(*rightVolume, 0.f, 1.f)) {
+ ALOGI("volume l=%f r=%f out of (0.f, 1.f) bounds, using 1.f", *leftVolume, *rightVolume);
+ // for backward compatibility use 1.f.
+ *leftVolume = *rightVolume = 1.f;
}
- else if (mMaxChannels > 32) {
- mMaxChannels = 32;
+ return false;
+}
+
+bool checkRate(float *rate)
+{
+ if (*rate != std::clamp(*rate, 0.125f, 8.f)) {
+ ALOGI("rate %f out of (0.125f, 8.f) bounds, clamping", *rate);
+ // for backward compatibility just clamp
+ *rate = std::clamp(*rate, 0.125f, 8.f);
}
- ALOGW_IF(maxChannels != mMaxChannels, "App requested %d channels", maxChannels);
+ return false;
+}
- mQuit = false;
- mMuted = false;
- mDecodeThread = 0;
- memcpy(&mAttributes, pAttributes, sizeof(audio_attributes_t));
- mAllocated = 0;
- mNextSampleID = 0;
- mNextChannelID = 0;
-
- mCallback = 0;
- mUserData = 0;
-
- mChannelPool = new SoundChannel[mMaxChannels];
- for (int i = 0; i < mMaxChannels; ++i) {
- mChannelPool[i].init(this);
- mChannels.push_back(&mChannelPool[i]);
+bool checkPriority(int32_t *priority)
+{
+ if (*priority < 0) {
+ ALOGI("negative priority %d, should be >= 0.", *priority);
+ // for backward compatibility, ignore.
}
+ return false;
+}
- // start decode thread
- startThreads();
+bool checkLoop(int32_t *loop)
+{
+ if (*loop < -1) {
+ ALOGI("loop %d, should be >= -1", *loop);
+ *loop = -1;
+ }
+ return false;
+}
+
+} // namespace
+
+SoundPool::SoundPool(int32_t maxStreams, const audio_attributes_t* attributes)
+ : mStreamManager(maxStreams, kStreamManagerThreads, attributes)
+{
+ ALOGV("%s(maxStreams=%d, attr={ content_type=%d, usage=%d, flags=0x%x, tags=%s })",
+ __func__, maxStreams,
+ attributes->content_type, attributes->usage, attributes->flags, attributes->tags);
}
SoundPool::~SoundPool()
{
- ALOGV("SoundPool destructor");
- mDecodeThread->quit();
- quit();
-
- Mutex::Autolock lock(&mLock);
-
- mChannels.clear();
- if (mChannelPool)
- delete [] mChannelPool;
- // clean up samples
- ALOGV("clear samples");
- mSamples.clear();
-
- if (mDecodeThread)
- delete mDecodeThread;
+ ALOGV("%s()", __func__);
}
-void SoundPool::addToRestartList(SoundChannel* channel)
+int32_t SoundPool::load(int fd, int64_t offset, int64_t length, int32_t priority)
{
- Mutex::Autolock lock(&mRestartLock);
- if (!mQuit) {
- mRestart.push_back(channel);
- mCondition.signal();
- }
+ ALOGV("%s(fd=%d, offset=%lld, length=%lld, priority=%d)",
+ __func__, fd, (long long)offset, (long long)length, priority);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ return mSoundManager.load(fd, offset, length, priority);
}
-void SoundPool::addToStopList(SoundChannel* channel)
+bool SoundPool::unload(int32_t soundID)
{
- Mutex::Autolock lock(&mRestartLock);
- if (!mQuit) {
- mStop.push_back(channel);
- mCondition.signal();
- }
+ ALOGV("%s(%d)", __func__, soundID);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ return mSoundManager.unload(soundID);
}
-int SoundPool::beginThread(void* arg)
+int32_t SoundPool::play(int32_t soundID, float leftVolume, float rightVolume,
+ int32_t priority, int32_t loop, float rate)
{
- SoundPool* p = (SoundPool*)arg;
- return p->run();
-}
+ ALOGV("%s(soundID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f)",
+ __func__, soundID, leftVolume, rightVolume, priority, loop, rate);
-int SoundPool::run()
-{
- mRestartLock.lock();
- while (!mQuit) {
- mCondition.wait(mRestartLock);
- ALOGV("awake");
- if (mQuit) break;
+ // New for R: check arguments to ensure track can be created.
+ // If SoundPool defers the creation of the AudioTrack to the StreamManager thread,
+ // the failure to create may not be visible to the caller, so this precheck is needed.
+ if (checkVolume(&leftVolume, &rightVolume)
+ || checkPriority(&priority)
+ || checkLoop(&loop)
+ || checkRate(&rate)) return 0;
- while (!mStop.empty()) {
- SoundChannel* channel;
- ALOGV("Getting channel from stop list");
- List<SoundChannel* >::iterator iter = mStop.begin();
- channel = *iter;
- mStop.erase(iter);
- mRestartLock.unlock();
- if (channel != 0) {
- Mutex::Autolock lock(&mLock);
- channel->stop();
- }
- mRestartLock.lock();
- if (mQuit) break;
- }
-
- while (!mRestart.empty()) {
- SoundChannel* channel;
- ALOGV("Getting channel from list");
- List<SoundChannel*>::iterator iter = mRestart.begin();
- channel = *iter;
- mRestart.erase(iter);
- mRestartLock.unlock();
- if (channel != 0) {
- Mutex::Autolock lock(&mLock);
- channel->nextEvent();
- }
- mRestartLock.lock();
- if (mQuit) break;
- }
- }
-
- mStop.clear();
- mRestart.clear();
- mCondition.signal();
- mRestartLock.unlock();
- ALOGV("goodbye");
- return 0;
-}
-
-void SoundPool::quit()
-{
- mRestartLock.lock();
- mQuit = true;
- mCondition.signal();
- mCondition.wait(mRestartLock);
- ALOGV("return from quit");
- mRestartLock.unlock();
-}
-
-bool SoundPool::startThreads()
-{
- createThreadEtc(beginThread, this, "SoundPool");
- if (mDecodeThread == NULL)
- mDecodeThread = new SoundPoolThread(this);
- return mDecodeThread != NULL;
-}
-
-sp<Sample> SoundPool::findSample(int sampleID)
-{
- Mutex::Autolock lock(&mLock);
- return findSample_l(sampleID);
-}
-
-sp<Sample> SoundPool::findSample_l(int sampleID)
-{
- return mSamples.valueFor(sampleID);
-}
-
-SoundChannel* SoundPool::findChannel(int channelID)
-{
- for (int i = 0; i < mMaxChannels; ++i) {
- if (mChannelPool[i].channelID() == channelID) {
- return &mChannelPool[i];
- }
- }
- return NULL;
-}
-
-SoundChannel* SoundPool::findNextChannel(int channelID)
-{
- for (int i = 0; i < mMaxChannels; ++i) {
- if (mChannelPool[i].nextChannelID() == channelID) {
- return &mChannelPool[i];
- }
- }
- return NULL;
-}
-
-int SoundPool::load(int fd, int64_t offset, int64_t length, int priority __unused)
-{
- ALOGV("load: fd=%d, offset=%" PRId64 ", length=%" PRId64 ", priority=%d",
- fd, offset, length, priority);
- int sampleID;
- {
- Mutex::Autolock lock(&mLock);
- sampleID = ++mNextSampleID;
- sp<Sample> sample = new Sample(sampleID, fd, offset, length);
- mSamples.add(sampleID, sample);
- sample->startLoad();
- }
- // mDecodeThread->loadSample() must be called outside of mLock.
- // mDecodeThread->loadSample() may block on mDecodeThread message queue space;
- // the message queue emptying may block on SoundPool::findSample().
- //
- // It theoretically possible that sample loads might decode out-of-order.
- mDecodeThread->loadSample(sampleID);
- return sampleID;
-}
-
-bool SoundPool::unload(int sampleID)
-{
- ALOGV("unload: sampleID=%d", sampleID);
- Mutex::Autolock lock(&mLock);
- return mSamples.removeItem(sampleID) >= 0; // removeItem() returns index or BAD_VALUE
-}
-
-int SoundPool::play(int sampleID, float leftVolume, float rightVolume,
- int priority, int loop, float rate)
-{
- ALOGV("play sampleID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f",
- sampleID, leftVolume, rightVolume, priority, loop, rate);
- SoundChannel* channel;
- int channelID;
-
- Mutex::Autolock lock(&mLock);
-
- if (mQuit) {
- return 0;
- }
- // is sample ready?
- sp<Sample> sample(findSample_l(sampleID));
- if ((sample == 0) || (sample->state() != Sample::READY)) {
- ALOGW(" sample %d not READY", sampleID);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ const std::shared_ptr<soundpool::Sound> sound = mSoundManager.findSound(soundID);
+ if (sound == nullptr || sound->getState() != soundpool::Sound::READY) {
+ ALOGW("%s soundID %d not READY", __func__, soundID);
return 0;
}
- dump();
-
- // allocate a channel
- channel = allocateChannel_l(priority, sampleID);
-
- // no channel allocated - return 0
- if (!channel) {
- ALOGV("No channel allocated");
- return 0;
- }
-
- channelID = ++mNextChannelID;
-
- ALOGV("play channel %p state = %d", channel, channel->state());
- channel->play(sample, channelID, leftVolume, rightVolume, priority, loop, rate);
- return channelID;
-}
-
-SoundChannel* SoundPool::allocateChannel_l(int priority, int sampleID)
-{
- List<SoundChannel*>::iterator iter;
- SoundChannel* channel = NULL;
-
- // check if channel for given sampleID still available
- if (!mChannels.empty()) {
- for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
- if (sampleID == (*iter)->getPrevSampleID() && (*iter)->state() == SoundChannel::IDLE) {
- channel = *iter;
- mChannels.erase(iter);
- ALOGV("Allocated recycled channel for same sampleID");
- break;
- }
- }
- }
-
- // allocate any channel
- if (!channel && !mChannels.empty()) {
- iter = mChannels.begin();
- if (priority >= (*iter)->priority()) {
- channel = *iter;
- mChannels.erase(iter);
- ALOGV("Allocated active channel");
- }
- }
-
- // update priority and put it back in the list
- if (channel) {
- channel->setPriority(priority);
- for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
- if (priority < (*iter)->priority()) {
- break;
- }
- }
- mChannels.insert(iter, channel);
- }
- return channel;
-}
-
-// move a channel from its current position to the front of the list
-void SoundPool::moveToFront_l(SoundChannel* channel)
-{
- for (List<SoundChannel*>::iterator iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
- if (*iter == channel) {
- mChannels.erase(iter);
- mChannels.push_front(channel);
- break;
- }
- }
-}
-
-void SoundPool::pause(int channelID)
-{
- ALOGV("pause(%d)", channelID);
- Mutex::Autolock lock(&mLock);
- SoundChannel* channel = findChannel(channelID);
- if (channel) {
- channel->pause();
- }
+ const int32_t streamID = mStreamManager.queueForPlay(
+ sound, soundID, leftVolume, rightVolume, priority, loop, rate);
+ ALOGV("%s returned %d", __func__, streamID);
+ return streamID;
}
void SoundPool::autoPause()
{
- ALOGV("autoPause()");
- Mutex::Autolock lock(&mLock);
- for (int i = 0; i < mMaxChannels; ++i) {
- SoundChannel* channel = &mChannelPool[i];
- channel->autoPause();
- }
-}
-
-void SoundPool::resume(int channelID)
-{
- ALOGV("resume(%d)", channelID);
- Mutex::Autolock lock(&mLock);
- SoundChannel* channel = findChannel(channelID);
- if (channel) {
- channel->resume();
- }
-}
-
-void SoundPool::mute(bool muting)
-{
- ALOGV("mute(%d)", muting);
- Mutex::Autolock lock(&mLock);
- mMuted = muting;
- if (!mChannels.empty()) {
- for (List<SoundChannel*>::iterator iter = mChannels.begin();
- iter != mChannels.end(); ++iter) {
- (*iter)->mute(muting);
- }
- }
+ ALOGV("%s()", __func__);
+ auto apiLock = std::make_unique<std::lock_guard<std::mutex>>(mApiLock);
+ mStreamManager.forEach([](soundpool::Stream *stream) { stream->autoPause(); });
}
void SoundPool::autoResume()
{
- ALOGV("autoResume()");
- Mutex::Autolock lock(&mLock);
- for (int i = 0; i < mMaxChannels; ++i) {
- SoundChannel* channel = &mChannelPool[i];
- channel->autoResume();
+ ALOGV("%s()", __func__);
+ auto apiLock = std::make_unique<std::lock_guard<std::mutex>>(mApiLock);
+ mStreamManager.forEach([](soundpool::Stream *stream) { stream->autoResume(); });
+}
+
+void SoundPool::mute(bool muting)
+{
+ ALOGV("%s(%d)", __func__, muting);
+ auto apiLock = std::make_unique<std::lock_guard<std::mutex>>(mApiLock);
+ mStreamManager.forEach([=](soundpool::Stream *stream) { stream->mute(muting); });
+}
+
+void SoundPool::pause(int32_t streamID)
+{
+ ALOGV("%s(%d)", __func__, streamID);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+ stream->pause(streamID);
}
}
-void SoundPool::stop(int channelID)
+void SoundPool::resume(int32_t streamID)
{
- ALOGV("stop(%d)", channelID);
- Mutex::Autolock lock(&mLock);
- SoundChannel* channel = findChannel(channelID);
- if (channel) {
- channel->stop();
- } else {
- channel = findNextChannel(channelID);
- if (channel)
- channel->clearNextEvent();
+ ALOGV("%s(%d)", __func__, streamID);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+ stream->resume(streamID);
}
}
-void SoundPool::setVolume(int channelID, float leftVolume, float rightVolume)
+void SoundPool::stop(int32_t streamID)
{
- Mutex::Autolock lock(&mLock);
- SoundChannel* channel = findChannel(channelID);
- if (channel) {
- channel->setVolume(leftVolume, rightVolume);
+ ALOGV("%s(%d)", __func__, streamID);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ soundpool::Stream* stream = mStreamManager.findStream(streamID);
+ if (stream != nullptr && stream->requestStop(streamID)) {
+ mStreamManager.moveToRestartQueue(stream);
}
}
-void SoundPool::setPriority(int channelID, int priority)
+void SoundPool::setVolume(int32_t streamID, float leftVolume, float rightVolume)
{
- ALOGV("setPriority(%d, %d)", channelID, priority);
- Mutex::Autolock lock(&mLock);
- SoundChannel* channel = findChannel(channelID);
- if (channel) {
- channel->setPriority(priority);
+ ALOGV("%s(%d, %f %f)", __func__, streamID, leftVolume, rightVolume);
+ if (checkVolume(&leftVolume, &rightVolume)) return;
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+ stream->setVolume(streamID, leftVolume, rightVolume);
}
}
-void SoundPool::setLoop(int channelID, int loop)
+void SoundPool::setPriority(int32_t streamID, int32_t priority)
{
- ALOGV("setLoop(%d, %d)", channelID, loop);
- Mutex::Autolock lock(&mLock);
- SoundChannel* channel = findChannel(channelID);
- if (channel) {
- channel->setLoop(loop);
+ ALOGV("%s(%d, %d)", __func__, streamID, priority);
+ if (checkPriority(&priority)) return;
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+ stream->setPriority(streamID, priority);
}
}
-void SoundPool::setRate(int channelID, float rate)
+void SoundPool::setLoop(int32_t streamID, int32_t loop)
{
- ALOGV("setRate(%d, %f)", channelID, rate);
- Mutex::Autolock lock(&mLock);
- SoundChannel* channel = findChannel(channelID);
- if (channel) {
- channel->setRate(rate);
+ ALOGV("%s(%d, %d)", __func__, streamID, loop);
+ if (checkLoop(&loop)) return;
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+ stream->setLoop(streamID, loop);
}
}
-// call with lock held
-void SoundPool::done_l(SoundChannel* channel)
+void SoundPool::setRate(int32_t streamID, float rate)
{
- ALOGV("done_l(%d)", channel->channelID());
- // if "stolen", play next event
- if (channel->nextChannelID() != 0) {
- ALOGV("add to restart list");
- addToRestartList(channel);
- }
-
- // return to idle state
- else {
- ALOGV("move to front");
- moveToFront_l(channel);
+ ALOGV("%s(%d, %f)", __func__, streamID, rate);
+ if (checkRate(&rate)) return;
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+ stream->setRate(streamID, rate);
}
}
void SoundPool::setCallback(SoundPoolCallback* callback, void* user)
{
- Mutex::Autolock lock(&mCallbackLock);
- mCallback = callback;
- mUserData = user;
+ ALOGV("%s(%p, %p)", __func__, callback, user);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ mSoundManager.setCallback(this, callback, user);
}
-void SoundPool::notify(SoundPoolEvent event)
+void* SoundPool::getUserData() const
{
- Mutex::Autolock lock(&mCallbackLock);
- if (mCallback != NULL) {
- mCallback(event, this, mUserData);
- }
-}
-
-void SoundPool::dump()
-{
- for (int i = 0; i < mMaxChannels; ++i) {
- mChannelPool[i].dump();
- }
-}
-
-
-Sample::Sample(int sampleID, int fd, int64_t offset, int64_t length)
-{
- init();
- mSampleID = sampleID;
- mFd = dup(fd);
- mOffset = offset;
- mLength = length;
- ALOGV("create sampleID=%d, fd=%d, offset=%" PRId64 " length=%" PRId64,
- mSampleID, mFd, mLength, mOffset);
-}
-
-void Sample::init()
-{
- mSize = 0;
- mRefCount = 0;
- mSampleID = 0;
- mState = UNLOADED;
- mFd = -1;
- mOffset = 0;
- mLength = 0;
-}
-
-Sample::~Sample()
-{
- ALOGV("Sample::destructor sampleID=%d, fd=%d", mSampleID, mFd);
- if (mFd > 0) {
- ALOGV("close(%d)", mFd);
- ::close(mFd);
- }
-}
-
-static status_t decode(int fd, int64_t offset, int64_t length,
- uint32_t *rate, int *numChannels, audio_format_t *audioFormat,
- audio_channel_mask_t *channelMask, sp<MemoryHeapBase> heap,
- size_t *memsize) {
-
- ALOGV("fd %d, offset %" PRId64 ", size %" PRId64, fd, offset, length);
- AMediaExtractor *ex = AMediaExtractor_new();
- status_t err = AMediaExtractor_setDataSourceFd(ex, fd, offset, length);
-
- if (err != AMEDIA_OK) {
- AMediaExtractor_delete(ex);
- return err;
- }
-
- *audioFormat = AUDIO_FORMAT_PCM_16_BIT;
-
- size_t numTracks = AMediaExtractor_getTrackCount(ex);
- for (size_t i = 0; i < numTracks; i++) {
- AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
- const char *mime;
- if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
- AMediaExtractor_delete(ex);
- AMediaFormat_delete(format);
- return UNKNOWN_ERROR;
- }
- if (strncmp(mime, "audio/", 6) == 0) {
-
- AMediaCodec *codec = AMediaCodec_createDecoderByType(mime);
- if (codec == NULL
- || AMediaCodec_configure(codec, format,
- NULL /* window */, NULL /* drm */, 0 /* flags */) != AMEDIA_OK
- || AMediaCodec_start(codec) != AMEDIA_OK
- || AMediaExtractor_selectTrack(ex, i) != AMEDIA_OK) {
- AMediaExtractor_delete(ex);
- AMediaCodec_delete(codec);
- AMediaFormat_delete(format);
- return UNKNOWN_ERROR;
- }
-
- bool sawInputEOS = false;
- bool sawOutputEOS = false;
- uint8_t* writePos = static_cast<uint8_t*>(heap->getBase());
- size_t available = heap->getSize();
- size_t written = 0;
-
- AMediaFormat_delete(format);
- format = AMediaCodec_getOutputFormat(codec);
-
- while (!sawOutputEOS) {
- if (!sawInputEOS) {
- ssize_t bufidx = AMediaCodec_dequeueInputBuffer(codec, 5000);
- ALOGV("input buffer %zd", bufidx);
- if (bufidx >= 0) {
- size_t bufsize;
- uint8_t *buf = AMediaCodec_getInputBuffer(codec, bufidx, &bufsize);
- if (buf == nullptr) {
- ALOGE("AMediaCodec_getInputBuffer returned nullptr, short decode");
- break;
- }
- int sampleSize = AMediaExtractor_readSampleData(ex, buf, bufsize);
- ALOGV("read %d", sampleSize);
- if (sampleSize < 0) {
- sampleSize = 0;
- sawInputEOS = true;
- ALOGV("EOS");
- }
- int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex);
-
- media_status_t mstatus = AMediaCodec_queueInputBuffer(codec, bufidx,
- 0 /* offset */, sampleSize, presentationTimeUs,
- sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
- if (mstatus != AMEDIA_OK) {
- // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
- ALOGE("AMediaCodec_queueInputBuffer returned status %d, short decode",
- (int)mstatus);
- break;
- }
- (void)AMediaExtractor_advance(ex);
- }
- }
-
- AMediaCodecBufferInfo info;
- int status = AMediaCodec_dequeueOutputBuffer(codec, &info, 1);
- ALOGV("dequeueoutput returned: %d", status);
- if (status >= 0) {
- if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
- ALOGV("output EOS");
- sawOutputEOS = true;
- }
- ALOGV("got decoded buffer size %d", info.size);
-
- uint8_t *buf = AMediaCodec_getOutputBuffer(codec, status, NULL /* out_size */);
- if (buf == nullptr) {
- ALOGE("AMediaCodec_getOutputBuffer returned nullptr, short decode");
- break;
- }
- size_t dataSize = info.size;
- if (dataSize > available) {
- dataSize = available;
- }
- memcpy(writePos, buf + info.offset, dataSize);
- writePos += dataSize;
- written += dataSize;
- available -= dataSize;
- media_status_t mstatus = AMediaCodec_releaseOutputBuffer(
- codec, status, false /* render */);
- if (mstatus != AMEDIA_OK) {
- // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
- ALOGE("AMediaCodec_releaseOutputBuffer returned status %d, short decode",
- (int)mstatus);
- break;
- }
- if (available == 0) {
- // there might be more data, but there's no space for it
- sawOutputEOS = true;
- }
- } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
- ALOGV("output buffers changed");
- } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
- AMediaFormat_delete(format);
- format = AMediaCodec_getOutputFormat(codec);
- ALOGV("format changed to: %s", AMediaFormat_toString(format));
- } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
- ALOGV("no output buffer right now");
- } else if (status <= AMEDIA_ERROR_BASE) {
- ALOGE("decode error: %d", status);
- break;
- } else {
- ALOGV("unexpected info code: %d", status);
- }
- }
-
- (void)AMediaCodec_stop(codec);
- (void)AMediaCodec_delete(codec);
- (void)AMediaExtractor_delete(ex);
- if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, (int32_t*) rate) ||
- !AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, numChannels)) {
- (void)AMediaFormat_delete(format);
- return UNKNOWN_ERROR;
- }
- if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_MASK,
- (int32_t*) channelMask)) {
- *channelMask = AUDIO_CHANNEL_NONE;
- }
- (void)AMediaFormat_delete(format);
- *memsize = written;
- return OK;
- }
- (void)AMediaFormat_delete(format);
- }
- (void)AMediaExtractor_delete(ex);
- return UNKNOWN_ERROR;
-}
-
-status_t Sample::doLoad()
-{
- uint32_t sampleRate;
- int numChannels;
- audio_format_t format;
- audio_channel_mask_t channelMask;
- status_t status;
- mHeap = new MemoryHeapBase(kDefaultHeapSize);
-
- ALOGV("Start decode");
- status = decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format,
- &channelMask, mHeap, &mSize);
- ALOGV("close(%d)", mFd);
- ::close(mFd);
- mFd = -1;
- if (status != NO_ERROR) {
- ALOGE("Unable to load sample");
- goto error;
- }
- ALOGV("pointer = %p, size = %zu, sampleRate = %u, numChannels = %d",
- mHeap->getBase(), mSize, sampleRate, numChannels);
-
- if (sampleRate > kMaxSampleRate) {
- ALOGE("Sample rate (%u) out of range", sampleRate);
- status = BAD_VALUE;
- goto error;
- }
-
- if ((numChannels < 1) || (numChannels > FCC_8)) {
- ALOGE("Sample channel count (%d) out of range", numChannels);
- status = BAD_VALUE;
- goto error;
- }
-
- mData = new MemoryBase(mHeap, 0, mSize);
- mSampleRate = sampleRate;
- mNumChannels = numChannels;
- mFormat = format;
- mChannelMask = channelMask;
- mState = READY;
- return NO_ERROR;
-
-error:
- mHeap.clear();
- return status;
-}
-
-
-void SoundChannel::init(SoundPool* soundPool)
-{
- mSoundPool = soundPool;
- mPrevSampleID = -1;
-}
-
-// call with sound pool lock held
-void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftVolume,
- float rightVolume, int priority, int loop, float rate)
-{
- sp<AudioTrack> oldTrack;
- sp<AudioTrack> newTrack;
- status_t status = NO_ERROR;
-
- { // scope for the lock
- Mutex::Autolock lock(&mLock);
-
- ALOGV("SoundChannel::play %p: sampleID=%d, channelID=%d, leftVolume=%f, rightVolume=%f,"
- " priority=%d, loop=%d, rate=%f",
- this, sample->sampleID(), nextChannelID, leftVolume, rightVolume,
- priority, loop, rate);
-
- // if not idle, this voice is being stolen
- if (mState != IDLE) {
- ALOGV("channel %d stolen - event queued for channel %d", channelID(), nextChannelID);
- mNextEvent.set(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate);
- stop_l();
- return;
- }
-
- // initialize track
- size_t afFrameCount;
- uint32_t afSampleRate;
- audio_stream_type_t streamType =
- AudioSystem::attributesToStreamType(*mSoundPool->attributes());
- if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
- afFrameCount = kDefaultFrameCount;
- }
- if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
- afSampleRate = kDefaultSampleRate;
- }
- int numChannels = sample->numChannels();
- uint32_t sampleRate = uint32_t(float(sample->sampleRate()) * rate + 0.5);
- size_t frameCount = 0;
-
- if (loop) {
- const audio_format_t format = sample->format();
- const size_t frameSize = audio_is_linear_pcm(format)
- ? numChannels * audio_bytes_per_sample(format) : 1;
- frameCount = sample->size() / frameSize;
- }
-
-#ifndef USE_SHARED_MEM_BUFFER
- uint32_t totalFrames = (kDefaultBufferCount * afFrameCount * sampleRate) / afSampleRate;
- // Ensure minimum audio buffer size in case of short looped sample
- if(frameCount < totalFrames) {
- frameCount = totalFrames;
- }
-#endif
-
- // check if the existing track has the same sample id.
- if (mAudioTrack != 0 && mPrevSampleID == sample->sampleID()) {
- // the sample rate may fail to change if the audio track is a fast track.
- if (mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
- newTrack = mAudioTrack;
- ALOGV("reusing track %p for sample %d", mAudioTrack.get(), sample->sampleID());
- }
- }
- if (newTrack == 0) {
- // mToggle toggles each time a track is started on a given channel.
- // The toggle is concatenated with the SoundChannel address and passed to AudioTrack
- // as callback user data. This enables the detection of callbacks received from the old
- // audio track while the new one is being started and avoids processing them with
- // wrong audio audio buffer size (mAudioBufferSize)
- unsigned long toggle = mToggle ^ 1;
- void *userData = (void *)((unsigned long)this | toggle);
- audio_channel_mask_t sampleChannelMask = sample->channelMask();
- // When sample contains a not none channel mask, use it as is.
- // Otherwise, use channel count to calculate channel mask.
- audio_channel_mask_t channelMask = sampleChannelMask != AUDIO_CHANNEL_NONE
- ? sampleChannelMask : audio_channel_out_mask_from_count(numChannels);
-
- // do not create a new audio track if current track is compatible with sample parameters
- #ifdef USE_SHARED_MEM_BUFFER
- newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
- channelMask, sample->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, callback, userData,
- 0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
- AudioTrack::TRANSFER_DEFAULT,
- NULL /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/, mSoundPool->attributes());
- #else
- uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
- newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
- channelMask, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,
- bufferFrames, AUDIO_SESSION_ALLOCATE, AudioTrack::TRANSFER_DEFAULT,
- NULL /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/, mSoundPool->attributes());
- #endif
- oldTrack = mAudioTrack;
- status = newTrack->initCheck();
- if (status != NO_ERROR) {
- ALOGE("Error creating AudioTrack");
- // newTrack goes out of scope, so reference count drops to zero
- goto exit;
- }
- // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
- mToggle = toggle;
- mAudioTrack = newTrack;
- ALOGV("using new track %p for sample %d", newTrack.get(), sample->sampleID());
- }
- if (mMuted) {
- newTrack->setVolume(0.0f, 0.0f);
- } else {
- newTrack->setVolume(leftVolume, rightVolume);
- }
- newTrack->setLoop(0, frameCount, loop);
- mPos = 0;
- mSample = sample;
- mChannelID = nextChannelID;
- mPriority = priority;
- mLoop = loop;
- mLeftVolume = leftVolume;
- mRightVolume = rightVolume;
- mNumChannels = numChannels;
- mRate = rate;
- clearNextEvent();
- mState = PLAYING;
- mAudioTrack->start();
- mAudioBufferSize = newTrack->frameCount()*newTrack->frameSize();
- }
-
-exit:
- ALOGV("delete oldTrack %p", oldTrack.get());
- if (status != NO_ERROR) {
- mAudioTrack.clear();
- }
-}
-
-void SoundChannel::nextEvent()
-{
- sp<Sample> sample;
- int nextChannelID;
- float leftVolume;
- float rightVolume;
- int priority;
- int loop;
- float rate;
-
- // check for valid event
- {
- Mutex::Autolock lock(&mLock);
- nextChannelID = mNextEvent.channelID();
- if (nextChannelID == 0) {
- ALOGV("stolen channel has no event");
- return;
- }
-
- sample = mNextEvent.sample();
- leftVolume = mNextEvent.leftVolume();
- rightVolume = mNextEvent.rightVolume();
- priority = mNextEvent.priority();
- loop = mNextEvent.loop();
- rate = mNextEvent.rate();
- }
-
- ALOGV("Starting stolen channel %d -> %d", channelID(), nextChannelID);
- play(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate);
-}
-
-void SoundChannel::callback(int event, void* user, void *info)
-{
- SoundChannel* channel = static_cast<SoundChannel*>((void *)((unsigned long)user & ~1));
-
- channel->process(event, info, (unsigned long)user & 1);
-}
-
-void SoundChannel::process(int event, void *info, unsigned long toggle)
-{
- //ALOGV("process(%d)", mChannelID);
-
- Mutex::Autolock lock(&mLock);
-
- AudioTrack::Buffer* b = NULL;
- if (event == AudioTrack::EVENT_MORE_DATA) {
- b = static_cast<AudioTrack::Buffer *>(info);
- }
-
- if (mToggle != toggle) {
- ALOGV("process wrong toggle %p channel %d", this, mChannelID);
- if (b != NULL) {
- b->size = 0;
- }
- return;
- }
-
- sp<Sample> sample = mSample;
-
-// ALOGV("SoundChannel::process event %d", event);
-
- if (event == AudioTrack::EVENT_MORE_DATA) {
-
- // check for stop state
- if (b->size == 0) return;
-
- if (mState == IDLE) {
- b->size = 0;
- return;
- }
-
- if (sample != 0) {
- // fill buffer
- uint8_t* q = (uint8_t*) b->i8;
- size_t count = 0;
-
- if (mPos < (int)sample->size()) {
- uint8_t* p = sample->data() + mPos;
- count = sample->size() - mPos;
- if (count > b->size) {
- count = b->size;
- }
- memcpy(q, p, count);
-// ALOGV("fill: q=%p, p=%p, mPos=%u, b->size=%u, count=%d", q, p, mPos, b->size,
-// count);
- } else if (mPos < mAudioBufferSize) {
- count = mAudioBufferSize - mPos;
- if (count > b->size) {
- count = b->size;
- }
- memset(q, 0, count);
-// ALOGV("fill extra: q=%p, mPos=%u, b->size=%u, count=%d", q, mPos, b->size, count);
- }
-
- mPos += count;
- b->size = count;
- //ALOGV("buffer=%p, [0]=%d", b->i16, b->i16[0]);
- }
- } else if (event == AudioTrack::EVENT_UNDERRUN || event == AudioTrack::EVENT_BUFFER_END) {
- ALOGV("process %p channel %d event %s",
- this, mChannelID, (event == AudioTrack::EVENT_UNDERRUN) ? "UNDERRUN" :
- "BUFFER_END");
- // Only BUFFER_END should happen as we use static tracks.
- setVolume_l(0.f, 0.f); // set volume to 0 to indicate no need to ramp volume down.
- mSoundPool->addToStopList(this);
- } else if (event == AudioTrack::EVENT_LOOP_END) {
- ALOGV("End loop %p channel %d", this, mChannelID);
- } else if (event == AudioTrack::EVENT_NEW_IAUDIOTRACK) {
- ALOGV("process %p channel %d NEW_IAUDIOTRACK", this, mChannelID);
- } else {
- ALOGW("SoundChannel::process unexpected event %d", event);
- }
-}
-
-
-// call with lock held
-bool SoundChannel::doStop_l()
-{
- if (mState != IDLE) {
- ALOGV("stop");
- if (mLeftVolume != 0.f || mRightVolume != 0.f) {
- setVolume_l(0.f, 0.f);
- if (mSoundPool->attributes()->usage != AUDIO_USAGE_GAME) {
- // Since we're forcibly halting the previously playing content,
- // we sleep here to ensure the volume is ramped down before we stop the track.
- // Ideally the sleep time is the mixer period, or an approximation thereof
- // (Fast vs Normal tracks are different).
- ALOGV("sleeping: ChannelID:%d SampleID:%d", mChannelID, mSample->sampleID());
- std::this_thread::sleep_for(std::chrono::milliseconds(20));
- }
- }
- mAudioTrack->stop();
- mPrevSampleID = mSample->sampleID();
- mSample.clear();
- mState = IDLE;
- mPriority = IDLE_PRIORITY;
- return true;
- }
- return false;
-}
-
-// call with lock held and sound pool lock held
-void SoundChannel::stop_l()
-{
- if (doStop_l()) {
- mSoundPool->done_l(this);
- }
-}
-
-// call with sound pool lock held
-void SoundChannel::stop()
-{
- bool stopped;
- {
- Mutex::Autolock lock(&mLock);
- stopped = doStop_l();
- }
-
- if (stopped) {
- mSoundPool->done_l(this);
- }
-}
-
-//FIXME: Pause is a little broken right now
-void SoundChannel::pause()
-{
- Mutex::Autolock lock(&mLock);
- if (mState == PLAYING) {
- ALOGV("pause track");
- mState = PAUSED;
- mAudioTrack->pause();
- }
-}
-
-void SoundChannel::autoPause()
-{
- Mutex::Autolock lock(&mLock);
- if (mState == PLAYING) {
- ALOGV("pause track");
- mState = PAUSED;
- mAutoPaused = true;
- mAudioTrack->pause();
- }
-}
-
-void SoundChannel::resume()
-{
- Mutex::Autolock lock(&mLock);
- if (mState == PAUSED) {
- ALOGV("resume track");
- mState = PLAYING;
- mAutoPaused = false;
- mAudioTrack->start();
- }
-}
-
-void SoundChannel::autoResume()
-{
- Mutex::Autolock lock(&mLock);
- if (mAutoPaused && (mState == PAUSED)) {
- ALOGV("resume track");
- mState = PLAYING;
- mAutoPaused = false;
- mAudioTrack->start();
- }
-}
-
-void SoundChannel::setRate(float rate)
-{
- Mutex::Autolock lock(&mLock);
- if (mAudioTrack != NULL && mSample != 0) {
- uint32_t sampleRate = uint32_t(float(mSample->sampleRate()) * rate + 0.5);
- mAudioTrack->setSampleRate(sampleRate);
- mRate = rate;
- }
-}
-
-// call with lock held
-void SoundChannel::setVolume_l(float leftVolume, float rightVolume)
-{
- mLeftVolume = leftVolume;
- mRightVolume = rightVolume;
- if (mAudioTrack != NULL && !mMuted)
- mAudioTrack->setVolume(leftVolume, rightVolume);
-}
-
-void SoundChannel::setVolume(float leftVolume, float rightVolume)
-{
- Mutex::Autolock lock(&mLock);
- setVolume_l(leftVolume, rightVolume);
-}
-
-void SoundChannel::mute(bool muting)
-{
- Mutex::Autolock lock(&mLock);
- mMuted = muting;
- if (mAudioTrack != NULL) {
- if (mMuted) {
- mAudioTrack->setVolume(0.0f, 0.0f);
- } else {
- mAudioTrack->setVolume(mLeftVolume, mRightVolume);
- }
- }
-}
-
-void SoundChannel::setLoop(int loop)
-{
- Mutex::Autolock lock(&mLock);
- if (mAudioTrack != NULL && mSample != 0) {
- uint32_t loopEnd = mSample->size()/mNumChannels/
- ((mSample->format() == AUDIO_FORMAT_PCM_16_BIT) ? sizeof(int16_t) : sizeof(uint8_t));
- mAudioTrack->setLoop(0, loopEnd, loop);
- mLoop = loop;
- }
-}
-
-SoundChannel::~SoundChannel()
-{
- ALOGV("SoundChannel destructor %p", this);
- {
- Mutex::Autolock lock(&mLock);
- clearNextEvent();
- doStop_l();
- }
- // do not call AudioTrack destructor with mLock held as it will wait for the AudioTrack
- // callback thread to exit which may need to execute process() and acquire the mLock.
- mAudioTrack.clear();
-}
-
-void SoundChannel::dump()
-{
- ALOGV("mState = %d mChannelID=%d, mNumChannels=%d, mPos = %d, mPriority=%d, mLoop=%d",
- mState, mChannelID, mNumChannels, mPos, mPriority, mLoop);
-}
-
-void SoundEvent::set(const sp<Sample>& sample, int channelID, float leftVolume,
- float rightVolume, int priority, int loop, float rate)
-{
- mSample = sample;
- mChannelID = channelID;
- mLeftVolume = leftVolume;
- mRightVolume = rightVolume;
- mPriority = priority;
- mLoop = loop;
- mRate =rate;
+ ALOGV("%s()", __func__);
+ auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+ return mSoundManager.getUserData();
}
} // end namespace android
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index 01e4faa..d5b16ef 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -14,227 +14,59 @@
* limitations under the License.
*/
-#ifndef SOUNDPOOL_H_
-#define SOUNDPOOL_H_
+#pragma once
-#include <utils/threads.h>
-#include <utils/List.h>
-#include <utils/Vector.h>
-#include <utils/KeyedVector.h>
-#include <media/AudioTrack.h>
-#include <binder/MemoryHeapBase.h>
-#include <binder/MemoryBase.h>
+#include "SoundManager.h"
+#include "StreamManager.h"
namespace android {
-static const int IDLE_PRIORITY = -1;
-
-// forward declarations
-class SoundEvent;
-class SoundPoolThread;
-class SoundPool;
-
-// for queued events
-class SoundPoolEvent {
-public:
- explicit SoundPoolEvent(int msg, int arg1=0, int arg2=0) :
- mMsg(msg), mArg1(arg1), mArg2(arg2) {}
- int mMsg;
- int mArg1;
- int mArg2;
- enum MessageType { INVALID, SAMPLE_LOADED };
-};
-
-// callback function prototype
-typedef void SoundPoolCallback(SoundPoolEvent event, SoundPool* soundPool, void* user);
-
-// tracks samples used by application
-class Sample : public RefBase {
-public:
- enum sample_state { UNLOADED, LOADING, READY, UNLOADING };
- Sample(int sampleID, int fd, int64_t offset, int64_t length);
- ~Sample();
- int sampleID() { return mSampleID; }
- int numChannels() { return mNumChannels; }
- int sampleRate() { return mSampleRate; }
- audio_format_t format() { return mFormat; }
- audio_channel_mask_t channelMask() { return mChannelMask; }
- size_t size() { return mSize; }
- int state() { return mState; }
- uint8_t* data() { return static_cast<uint8_t*>(mData->unsecurePointer()); }
- status_t doLoad();
- void startLoad() { mState = LOADING; }
- sp<IMemory> getIMemory() { return mData; }
-
-private:
- void init();
-
- size_t mSize;
- volatile int32_t mRefCount;
- uint16_t mSampleID;
- uint16_t mSampleRate;
- uint8_t mState;
- uint8_t mNumChannels;
- audio_format_t mFormat;
- audio_channel_mask_t mChannelMask;
- int mFd;
- int64_t mOffset;
- int64_t mLength;
- sp<IMemory> mData;
- sp<MemoryHeapBase> mHeap;
-};
-
-// stores pending events for stolen channels
-class SoundEvent
-{
-public:
- SoundEvent() : mChannelID(0), mLeftVolume(0), mRightVolume(0),
- mPriority(IDLE_PRIORITY), mLoop(0), mRate(0) {}
- void set(const sp<Sample>& sample, int channelID, float leftVolume,
- float rightVolume, int priority, int loop, float rate);
- sp<Sample> sample() { return mSample; }
- int channelID() { return mChannelID; }
- float leftVolume() { return mLeftVolume; }
- float rightVolume() { return mRightVolume; }
- int priority() { return mPriority; }
- int loop() { return mLoop; }
- float rate() { return mRate; }
- void clear() { mChannelID = 0; mSample.clear(); }
-
-protected:
- sp<Sample> mSample;
- int mChannelID;
- float mLeftVolume;
- float mRightVolume;
- int mPriority;
- int mLoop;
- float mRate;
-};
-
-// for channels aka AudioTracks
-class SoundChannel : public SoundEvent {
-public:
- enum state { IDLE, RESUMING, STOPPING, PAUSED, PLAYING };
- SoundChannel() : mState(IDLE), mNumChannels(1),
- mPos(0), mToggle(0), mAutoPaused(false), mMuted(false) {}
- ~SoundChannel();
- void init(SoundPool* soundPool);
- void play(const sp<Sample>& sample, int channelID, float leftVolume, float rightVolume,
- int priority, int loop, float rate);
- void setVolume_l(float leftVolume, float rightVolume);
- void setVolume(float leftVolume, float rightVolume);
- void mute(bool muting);
- void stop_l();
- void stop();
- void pause();
- void autoPause();
- void resume();
- void autoResume();
- void setRate(float rate);
- int state() { return mState; }
- void setPriority(int priority) { mPriority = priority; }
- void setLoop(int loop);
- int numChannels() { return mNumChannels; }
- void clearNextEvent() { mNextEvent.clear(); }
- void nextEvent();
- int nextChannelID() { return mNextEvent.channelID(); }
- void dump();
- int getPrevSampleID(void) { return mPrevSampleID; }
-
-private:
- static void callback(int event, void* user, void *info);
- void process(int event, void *info, unsigned long toggle);
- bool doStop_l();
-
- SoundPool* mSoundPool;
- sp<AudioTrack> mAudioTrack;
- SoundEvent mNextEvent;
- Mutex mLock;
- int mState;
- int mNumChannels;
- int mPos;
- int mAudioBufferSize;
- unsigned long mToggle;
- bool mAutoPaused;
- int mPrevSampleID;
- bool mMuted;
-};
-
-// application object for managing a pool of sounds
+/**
+ * Native class for Java SoundPool, manages a pool of sounds.
+ *
+ * See the Android SoundPool Java documentation for description of valid values.
+ * https://developer.android.com/reference/android/media/SoundPool
+ */
class SoundPool {
- friend class SoundPoolThread;
- friend class SoundChannel;
public:
- SoundPool(int maxChannels, const audio_attributes_t* pAttributes);
+ SoundPool(int32_t maxStreams, const audio_attributes_t* attributes);
~SoundPool();
- int load(int fd, int64_t offset, int64_t length, int priority);
- bool unload(int sampleID);
- int play(int sampleID, float leftVolume, float rightVolume, int priority,
- int loop, float rate);
- void pause(int channelID);
- void mute(bool muting);
+
+ // SoundPool Java API support
+ int32_t load(int fd, int64_t offset, int64_t length, int32_t priority);
+ bool unload(int32_t soundID);
+ int32_t play(int32_t soundID, float leftVolume, float rightVolume, int32_t priority,
+ int32_t loop, float rate);
+ void pause(int32_t streamID);
void autoPause();
- void resume(int channelID);
+ void resume(int32_t streamID);
void autoResume();
- void stop(int channelID);
- void setVolume(int channelID, float leftVolume, float rightVolume);
- void setPriority(int channelID, int priority);
- void setLoop(int channelID, int loop);
- void setRate(int channelID, float rate);
- const audio_attributes_t* attributes() { return &mAttributes; }
-
- // called from SoundPoolThread
- void sampleLoaded(int sampleID);
- sp<Sample> findSample(int sampleID);
-
- // called from AudioTrack thread
- void done_l(SoundChannel* channel);
-
- // callback function
+ void stop(int32_t streamID);
+ void setVolume(int32_t streamID, float leftVolume, float rightVolume);
+ void setPriority(int32_t streamID, int32_t priority);
+ void setLoop(int32_t streamID, int32_t loop);
+ void setRate(int32_t streamID, float rate);
void setCallback(SoundPoolCallback* callback, void* user);
- void* getUserData() { return mUserData; }
+ void* getUserData() const;
+
+ // not exposed in the public Java API, used for internal playerSetVolume() muting.
+ void mute(bool muting);
private:
- SoundPool() {} // no default constructor
- bool startThreads();
- sp<Sample> findSample_l(int sampleID);
- SoundChannel* findChannel (int channelID);
- SoundChannel* findNextChannel (int channelID);
- SoundChannel* allocateChannel_l(int priority, int sampleID);
- void moveToFront_l(SoundChannel* channel);
- void notify(SoundPoolEvent event);
- void dump();
- // restart thread
- void addToRestartList(SoundChannel* channel);
- void addToStopList(SoundChannel* channel);
- static int beginThread(void* arg);
- int run();
- void quit();
+ // Constructor initialized variables
+ // Can access without lock as they are internally locked,
+ // though care needs to be taken that the final result composed of
+ // individually consistent actions are consistent.
+ soundpool::SoundManager mSoundManager;
+ soundpool::StreamManager mStreamManager;
- Mutex mLock;
- Mutex mRestartLock;
- Condition mCondition;
- SoundPoolThread* mDecodeThread;
- SoundChannel* mChannelPool;
- List<SoundChannel*> mChannels;
- List<SoundChannel*> mRestart;
- List<SoundChannel*> mStop;
- DefaultKeyedVector< int, sp<Sample> > mSamples;
- int mMaxChannels;
- audio_attributes_t mAttributes;
- int mAllocated;
- int mNextSampleID;
- int mNextChannelID;
- bool mQuit;
- bool mMuted;
-
- // callback
- Mutex mCallbackLock;
- SoundPoolCallback* mCallback;
- void* mUserData;
+ // mApiLock serializes SoundPool application calls (configurable by kUseApiLock).
+ // It only locks at the SoundPool layer and not below. At this level,
+ // mApiLock is only required for autoPause() and autoResume() to prevent zippering
+ // of the individual pauses and resumes, and mute() for self-interaction with itself.
+ // It is optional for all other apis.
+ mutable std::mutex mApiLock;
};
} // end namespace android
-
-#endif /*SOUNDPOOL_H_*/
diff --git a/media/jni/soundpool/SoundPoolThread.cpp b/media/jni/soundpool/SoundPoolThread.cpp
deleted file mode 100644
index ba3b482..0000000
--- a/media/jni/soundpool/SoundPoolThread.cpp
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 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
- *
- * 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.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "SoundPoolThread"
-#include "utils/Log.h"
-
-#include "SoundPoolThread.h"
-
-namespace android {
-
-void SoundPoolThread::write(SoundPoolMsg msg) {
- Mutex::Autolock lock(&mLock);
- while (mMsgQueue.size() >= maxMessages) {
- mCondition.wait(mLock);
- }
-
- // if thread is quitting, don't add to queue
- if (mRunning) {
- mMsgQueue.push(msg);
- mCondition.signal();
- }
-}
-
-const SoundPoolMsg SoundPoolThread::read() {
- Mutex::Autolock lock(&mLock);
- while (mMsgQueue.size() == 0) {
- mCondition.wait(mLock);
- }
- SoundPoolMsg msg = mMsgQueue[0];
- mMsgQueue.removeAt(0);
- mCondition.signal();
- return msg;
-}
-
-void SoundPoolThread::quit() {
- Mutex::Autolock lock(&mLock);
- if (mRunning) {
- mRunning = false;
- mMsgQueue.clear();
- mMsgQueue.push(SoundPoolMsg(SoundPoolMsg::KILL, 0));
- mCondition.signal();
- mCondition.wait(mLock);
- }
- ALOGV("return from quit");
-}
-
-SoundPoolThread::SoundPoolThread(SoundPool* soundPool) :
- mSoundPool(soundPool)
-{
- mMsgQueue.setCapacity(maxMessages);
- if (createThreadEtc(beginThread, this, "SoundPoolThread")) {
- mRunning = true;
- }
-}
-
-SoundPoolThread::~SoundPoolThread()
-{
- quit();
-}
-
-int SoundPoolThread::beginThread(void* arg) {
- ALOGV("beginThread");
- SoundPoolThread* soundPoolThread = (SoundPoolThread*)arg;
- return soundPoolThread->run();
-}
-
-int SoundPoolThread::run() {
- ALOGV("run");
- for (;;) {
- SoundPoolMsg msg = read();
- ALOGV("Got message m=%d, mData=%d", msg.mMessageType, msg.mData);
- switch (msg.mMessageType) {
- case SoundPoolMsg::KILL:
- ALOGV("goodbye");
- return NO_ERROR;
- case SoundPoolMsg::LOAD_SAMPLE:
- doLoadSample(msg.mData);
- break;
- default:
- ALOGW("run: Unrecognized message %d\n",
- msg.mMessageType);
- break;
- }
- }
-}
-
-void SoundPoolThread::loadSample(int sampleID) {
- write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, sampleID));
-}
-
-void SoundPoolThread::doLoadSample(int sampleID) {
- sp <Sample> sample = mSoundPool->findSample(sampleID);
- status_t status = -1;
- if (sample != 0) {
- status = sample->doLoad();
- }
- mSoundPool->notify(SoundPoolEvent(SoundPoolEvent::SAMPLE_LOADED, sampleID, status));
-}
-
-} // end namespace android
diff --git a/media/jni/soundpool/SoundPoolThread.h b/media/jni/soundpool/SoundPoolThread.h
deleted file mode 100644
index 7b3e1dd..0000000
--- a/media/jni/soundpool/SoundPoolThread.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 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
- *
- * 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.
- */
-
-#ifndef SOUNDPOOLTHREAD_H_
-#define SOUNDPOOLTHREAD_H_
-
-#include <utils/threads.h>
-#include <utils/Vector.h>
-#include <media/AudioTrack.h>
-
-#include "SoundPool.h"
-
-namespace android {
-
-class SoundPoolMsg {
-public:
- enum MessageType { INVALID, KILL, LOAD_SAMPLE };
- SoundPoolMsg() : mMessageType(INVALID), mData(0) {}
- SoundPoolMsg(MessageType MessageType, int data) :
- mMessageType(MessageType), mData(data) {}
- uint16_t mMessageType;
- uint16_t mData;
-};
-
-/*
- * This class handles background requests from the SoundPool
- */
-class SoundPoolThread {
-public:
- explicit SoundPoolThread(SoundPool* SoundPool);
- ~SoundPoolThread();
- void loadSample(int sampleID);
- void quit();
- void write(SoundPoolMsg msg);
-
-private:
- static const size_t maxMessages = 128;
-
- static int beginThread(void* arg);
- int run();
- void doLoadSample(int sampleID);
- const SoundPoolMsg read();
-
- Mutex mLock;
- Condition mCondition;
- Vector<SoundPoolMsg> mMsgQueue;
- SoundPool* mSoundPool;
- bool mRunning;
-};
-
-} // end namespace android
-
-#endif /*SOUNDPOOLTHREAD_H_*/
diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp
new file mode 100644
index 0000000..e7d4d90
--- /dev/null
+++ b/media/jni/soundpool/Stream.cpp
@@ -0,0 +1,448 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::Stream"
+#include <utils/Log.h>
+
+#include "Stream.h"
+
+#include "StreamManager.h"
+
+namespace android::soundpool {
+
+Stream::~Stream()
+{
+ ALOGV("%s(%p)", __func__, this);
+}
+
+void Stream::autoPause()
+{
+ std::lock_guard lock(mLock);
+ if (mState == PLAYING) {
+ ALOGV("%s: track streamID: %d", __func__, (int)mStreamID);
+ mState = PAUSED;
+ mAutoPaused = true;
+ if (mAudioTrack != nullptr) {
+ mAudioTrack->pause();
+ }
+ }
+}
+
+void Stream::autoResume()
+{
+ std::lock_guard lock(mLock);
+ if (mAutoPaused) {
+ if (mState == PAUSED) {
+ ALOGV("%s: track streamID: %d", __func__, (int)mStreamID);
+ mState = PLAYING;
+ if (mAudioTrack != nullptr) {
+ mAudioTrack->start();
+ }
+ }
+ mAutoPaused = false; // New for R: always reset autopause (consistent with API spec).
+ }
+}
+
+void Stream::mute(bool muting)
+{
+ std::lock_guard lock(mLock);
+ mMuted = muting;
+ if (mAudioTrack != nullptr) {
+ if (mMuted) {
+ mAudioTrack->setVolume(0.0f, 0.0f);
+ } else {
+ mAudioTrack->setVolume(mLeftVolume, mRightVolume);
+ }
+ }
+}
+
+void Stream::pause(int32_t streamID)
+{
+ std::lock_guard lock(mLock);
+ if (streamID == mStreamID) {
+ if (mState == PLAYING) {
+ ALOGV("%s: track streamID: %d", __func__, streamID);
+ mState = PAUSED;
+ if (mAudioTrack != nullptr) {
+ mAudioTrack->pause();
+ }
+ }
+ }
+}
+
+void Stream::resume(int32_t streamID)
+{
+ std::lock_guard lock(mLock);
+ if (streamID == mStreamID) {
+ if (mState == PAUSED) {
+ ALOGV("%s: track streamID: %d", __func__, streamID);
+ mState = PLAYING;
+ if (mAudioTrack != nullptr) {
+ mAudioTrack->start();
+ }
+ mAutoPaused = false; // TODO: is this right? (ambiguous per spec), move outside?
+ }
+ }
+}
+
+void Stream::setRate(int32_t streamID, float rate)
+{
+ std::lock_guard lock(mLock);
+ if (streamID == mStreamID) {
+ mRate = rate;
+ if (mAudioTrack != nullptr && mSound != nullptr) {
+ const uint32_t sampleRate = uint32_t(float(mSound->getSampleRate()) * rate + 0.5);
+ mAudioTrack->setSampleRate(sampleRate);
+ }
+ }
+}
+
+void Stream::setVolume_l(float leftVolume, float rightVolume)
+{
+ mLeftVolume = leftVolume;
+ mRightVolume = rightVolume;
+ if (mAudioTrack != nullptr && !mMuted) {
+ mAudioTrack->setVolume(leftVolume, rightVolume);
+ }
+}
+
+void Stream::setVolume(int32_t streamID, float leftVolume, float rightVolume)
+{
+ std::lock_guard lock(mLock);
+ if (streamID == mStreamID) {
+ setVolume_l(leftVolume, rightVolume);
+ }
+}
+
+void Stream::setPriority(int32_t streamID, int32_t priority)
+{
+ std::lock_guard lock(mLock);
+ if (streamID == mStreamID) {
+ mPriority = priority;
+ }
+}
+
+void Stream::setLoop(int32_t streamID, int32_t loop)
+{
+ std::lock_guard lock(mLock);
+ if (streamID == mStreamID) {
+ if (mAudioTrack != nullptr && mSound != nullptr) {
+ const uint32_t loopEnd = mSound->getSizeInBytes() / mSound->getChannelCount() /
+ (mSound->getFormat() == AUDIO_FORMAT_PCM_16_BIT
+ ? sizeof(int16_t) : sizeof(uint8_t));
+ mAudioTrack->setLoop(0, loopEnd, loop);
+ }
+ mLoop = loop;
+ }
+}
+
+void Stream::setPlay(
+ int32_t streamID, const std::shared_ptr<Sound> &sound, int32_t soundID,
+ float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate)
+{
+ std::lock_guard lock(mLock);
+ // We must be idle, or we must be repurposing a pending Stream.
+ LOG_ALWAYS_FATAL_IF(mState != IDLE && mAudioTrack != nullptr, "State %d must be IDLE", mState);
+ mSound = sound;
+ mSoundID = soundID;
+ mLeftVolume = leftVolume;
+ mRightVolume = rightVolume;
+ mPriority = priority;
+ mLoop = loop;
+ mRate = rate;
+ mState = PLAYING;
+ mAutoPaused = false; // New for R (consistent with Java API spec).
+ mStreamID = streamID; // prefer this to be the last, as it is an atomic sync point
+}
+
+void Stream::setStopTimeNs(int64_t stopTimeNs)
+{
+ std::lock_guard lock(mLock);
+ mStopTimeNs = stopTimeNs;
+}
+
+bool Stream::requestStop(int32_t streamID)
+{
+ std::lock_guard lock(mLock);
+ if (streamID == mStreamID) {
+ if (mAudioTrack != nullptr) {
+ if (mState == PLAYING && !mMuted && (mLeftVolume != 0.f || mRightVolume != 0.f)) {
+ setVolume_l(0.f, 0.f);
+ mStopTimeNs = systemTime() + kStopWaitTimeNs;
+ } else {
+ mStopTimeNs = systemTime();
+ }
+ return true; // must be queued on the restart list.
+ }
+ stop_l();
+ }
+ return false;
+}
+
+void Stream::stop()
+{
+ std::lock_guard lock(mLock);
+ stop_l();
+}
+
+void Stream::stop_l()
+{
+ if (mState != IDLE) {
+ if (mAudioTrack != nullptr) {
+ mAudioTrack->stop();
+ }
+ mSound.reset();
+ mState = IDLE;
+ }
+}
+
+void Stream::clearAudioTrack()
+{
+ // This will invoke the destructor which waits for the AudioTrack thread to join,
+ // and is currently the only safe way to ensure there are no callbacks afterwards.
+ mAudioTrack.clear();
+}
+
+Stream* Stream::getPairStream() const
+{
+ return mStreamManager->getPairStream(this);
+}
+
+Stream* Stream::playPairStream() {
+ Stream* pairStream = getPairStream();
+ LOG_ALWAYS_FATAL_IF(pairStream == nullptr, "No pair stream!");
+ sp<AudioTrack> releaseTracks[2];
+ {
+ // TODO: Do we really want to force a simultaneous synchronization between
+ // the stream and its pair?
+
+ // note locking order - the paired stream is obtained before the queued stream.
+ // we can invert the locking order, but it is slightly more optimal to do it this way.
+ std::lock_guard lockp(pairStream->mLock);
+ if (pairStream->mSound == nullptr) {
+ return nullptr; // no pair sound
+ }
+ {
+ std::lock_guard lock(mLock);
+ LOG_ALWAYS_FATAL_IF(mState != IDLE, "State: %d must be IDLE", mState);
+ // TODO: do we want a specific set() here?
+ pairStream->mAudioTrack = mAudioTrack;
+ pairStream->mSoundID = mSoundID; // optimization to reuse AudioTrack.
+ pairStream->mToggle = mToggle;
+ pairStream->mAutoPaused = mAutoPaused; // save autopause state
+ pairStream->mMuted = mMuted;
+ mAudioTrack.clear(); // the pair owns the audiotrack.
+ mSound.reset();
+ mSoundID = 0;
+ }
+ // TODO: do we need a specific play_l() anymore?
+ const int pairState = pairStream->mState;
+ pairStream->play_l(pairStream->mSound, pairStream->mStreamID,
+ pairStream->mLeftVolume, pairStream->mRightVolume, pairStream->mPriority,
+ pairStream->mLoop, pairStream->mRate, releaseTracks);
+ if (pairStream->mState == IDLE) {
+ return nullptr; // AudioTrack error
+ }
+ if (pairState == PAUSED) { // reestablish pause
+ pairStream->mState = PAUSED;
+ pairStream->mAudioTrack->pause();
+ }
+ }
+ // release tracks outside of Stream lock
+ return pairStream;
+}
+
+void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID,
+ float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate,
+ sp<AudioTrack> releaseTracks[2])
+{
+ // These tracks are released without the lock.
+ sp<AudioTrack> &oldTrack = releaseTracks[0];
+ sp<AudioTrack> &newTrack = releaseTracks[1];
+ status_t status = NO_ERROR;
+
+ {
+ ALOGV("%s(%p)(soundID=%d, streamID=%d, leftVolume=%f, rightVolume=%f,"
+ " priority=%d, loop=%d, rate=%f)",
+ __func__, this, sound->getSoundID(), nextStreamID, leftVolume, rightVolume,
+ priority, loop, rate);
+
+ // initialize track
+ const audio_stream_type_t streamType =
+ AudioSystem::attributesToStreamType(*mStreamManager->getAttributes());
+ const int32_t channelCount = sound->getChannelCount();
+ const uint32_t sampleRate = uint32_t(float(sound->getSampleRate()) * rate + 0.5);
+ size_t frameCount = 0;
+
+ if (loop) {
+ const audio_format_t format = sound->getFormat();
+ const size_t frameSize = audio_is_linear_pcm(format)
+ ? channelCount * audio_bytes_per_sample(format) : 1;
+ frameCount = sound->getSizeInBytes() / frameSize;
+ }
+
+ // check if the existing track has the same sound id.
+ if (mAudioTrack != nullptr && mSoundID == sound->getSoundID()) {
+ // the sample rate may fail to change if the audio track is a fast track.
+ if (mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
+ newTrack = mAudioTrack;
+ ALOGV("%s: reusing track %p for sound %d",
+ __func__, mAudioTrack.get(), sound->getSoundID());
+ }
+ }
+ if (newTrack == 0) {
+ // mToggle toggles each time a track is started on a given stream.
+ // The toggle is concatenated with the Stream address and passed to AudioTrack
+ // as callback user data. This enables the detection of callbacks received from the old
+ // audio track while the new one is being started and avoids processing them with
+ // wrong audio audio buffer size (mAudioBufferSize)
+ auto toggle = mToggle ^ 1;
+ void* userData = (void*)((uintptr_t)this | toggle);
+ audio_channel_mask_t soundChannelMask = sound->getChannelMask();
+ // When sound contains a valid channel mask, use it as is.
+ // Otherwise, use stream count to calculate channel mask.
+ audio_channel_mask_t channelMask = soundChannelMask != AUDIO_CHANNEL_NONE
+ ? soundChannelMask : audio_channel_out_mask_from_count(channelCount);
+
+ // do not create a new audio track if current track is compatible with sound parameters
+
+ newTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(),
+ channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST,
+ staticCallback, userData,
+ 0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
+ AudioTrack::TRANSFER_DEFAULT,
+ nullptr /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/,
+ mStreamManager->getAttributes());
+
+ oldTrack = mAudioTrack;
+ status = newTrack->initCheck();
+ if (status != NO_ERROR) {
+ ALOGE("%s: error creating AudioTrack", __func__);
+ // newTrack goes out of scope, so reference count drops to zero
+ goto exit;
+ }
+ // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
+ mToggle = toggle;
+ mAudioTrack = newTrack;
+ ALOGV("%s: using new track %p for sound %d",
+ __func__, newTrack.get(), sound->getSoundID());
+ }
+ if (mMuted) {
+ newTrack->setVolume(0.0f, 0.0f);
+ } else {
+ newTrack->setVolume(leftVolume, rightVolume);
+ }
+ newTrack->setLoop(0, frameCount, loop);
+ mAudioTrack->start();
+ mSound = sound;
+ mSoundID = sound->getSoundID();
+ mPriority = priority;
+ mLoop = loop;
+ mLeftVolume = leftVolume;
+ mRightVolume = rightVolume;
+ mRate = rate;
+ mState = PLAYING;
+ mStopTimeNs = 0;
+ mStreamID = nextStreamID; // prefer this to be the last, as it is an atomic sync point
+ }
+
+exit:
+ ALOGV("%s: delete oldTrack %p", __func__, oldTrack.get());
+ if (status != NO_ERROR) {
+ // TODO: should we consider keeping the soundID if the old track is OK?
+ // Do not attempt to restart this track (should we remove the stream id?)
+ mState = IDLE;
+ mSoundID = 0;
+ mSound.reset();
+ mAudioTrack.clear(); // actual release from releaseTracks[]
+ }
+}
+
+/* static */
+void Stream::staticCallback(int event, void* user, void* info)
+{
+ const uintptr_t userAsInt = (uintptr_t)user;
+ Stream* stream = reinterpret_cast<Stream*>(userAsInt & ~1);
+ stream->callback(event, info, userAsInt & 1, 0 /* tries */);
+}
+
+void Stream::callback(int event, void* info, int toggle, int tries)
+{
+ ALOGV("%s streamID %d", __func__, (int)mStreamID);
+ int32_t activeStreamIDToRestart = 0;
+ {
+ std::unique_lock lock(mLock);
+
+ if (mAudioTrack == nullptr) {
+ // The AudioTrack is either with this stream or its pair.
+ // if this swaps a few times, the toggle is bound to be wrong, so we fail then.
+ //
+ // TODO: Modify AudioTrack callbacks to avoid the hacky toggle and retry
+ // logic here.
+ if (tries < 3) {
+ lock.unlock();
+ getPairStream()->callback(event, info, toggle, tries + 1);
+ } else {
+ ALOGW("%s streamID %d cannot find track", __func__, (int)mStreamID);
+ }
+ return;
+ }
+ if (mToggle != toggle) {
+ ALOGD("%s streamID %d wrong toggle", __func__, (int)mStreamID);
+ return;
+ }
+ switch (event) {
+ case AudioTrack::EVENT_MORE_DATA:
+ ALOGW("%s streamID %d Invalid EVENT_MORE_DATA for static track",
+ __func__, (int)mStreamID);
+ break;
+ case AudioTrack::EVENT_UNDERRUN:
+ ALOGW("%s streamID %d Invalid EVENT_UNDERRUN for static track",
+ __func__, (int)mStreamID);
+ break;
+ case AudioTrack::EVENT_BUFFER_END:
+ ALOGV("%s streamID %d EVENT_BUFFER_END", __func__, (int)mStreamID);
+ if (mState != IDLE) {
+ activeStreamIDToRestart = mStreamID;
+ mStopTimeNs = systemTime();
+ }
+ break;
+ case AudioTrack::EVENT_LOOP_END:
+ ALOGV("%s streamID %d EVENT_LOOP_END", __func__, (int)mStreamID);
+ break;
+ case AudioTrack::EVENT_NEW_IAUDIOTRACK:
+ ALOGV("%s streamID %d NEW_IAUDIOTRACK", __func__, (int)mStreamID);
+ break;
+ default:
+ ALOGW("%s streamID %d Invalid event %d", __func__, (int)mStreamID, event);
+ break;
+ }
+ } // lock ends here. This is on the callback thread, no need to be precise.
+ if (activeStreamIDToRestart > 0) {
+ // Restart only if a particular streamID is still current and active.
+ ALOGV("%s: moveToRestartQueue %d", __func__, activeStreamIDToRestart);
+ mStreamManager->moveToRestartQueue(this, activeStreamIDToRestart);
+ }
+}
+
+void Stream::dump() const
+{
+ ALOGV("mPairStream=%p, mState=%d, mStreamID=%d, mSoundID=%d, mPriority=%d, mLoop=%d",
+ getPairStream(), mState, (int)mStreamID, mSoundID, mPriority, mLoop);
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/Stream.h b/media/jni/soundpool/Stream.h
new file mode 100644
index 0000000..82d2690
--- /dev/null
+++ b/media/jni/soundpool/Stream.h
@@ -0,0 +1,150 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Sound.h"
+
+#include <audio_utils/clock.h>
+#include <media/AudioTrack.h>
+
+namespace android::soundpool {
+
+// This is the amount of time to wait after stop is called when stealing an
+// AudioTrack to allow the sound to ramp down. If this is 0, glitches
+// may occur when stealing an AudioTrack.
+inline constexpr int64_t kStopWaitTimeNs = 20 * NANOS_PER_MILLISECOND;
+
+inline constexpr size_t kCacheLineSize = 64; /* std::hardware_constructive_interference_size */
+
+class StreamManager; // forward decl
+
+/**
+ * A Stream is associated with a StreamID exposed to the app to play a Sound.
+ *
+ * The Stream uses monitor locking strategy on mLock.
+ * https://en.wikipedia.org/wiki/Monitor_(synchronization)
+ *
+ * where public methods are guarded by a lock (as needed)
+ *
+ * For Java equivalent APIs, see
+ * https://developer.android.com/reference/android/media/SoundPool
+ *
+ * Streams are paired by the StreamManager, so one stream in the pair may be "stopping"
+ * while the other stream of the pair has been prepared to run
+ * (and the streamID returned to the app) pending its pair to be stopped.
+ * The pair of a Stream may be obtained by calling getPairStream(),
+ * where this->getPairStream()->getPairStream() == this; (pair is a commutative relationship).
+ *
+ * playPairStream() and getPairPriority() access the paired stream.
+ * See also StreamManager.h for details of physical layout implications of paired streams.
+ */
+class alignas(kCacheLineSize) Stream {
+public:
+ enum state { IDLE, PAUSED, PLAYING };
+ // The PAUSED, PLAYING state directly corresponds to the AudioTrack state of an active Stream.
+ //
+ // The IDLE state indicates an inactive Stream. An IDLE Stream may have a non-nullptr
+ // AudioTrack, which may be recycled for use if the SoundID matches the next Stream playback.
+ //
+ // PAUSED -> PLAYING through resume() (see also autoResume())
+ // PLAYING -> PAUSED through pause() (see also autoPause())
+ //
+ // IDLE is the initial state of a Stream and also when a stream becomes inactive.
+ // {PAUSED, PLAYING} -> IDLE through stop() (or if the Sound finishes playing)
+ // IDLE -> PLAYING through play(). (there is no way to start a Stream in paused mode).
+
+ ~Stream();
+ void setStreamManager(StreamManager* streamManager) { // non-nullptr
+ mStreamManager = streamManager; // set in StreamManager constructor, not changed
+ }
+
+ // The following methods are monitor locked by mLock.
+ //
+ // For methods taking a streamID:
+ // if the streamID matches the Stream's mStreamID, then method proceeds
+ // else the command is ignored with no effect.
+
+ // returns true if the stream needs to be explicitly stopped.
+ bool requestStop(int32_t streamID);
+ void stop(); // explicit stop(), typically called from the worker thread.
+ void clearAudioTrack();
+ void pause(int32_t streamID);
+ void autoPause(); // see the Java SoundPool.autoPause documentation for details.
+ void resume(int32_t streamID);
+ void autoResume();
+ void mute(bool muting);
+ void dump() const;
+
+ // returns the pair stream if successful, nullptr otherwise
+ Stream* playPairStream();
+
+ // These parameters are explicitly checked in the SoundPool class
+ // so never deviate from the Java API specified values.
+ void setVolume(int32_t streamID, float leftVolume, float rightVolume);
+ void setRate(int32_t streamID, float rate);
+ void setPriority(int32_t streamID, int priority);
+ void setLoop(int32_t streamID, int loop);
+ void setPlay(int32_t streamID, const std::shared_ptr<Sound> &sound, int32_t soundID,
+ float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate);
+ void setStopTimeNs(int64_t stopTimeNs); // systemTime() clock monotonic.
+
+ // The following getters are not locked and have weak consistency.
+ // These are considered advisory only - being stale is of nuisance.
+ int32_t getPriority() const { return mPriority; }
+ int32_t getPairPriority() const { return getPairStream()->getPriority(); }
+ int64_t getStopTimeNs() const { return mStopTimeNs; }
+
+ int32_t getStreamID() const { return mStreamID; } // Can change with setPlay()
+ int32_t getSoundID() const { return mSoundID; } // Can change with play_l()
+ bool hasSound() const { return mSound.get() != nullptr; }
+
+ Stream* getPairStream() const; // this never changes. See top of header.
+
+private:
+ void play_l(const std::shared_ptr<Sound>& sound, int streamID,
+ float leftVolume, float rightVolume, int priority, int loop, float rate,
+ sp<AudioTrack> releaseTracks[2]);
+ void stop_l();
+ void setVolume_l(float leftVolume, float rightVolume);
+
+ // For use with AudioTrack callback.
+ static void staticCallback(int event, void* user, void* info);
+ void callback(int event, void* info, int toggle, int tries);
+
+ // StreamManager should be set on construction and not changed.
+ // release mLock before calling into StreamManager
+ StreamManager* mStreamManager = nullptr;
+
+ mutable std::mutex mLock;
+ std::atomic_int32_t mStreamID = 0; // Note: valid streamIDs are always positive.
+ int mState = IDLE;
+ std::shared_ptr<Sound> mSound; // Non-null if playing.
+ int32_t mSoundID = 0; // The sound ID associated with the AudioTrack.
+ float mLeftVolume = 0.f;
+ float mRightVolume = 0.f;
+ int32_t mPriority = INT32_MIN;
+ int32_t mLoop = 0;
+ float mRate = 0.f;
+ bool mAutoPaused = false;
+ bool mMuted = false;
+
+ sp<AudioTrack> mAudioTrack;
+ int mToggle = 0;
+ int64_t mStopTimeNs = 0; // if nonzero, time to wait for stop.
+};
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
new file mode 100644
index 0000000..8928c47
--- /dev/null
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -0,0 +1,407 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::StreamManager"
+#include <utils/Log.h>
+
+#include "StreamManager.h"
+
+#include <audio_utils/clock.h>
+#include <audio_utils/roundup.h>
+
+namespace android::soundpool {
+
+// kMaxStreams is number that should be less than the current AudioTrack max per UID of 40.
+// It is the maximum number of AudioTrack resources allowed in the SoundPool.
+// We suggest a value at least 4 or greater to allow CTS tests to pass.
+static constexpr int32_t kMaxStreams = 32;
+
+// kStealActiveStream_OldestFirst = false historically (Q and earlier)
+// Changing to true could break app expectations but could change behavior beneficially.
+// In R, we change this to true, as it is the correct way per SoundPool documentation.
+static constexpr bool kStealActiveStream_OldestFirst = true;
+
+// kPlayOnCallingThread = true prior to R.
+// Changing to false means calls to play() are almost instantaneous instead of taking around
+// ~10ms to launch the AudioTrack. It is perhaps 100x faster.
+static constexpr bool kPlayOnCallingThread = false;
+
+// Amount of time for a StreamManager thread to wait before closing.
+static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND;
+
+////////////
+
+StreamMap::StreamMap(int32_t streams) {
+ ALOGV("%s(%d)", __func__, streams);
+ if (streams > kMaxStreams) {
+ ALOGW("%s: requested %d streams, clamping to %d", __func__, streams, kMaxStreams);
+ streams = kMaxStreams;
+ } else if (streams < 1) {
+ ALOGW("%s: requested %d streams, clamping to 1", __func__, streams);
+ streams = 1;
+ }
+ mStreamPoolSize = streams * 2;
+ mStreamPool.reset(new Stream[mStreamPoolSize]);
+ // we use a perfect hash table with 2x size to map StreamIDs to Stream pointers.
+ mPerfectHash = std::make_unique<PerfectHash<int32_t, Stream *>>(roundup(mStreamPoolSize * 2));
+}
+
+Stream* StreamMap::findStream(int32_t streamID) const
+{
+ Stream *stream = lookupStreamFromId(streamID);
+ return stream != nullptr && stream->getStreamID() == streamID ? stream : nullptr;
+}
+
+size_t StreamMap::streamPosition(const Stream* stream) const
+{
+ ptrdiff_t index = stream - mStreamPool.get();
+ LOG_ALWAYS_FATAL_IF(index < 0 || index >= mStreamPoolSize,
+ "%s: stream position out of range: %td", __func__, index);
+ return (size_t)index;
+}
+
+Stream* StreamMap::lookupStreamFromId(int32_t streamID) const
+{
+ return streamID > 0 ? mPerfectHash->getValue(streamID).load() : nullptr;
+}
+
+int32_t StreamMap::getNextIdForStream(Stream* stream) const {
+ // even though it is const, it mutates the internal hash table.
+ const int32_t id = mPerfectHash->generateKey(
+ stream,
+ [] (Stream *stream) {
+ return stream == nullptr ? 0 : stream->getStreamID();
+ }, /* getKforV() */
+ stream->getStreamID() /* oldID */);
+ return id;
+}
+
+////////////
+
+StreamManager::StreamManager(
+ int32_t streams, size_t threads, const audio_attributes_t* attributes)
+ : StreamMap(streams)
+ , mAttributes(*attributes)
+{
+ ALOGV("%s(%d, %zu, ...)", __func__, streams, threads);
+ forEach([this](Stream *stream) {
+ stream->setStreamManager(this);
+ if ((streamPosition(stream) & 1) == 0) { // put the first stream of pair as available.
+ mAvailableStreams.insert(stream);
+ }
+ });
+
+ mThreadPool = std::make_unique<ThreadPool>(
+ std::min(threads, (size_t)std::thread::hardware_concurrency()),
+ "SoundPool_");
+}
+
+StreamManager::~StreamManager()
+{
+ ALOGV("%s", __func__);
+ {
+ std::unique_lock lock(mStreamManagerLock);
+ mQuit = true;
+ mStreamManagerCondition.notify_all();
+ }
+ mThreadPool->quit();
+
+ // call stop on the stream pool
+ forEach([](Stream *stream) { stream->stop(); });
+
+ // This invokes the destructor on the AudioTracks -
+ // we do it here to ensure that AudioTrack callbacks will not occur
+ // afterwards.
+ forEach([](Stream *stream) { stream->clearAudioTrack(); });
+}
+
+
+int32_t StreamManager::queueForPlay(const std::shared_ptr<Sound> &sound,
+ int32_t soundID, float leftVolume, float rightVolume,
+ int32_t priority, int32_t loop, float rate)
+{
+ ALOGV("%s(sound=%p, soundID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f)",
+ __func__, sound.get(), soundID, leftVolume, rightVolume, priority, loop, rate);
+ bool launchThread = false;
+ int32_t streamID = 0;
+
+ { // for lock
+ std::unique_lock lock(mStreamManagerLock);
+ Stream *newStream = nullptr;
+ bool fromAvailableQueue = false;
+ ALOGV("%s: mStreamManagerLock lock acquired", __func__);
+
+ sanityCheckQueue_l();
+ // find an available stream, prefer one that has matching sound id.
+ if (mAvailableStreams.size() > 0) {
+ newStream = *mAvailableStreams.begin();
+ for (auto stream : mAvailableStreams) {
+ if (stream->getSoundID() == soundID) {
+ newStream = stream;
+ break;
+ }
+ }
+ if (newStream != nullptr) {
+ newStream->setStopTimeNs(systemTime());
+ }
+ fromAvailableQueue = true;
+ }
+
+ // also look in the streams restarting (if the paired stream doesn't have a pending play)
+ if (newStream == nullptr || newStream->getSoundID() != soundID) {
+ for (auto [unused , stream] : mRestartStreams) {
+ if (!stream->getPairStream()->hasSound()) {
+ if (stream->getSoundID() == soundID) {
+ newStream = stream;
+ break;
+ } else if (newStream == nullptr) {
+ newStream = stream;
+ }
+ }
+ }
+ }
+
+ // no available streams, look for one to steal from the active list
+ if (newStream == nullptr) {
+ for (auto stream : mActiveStreams) {
+ if (stream->getPriority() <= priority) {
+ if (newStream == nullptr
+ || newStream->getPriority() > stream->getPriority()) {
+ newStream = stream;
+ }
+ }
+ }
+ if (newStream != nullptr) { // we need to mute as it is still playing.
+ (void)newStream->requestStop(newStream->getStreamID());
+ }
+ }
+
+ // none found, look for a stream that is restarting, evict one.
+ if (newStream == nullptr) {
+ for (auto [unused, stream] : mRestartStreams) {
+ if (stream->getPairPriority() <= priority) {
+ newStream = stream;
+ break;
+ }
+ }
+ }
+
+ // DO NOT LOOK into mProcessingStreams as those are held by the StreamManager threads.
+
+ if (newStream == nullptr) {
+ ALOGD("%s: unable to find stream, returning 0", __func__);
+ return 0; // unable to find available stream
+ }
+
+ Stream *pairStream = newStream->getPairStream();
+ streamID = getNextIdForStream(pairStream);
+ pairStream->setPlay(
+ streamID, sound, soundID, leftVolume, rightVolume, priority, loop, rate);
+ if (fromAvailableQueue && kPlayOnCallingThread) {
+ removeFromQueues_l(newStream);
+ mProcessingStreams.emplace(newStream);
+ lock.unlock();
+ if (Stream* nextStream = newStream->playPairStream()) {
+ lock.lock();
+ ALOGV("%s: starting streamID:%d", __func__, nextStream->getStreamID());
+ addToActiveQueue_l(nextStream);
+ } else {
+ lock.lock();
+ mAvailableStreams.insert(newStream);
+ streamID = 0;
+ }
+ mProcessingStreams.erase(newStream);
+ } else {
+ launchThread = moveToRestartQueue_l(newStream) && needMoreThreads_l();
+ }
+ sanityCheckQueue_l();
+ ALOGV("%s: mStreamManagerLock released", __func__);
+ } // lock
+
+ if (launchThread) {
+ const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
+ ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
+ }
+ ALOGV("%s: returning %d", __func__, streamID);
+ return streamID;
+}
+
+void StreamManager::moveToRestartQueue(
+ Stream* stream, int32_t activeStreamIDToMatch)
+{
+ ALOGV("%s(stream(ID)=%d, activeStreamIDToMatch=%d)",
+ __func__, stream->getStreamID(), activeStreamIDToMatch);
+ bool restart;
+ {
+ std::lock_guard lock(mStreamManagerLock);
+ sanityCheckQueue_l();
+ if (mProcessingStreams.count(stream) > 0 ||
+ mProcessingStreams.count(stream->getPairStream()) > 0) {
+ ALOGD("%s: attempting to restart processing stream(%d)",
+ __func__, stream->getStreamID());
+ restart = false;
+ } else {
+ moveToRestartQueue_l(stream, activeStreamIDToMatch);
+ restart = needMoreThreads_l();
+ }
+ sanityCheckQueue_l();
+ }
+ if (restart) {
+ const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
+ ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
+ }
+}
+
+bool StreamManager::moveToRestartQueue_l(
+ Stream* stream, int32_t activeStreamIDToMatch)
+{
+ ALOGV("%s(stream(ID)=%d, activeStreamIDToMatch=%d)",
+ __func__, stream->getStreamID(), activeStreamIDToMatch);
+ if (activeStreamIDToMatch > 0 && stream->getStreamID() != activeStreamIDToMatch) {
+ return false;
+ }
+ const ssize_t found = removeFromQueues_l(stream, activeStreamIDToMatch);
+ if (found < 0) return false;
+
+ LOG_ALWAYS_FATAL_IF(found > 1, "stream on %zd > 1 stream lists", found);
+
+ addToRestartQueue_l(stream);
+ mStreamManagerCondition.notify_one();
+ return true;
+}
+
+ssize_t StreamManager::removeFromQueues_l(
+ Stream* stream, int32_t activeStreamIDToMatch) {
+ size_t found = 0;
+ for (auto it = mActiveStreams.begin(); it != mActiveStreams.end(); ++it) {
+ if (*it == stream) {
+ mActiveStreams.erase(it); // we erase the iterator and break (otherwise it not safe).
+ ++found;
+ break;
+ }
+ }
+ // activeStreamIDToMatch is nonzero indicates we proceed only if found.
+ if (found == 0 && activeStreamIDToMatch > 0) {
+ return -1; // special code: not present on active streams, ignore restart request
+ }
+
+ for (auto it = mRestartStreams.begin(); it != mRestartStreams.end(); ++it) {
+ if (it->second == stream) {
+ mRestartStreams.erase(it);
+ ++found;
+ break;
+ }
+ }
+ found += mAvailableStreams.erase(stream);
+
+ // streams on mProcessingStreams are undergoing processing by the StreamManager thread
+ // and do not participate in normal stream migration.
+ return found;
+}
+
+void StreamManager::addToRestartQueue_l(Stream *stream) {
+ mRestartStreams.emplace(stream->getStopTimeNs(), stream);
+}
+
+void StreamManager::addToActiveQueue_l(Stream *stream) {
+ if (kStealActiveStream_OldestFirst) {
+ mActiveStreams.push_back(stream); // oldest to newest
+ } else {
+ mActiveStreams.push_front(stream); // newest to oldest
+ }
+}
+
+void StreamManager::run(int32_t id)
+{
+ ALOGV("%s(%d) entering", __func__, id);
+ int64_t waitTimeNs = kWaitTimeBeforeCloseNs;
+ std::unique_lock lock(mStreamManagerLock);
+ while (!mQuit) {
+ mStreamManagerCondition.wait_for(
+ lock, std::chrono::duration<int64_t, std::nano>(waitTimeNs));
+ ALOGV("%s(%d) awake", __func__, id);
+
+ sanityCheckQueue_l();
+
+ if (mQuit || (mRestartStreams.empty() && waitTimeNs == kWaitTimeBeforeCloseNs)) {
+ break; // end the thread
+ }
+
+ waitTimeNs = kWaitTimeBeforeCloseNs;
+ while (!mQuit && !mRestartStreams.empty()) {
+ const nsecs_t nowNs = systemTime();
+ auto it = mRestartStreams.begin();
+ Stream* const stream = it->second;
+ const int64_t diffNs = stream->getStopTimeNs() - nowNs;
+ if (diffNs > 0) {
+ waitTimeNs = std::min(waitTimeNs, diffNs);
+ break;
+ }
+ mRestartStreams.erase(it);
+ mProcessingStreams.emplace(stream);
+ lock.unlock();
+ stream->stop();
+ ALOGV("%s(%d) stopping streamID:%d", __func__, id, stream->getStreamID());
+ if (Stream* nextStream = stream->playPairStream()) {
+ ALOGV("%s(%d) starting streamID:%d", __func__, id, nextStream->getStreamID());
+ lock.lock();
+ if (nextStream->getStopTimeNs() > 0) {
+ // the next stream was stopped before we can move it to the active queue.
+ ALOGV("%s(%d) stopping started streamID:%d",
+ __func__, id, nextStream->getStreamID());
+ moveToRestartQueue_l(nextStream);
+ } else {
+ addToActiveQueue_l(nextStream);
+ }
+ } else {
+ lock.lock();
+ mAvailableStreams.insert(stream);
+ }
+ mProcessingStreams.erase(stream);
+ sanityCheckQueue_l();
+ }
+ }
+ ALOGV("%s(%d) exiting", __func__, id);
+}
+
+void StreamManager::dump() const
+{
+ forEach([](const Stream *stream) { stream->dump(); });
+}
+
+void StreamManager::sanityCheckQueue_l() const
+{
+ // We want to preserve the invariant that each stream pair is exactly on one of the queues.
+ const size_t availableStreams = mAvailableStreams.size();
+ const size_t restartStreams = mRestartStreams.size();
+ const size_t activeStreams = mActiveStreams.size();
+ const size_t processingStreams = mProcessingStreams.size();
+ const size_t managedStreams = availableStreams + restartStreams + activeStreams
+ + processingStreams;
+ const size_t totalStreams = getStreamMapSize() >> 1;
+ LOG_ALWAYS_FATAL_IF(managedStreams != totalStreams,
+ "%s: mAvailableStreams:%zu + mRestartStreams:%zu + "
+ "mActiveStreams:%zu + mProcessingStreams:%zu = %zu != total streams %zu",
+ __func__, availableStreams, restartStreams, activeStreams, processingStreams,
+ managedStreams, totalStreams);
+ ALOGV("%s: mAvailableStreams:%zu + mRestartStreams:%zu + "
+ "mActiveStreams:%zu + mProcessingStreams:%zu = %zu (total streams: %zu)",
+ __func__, availableStreams, restartStreams, activeStreams, processingStreams,
+ managedStreams, totalStreams);
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/StreamManager.h b/media/jni/soundpool/StreamManager.h
new file mode 100644
index 0000000..8c98ac9
--- /dev/null
+++ b/media/jni/soundpool/StreamManager.h
@@ -0,0 +1,467 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Stream.h"
+
+#include <condition_variable>
+#include <future>
+#include <list>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <unordered_set>
+#include <vector>
+
+#include <utils/AndroidThreads.h>
+
+namespace android::soundpool {
+
+// TODO: Move helper classes to a utility file, with separate test.
+
+/**
+ * JavaThread is used like std::thread but for threads that may call the JVM.
+ *
+ * std::thread does not easily attach to the JVM. We need JVM capable threads
+ * from createThreadEtc() since android binder call optimization may attempt to
+ * call back into Java if the SoundPool runs in system server.
+ *
+ *
+ * No locking is required - the member variables are inherently thread-safe.
+ */
+class JavaThread {
+public:
+ JavaThread(std::function<void()> f, const char *name)
+ : mF{std::move(f)} {
+ createThreadEtc(staticFunction, this, name);
+ }
+
+ JavaThread(JavaThread &&) = delete; // uses "this" ptr, not moveable.
+
+ void join() const {
+ mFuture.wait();
+ }
+
+ bool isClosed() const {
+ return mIsClosed;
+ }
+
+private:
+ static int staticFunction(void *data) {
+ JavaThread *jt = static_cast<JavaThread *>(data);
+ jt->mF();
+ jt->mIsClosed = true;
+ jt->mPromise.set_value();
+ return 0;
+ }
+
+ // No locking is provided as these variables are initialized in the constructor
+ // and the members referenced are thread-safe objects.
+ // (mFuture.wait() can block multiple threads.)
+ // Note the order of member variables is reversed for destructor.
+ const std::function<void()> mF;
+ // Used in join() to block until the thread completes.
+ // See https://en.cppreference.com/w/cpp/thread/promise for the void specialization of
+ // promise.
+ std::promise<void> mPromise;
+ std::future<void> mFuture{mPromise.get_future()};
+ std::atomic_bool mIsClosed = false;
+};
+
+/**
+ * The ThreadPool manages thread lifetimes of SoundPool worker threads.
+ *
+ * TODO: the (eventual) goal of ThreadPool is to transparently and cooperatively
+ * maximize CPU utilization while avoiding starvation of other applications.
+ * Some possibilities:
+ *
+ * We should create worker threads when we have SoundPool work and the system is idle.
+ * CPU cycles are "use-it-or-lose-it" when the system is idle.
+ *
+ * We should adjust the priority of worker threads so that the second (and subsequent) worker
+ * threads have lower priority (should we try to promote priority also?).
+ *
+ * We should throttle the spawning of new worker threads, spacing over time, to avoid
+ * creating too many new threads all at once, on initialization.
+ */
+class ThreadPool {
+public:
+ ThreadPool(size_t maxThreadCount, std::string name)
+ : mMaxThreadCount(maxThreadCount)
+ , mName{std::move(name)} { }
+
+ ~ThreadPool() { quit(); }
+
+ size_t getActiveThreadCount() const { return mActiveThreadCount; }
+ size_t getMaxThreadCount() const { return mMaxThreadCount; }
+
+ void quit() {
+ std::list<std::unique_ptr<JavaThread>> threads;
+ {
+ std::lock_guard lock(mThreadLock);
+ if (mQuit) return; // already joined.
+ mQuit = true;
+ threads = std::move(mThreads);
+ mThreads.clear();
+ }
+ // mQuit set under lock, no more threads will be created.
+ for (auto &thread : threads) {
+ thread->join();
+ thread.reset();
+ }
+ LOG_ALWAYS_FATAL_IF(mActiveThreadCount != 0,
+ "Invalid Active Threads: %zu", (size_t)mActiveThreadCount);
+ }
+
+ // returns a non-zero id if successful, the id is to help logging messages.
+ int32_t launch(std::function<void(int32_t /* id */)> f) {
+ std::list<std::unique_ptr<JavaThread>> threadsToRelease; // release outside of lock.
+ std::lock_guard lock(mThreadLock);
+ if (mQuit) return 0; // ignore if we have quit
+
+ // clean up threads.
+ for (auto it = mThreads.begin(); it != mThreads.end(); ) {
+ if ((*it)->isClosed()) {
+ threadsToRelease.emplace_back(std::move(*it));
+ it = mThreads.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ const size_t threadCount = mThreads.size();
+ if (threadCount < mMaxThreadCount) {
+ // if the id wraps, we don't care about collisions. it's just for logging.
+ mNextThreadId = mNextThreadId == INT32_MAX ? 1 : ++mNextThreadId;
+ const int32_t id = mNextThreadId;
+ mThreads.emplace_back(std::make_unique<JavaThread>(
+ [this, id, mf = std::move(f)] { mf(id); --mActiveThreadCount; },
+ (mName + std::to_string(id)).c_str()));
+ ++mActiveThreadCount;
+ return id;
+ }
+ return 0;
+ }
+
+ // TODO: launch only if load average is low.
+ // This gets the load average
+ // See also std::thread::hardware_concurrency() for the concurrent capability.
+ static double getLoadAvg() {
+ double loadAvg[1];
+ if (getloadavg(loadAvg, std::size(loadAvg)) > 0) {
+ return loadAvg[0];
+ }
+ return -1.;
+ }
+
+private:
+ const size_t mMaxThreadCount;
+ const std::string mName;
+
+ std::atomic_size_t mActiveThreadCount = 0;
+
+ std::mutex mThreadLock;
+ bool mQuit = false; // GUARDED_BY(mThreadLock)
+ int32_t mNextThreadId = 0; // GUARDED_BY(mThreadLock)
+ std::list<std::unique_ptr<JavaThread>> mThreads; // GUARDED_BY(mThreadLock)
+};
+
+/**
+ * A Perfect HashTable for IDs (key) to pointers (value).
+ *
+ * There are no collisions. Why? because we generate the IDs for you to look up :-).
+ *
+ * The goal of this hash table is to map an integer ID handle > 0 to a pointer.
+ * We give these IDs in monotonic order (though we may skip if it were to cause a collision).
+ *
+ * The size of the hashtable must be large enough to accommodate the max number of keys.
+ * We suggest 2x.
+ *
+ * Readers are lockless
+ * Single writer could be lockless, but we allow multiple writers through an internal lock.
+ *
+ * For the Key type K, valid keys generated are > 0 (signed or unsigned)
+ * For the Value type V, values are pointers - nullptr means empty.
+ */
+template <typename K, typename V>
+class PerfectHash {
+public:
+ PerfectHash(size_t hashCapacity)
+ : mHashCapacity(hashCapacity)
+ , mK2V{new std::atomic<V>[hashCapacity]()} {
+ }
+
+ // Generate a key for a value V.
+ // There is a testing function getKforV() which checks what the value reports as its key.
+ //
+ // Calls back into getKforV under lock.
+ //
+ // We expect that the hashCapacity is 2x the number of stored keys in order
+ // to have one or two tries to find an empty slot
+ K generateKey(V value, std::function<K(V)> getKforV, K oldKey = 0) {
+ std::lock_guard lock(mHashLock);
+ // try to remove the old key.
+ if (oldKey > 0) { // key valid
+ const V v = getValue(oldKey);
+ if (v != nullptr) { // value still valid
+ const K atPosition = getKforV(v);
+ if (atPosition < 0 || // invalid value
+ atPosition == oldKey || // value's key still valid and matches old key
+ ((atPosition ^ oldKey) & (mHashCapacity - 1)) != 0) { // stale key entry
+ getValue(oldKey) = nullptr; // invalidate
+ }
+ } // else if value is invalid, no need to invalidate.
+ }
+ // check if we are invalidating only.
+ if (value == nullptr) return 0;
+ // now insert the new value and return the key.
+ size_t tries = 0;
+ for (; tries < mHashCapacity; ++tries) {
+ mNextKey = mNextKey == std::numeric_limits<K>::max() ? 1 : mNextKey + 1;
+ const V v = getValue(mNextKey);
+ //ALOGD("tries: %zu, key:%d value:%p", tries, (int)mNextKey, v);
+ if (v == nullptr) break; // empty
+ const K atPosition = getKforV(v);
+ //ALOGD("tries: %zu key atPosition:%d", tries, (int)atPosition);
+ if (atPosition < 0 || // invalid value
+ ((atPosition ^ mNextKey) & (mHashCapacity - 1)) != 0) { // stale key entry
+ break;
+ }
+ }
+ LOG_ALWAYS_FATAL_IF(tries == mHashCapacity, "hash table overflow!");
+ //ALOGD("%s: found after %zu tries", __func__, tries);
+ getValue(mNextKey) = value;
+ return mNextKey;
+ }
+
+ std::atomic<V> &getValue(K key) { return mK2V[key & (mHashCapacity - 1)]; }
+ const std::atomic_int32_t &getValue(K key) const { return mK2V[key & (mHashCapacity - 1)]; }
+
+private:
+ mutable std::mutex mHashLock;
+ const size_t mHashCapacity; // size of mK2V no lock needed.
+ std::unique_ptr<std::atomic<V>[]> mK2V; // no lock needed for read access.
+ K mNextKey{}; // GUARDED_BY(mHashLock)
+};
+
+/**
+ * StreamMap contains the all the valid streams available to SoundPool.
+ *
+ * There is no Lock required for this class because the streams are
+ * allocated in the constructor, the lookup is lockless, and the Streams
+ * returned are locked internally.
+ *
+ * The lookup uses a perfect hash.
+ * It is possible to use a lockless hash table or to use a stripe-locked concurrent
+ * hashmap for essentially lock-free lookup.
+ *
+ * This follows Map-Reduce parallelism model.
+ * https://en.wikipedia.org/wiki/MapReduce
+ *
+ * Conceivably the forEach could be parallelized using std::for_each with a
+ * std::execution::par policy.
+ *
+ * https://en.cppreference.com/w/cpp/algorithm/for_each
+ */
+class StreamMap {
+public:
+ explicit StreamMap(int32_t streams);
+
+ // Returns the stream associated with streamID or nullptr if not found.
+ // This need not be locked.
+ // The stream ID will never migrate to another Stream, but it may change
+ // underneath you. The Stream operations that take a streamID will confirm
+ // that the streamID matches under the Stream lock before executing otherwise
+ // it ignores the command as stale.
+ Stream* findStream(int32_t streamID) const;
+
+ // Iterates through the stream pool applying the function f.
+ // Since this enumerates over every single stream, it is unlocked.
+ //
+ // See related: https://en.cppreference.com/w/cpp/algorithm/for_each
+ void forEach(std::function<void(const Stream *)>f) const {
+ for (size_t i = 0; i < mStreamPoolSize; ++i) {
+ f(&mStreamPool[i]);
+ }
+ }
+
+ void forEach(std::function<void(Stream *)>f) {
+ for (size_t i = 0; i < mStreamPoolSize; ++i) {
+ f(&mStreamPool[i]);
+ }
+ }
+
+ // Returns the pair stream for a given Stream.
+ // This need not be locked as it is a property of the pointer address.
+ Stream* getPairStream(const Stream* stream) const {
+ const size_t index = streamPosition(stream);
+ return &mStreamPool[index ^ 1];
+ }
+
+ // find the position of the stream in mStreamPool array.
+ size_t streamPosition(const Stream* stream) const; // no lock needed
+
+ size_t getStreamMapSize() const {
+ return mStreamPoolSize;
+ }
+
+ // find the next valid ID for a stream and store in hash table.
+ int32_t getNextIdForStream(Stream* stream) const;
+
+private:
+
+ // use the hash table to attempt to find the stream.
+ // nullptr is returned if the lookup fails.
+ Stream* lookupStreamFromId(int32_t streamID) const;
+
+ // The stream pool is initialized in the constructor, effectively const.
+ // no locking required for access.
+ //
+ // The constructor parameter "streams" results in streams pairs of streams.
+ // We have twice as many streams because we wish to return a streamID "handle"
+ // back to the app immediately, while we may be stopping the other stream in the
+ // pair to get its AudioTrack :-).
+ //
+ // Of the stream pair, only one of the streams may have an AudioTrack.
+ // The fixed association of a stream pair allows callbacks from the AudioTrack
+ // to be associated properly to either one or the other of the stream pair.
+ //
+ // TODO: The stream pair arrangement can be removed if we have better AudioTrack
+ // callback handling (being able to remove and change the callback after construction).
+ //
+ // Streams may be accessed anytime off of the stream pool
+ // as there is internal locking on each stream.
+ std::unique_ptr<Stream[]> mStreamPool; // no lock needed for access.
+ size_t mStreamPoolSize; // no lock needed for access.
+
+ // In order to find the Stream from a StreamID, we could do a linear lookup in mStreamPool.
+ // As an alternative, one could use stripe-locked or lock-free concurrent hashtables.
+ //
+ // When considering linear search vs hashmap, verify the typical use-case size.
+ // Linear search is faster than std::unordered_map (circa 2018) for less than 40 elements.
+ // [ Skarupke, M. (2018), "You Can Do Better than std::unordered_map: New and Recent
+ // Improvements to Hash Table Performance." C++Now 2018. cppnow.org, see
+ // https://www.youtube.com/watch?v=M2fKMP47slQ ]
+ //
+ // Here, we use a PerfectHash of Id to Stream *, since we can control the
+ // StreamID returned to the user. This allows O(1) read access to mStreamPool lock-free.
+ //
+ // We prefer that the next stream ID is monotonic for aesthetic reasons
+ // (if we didn't care about monotonicity, a simple method is to apply a generation count
+ // to each stream in the unused upper bits of its index in mStreamPool for the id).
+ //
+ std::unique_ptr<PerfectHash<int32_t, Stream *>> mPerfectHash;
+};
+
+/**
+ * StreamManager is used to manage the streams (accessed by StreamID from Java).
+ *
+ * Locking order (proceeds from application to component).
+ * SoundPool mApiLock (if needed) -> StreamManager mStreamManagerLock
+ * -> pair Stream mLock -> queued Stream mLock
+ */
+class StreamManager : public StreamMap {
+public:
+ // Note: the SoundPool pointer is only used for stream initialization.
+ // It is not stored in StreamManager.
+ StreamManager(int32_t streams, size_t threads, const audio_attributes_t* attributes);
+ ~StreamManager();
+
+ // Returns positive streamID on success, 0 on failure. This is locked.
+ int32_t queueForPlay(const std::shared_ptr<Sound> &sound,
+ int32_t soundID, float leftVolume, float rightVolume,
+ int32_t priority, int32_t loop, float rate);
+
+ ///////////////////////////////////////////////////////////////////////
+ // Called from soundpool::Stream
+
+ const audio_attributes_t* getAttributes() const { return &mAttributes; }
+
+ // Moves the stream to the restart queue (called upon BUFFER_END of the static track)
+ // this is locked internally.
+ // If activeStreamIDToMatch is nonzero, it will only move to the restart queue
+ // if the streamIDToMatch is found on the active queue.
+ void moveToRestartQueue(Stream* stream, int32_t activeStreamIDToMatch = 0);
+
+private:
+
+ void run(int32_t id); // worker thread, takes lock internally.
+ void dump() const; // no lock needed
+
+ // returns true if more worker threads are needed.
+ bool needMoreThreads_l() {
+ return mRestartStreams.size() > 0 &&
+ (mThreadPool->getActiveThreadCount() == 0
+ || std::distance(mRestartStreams.begin(),
+ mRestartStreams.upper_bound(systemTime()))
+ > (ptrdiff_t)mThreadPool->getActiveThreadCount());
+ }
+
+ // returns true if the stream was added.
+ bool moveToRestartQueue_l(Stream* stream, int32_t activeStreamIDToMatch = 0);
+ // returns number of queues the stream was removed from (should be 0 or 1);
+ // a special code of -1 is returned if activeStreamIDToMatch is > 0 and
+ // the stream wasn't found on the active queue.
+ ssize_t removeFromQueues_l(Stream* stream, int32_t activeStreamIDToMatch = 0);
+ void addToRestartQueue_l(Stream *stream);
+ void addToActiveQueue_l(Stream *stream);
+ void sanityCheckQueue_l() const;
+
+ const audio_attributes_t mAttributes;
+ std::unique_ptr<ThreadPool> mThreadPool; // locked internally
+
+ // mStreamManagerLock is used to lock access for transitions between the
+ // 4 stream queues by the Manager Thread or by the user initiated play().
+ // A stream pair has exactly one stream on exactly one of the queues.
+ std::mutex mStreamManagerLock;
+ std::condition_variable mStreamManagerCondition;
+
+ bool mQuit = false; // GUARDED_BY(mStreamManagerLock)
+
+ // There are constructor arg "streams" pairs of streams, only one of each
+ // pair on the 4 stream queues below. The other stream in the pair serves as
+ // placeholder to accumulate user changes, pending actual availability of the
+ // AudioTrack, as it may be in use, requiring stop-then-restart.
+ //
+ // The 4 queues are implemented in the appropriate STL container based on perceived
+ // optimality.
+
+ // 1) mRestartStreams: Streams awaiting stop.
+ // The paired stream may be active (but with no AudioTrack), and will be restarted
+ // with an active AudioTrack when the current stream is stopped.
+ std::multimap<int64_t /* stopTimeNs */, Stream*>
+ mRestartStreams; // GUARDED_BY(mStreamManagerLock)
+
+ // 2) mActiveStreams: Streams that are active.
+ // The paired stream will be inactive.
+ // This is in order of specified by kStealActiveStream_OldestFirst
+ std::list<Stream*> mActiveStreams; // GUARDED_BY(mStreamManagerLock)
+
+ // 3) mAvailableStreams: Streams that are inactive.
+ // The paired stream will also be inactive.
+ // No particular order.
+ std::unordered_set<Stream*> mAvailableStreams; // GUARDED_BY(mStreamManagerLock)
+
+ // 4) mProcessingStreams: Streams that are being processed by the ManagerThreads
+ // When on this queue, the stream and its pair are not available for stealing.
+ // Each ManagerThread will have at most one stream on the mProcessingStreams queue.
+ // The paired stream may be active or restarting.
+ // No particular order.
+ std::unordered_set<Stream*> mProcessingStreams; // GUARDED_BY(mStreamManagerLock)
+};
+
+} // namespace android::soundpool
diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp
index a0d2050..2da45b6 100644
--- a/media/native/midi/Android.bp
+++ b/media/native/midi/Android.bp
@@ -17,6 +17,7 @@
srcs: [
"amidi.cpp",
+ "MidiDeviceInfo.cpp",
":IMidiDeviceServer.aidl",
],
@@ -31,12 +32,14 @@
"-fvisibility=hidden",
],
+ header_libs: [
+ "media_ndk_headers",
+ ],
+
shared_libs: [
"liblog",
"libbinder",
"libutils",
- "libmedia",
- "libmediandk",
"libandroid_runtime",
],
diff --git a/media/native/midi/MidiDeviceInfo.cpp b/media/native/midi/MidiDeviceInfo.cpp
new file mode 100644
index 0000000..ac68d26
--- /dev/null
+++ b/media/native/midi/MidiDeviceInfo.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#define LOG_TAG "MidiDeviceInfo"
+
+#include <MidiDeviceInfo.h>
+
+#include <binder/Parcel.h>
+#include <log/log.h>
+#include <utils/Errors.h>
+#include <utils/String16.h>
+
+namespace android {
+namespace media {
+namespace midi {
+
+// The constant values need to be kept in sync with MidiDeviceInfo.java.
+// static
+const char* const MidiDeviceInfo::PROPERTY_NAME = "name";
+const char* const MidiDeviceInfo::PROPERTY_MANUFACTURER = "manufacturer";
+const char* const MidiDeviceInfo::PROPERTY_PRODUCT = "product";
+const char* const MidiDeviceInfo::PROPERTY_VERSION = "version";
+const char* const MidiDeviceInfo::PROPERTY_SERIAL_NUMBER = "serial_number";
+const char* const MidiDeviceInfo::PROPERTY_ALSA_CARD = "alsa_card";
+const char* const MidiDeviceInfo::PROPERTY_ALSA_DEVICE = "alsa_device";
+
+String16 MidiDeviceInfo::getProperty(const char* propertyName) {
+ String16 value;
+ if (mProperties.getString(String16(propertyName), &value)) {
+ return value;
+ } else {
+ return String16();
+ }
+}
+
+#define RETURN_IF_FAILED(calledOnce) \
+ { \
+ status_t returnStatus = calledOnce; \
+ if (returnStatus) { \
+ ALOGE("Failed at %s:%d (%s)", __FILE__, __LINE__, __func__); \
+ return returnStatus; \
+ } \
+ }
+
+status_t MidiDeviceInfo::writeToParcel(Parcel* parcel) const {
+ // Needs to be kept in sync with code in MidiDeviceInfo.java
+ RETURN_IF_FAILED(parcel->writeInt32(mType));
+ RETURN_IF_FAILED(parcel->writeInt32(mId));
+ RETURN_IF_FAILED(parcel->writeInt32((int32_t)mInputPortNames.size()));
+ RETURN_IF_FAILED(parcel->writeInt32((int32_t)mOutputPortNames.size()));
+ RETURN_IF_FAILED(writeStringVector(parcel, mInputPortNames));
+ RETURN_IF_FAILED(writeStringVector(parcel, mOutputPortNames));
+ RETURN_IF_FAILED(parcel->writeInt32(mIsPrivate ? 1 : 0));
+ RETURN_IF_FAILED(mProperties.writeToParcel(parcel));
+ // This corresponds to "extra" properties written by Java code
+ RETURN_IF_FAILED(mProperties.writeToParcel(parcel));
+ return OK;
+}
+
+status_t MidiDeviceInfo::readFromParcel(const Parcel* parcel) {
+ // Needs to be kept in sync with code in MidiDeviceInfo.java
+ RETURN_IF_FAILED(parcel->readInt32(&mType));
+ RETURN_IF_FAILED(parcel->readInt32(&mId));
+ int32_t inputPortCount;
+ RETURN_IF_FAILED(parcel->readInt32(&inputPortCount));
+ int32_t outputPortCount;
+ RETURN_IF_FAILED(parcel->readInt32(&outputPortCount));
+ RETURN_IF_FAILED(readStringVector(parcel, &mInputPortNames, inputPortCount));
+ RETURN_IF_FAILED(readStringVector(parcel, &mOutputPortNames, outputPortCount));
+ int32_t isPrivate;
+ RETURN_IF_FAILED(parcel->readInt32(&isPrivate));
+ mIsPrivate = isPrivate == 1;
+ RETURN_IF_FAILED(mProperties.readFromParcel(parcel));
+ // Ignore "extra" properties as they may contain Java Parcelables
+ return OK;
+}
+
+status_t MidiDeviceInfo::readStringVector(
+ const Parcel* parcel, Vector<String16> *vectorPtr, size_t defaultLength) {
+ std::unique_ptr<std::vector<std::unique_ptr<String16>>> v;
+ status_t result = parcel->readString16Vector(&v);
+ if (result != OK) return result;
+ vectorPtr->clear();
+ if (v.get() != nullptr) {
+ for (const auto& iter : *v) {
+ if (iter.get() != nullptr) {
+ vectorPtr->push_back(*iter);
+ } else {
+ vectorPtr->push_back(String16());
+ }
+ }
+ } else {
+ vectorPtr->resize(defaultLength);
+ }
+ return OK;
+}
+
+status_t MidiDeviceInfo::writeStringVector(Parcel* parcel, const Vector<String16>& vector) const {
+ std::vector<String16> v;
+ for (size_t i = 0; i < vector.size(); ++i) {
+ v.push_back(vector[i]);
+ }
+ return parcel->writeString16Vector(v);
+}
+
+// Vector does not define operator==
+static inline bool areVectorsEqual(const Vector<String16>& lhs, const Vector<String16>& rhs) {
+ if (lhs.size() != rhs.size()) return false;
+ for (size_t i = 0; i < lhs.size(); ++i) {
+ if (lhs[i] != rhs[i]) return false;
+ }
+ return true;
+}
+
+bool operator==(const MidiDeviceInfo& lhs, const MidiDeviceInfo& rhs) {
+ return (lhs.mType == rhs.mType && lhs.mId == rhs.mId &&
+ areVectorsEqual(lhs.mInputPortNames, rhs.mInputPortNames) &&
+ areVectorsEqual(lhs.mOutputPortNames, rhs.mOutputPortNames) &&
+ lhs.mProperties == rhs.mProperties &&
+ lhs.mIsPrivate == rhs.mIsPrivate);
+}
+
+} // namespace midi
+} // namespace media
+} // namespace android
diff --git a/media/native/midi/MidiDeviceInfo.h b/media/native/midi/MidiDeviceInfo.h
new file mode 100644
index 0000000..5b4a241
--- /dev/null
+++ b/media/native/midi/MidiDeviceInfo.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef ANDROID_MEDIA_MIDI_DEVICE_INFO_H
+#define ANDROID_MEDIA_MIDI_DEVICE_INFO_H
+
+#include <binder/Parcelable.h>
+#include <binder/PersistableBundle.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+namespace android {
+namespace media {
+namespace midi {
+
+class MidiDeviceInfo : public Parcelable {
+public:
+ MidiDeviceInfo() = default;
+ virtual ~MidiDeviceInfo() = default;
+ MidiDeviceInfo(const MidiDeviceInfo& midiDeviceInfo) = default;
+
+ status_t writeToParcel(Parcel* parcel) const override;
+ status_t readFromParcel(const Parcel* parcel) override;
+
+ int getType() const { return mType; }
+ int getUid() const { return mId; }
+ bool isPrivate() const { return mIsPrivate; }
+ const Vector<String16>& getInputPortNames() const { return mInputPortNames; }
+ const Vector<String16>& getOutputPortNames() const { return mOutputPortNames; }
+ String16 getProperty(const char* propertyName);
+
+ // The constants need to be kept in sync with MidiDeviceInfo.java
+ enum {
+ TYPE_USB = 1,
+ TYPE_VIRTUAL = 2,
+ TYPE_BLUETOOTH = 3,
+ };
+ static const char* const PROPERTY_NAME;
+ static const char* const PROPERTY_MANUFACTURER;
+ static const char* const PROPERTY_PRODUCT;
+ static const char* const PROPERTY_VERSION;
+ static const char* const PROPERTY_SERIAL_NUMBER;
+ static const char* const PROPERTY_ALSA_CARD;
+ static const char* const PROPERTY_ALSA_DEVICE;
+
+ friend bool operator==(const MidiDeviceInfo& lhs, const MidiDeviceInfo& rhs);
+ friend bool operator!=(const MidiDeviceInfo& lhs, const MidiDeviceInfo& rhs) {
+ return !(lhs == rhs);
+ }
+
+private:
+ status_t readStringVector(
+ const Parcel* parcel, Vector<String16> *vectorPtr, size_t defaultLength);
+ status_t writeStringVector(Parcel* parcel, const Vector<String16>& vector) const;
+
+ int32_t mType;
+ int32_t mId;
+ Vector<String16> mInputPortNames;
+ Vector<String16> mOutputPortNames;
+ os::PersistableBundle mProperties;
+ bool mIsPrivate;
+};
+
+} // namespace midi
+} // namespace media
+} // namespace android
+
+#endif // ANDROID_MEDIA_MIDI_DEVICE_INFO_H
diff --git a/media/native/midi/amidi.cpp b/media/native/midi/amidi.cpp
index 46f2815..35c4d42 100644
--- a/media/native/midi/amidi.cpp
+++ b/media/native/midi/amidi.cpp
@@ -26,7 +26,7 @@
#include <core_jni_helpers.h>
#include "android/media/midi/BpMidiDeviceServer.h"
-#include "media/MidiDeviceInfo.h"
+#include "MidiDeviceInfo.h"
#include "include/amidi/AMidi.h"
#include "amidi_internal.h"
diff --git a/native/android/aidl/com/android/internal/compat/IPlatformCompatNative.aidl b/native/android/aidl/com/android/internal/compat/IPlatformCompatNative.aidl
index c022388..347e4e8 100644
--- a/native/android/aidl/com/android/internal/compat/IPlatformCompatNative.aidl
+++ b/native/android/aidl/com/android/internal/compat/IPlatformCompatNative.aidl
@@ -33,9 +33,10 @@
* you do not need to call this API directly. The change will be reported for you.
*
* @param changeId The ID of the compatibility change taking effect.
+ * @param userId The ID of the user that the operation is done for.
* @param packageName The package name of the app in question.
*/
- void reportChangeByPackageName(long changeId, @utf8InCpp String packageName);
+ void reportChangeByPackageName(long changeId, @utf8InCpp String packageName, int userId);
/**
* Reports that a compatibility change is affecting an app process now.
@@ -64,9 +65,10 @@
*
* @param changeId The ID of the compatibility change in question.
* @param packageName The package name of the app in question.
+ * @param userId The ID of the user that the operation is done for.
* @return {@code true} if the change is enabled for the current app.
*/
- boolean isChangeEnabledByPackageName(long changeId, @utf8InCpp String packageName);
+ boolean isChangeEnabledByPackageName(long changeId, @utf8InCpp String packageName, int userId);
/**
* Query if a given compatibility change is enabled for an app process. This method should
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 53c0122..b34b31a 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -294,7 +294,7 @@
auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
- for (const auto& [surfaceControl, acquireTime, previousReleaseFence] : surfaceControlStats) {
+ for (const auto& [surfaceControl, acquireTime, previousReleaseFence, transformHint] : surfaceControlStats) {
ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime;
aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence;
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
index 011d5ea..be4b889 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
@@ -20,6 +20,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.dagger.SystemUIRootComponent;
import com.android.systemui.navigationbar.car.CarFacetButtonController;
import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index b1067f8..93e553f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -23,6 +23,7 @@
import com.android.systemui.car.CarNotificationEntryManager;
import com.android.systemui.car.CarNotificationInterruptionStateProvider;
+import com.android.systemui.dagger.SystemUIRootComponent;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.power.EnhancedEstimates;
@@ -101,6 +102,9 @@
CarSystemUIRootComponent systemUIRootComponent);
@Binds
+ public abstract StatusBar bindStatusBar(CarStatusBar statusBar);
+
+ @Binds
@IntoMap
@ClassKey(StatusBar.class)
public abstract SystemUI providesStatusBar(CarStatusBar statusBar);
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
index 325c988..c2847c8 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
@@ -16,6 +16,12 @@
package com.android.systemui;
+import com.android.systemui.dagger.DependencyBinder;
+import com.android.systemui.dagger.DependencyProvider;
+import com.android.systemui.dagger.SystemServicesModule;
+import com.android.systemui.dagger.SystemUIModule;
+import com.android.systemui.dagger.SystemUIRootComponent;
+
import javax.inject.Singleton;
import dagger.Component;
@@ -26,6 +32,7 @@
DependencyProvider.class,
DependencyBinder.class,
SystemUIFactory.ContextHolder.class,
+ SystemServicesModule.class,
SystemUIModule.class,
CarSystemUIModule.class,
CarSystemUIBinder.class
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
index 5bb3530..6fba1d5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -16,8 +16,6 @@
package com.android.systemui.navigationbar.car;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
import android.content.Context;
import android.graphics.PixelFormat;
import android.inputmethodservice.InputMethodService;
@@ -35,6 +33,7 @@
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NavigationBarController;
@@ -47,7 +46,6 @@
import java.io.PrintWriter;
import javax.inject.Inject;
-import javax.inject.Named;
import dagger.Lazy;
@@ -96,7 +94,7 @@
WindowManager windowManager,
DeviceProvisionedController deviceProvisionedController,
Lazy<FacetButtonTaskStackListener> facetButtonTaskStackListener,
- @Named(MAIN_HANDLER_NAME) Handler mainHandler,
+ @MainHandler Handler mainHandler,
Lazy<KeyguardStateController> keyguardStateController,
Lazy<CarFacetButtonController> facetButtonController,
Lazy<NavigationBarController> navigationBarController,
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/NavigationBarViewFactory.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/NavigationBarViewFactory.java
index 128721e..519b33a2 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/NavigationBarViewFactory.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/NavigationBarViewFactory.java
@@ -108,7 +108,7 @@
private ViewGroup getWindowCached(Type type) {
if (mCachedContainerMap.containsKey(type)) {
- return mCachedViewMap.get(type);
+ return mCachedContainerMap.get(type);
}
ViewGroup window = (ViewGroup) View.inflate(mContext,
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 3424eea..52aaf4f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -32,6 +32,7 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.PowerManager;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
@@ -106,8 +107,11 @@
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.DozeScrimController;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -161,9 +165,18 @@
private Drawable mNotificationPanelBackground;
private ViewGroup mTopNavigationBarContainer;
+ private ViewGroup mNavigationBarWindow;
+ private ViewGroup mLeftNavigationBarWindow;
+ private ViewGroup mRightNavigationBarWindow;
private CarNavigationBarView mTopNavigationBarView;
+ private CarNavigationBarView mNavigationBarView;
+ private CarNavigationBarView mLeftNavigationBarView;
+ private CarNavigationBarView mRightNavigationBarView;
private final Object mQueueLock = new Object();
+ private boolean mShowLeft;
+ private boolean mShowRight;
+ private boolean mShowBottom;
private final NavigationBarViewFactory mNavigationBarViewFactory;
private CarFacetButtonController mCarFacetButtonController;
private DeviceProvisionedController mDeviceProvisionedController;
@@ -290,6 +303,10 @@
DozeParameters dozeParameters,
ScrimController scrimController,
Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
+ Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+ DozeServiceHost dozeServiceHost,
+ PowerManager powerManager,
+ DozeScrimController dozeScrimController,
/* Car Settings injected components. */
NavigationBarViewFactory navigationBarViewFactory) {
@@ -352,7 +369,11 @@
notifLog,
dozeParameters,
scrimController,
- lockscreenWallpaperLazy);
+ lockscreenWallpaperLazy,
+ biometricUnlockControllerLazy,
+ dozeServiceHost,
+ powerManager,
+ dozeScrimController);
mScrimController = scrimController;
mNavigationBarViewFactory = navigationBarViewFactory;
}
@@ -429,6 +450,16 @@
mHvacController.removeAllComponents();
mCarFacetButtonController.removeAll();
+ if (mNavigationBarWindow != null) {
+ mNavigationBarView = null;
+ }
+ if (mLeftNavigationBarWindow != null) {
+ mLeftNavigationBarView = null;
+ }
+ if (mRightNavigationBarWindow != null) {
+ mRightNavigationBarView = null;
+ }
+
buildNavBarContent();
// CarFacetButtonController was reset therefore we need to re-add the status bar elements
// to the controller.
@@ -451,28 +482,28 @@
* the full screen user selector is shown.
*/
void setNavBarVisibility(@View.Visibility int visibility) {
- if (mNavigationBarViewFactory.getBottomWindow() != null) {
- mNavigationBarViewFactory.getBottomWindow().setVisibility(visibility);
+ if (mNavigationBarWindow != null) {
+ mNavigationBarWindow.setVisibility(visibility);
}
- if (mNavigationBarViewFactory.getLeftWindow() != null) {
- mNavigationBarViewFactory.getLeftWindow().setVisibility(visibility);
+ if (mLeftNavigationBarWindow != null) {
+ mLeftNavigationBarWindow.setVisibility(visibility);
}
- if (mNavigationBarViewFactory.getRightWindow() != null) {
- mNavigationBarViewFactory.getRightWindow().setVisibility(visibility);
+ if (mRightNavigationBarWindow != null) {
+ mRightNavigationBarWindow.setVisibility(visibility);
}
}
@Override
public boolean hideKeyguard() {
boolean result = super.hideKeyguard();
- if (mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser) != null) {
- mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser).hideKeyguardButtons();
+ if (mNavigationBarView != null) {
+ mNavigationBarView.hideKeyguardButtons();
}
- if (mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser) != null) {
- mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser).hideKeyguardButtons();
+ if (mLeftNavigationBarView != null) {
+ mLeftNavigationBarView.hideKeyguardButtons();
}
- if (mNavigationBarViewFactory.getRightBar(mDeviceIsSetUpForUser) != null) {
- mNavigationBarViewFactory.getRightBar(mDeviceIsSetUpForUser).hideKeyguardButtons();
+ if (mRightNavigationBarView != null) {
+ mRightNavigationBarView.hideKeyguardButtons();
}
return result;
}
@@ -487,14 +518,14 @@
* Switch to the keyguard applicable content contained in the nav bars
*/
private void updateNavBarForKeyguardContent() {
- if (mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser) != null) {
- mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser).showKeyguardButtons();
+ if (mNavigationBarView != null) {
+ mNavigationBarView.showKeyguardButtons();
}
- if (mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser) != null) {
- mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser).showKeyguardButtons();
+ if (mLeftNavigationBarView != null) {
+ mLeftNavigationBarView.showKeyguardButtons();
}
- if (mNavigationBarViewFactory.getRightBar(mDeviceIsSetUpForUser) != null) {
- mNavigationBarViewFactory.getRightBar(mDeviceIsSetUpForUser).showKeyguardButtons();
+ if (mRightNavigationBarView != null) {
+ mRightNavigationBarView.showKeyguardButtons();
}
}
@@ -599,26 +630,19 @@
mNotificationDataManager = new NotificationDataManager();
mNotificationDataManager.setOnUnseenCountUpdateListener(
() -> {
- if (mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser) != null
- && mNotificationDataManager != null) {
+ if (mNavigationBarView != null && mNotificationDataManager != null) {
Boolean hasUnseen =
mNotificationDataManager.getUnseenNotificationCount() > 0;
- if (mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser) != null) {
- mNavigationBarViewFactory.getBottomBar(
- mDeviceIsSetUpForUser).toggleNotificationUnseenIndicator(
- hasUnseen);
+ if (mNavigationBarView != null) {
+ mNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
}
- if (mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser) != null) {
- mNavigationBarViewFactory.getLeftBar(
- mDeviceIsSetUpForUser).toggleNotificationUnseenIndicator(
- hasUnseen);
+ if (mLeftNavigationBarView != null) {
+ mLeftNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
}
- if (mNavigationBarViewFactory.getRightBar(mDeviceIsSetUpForUser) != null) {
- mNavigationBarViewFactory.getRightBar(
- mDeviceIsSetUpForUser).toggleNotificationUnseenIndicator(
- hasUnseen);
+ if (mRightNavigationBarView != null) {
+ mRightNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
}
}
});
@@ -873,6 +897,10 @@
@Override
protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
+ mShowBottom = mContext.getResources().getBoolean(R.bool.config_enableBottomNavigationBar);
+ mShowLeft = mContext.getResources().getBoolean(R.bool.config_enableLeftNavigationBar);
+ mShowRight = mContext.getResources().getBoolean(R.bool.config_enableRightNavigationBar);
+
buildNavBarWindows();
buildNavBarContent();
}
@@ -880,22 +908,40 @@
private void buildNavBarContent() {
buildTopBar();
- CarNavigationBarView bottom = mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser);
- bottom.setStatusBar(this);
- bottom.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
+ if (mShowBottom) {
+ mNavigationBarView = mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser);
+ mNavigationBarView.setStatusBar(this);
+ mNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
+ }
- CarNavigationBarView left = mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser);
- left.setStatusBar(this);
- left.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
+ if (mShowLeft) {
+ mLeftNavigationBarView = mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser);
+ mLeftNavigationBarView.setStatusBar(this);
+ mLeftNavigationBarView.setStatusBarWindowTouchListener(
+ mNavBarNotificationTouchListener);
+ }
- CarNavigationBarView right = mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser);
- right.setStatusBar(this);
- right.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
+ if (mShowRight) {
+ mRightNavigationBarView = mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser);
+ mRightNavigationBarView.setStatusBar(this);
+ mRightNavigationBarView.setStatusBarWindowTouchListener(
+ mNavBarNotificationTouchListener);
+ }
}
private void buildNavBarWindows() {
mTopNavigationBarContainer = mStatusBarWindow
.findViewById(R.id.car_top_navigation_bar_container);
+
+ if (mShowBottom) {
+ mNavigationBarWindow = mNavigationBarViewFactory.getBottomWindow();
+ }
+ if (mShowLeft) {
+ mLeftNavigationBarWindow = mNavigationBarViewFactory.getLeftWindow();
+ }
+ if (mShowRight) {
+ mRightNavigationBarWindow = mNavigationBarViewFactory.getRightWindow();
+ }
}
private void buildTopBar() {
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index 3014452..3b48259 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -18,6 +18,7 @@
import static android.content.DialogInterface.BUTTON_NEGATIVE;
import static android.content.DialogInterface.BUTTON_POSITIVE;
+import static android.os.UserManager.DISALLOW_ADD_USER;
import android.app.ActivityManager;
import android.app.AlertDialog;
@@ -145,7 +146,8 @@
userRecords.add(createStartGuestUserRecord());
// Add add user record if the foreground user can add users
- if (mCarUserManagerHelper.canForegroundUserAddUsers()) {
+ UserHandle fgUserHandle = UserHandle.of(ActivityManager.getCurrentUser());
+ if (!mUserManager.hasUserRestriction(DISALLOW_ADD_USER, fgUserHandle)) {
userRecords.add(createAddUserRecord());
}
@@ -285,7 +287,7 @@
}
private void handleAddUserClicked() {
- if (mCarUserManagerHelper.isUserLimitReached()) {
+ if (!mUserManager.canAddMoreUsers()) {
mAddUserView.setEnabled(true);
showMaxUserLimitReachedDialog();
} else {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 1f923af..a855741 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -127,6 +127,9 @@
<!-- Summary for Connected wifi network without internet -->
<string name="wifi_connected_no_internet">Connected, no internet</string>
+ <!-- Summary for connected network without internet due to private dns validation failed [CHAR LIMIT=NONE] -->
+ <string name="private_dns_broken">Private DNS server cannot be accessed</string>
+
<!-- Summary for connected wifi network with partial internet connectivity [CHAR LIMIT=50] -->
<string name="wifi_limited_connection">Limited connection</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 81d1ea5..16fd51f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -50,6 +50,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -1568,7 +1569,13 @@
NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
return context.getString(R.string.wifi_limited_connection);
} else if (!nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
- return context.getString(R.string.wifi_connected_no_internet);
+ final String mode = Settings.Global.getString(context.getContentResolver(),
+ Settings.Global.PRIVATE_DNS_MODE);
+ if (nc.isPrivateDnsBroken()) {
+ return context.getString(R.string.private_dns_broken);
+ } else {
+ return context.getString(R.string.wifi_connected_no_internet);
+ }
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 5352936..b11585a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -31,6 +31,7 @@
import android.net.wifi.WifiSsid;
import android.os.Handler;
import android.os.Looper;
+import android.provider.Settings;
import com.android.settingslib.R;
@@ -163,7 +164,13 @@
statusLabel = mContext.getString(R.string.wifi_limited_connection);
return;
} else if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
- statusLabel = mContext.getString(R.string.wifi_status_no_internet);
+ final String mode = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.PRIVATE_DNS_MODE);
+ if (networkCapabilities.isPrivateDnsBroken()) {
+ statusLabel = mContext.getString(R.string.private_dns_broken);
+ } else {
+ statusLabel = mContext.getString(R.string.wifi_status_no_internet);
+ }
return;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index 4e6c005..658a0b5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -93,6 +93,7 @@
if (bssid != null) {
visibility.append(" ").append(bssid);
}
+ visibility.append(" technology = ").append(info.getWifiTechnology());
visibility.append(" rssi=").append(info.getRssi());
visibility.append(" ");
visibility.append(" score=").append(info.score);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 17c621e..44de09b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -1497,21 +1497,7 @@
}
if (upgradeVersion == 94) {
- // Add wireless charging started sound setting
- if (mUserHandle == UserHandle.USER_SYSTEM) {
- db.beginTransaction();
- SQLiteStatement stmt = null;
- try {
- stmt = db.compileStatement("INSERT OR REPLACE INTO global(name,value)"
- + " VALUES(?,?);");
- loadStringSetting(stmt, Settings.Global.CHARGING_STARTED_SOUND,
- R.string.def_wireless_charging_started_sound);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- if (stmt != null) stmt.close();
- }
- }
+ // charging sound moved to SettingsProvider version 184
upgradeVersion = 95;
}
@@ -2562,8 +2548,6 @@
R.string.def_car_dock_sound);
loadStringSetting(stmt, Settings.Global.CAR_UNDOCK_SOUND,
R.string.def_car_undock_sound);
- loadStringSetting(stmt, Settings.Global.CHARGING_STARTED_SOUND,
- R.string.def_wireless_charging_started_sound);
loadIntegerSetting(stmt, Settings.Global.DOCK_AUDIO_MEDIA_ENABLED,
R.integer.def_dock_audio_media_enabled);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 6bd26bf..e24d387 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1347,6 +1347,9 @@
dumpSetting(s, p,
Settings.Global.CHARGING_STARTED_SOUND,
GlobalSettingsProto.Sounds.CHARGING_STARTED);
+ dumpSetting(s, p,
+ Settings.Global.WIRELESS_CHARGING_STARTED_SOUND,
+ GlobalSettingsProto.Sounds.WIRELESS_CHARGING_STARTED);
p.end(soundsToken);
final long soundTriggerToken = p.start(GlobalSettingsProto.SOUND_TRIGGER);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 0639864..80faf476 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3206,7 +3206,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 183;
+ private static final int SETTINGS_VERSION = 184;
private final int mUserId;
@@ -3845,23 +3845,7 @@
}
if (currentVersion == 155) {
- // Version 156: Set the default value for CHARGING_STARTED_SOUND.
- final SettingsState globalSettings = getGlobalSettingsLocked();
- final String oldValue = globalSettings.getSettingLocked(
- Global.CHARGING_STARTED_SOUND).getValue();
- final String oldDefault = getContext().getResources().getString(
- R.string.def_wireless_charging_started_sound);
- if (TextUtils.equals(null, oldValue)
- || TextUtils.equals(oldValue, oldDefault)) {
- final String defaultValue = getContext().getResources().getString(
- R.string.def_charging_started_sound);
- if (!TextUtils.isEmpty(defaultValue)) {
- globalSettings.insertSettingLocked(
- Settings.Global.CHARGING_STARTED_SOUND, defaultValue,
- null, true, SettingsState.SYSTEM_PACKAGE_NAME);
- }
-
- }
+ // Version 156: migrated to version 184
currentVersion = 156;
}
@@ -4416,6 +4400,48 @@
currentVersion = 183;
}
+ if (currentVersion == 183) {
+ // Version 184: Set default values for WIRELESS_CHARGING_STARTED_SOUND
+ // and CHARGING_STARTED_SOUND
+ final SettingsState globalSettings = getGlobalSettingsLocked();
+
+ final String oldValueWireless = globalSettings.getSettingLocked(
+ Global.WIRELESS_CHARGING_STARTED_SOUND).getValue();
+ final String oldValueWired = globalSettings.getSettingLocked(
+ Global.CHARGING_STARTED_SOUND).getValue();
+
+ final String defaultValueWireless = getContext().getResources().getString(
+ R.string.def_wireless_charging_started_sound);
+ final String defaultValueWired = getContext().getResources().getString(
+ R.string.def_charging_started_sound);
+
+ // wireless charging sound
+ if (oldValueWireless == null
+ || TextUtils.equals(oldValueWireless, defaultValueWired)) {
+ if (!TextUtils.isEmpty(defaultValueWireless)) {
+ globalSettings.insertSettingLocked(
+ Global.WIRELESS_CHARGING_STARTED_SOUND, defaultValueWireless,
+ null /* tag */, true /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ } else if (!TextUtils.isEmpty(defaultValueWired)) {
+ // if the wireless sound is empty, use the wired charging sound
+ globalSettings.insertSettingLocked(
+ Global.WIRELESS_CHARGING_STARTED_SOUND, defaultValueWired,
+ null /* tag */, true /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ }
+
+ // wired charging sound
+ if (oldValueWired == null && !TextUtils.isEmpty(defaultValueWired)) {
+ globalSettings.insertSettingLocked(
+ Global.CHARGING_STARTED_SOUND, defaultValueWired,
+ null /* tag */, true /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ currentVersion = 184;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 62827bc..179ba8a 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -559,6 +559,7 @@
Settings.Global.WIFI_WATCHDOG_ON,
Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON,
Settings.Global.CHARGING_STARTED_SOUND,
+ Settings.Global.WIRELESS_CHARGING_STARTED_SOUND,
Settings.Global.WINDOW_ANIMATION_SCALE,
Settings.Global.WTF_IS_FATAL,
Settings.Global.ZEN_MODE,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index e11c063..54e291f 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -247,23 +247,6 @@
android:exported="false" />
<receiver
- android:name=".BugreportReceiver"
- android:permission="android.permission.DUMP">
- <intent-filter>
- <action android:name="com.android.internal.intent.action.BUGREPORT_STARTED" />
- <action android:name="com.android.internal.intent.action.BUGREPORT_FINISHED" />
- </intent-filter>
- </receiver>
-
- <receiver
- android:name=".RemoteBugreportReceiver"
- android:permission="android.permission.DUMP">
- <intent-filter>
- <action android:name="com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED" />
- </intent-filter>
- </receiver>
-
- <receiver
android:name=".BugreportRequestedReceiver"
android:permission="android.permission.TRIGGER_SHELL_BUGREPORT">
<intent-filter>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 665bde3..1b35770 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -56,16 +56,11 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
-import android.os.IDumpstate;
-import android.os.IDumpstateListener;
-import android.os.IDumpstateToken;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -80,7 +75,6 @@
import android.view.ContextThemeWrapper;
import android.view.IWindowManager;
import android.view.View;
-import android.view.View.OnFocusChangeListener;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
@@ -122,31 +116,9 @@
import java.util.zip.ZipOutputStream;
/**
- * Service used to keep progress of bugreport processes ({@code dumpstate} and
- * {@code BugreportManager}).
+ * Service used to trigger system bugreports.
* <p>
- * There can be 2 workflows. One workflow via ({@code dumpstate}) is:
- * <ol>
- * <li>When {@code dumpstate} starts, it sends a {@code BUGREPORT_STARTED} with a sequential id,
- * its pid, and the estimated total effort.
- * <li>{@link BugreportReceiver} receives the intent and delegates it to this service.
- * <li>Upon start, this service:
- * <ol>
- * <li>Issues a system notification so user can watch the progress (which is 0% initially).
- * <li>Polls the {@link SystemProperties} for updates on the {@code dumpstate} progress.
- * <li>If the progress changed, it updates the system notification.
- * </ol>
- * <li>As {@code dumpstate} progresses, it updates the system property.
- * <li>When {@code dumpstate} finishes, it sends a {@code BUGREPORT_FINISHED} intent.
- * <li>{@link BugreportReceiver} receives the intent and delegates it to this service, which in
- * turn:
- * <ol>
- * <li>Updates the system notification so user can share the bugreport.
- * <li>Stops monitoring that {@code dumpstate} process.
- * <li>Stops itself if it doesn't have any process left to monitor.
- * </ol>
- * </ol>
- * The second workflow using Bugreport API({@code BugreportManager}) is:
+ * The workflow uses Bugreport API({@code BugreportManager}) and is as follows:
* <ol>
* <li>System apps like Settings or SysUI broadcasts {@code BUGREPORT_REQUESTED}.
* <li>{@link BugreportRequestedReceiver} receives the intent and delegates it to this service.
@@ -164,18 +136,14 @@
private static final String AUTHORITY = "com.android.shell";
- // External intents sent by dumpstate.
- static final String INTENT_BUGREPORT_STARTED =
- "com.android.internal.intent.action.BUGREPORT_STARTED";
- static final String INTENT_BUGREPORT_FINISHED =
- "com.android.internal.intent.action.BUGREPORT_FINISHED";
- static final String INTENT_REMOTE_BUGREPORT_FINISHED =
- "com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED";
-
// External intent used to trigger bugreport API.
static final String INTENT_BUGREPORT_REQUESTED =
"com.android.internal.intent.action.BUGREPORT_REQUESTED";
+ // Intent sent to notify external apps that bugreport finished
+ static final String INTENT_BUGREPORT_FINISHED =
+ "com.android.internal.intent.action.BUGREPORT_FINISHED";
+
// Internal intents used on notification actions.
static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL";
static final String INTENT_BUGREPORT_SHARE = "android.intent.action.BUGREPORT_SHARE";
@@ -188,7 +156,6 @@
static final String EXTRA_BUGREPORT_TYPE = "android.intent.extra.BUGREPORT_TYPE";
static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
static final String EXTRA_ID = "android.intent.extra.ID";
- static final String EXTRA_PID = "android.intent.extra.PID";
static final String EXTRA_MAX = "android.intent.extra.MAX";
static final String EXTRA_NAME = "android.intent.extra.NAME";
static final String EXTRA_TITLE = "android.intent.extra.TITLE";
@@ -218,15 +185,9 @@
*/
static final int SCREENSHOT_DELAY_SECONDS = 3;
- // TODO: will be gone once fully migrated to Binder
- /** System properties used to communicate with dumpstate progress. */
- private static final String DUMPSTATE_PREFIX = "dumpstate.";
- private static final String NAME_SUFFIX = ".name";
+ /** System property where dumpstate stores last triggered bugreport id */
private static final String PROPERTY_LAST_ID = "dumpstate.last_id";
- /** System property (and value) used to stop dumpstate. */
- // TODO: should call ActiveManager API instead
- private static final String CTL_STOP = "ctl.stop";
private static final String BUGREPORT_SERVICE = "bugreport";
/**
@@ -273,8 +234,6 @@
private File mScreenshotsDir;
- private boolean mUsingBugreportApi;
-
private BugreportManager mBugreportManager;
/**
@@ -452,9 +411,9 @@
cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE);
final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath);
- addScreenshotToIntent(intent, mInfo);
+ intent.putExtra(EXTRA_SCREENSHOT, getScreenshotForIntent(mInfo));
mContext.sendBroadcast(intent, android.Manifest.permission.DUMP);
- onBugreportFinished(mInfo.id);
+ onBugreportFinished(mInfo);
}
}
}
@@ -475,14 +434,17 @@
android.Manifest.permission.DUMP);
}
- private static void addScreenshotToIntent(Intent intent, BugreportInfo info) {
- final File screenshotFile = info.screenshotFiles.isEmpty()
- ? null : info.screenshotFiles.get(0);
- if (screenshotFile != null && screenshotFile.length() > 0) {
+ /**
+ * Checks if screenshot array is non-empty and returns the first screenshot's path. The first
+ * screenshot is the default screenshot for the bugreport types that take it.
+ */
+ private static String getScreenshotForIntent(BugreportInfo info) {
+ if (!info.screenshotFiles.isEmpty()) {
+ final File screenshotFile = info.screenshotFiles.get(0);
final String screenshotFilePath = screenshotFile.getAbsolutePath();
- intent.putExtra(EXTRA_SCREENSHOT, screenshotFilePath);
+ return screenshotFilePath;
}
- return;
+ return null;
}
private static String generateFileHash(String fileName) {
@@ -558,40 +520,24 @@
Log.v(TAG, "handleMessage(): " + dumpIntent((Intent) parcel));
final Intent intent;
if (parcel instanceof Intent) {
- // The real intent was passed to BugreportReceiver, which delegated to the service.
+ // The real intent was passed to BugreportRequestedReceiver,
+ // which delegated to the service.
intent = (Intent) parcel;
} else {
intent = (Intent) msg.obj;
}
final String action = intent.getAction();
- final int pid = intent.getIntExtra(EXTRA_PID, 0);
final int id = intent.getIntExtra(EXTRA_ID, 0);
final int max = intent.getIntExtra(EXTRA_MAX, -1);
final String name = intent.getStringExtra(EXTRA_NAME);
if (DEBUG)
- Log.v(TAG, "action: " + action + ", name: " + name + ", id: " + id + ", pid: "
- + pid + ", max: " + max);
+ Log.v(TAG, "action: " + action + ", name: " + name + ", id: " + id
+ + ", max: " + max);
switch (action) {
case INTENT_BUGREPORT_REQUESTED:
startBugreportAPI(intent);
break;
- case INTENT_BUGREPORT_STARTED:
- if (!mUsingBugreportApi && !startProgress(name, id, pid, max)) {
- stopSelfWhenDone();
- return;
- }
- break;
- case INTENT_BUGREPORT_FINISHED:
- if (!mUsingBugreportApi) {
- if (id == 0) {
- // Shouldn't happen, unless BUGREPORT_FINISHED is received
- // from a legacy, out-of-sync dumpstate process.
- Log.w(TAG, "Missing " + EXTRA_ID + " on intent " + intent);
- }
- onBugreportFinished(id, intent);
- }
- break;
case INTENT_BUGREPORT_INFO_LAUNCH:
launchBugreportInfoDialog(id);
break;
@@ -633,52 +579,12 @@
private BugreportInfo getInfo(int id) {
final BugreportInfo bugreportInfo = mBugreportInfos.get(id);
if (bugreportInfo == null) {
- Log.w(TAG, "Not monitoring process with ID " + id);
+ Log.w(TAG, "Not monitoring bugreports with ID " + id);
return null;
}
return bugreportInfo;
}
- /**
- * Creates the {@link BugreportInfo} for a process and issue a system notification to
- * indicate its progress.
- *
- * @return whether it succeeded or not.
- */
- private boolean startProgress(String name, int id, int pid, int max) {
- if (name == null) {
- Log.w(TAG, "Missing " + EXTRA_NAME + " on start intent");
- }
- if (id == -1) {
- Log.e(TAG, "Missing " + EXTRA_ID + " on start intent");
- return false;
- }
- if (pid == -1) {
- Log.e(TAG, "Missing " + EXTRA_PID + " on start intent");
- return false;
- }
- if (max <= 0) {
- Log.e(TAG, "Invalid value for extra " + EXTRA_MAX + ": " + max);
- return false;
- }
-
- final BugreportInfo info = new BugreportInfo(mContext, id, pid, name, max);
- if (mBugreportInfos.indexOfKey(id) >= 0) {
- // BUGREPORT_STARTED intent was already received; ignore it.
- Log.w(TAG, "ID " + id + " already watched");
- return true;
- }
- final DumpstateListener listener = new DumpstateListener(info);
- mBugreportInfos.put(info.id, info);
- if (listener.connect()) {
- updateProgress(info);
- return true;
- } else {
- Log.w(TAG, "not updating progress because it could not connect to dumpstate");
- return false;
- }
- }
-
private String getBugreportBaseName(@BugreportParams.BugreportMode int type) {
String buildId = SystemProperties.get("ro.build.id", "UNKNOWN_BUILD");
String deviceName = SystemProperties.get("ro.product.name", "UNKNOWN_DEVICE");
@@ -694,7 +600,6 @@
}
private void startBugreportAPI(Intent intent) {
- mUsingBugreportApi = true;
String shareTitle = intent.getStringExtra(EXTRA_TITLE);
String shareDescription = intent.getStringExtra(EXTRA_DESCRIPTION);
int bugreportType = intent.getIntExtra(EXTRA_BUGREPORT_TYPE,
@@ -702,10 +607,8 @@
String baseName = getBugreportBaseName(bugreportType);
String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
- // pid not used in this workflow, so setting default = 0
- BugreportInfo info = new BugreportInfo(mContext, 0 /* pid */, baseName, name,
- 100 /* max progress*/, shareTitle, shareDescription, bugreportType,
- mUsingBugreportApi);
+ BugreportInfo info = new BugreportInfo(mContext, baseName, name,
+ 100 /* max progress*/, shareTitle, shareDescription, bugreportType);
ParcelFileDescriptor bugreportFd = info.createBugreportFd();
if (bugreportFd == null) {
@@ -885,11 +788,7 @@
final BugreportInfo info = getInfo(id);
if (info != null && !info.finished) {
Log.i(TAG, "Cancelling bugreport service (ID=" + id + ") on user's request");
- if (mUsingBugreportApi) {
- mBugreportManager.cancelBugreport();
- } else {
- setSystemProperty(CTL_STOP, BUGREPORT_SERVICE);
- }
+ mBugreportManager.cancelBugreport();
deleteScreenshots(info);
}
synchronized (mLock) {
@@ -1087,94 +986,15 @@
}
/**
- * Handles the BUGREPORT_FINISHED intent sent by {@code dumpstate}.
- */
- private void onBugreportFinished(int id, Intent intent) {
- final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
- if (bugreportFile == null) {
- // Should never happen, dumpstate always set the file.
- Log.wtf(TAG, "Missing " + EXTRA_BUGREPORT + " on intent " + intent);
- return;
- }
- final int max = intent.getIntExtra(EXTRA_MAX, -1);
- final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
- final String shareTitle = intent.getStringExtra(EXTRA_TITLE);
- final String shareDescription = intent.getStringExtra(EXTRA_DESCRIPTION);
- onBugreportFinished(id, bugreportFile, screenshotFile, shareTitle, shareDescription, max);
- }
-
- /**
- * Handles the onfinish() call by BugreportCallbackImpl using the id
- */
- private void onBugreportFinished(int id) {
- BugreportInfo info = getInfo(id);
- final File bugreportFile = info.bugreportFile;
- final int max = -1; // this is to log metrics for dumpstate duration.
- File screenshotFile = info.screenshotFiles.isEmpty()
- ? null : info.screenshotFiles.get(0);
- // If the screenshot file did not get populated implies this type of bugreport does not
- // need the screenshot file; setting the file to null so that empty file doesnt get shared
- if (screenshotFile != null && screenshotFile.length() == 0) {
- if (screenshotFile.delete()) {
- Log.d(TAG, "screenshot file deleted successfully.");
- }
- info.screenshotFiles.remove(0);
- // TODO(b/136066578): Will soon stop using the below variable as it is a no-op in
- // API flow and the screenshotFile value is read from info.screenshotFiles
- screenshotFile = null;
- }
- // TODO: Since we are passing id to the function, it should be able to find the info linked
- // to the id and therefore use the value of shareTitle and shareDescription.
- onBugreportFinished(id, bugreportFile, screenshotFile, info.shareTitle,
- info.shareDescription, max);
- }
-
-
- /**
* Wraps up bugreport generation and triggers a notification to share the bugreport.
*/
- private void onBugreportFinished(int id, File bugreportFile, @Nullable File screenshotFile,
- String shareTitle, String shareDescription, int max) {
- mInfoDialog.onBugreportFinished();
- BugreportInfo info = getInfo(id);
- if (info == null) {
- // Happens when BUGREPORT_FINISHED was received without a BUGREPORT_STARTED first.
- Log.v(TAG, "Creating info for untracked ID " + id);
- info = new BugreportInfo(mContext, id);
- DumpstateListener dumpstateListener = new DumpstateListener(info);
- mBugreportInfos.put(id, info);
- }
- if (!mUsingBugreportApi) {
- // Rename in API flow happens before sending the broadcast for remote bugreport (to
- // handle renaming before sending broadcasts)
- info.renameScreenshots(mScreenshotsDir);
- // API workflow already has the bugreport file. This was required in legacy flow, when
- // FINISHED broadcast would send the final bugreport files.
- // TODO(b/136066578): Change function definition to not include bugreport/screenshot
- // file in params
- info.bugreportFile = bugreportFile;
- if (screenshotFile != null) {
- info.addScreenshot(screenshotFile);
- }
- }
-
- if (max != -1) {
- MetricsLogger.histogram(this, "dumpstate_duration", max);
- info.max = max;
- }
-
- if (!TextUtils.isEmpty(shareTitle)) {
- info.title = shareTitle;
- if (!TextUtils.isEmpty(shareDescription)) {
- info.shareDescription= shareDescription;
- }
- Log.d(TAG, "Bugreport title is " + info.title + ","
- + " shareDescription is " + info.shareDescription);
- }
+ private void onBugreportFinished(BugreportInfo info) {
+ Log.d(TAG, "Bugreport finished with title: " + info.title
+ + " and shareDescription: " + info.shareDescription);
info.finished = true;
// Stop running on foreground, otherwise share notification cannot be dismissed.
- stopForegroundWhenDone(id);
+ stopForegroundWhenDone(info.id);
triggerLocalNotification(mContext, info);
}
@@ -1214,7 +1034,11 @@
/**
* Build {@link Intent} that can be used to share the given bugreport.
*/
- private static Intent buildSendIntent(Context context, BugreportInfo info) {
+ private static Intent buildSendIntent(Context context, BugreportInfo info,
+ File screenshotsDir) {
+ // Rename files (if required) before sharing
+ info.renameBugreportFile();
+ info.renameScreenshots(screenshotsDir);
// Files are kept on private storage, so turn into Uris that we can
// grant temporary permissions for.
final Uri bugreportUri;
@@ -1300,7 +1124,7 @@
addDetailsToZipFile(info);
- final Intent sendIntent = buildSendIntent(mContext, info);
+ final Intent sendIntent = buildSendIntent(mContext, info, mScreenshotsDir);
if (sendIntent == null) {
Log.w(TAG, "Stopping progres on ID " + id + " because share intent could not be built");
synchronized (mLock) {
@@ -1628,12 +1452,11 @@
}
String action = intent.getAction();
if (action == null) {
- // Happens when BugreportReceiver calls startService...
+ // Happens when startService is called...
action = "no action";
}
final StringBuilder buffer = new StringBuilder(action).append(" extras: ");
addExtra(buffer, intent, EXTRA_ID);
- addExtra(buffer, intent, EXTRA_PID);
addExtra(buffer, intent, EXTRA_MAX);
addExtra(buffer, intent, EXTRA_NAME);
addExtra(buffer, intent, EXTRA_DESCRIPTION);
@@ -1678,15 +1501,6 @@
}
/**
- * Updates the system property used by {@code dumpstate} to rename the final bugreport files.
- */
- private boolean setBugreportNameProperty(int pid, String name) {
- Log.d(TAG, "Updating bugreport name to " + name);
- final String key = DUMPSTATE_PREFIX + pid + NAME_SUFFIX;
- return setSystemProperty(key, name);
- }
-
- /**
* Updates the user-provided details of a bugreport.
*/
private void updateBugreportInfo(int id, String name, String title, String description) {
@@ -1770,30 +1584,6 @@
private AlertDialog mDialog;
private Button mOkButton;
private int mId;
- private int mPid;
-
- /**
- * Last "committed" value of the bugreport name.
- * <p>
- * Once initially set, it's only updated when user clicks the OK button.
- */
- private String mSavedName;
-
- /**
- * Last value of the bugreport name as entered by the user.
- * <p>
- * Every time it's changed the equivalent system property is changed as well, but if the
- * user clicks CANCEL, the old value (stored on {@code mSavedName} is restored.
- * <p>
- * This logic handles the corner-case scenario where {@code dumpstate} finishes after the
- * user changed the name but didn't clicked OK yet (for example, because the user is typing
- * the description). The only drawback is that if the user changes the name while
- * {@code dumpstate} is running but clicks CANCEL after it finishes, then the final name
- * will be the one that has been canceled. But when {@code dumpstate} finishes the {code
- * name} UI is disabled and the old name restored anyways, so the user will be "alerted" of
- * such drawback.
- */
- private String mTempName;
/**
* Sets its internal state and displays the dialog.
@@ -1813,18 +1603,6 @@
mInfoName = (EditText) view.findViewById(R.id.name);
mInfoTitle = (EditText) view.findViewById(R.id.title);
mInfoDescription = (EditText) view.findViewById(R.id.description);
-
- mInfoName.setOnFocusChangeListener(new OnFocusChangeListener() {
-
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- return;
- }
- sanitizeName(info);
- }
- });
-
mDialog = new AlertDialog.Builder(themedContext)
.setView(view)
.setTitle(dialogTitle)
@@ -1839,15 +1617,6 @@
{
MetricsLogger.action(context,
MetricsEvent.ACTION_BUGREPORT_DETAILS_CANCELED);
- if (!mTempName.equals(mSavedName)) {
- // Must restore bugreport's name since it was changed
- // before user clicked OK.
- if (mUsingBugreportApi) {
- info.name = mSavedName;
- } else {
- setBugreportNameProperty(mPid, mSavedName);
- }
- }
}
})
.create();
@@ -1866,9 +1635,7 @@
}
// Then set fields.
- mSavedName = mTempName = info.name;
mId = info.id;
- mPid = info.pid;
if (!TextUtils.isEmpty(info.name)) {
mInfoName.setText(info.name);
}
@@ -1895,7 +1662,7 @@
@Override
public void onClick(View view) {
MetricsLogger.action(context, MetricsEvent.ACTION_BUGREPORT_DETAILS_SAVED);
- sanitizeName(info);
+ sanitizeName(info.name);
final String name = mInfoName.getText().toString();
final String title = mInfoTitle.getText().toString();
final String description = mInfoDescription.getText().toString();
@@ -1911,9 +1678,9 @@
* Sanitizes the user-provided value for the {@code name} field, automatically replacing
* invalid characters if necessary.
*/
- private void sanitizeName(BugreportInfo info) {
+ private void sanitizeName(String savedName) {
String name = mInfoName.getText().toString();
- if (name.equals(mTempName)) {
+ if (name.equals(savedName)) {
if (DEBUG) Log.v(TAG, "name didn't change, no need to sanitize: " + name);
return;
}
@@ -1933,28 +1700,6 @@
name = safeName.toString();
mInfoName.setText(name);
}
- mTempName = name;
- if (mUsingBugreportApi) {
- info.name = name;
- } else {
- // Must update system property for the cases where dumpstate finishes
- // while the user is still entering other fields (like title or
- // description)
- setBugreportNameProperty(mPid, name);
- }
- }
-
- /**
- * Notifies the dialog that the bugreport has finished so it disables the {@code name}
- * field.
- * <p>Once the bugreport is finished dumpstate has already generated the final files, so
- * changing the name would have no effect.
- */
- void onBugreportFinished() {
- if (mInfoName != null) {
- mInfoName.setEnabled(false);
- mInfoName.setText(mSavedName);
- }
}
void cancel() {
@@ -1976,18 +1721,10 @@
int id;
/**
- * {@code pid} of the {@code dumpstate} process generating the bugreport.
- * pid is unused in the API flow
- * TODO(b/136066578): Remove pid
- */
- final int pid;
-
- /**
* Prefix name of the bugreport, this is uneditable.
* The baseName consists of the string "bugreport" + deviceName + buildID
* This will end with the string "wifi"/"telephony" for wifi/telephony bugreports.
* Bugreport zip file name = "<baseName>-<name>.zip"
- * Bugreport png file name = "<baseName>-<name>.png"
*/
String baseName;
@@ -1998,6 +1735,12 @@
String name;
/**
+ * Initial value of the field name. This is required to rename the files later on, as they
+ * are created using initial value of name.
+ */
+ String initialName;
+
+ /**
* User-provided, one-line summary of the bug; when set, will be used as the subject
* of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
*/
@@ -2063,12 +1806,6 @@
boolean finished;
/**
- * Whether this bugreport is using API workflow.
- * TODO(b/136066578): Remove when legacy bugreport methods are removed
- */
- boolean usingApi;
-
- /**
* Whether the details entries have been added to the bugreport yet.
*/
boolean addingDetailsToZip;
@@ -2092,42 +1829,18 @@
int type;
/**
- * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_STARTED.
- */
- BugreportInfo(Context context, int id, int pid, String name, int max) {
- // bugreports triggered by STARTED broadcast do not use callback functions,
- // onFinished() callback method is the only function where type is used.
- // Set type to -1 as it is unused in this workflow.
- // This constructor will soon be removed.
- this(context, pid, "" /* dumpstate handles basename */, name, max, null, null, -1,
- false);
- this.id = id;
- }
-
- /**
* Constructor for tracked bugreports - typically called upon receiving BUGREPORT_REQUESTED.
*/
- BugreportInfo(Context context, int pid, String baseName, String name, int max,
+ BugreportInfo(Context context, String baseName, String name, int max,
@Nullable String shareTitle, @Nullable String shareDescription,
- @BugreportParams.BugreportMode int type, boolean usingApi) {
+ @BugreportParams.BugreportMode int type) {
this.context = context;
- this.pid = pid;
- this.name = name;
+ this.name = this.initialName = name;
this.max = this.realMax = max;
this.shareTitle = shareTitle == null ? "" : shareTitle;
this.shareDescription = shareDescription == null ? "" : shareDescription;
this.type = type;
this.baseName = baseName;
- this.usingApi = usingApi;
- }
-
- /**
- * Constructor for untracked bugreports - typically called upon receiving BUGREPORT_FINISHED
- * without a previous call to BUGREPORT_STARTED.
- */
- BugreportInfo(Context context, int id) {
- this(context, id, id, null, 0);
- this.finished = true;
}
ParcelFileDescriptor createBugreportFd() {
@@ -2136,17 +1849,24 @@
}
ParcelFileDescriptor createScreenshotFd() {
- File screenshotFile = new File(BUGREPORT_DIR, getFileName(this, ".png"));
+ File screenshotFile = new File(BUGREPORT_DIR, getScreenshotName("default"));
addScreenshot(screenshotFile);
return createReadWriteFile(screenshotFile);
}
/**
- * Gets the name for next screenshot file.
+ * Gets the name for next user triggered screenshot file.
*/
String getPathNextScreenshot() {
screenshotCounter ++;
- return "screenshot-" + pid + "-" + screenshotCounter + ".png";
+ return getScreenshotName(Integer.toString(screenshotCounter));
+ }
+
+ /**
+ * Gets the name for screenshot file based on the suffix that is passed.
+ */
+ String getScreenshotName(String suffix) {
+ return "screenshot-" + initialName + "-" + suffix + ".png";
}
/**
@@ -2157,7 +1877,8 @@
}
/**
- * Rename all screenshots files so that they contain the user-generated name instead of pid.
+ * Rename all screenshots files so that they contain the new {@code name} instead of the
+ * {@code initialName} if user has changed it.
*/
void renameScreenshots(File screenshotDir) {
if (TextUtils.isEmpty(name)) {
@@ -2166,22 +1887,21 @@
final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size());
for (File oldFile : screenshotFiles) {
final String oldName = oldFile.getName();
- final String newName;
- if (usingApi) {
- newName = getFileName(this, ".png");
- } else {
- newName = oldName.replaceFirst(Integer.toString(pid), name);
- }
+ final String newName = oldName.replaceFirst(initialName, name);
final File newFile;
if (!newName.equals(oldName)) {
final File renamedFile = new File(screenshotDir, newName);
Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
} else {
- Log.w(TAG, "Name didn't change: " + oldName); // Shouldn't happen.
+ Log.w(TAG, "Name didn't change: " + oldName);
newFile = oldFile;
}
- renamedFiles.add(newFile);
+ if (newFile.length() > 0) {
+ renamedFiles.add(newFile);
+ } else if (newFile.delete()) {
+ Log.d(TAG, "screenshot file: " + newFile + "deleted successfully.");
+ }
}
screenshotFiles = renamedFiles;
}
@@ -2215,11 +1935,10 @@
final StringBuilder builder = new StringBuilder()
.append("\tid: ").append(id)
- .append(", pid: ").append(pid)
.append(", baseName: ").append(baseName)
.append(", name: ").append(name)
+ .append(", initialName: ").append(initialName)
.append(", finished: ").append(finished)
- .append(", usingApi: ").append(usingApi)
.append("\n\ttitle: ").append(title)
.append("\n\tdescription: ");
if (description == null) {
@@ -2250,9 +1969,9 @@
protected BugreportInfo(Parcel in) {
context = null;
id = in.readInt();
- pid = in.readInt();
baseName = in.readString();
name = in.readString();
+ initialName = in.readString();
title = in.readString();
description = in.readString();
max = in.readInt();
@@ -2269,7 +1988,6 @@
}
finished = in.readInt() == 1;
- usingApi = in.readInt() == 1;
screenshotCounter = in.readInt();
shareDescription = in.readString();
shareTitle = in.readString();
@@ -2278,9 +1996,9 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
- dest.writeInt(pid);
dest.writeString(baseName);
dest.writeString(name);
+ dest.writeString(initialName);
dest.writeString(title);
dest.writeString(description);
dest.writeInt(max);
@@ -2297,7 +2015,6 @@
}
dest.writeInt(finished ? 1 : 0);
- dest.writeInt(usingApi ? 1 : 0);
dest.writeInt(screenshotCounter);
dest.writeString(shareDescription);
dest.writeString(shareTitle);
@@ -2333,80 +2050,6 @@
}
- private final class DumpstateListener extends IDumpstateListener.Stub
- implements DeathRecipient {
-
- private final BugreportInfo info;
- private IDumpstateToken token;
-
- DumpstateListener(BugreportInfo info) {
- this.info = info;
- }
-
- /**
- * Connects to the {@code dumpstate} binder to receive updates.
- */
- boolean connect() {
- if (token != null) {
- Log.d(TAG, "connect(): " + info.id + " already connected");
- return true;
- }
- final IBinder service = ServiceManager.getService("dumpstate");
- if (service == null) {
- Log.d(TAG, "dumpstate service not bound yet");
- return true;
- }
- final IDumpstate dumpstate = IDumpstate.Stub.asInterface(service);
- try {
- token = dumpstate.setListener("Shell", this, /* perSectionDetails= */ false);
- if (token != null) {
- token.asBinder().linkToDeath(this, 0);
- }
- } catch (Exception e) {
- Log.e(TAG, "Could not set dumpstate listener: " + e);
- }
- return token != null;
- }
-
- @Override
- public void binderDied() {
- if (!info.finished) {
- // TODO: linkToDeath() might be called BEFORE Shell received the
- // BUGREPORT_FINISHED broadcast, in which case the statements below
- // spam logcat (but are harmless).
- // The right, long-term solution is to provide an onFinished() callback
- // on IDumpstateListener and call it instead of using a broadcast.
- Log.w(TAG, "Dumpstate process died:\n" + info);
- synchronized (mLock) {
- stopProgressLocked(info.id);
- }
- }
- token.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void onProgress(int progress) throws RemoteException {
- synchronized (mLock) {
- checkProgressUpdatedLocked(info, progress);
- }
- }
-
- @Override
- public void onError(int errorCode) throws RemoteException {
- // TODO(b/111441001): implement
- }
-
- @Override
- public void onFinished() throws RemoteException {
- // TODO(b/111441001): implement
- }
-
- public void dump(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("token: "); pw.println(token);
- }
-
- }
-
@GuardedBy("mLock")
private void checkProgressUpdatedLocked(BugreportInfo info, int progress) {
if (progress > CAPPED_PROGRESS) {
@@ -2418,11 +2061,11 @@
private void updateProgressInfo(BugreportInfo info, int progress, int max) {
if (DEBUG) {
if (progress != info.progress) {
- Log.v(TAG, "Updating progress for PID " + info.pid + "(id: " + info.id
+ Log.v(TAG, "Updating progress for name " + info.name + "(id: " + info.id
+ ") from " + info.progress + " to " + progress);
}
if (max != info.max) {
- Log.v(TAG, "Updating max progress for PID " + info.pid + "(id: " + info.id
+ Log.v(TAG, "Updating max progress for name " + info.name + "(id: " + info.id
+ ") from " + info.max + " to " + max);
}
}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
deleted file mode 100644
index 15ce90f..0000000
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2013 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.shell;
-
-import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
-import static com.android.shell.BugreportProgressService.EXTRA_ORIGINAL_INTENT;
-import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED;
-import static com.android.shell.BugreportProgressService.getFileExtra;
-import static com.android.shell.BugreportProgressService.dumpIntent;
-
-import java.io.File;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.FileUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-
-/**
- * Receiver that handles finished bugreports, usually by attaching them to an
- * {@link Intent#ACTION_SEND_MULTIPLE}.
- */
-public class BugreportReceiver extends BroadcastReceiver {
- private static final String TAG = "BugreportReceiver";
-
- /**
- * Always keep the newest 8 bugreport files.
- */
- private static final int MIN_KEEP_COUNT = 8;
-
- /**
- * Always keep bugreports taken in the last week.
- */
- private static final long MIN_KEEP_AGE = DateUtils.WEEK_IN_MILLIS;
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "onReceive(): " + dumpIntent(intent));
- // Clean up older bugreports in background
- cleanupOldFiles(this, intent, INTENT_BUGREPORT_FINISHED, MIN_KEEP_COUNT, MIN_KEEP_AGE);
-
- // Delegate intent handling to service.
- Intent serviceIntent = new Intent(context, BugreportProgressService.class);
- serviceIntent.putExtra(EXTRA_ORIGINAL_INTENT, intent);
- context.startService(serviceIntent);
- }
-
- static void cleanupOldFiles(BroadcastReceiver br, Intent intent, String expectedAction,
- final int minCount, final long minAge) {
- if (!expectedAction.equals(intent.getAction())) {
- return;
- }
- final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
- if (bugreportFile == null || !bugreportFile.exists()) {
- Log.e(TAG, "Not deleting old files because file " + bugreportFile + " doesn't exist");
- return;
- }
- final PendingResult result = br.goAsync();
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- try {
- FileUtils.deleteOlderFiles(bugreportFile.getParentFile(), minCount, minAge);
- } catch (RuntimeException e) {
- Log.e(TAG, "RuntimeException deleting old files", e);
- }
- result.finish();
- return null;
- }
- }.execute();
- }
-}
diff --git a/packages/Shell/src/com/android/shell/RemoteBugreportReceiver.java b/packages/Shell/src/com/android/shell/RemoteBugreportReceiver.java
deleted file mode 100644
index 634c3b4..0000000
--- a/packages/Shell/src/com/android/shell/RemoteBugreportReceiver.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2015 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.shell;
-
-import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
-import static com.android.shell.BugreportProgressService.INTENT_REMOTE_BUGREPORT_FINISHED;
-import static com.android.shell.BugreportProgressService.getFileExtra;
-import static com.android.shell.BugreportProgressService.getUri;
-import static com.android.shell.BugreportReceiver.cleanupOldFiles;
-
-import java.io.File;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.text.format.DateUtils;
-
-/**
- * Receiver that handles finished remote bugreports, by re-sending
- * the intent with appended bugreport zip file URI.
- *
- * <p> Remote bugreport never contains a screenshot.
- */
-public class RemoteBugreportReceiver extends BroadcastReceiver {
-
- private static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
-
- /** Always keep just the last remote bugreport's files around. */
- private static final int REMOTE_BUGREPORT_FILES_AMOUNT = 3;
-
- /** Always keep remote bugreport files created in the last day. */
- private static final long MIN_KEEP_AGE = DateUtils.DAY_IN_MILLIS;
-
- @Override
- public void onReceive(Context context, Intent intent) {
- cleanupOldFiles(this, intent, INTENT_REMOTE_BUGREPORT_FINISHED,
- REMOTE_BUGREPORT_FILES_AMOUNT, MIN_KEEP_AGE);
-
- final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
- final Uri bugreportUri = getUri(context, bugreportFile);
- final String bugreportHash = intent.getStringExtra(
- DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH);
-
- final Intent newIntent = new Intent(DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH);
- newIntent.setDataAndType(bugreportUri, BUGREPORT_MIMETYPE);
- newIntent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH, bugreportHash);
- context.sendBroadcastAsUser(newIntent, UserHandle.SYSTEM,
- android.Manifest.permission.DUMP);
- }
-}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 3a71632..bb298e9 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -29,10 +29,8 @@
import static com.android.shell.BugreportProgressService.EXTRA_ID;
import static com.android.shell.BugreportProgressService.EXTRA_MAX;
import static com.android.shell.BugreportProgressService.EXTRA_NAME;
-import static com.android.shell.BugreportProgressService.EXTRA_PID;
import static com.android.shell.BugreportProgressService.EXTRA_SCREENSHOT;
import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED;
-import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_STARTED;
import static com.android.shell.BugreportProgressService.SCREENSHOT_DELAY_SECONDS;
import static org.junit.Assert.assertEquals;
@@ -145,6 +143,10 @@
private static final String TITLE2 = "Master of the Universe";
private static final String DESCRIPTION = "One's description...";
private static final String DESCRIPTION2 = "...is another's treasure.";
+ // TODO(b/143130523): Fix (update) tests and add to presubmit
+ private static final String EXTRA_PID = "android.intent.extra.PID";
+ private static final String INTENT_BUGREPORT_STARTED =
+ "com.android.internal.intent.action.BUGREPORT_STARTED";
private static final String NO_DESCRIPTION = null;
private static final String NO_NAME = null;
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java
new file mode 100644
index 0000000..802a8da
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java
@@ -0,0 +1,72 @@
+/*
+ * 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.systemui.plugins;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.graphics.drawable.Drawable;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/** Custom logic that can extract a PeopleHub "person" from a notification. */
+@ProvidesInterface(
+ action = NotificationPersonExtractorPlugin.ACTION,
+ version = NotificationPersonExtractorPlugin.VERSION)
+@DependsOn(target = NotificationPersonExtractorPlugin.PersonData.class)
+public interface NotificationPersonExtractorPlugin extends Plugin {
+
+ String ACTION = "com.android.systemui.action.PEOPLE_HUB_PERSON_EXTRACTOR";
+ int VERSION = 0;
+
+ /**
+ * Attempts to extract a person from a notification. Returns {@code null} if one is not found.
+ */
+ @Nullable PersonData extractPerson(StatusBarNotification sbn);
+
+ /**
+ * Attempts to extract a person id from a notification. Returns {@code null} if one is not
+ * found.
+ *
+ * This method can be overridden in order to provide a faster implementation.
+ */
+ @Nullable
+ default String extractPersonKey(StatusBarNotification sbn) {
+ return extractPerson(sbn).key;
+ }
+
+ /** A person to be surfaced in PeopleHub. */
+ @ProvidesInterface(version = PersonData.VERSION)
+ final class PersonData {
+
+ public static final int VERSION = 0;
+
+ public final String key;
+ public final CharSequence name;
+ public final Drawable avatar;
+ public final PendingIntent clickIntent;
+
+ public PersonData(String key, CharSequence name, Drawable avatar,
+ PendingIntent clickIntent) {
+ this.key = key;
+ this.name = name;
+ this.avatar = avatar;
+ this.clickIntent = clickIntent;
+ }
+ }
+}
diff --git a/packages/SystemUI/res/layout/people_strip.xml b/packages/SystemUI/res/layout/people_strip.xml
index b314db8..f0ac08b 100644
--- a/packages/SystemUI/res/layout/people_strip.xml
+++ b/packages/SystemUI/res/layout/people_strip.xml
@@ -33,10 +33,9 @@
<LinearLayout
android:id="@+id/people_list"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
+ android:layout_height="match_parent"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
android:gravity="center"
android:orientation="horizontal">
@@ -49,7 +48,7 @@
<LinearLayout
android:layout_width="70dp"
android:layout_height="match_parent"
- android:gravity="center"
+ android:gravity="center_horizontal"
android:orientation="vertical"
android:visibility="invisible">
@@ -87,7 +86,7 @@
<LinearLayout
android:layout_width="70dp"
android:layout_height="match_parent"
- android:gravity="center"
+ android:gravity="center_horizontal"
android:orientation="vertical"
android:visibility="invisible">
@@ -125,7 +124,7 @@
<LinearLayout
android:layout_width="70dp"
android:layout_height="match_parent"
- android:gravity="center"
+ android:gravity="center_horizontal"
android:orientation="vertical"
android:visibility="invisible">
@@ -163,7 +162,7 @@
<LinearLayout
android:layout_width="70dp"
android:layout_height="match_parent"
- android:gravity="center"
+ android:gravity="center_horizontal"
android:orientation="vertical"
android:visibility="invisible">
@@ -201,7 +200,7 @@
<LinearLayout
android:layout_width="70dp"
android:layout_height="match_parent"
- android:gravity="center"
+ android:gravity="center_horizontal"
android:orientation="vertical"
android:visibility="invisible">
diff --git a/packages/SystemUI/res/layout/qqs_media_panel.xml b/packages/SystemUI/res/layout/qqs_media_panel.xml
new file mode 100644
index 0000000..1189371
--- /dev/null
+++ b/packages/SystemUI/res/layout/qqs_media_panel.xml
@@ -0,0 +1,100 @@
+<?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
+ -->
+
+<!-- Layout for QQS media controls -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/qqs_media_controls"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:padding="10dp"
+ >
+ <!-- Top line: icon + artist name -->
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:gravity="center"
+ >
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/icon"
+ android:layout_width="15dp"
+ android:layout_height="15dp"
+ android:layout_marginEnd="5dp"
+ />
+ <TextView
+ android:id="@+id/header_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:singleLine="true"
+ />
+ </LinearLayout>
+
+ <!-- Second line: song name -->
+ <TextView
+ android:id="@+id/header_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:gravity="center"/>
+
+ <!-- Bottom section: controls -->
+ <LinearLayout
+ android:id="@+id/media_actions"
+ android:orientation="horizontal"
+ android:layoutDirection="ltr"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ >
+ <ImageButton
+ style="@android:style/Widget.Material.Button.Borderless.Small"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="8dp"
+ android:layout_marginEnd="2dp"
+ android:gravity="center"
+ android:visibility="gone"
+ android:id="@+id/action0"
+ />
+ <ImageButton
+ style="@android:style/Widget.Material.Button.Borderless.Small"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="8dp"
+ android:layout_marginEnd="2dp"
+ android:gravity="center"
+ android:visibility="gone"
+ android:id="@+id/action1"
+ />
+ <ImageButton
+ style="@android:style/Widget.Material.Button.Borderless.Small"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="8dp"
+ android:layout_marginEnd="2dp"
+ android:gravity="center"
+ android:visibility="gone"
+ android:id="@+id/action2"
+ />
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/qs_media_panel.xml
new file mode 100644
index 0000000..dd42276
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_media_panel.xml
@@ -0,0 +1,124 @@
+<?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
+ -->
+
+<!-- Layout for media controls inside QSPanel carousel -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/qs_media_controls"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center_horizontal|fill_vertical"
+ android:padding="10dp"
+ >
+
+ <!-- placeholder for notification header -->
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/header"
+ android:padding="3dp"
+ android:layout_marginEnd="-12dp"
+ />
+
+ <!-- Top line: artist name -->
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ >
+ <TextView
+ android:id="@+id/header_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:singleLine="true"
+ />
+ </LinearLayout>
+
+ <!-- Second line: song name -->
+ <TextView
+ android:id="@+id/header_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:gravity="center"/>
+
+ <!-- Bottom section: controls -->
+ <LinearLayout
+ android:id="@+id/media_actions"
+ android:orientation="horizontal"
+ android:layoutDirection="ltr"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ >
+ <ImageButton
+ style="@android:style/Widget.Material.Button.Borderless.Small"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="8dp"
+ android:layout_marginEnd="2dp"
+ android:gravity="center"
+ android:visibility="gone"
+ android:id="@+id/action0"
+ />
+ <ImageButton
+ style="@android:style/Widget.Material.Button.Borderless.Small"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="8dp"
+ android:layout_marginEnd="2dp"
+ android:gravity="center"
+ android:visibility="gone"
+ android:id="@+id/action1"
+ />
+ <ImageButton
+ style="@android:style/Widget.Material.Button.Borderless.Small"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="8dp"
+ android:layout_marginEnd="2dp"
+ android:gravity="center"
+ android:visibility="gone"
+ android:id="@+id/action2"
+ />
+ <ImageButton
+ style="@android:style/Widget.Material.Button.Borderless.Small"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="8dp"
+ android:layout_marginEnd="2dp"
+ android:gravity="center"
+ android:visibility="gone"
+ android:id="@+id/action3"
+ />
+ <ImageButton
+ style="@android:style/Widget.Material.Button.Borderless.Small"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="8dp"
+ android:layout_marginEnd="2dp"
+ android:gravity="center"
+ android:visibility="gone"
+ android:id="@+id/action4"
+ />
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index ed18dc7..e99b917 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -43,7 +43,7 @@
<com.android.systemui.qs.QuickQSPanel
android:id="@+id/quick_qs_panel"
android:layout_width="match_parent"
- android:layout_height="48dp"
+ android:layout_height="wrap_content"
android:layout_below="@id/quick_qs_status_icons"
android:layout_marginStart="@dimen/qs_header_tile_margin_horizontal"
android:layout_marginEnd="@dimen/qs_header_tile_margin_horizontal"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d722d61..14371fe 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1164,4 +1164,11 @@
<!-- Size of the RAT type for CellularTile -->
<dimen name="celltile_rat_type_size">10sp</dimen>
+
+ <dimen name="new_qs_vertical_margin">8dp</dimen>
+
+ <!-- Size of media cards in the QSPanel carousel -->
+ <dimen name="qs_media_height">150dp</dimen>
+ <dimen name="qs_media_width">350dp</dimen>
+ <dimen name="qs_media_padding">8dp</dimen>
</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index ebac3d9..caee8cc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -131,8 +131,7 @@
protected void verifyPasswordAndUnlock() {
if (mDismissing) return; // already verified but haven't been dismissed; don't do it again.
- final LockscreenCredential password =
- LockscreenCredential.createPassword(getPasswordText());
+ final LockscreenCredential password = getEnteredCredential();
setPasswordEntryInputEnabled(false);
if (mPendingLockCheck != null) {
mPendingLockCheck.cancel(false);
@@ -223,7 +222,7 @@
}
protected abstract void resetPasswordText(boolean animate, boolean announce);
- protected abstract CharSequence getPasswordText();
+ protected abstract LockscreenCredential getEnteredCredential();
protected abstract void setPasswordEntryEnabled(boolean enabled);
protected abstract void setPasswordEntryInputEnabled(boolean enabled);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 12c9fc9..f8f3dc8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -36,6 +36,7 @@
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
+import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.TextViewInputDisabler;
import com.android.systemui.R;
@@ -243,8 +244,8 @@
}
@Override
- protected CharSequence getPasswordText() {
- return mPasswordEntry.getText();
+ protected LockscreenCredential getEnteredCredential() {
+ return LockscreenCredential.createPasswordOrNone(mPasswordEntry.getText());
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 8e9df55..c67decc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -23,6 +23,7 @@
import android.view.MotionEvent;
import android.view.View;
+import com.android.internal.widget.LockscreenCredential;
import com.android.systemui.R;
/**
@@ -167,8 +168,8 @@
}
@Override
- protected CharSequence getPasswordText() {
- return mPasswordEntry.getText();
+ protected LockscreenCredential getEnteredCredential() {
+ return LockscreenCredential.createPinOrNone(mPasswordEntry.getText());
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index ac5e255..27410be 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -39,7 +39,6 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
import android.annotation.AnyThread;
import android.annotation.MainThread;
@@ -101,6 +100,7 @@
import com.android.settingslib.WirelessUtils;
import com.android.systemui.DejankUtils;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.MainLooper;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -118,7 +118,6 @@
import java.util.function.Consumer;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
/**
@@ -1500,7 +1499,7 @@
@VisibleForTesting
@Inject
- protected KeyguardUpdateMonitor(Context context, @Named(MAIN_LOOPER_NAME) Looper mainLooper) {
+ protected KeyguardUpdateMonitor(Context context, @MainLooper Looper mainLooper) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index ff8a932..486d02c 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -40,6 +40,10 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.BgHandler;
+import com.android.systemui.dagger.qualifiers.BgLooper;
+import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.MainLooper;
import com.android.systemui.dock.DockManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.ScreenLifecycle;
@@ -148,16 +152,16 @@
/**
* Key for getting a the main looper.
*/
- public static final String MAIN_LOOPER_NAME = "main_looper";
+ private static final String MAIN_LOOPER_NAME = "main_looper";
/**
* Key for getting a background Looper for background work.
*/
- public static final String BG_LOOPER_NAME = "background_looper";
+ private static final String BG_LOOPER_NAME = "background_looper";
/**
* Key for getting a background Handler for background work.
*/
- public static final String BG_HANDLER_NAME = "background_handler";
+ private static final String BG_HANDLER_NAME = "background_handler";
/**
* Key for getting a Handler for receiving time tick broadcasts on.
*/
@@ -165,7 +169,7 @@
/**
* Generic handler on the main thread.
*/
- public static final String MAIN_HANDLER_NAME = "main_handler";
+ private static final String MAIN_HANDLER_NAME = "main_handler";
/**
* An email address to send memory leak reports to by default.
@@ -300,10 +304,10 @@
@Inject Lazy<AutoHideController> mAutoHideController;
@Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener;
@Inject Lazy<PrivacyItemController> mPrivacyItemController;
- @Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper;
- @Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler;
- @Inject @Named(MAIN_LOOPER_NAME) Lazy<Looper> mMainLooper;
- @Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler;
+ @Inject @BgLooper Lazy<Looper> mBgLooper;
+ @Inject @BgHandler Lazy<Handler> mBgHandler;
+ @Inject @MainLooper Lazy<Looper> mMainLooper;
+ @Inject @MainHandler Lazy<Handler> mMainHandler;
@Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
@Nullable
@Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index ddbabee..30a60ab 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -16,6 +16,8 @@
package com.android.systemui;
+import static android.os.PowerManager.WAKE_REASON_UNKNOWN;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -28,21 +30,32 @@
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
-import com.android.systemui.statusbar.phone.StatusBar;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
/**
* Class that only runs on debuggable builds that listens to broadcasts that simulate actions in the
* system that are used for testing the latency.
*/
+@Singleton
public class LatencyTester extends SystemUI {
- private static final String ACTION_FINGERPRINT_WAKE =
+ private static final String
+ ACTION_FINGERPRINT_WAKE =
"com.android.systemui.latency.ACTION_FINGERPRINT_WAKE";
- private static final String ACTION_TURN_ON_SCREEN =
+ private static final String
+ ACTION_TURN_ON_SCREEN =
"com.android.systemui.latency.ACTION_TURN_ON_SCREEN";
+ private final BiometricUnlockController mBiometricUnlockController;
+ private final PowerManager mPowerManager;
- public LatencyTester(Context context) {
+ @Inject
+ public LatencyTester(Context context, BiometricUnlockController biometricUnlockController,
+ PowerManager powerManager) {
super(context);
+ mBiometricUnlockController = biometricUnlockController;
+ mPowerManager = powerManager;
}
@Override
@@ -68,19 +81,17 @@
}
private void fakeTurnOnScreen() {
- PowerManager powerManager = mContext.getSystemService(PowerManager.class);
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionStart(
LatencyTracker.ACTION_TURN_ON_SCREEN);
}
- powerManager.wakeUp(SystemClock.uptimeMillis(), "android.policy:LATENCY_TESTS");
+ mPowerManager.wakeUp(
+ SystemClock.uptimeMillis(), WAKE_REASON_UNKNOWN, "android.policy:LATENCY_TESTS");
}
private void fakeWakeAndUnlock() {
- BiometricUnlockController biometricUnlockController = getComponent(StatusBar.class)
- .getBiometricUnlockController();
- biometricUnlockController.onBiometricAcquired(BiometricSourceType.FINGERPRINT);
- biometricUnlockController.onBiometricAuthenticated(
+ mBiometricUnlockController.onBiometricAcquired(BiometricSourceType.FINGERPRINT);
+ mBiometricUnlockController.onBiometricAuthenticated(
KeyguardUpdateMonitor.getCurrentUser(), BiometricSourceType.FINGERPRINT);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index ad20986..8a99e451 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -81,10 +81,16 @@
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
/**
* An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
* for antialiasing and emulation purposes.
*/
+@Singleton
public class ScreenDecorations extends SystemUI implements Tunable {
private static final boolean DEBUG = false;
private static final String TAG = "ScreenDecorations";
@@ -94,6 +100,7 @@
private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
private static final boolean VERBOSE = false;
+ private final Lazy<StatusBar> mStatusBarLazy;
private DisplayManager mDisplayManager;
private DisplayManager.DisplayListener mDisplayListener;
@@ -132,8 +139,10 @@
return result;
}
- public ScreenDecorations(Context context) {
+ @Inject
+ public ScreenDecorations(Context context, Lazy<StatusBar> statusBarLazy) {
super(context);
+ mStatusBarLazy = statusBarLazy;
}
@Override
@@ -434,13 +443,13 @@
private void setupStatusBarPadding(int padding) {
// Add some padding to all the content near the edge of the screen.
- StatusBar sb = getComponent(StatusBar.class);
- View statusBar = (sb != null ? sb.getStatusBarWindow() : null);
- if (statusBar != null) {
- TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING,
- padding, FLAG_END);
+ StatusBar statusBar = mStatusBarLazy.get();
+ View statusBarWindow = statusBar.getStatusBarWindow();
+ if (statusBarWindow != null) {
+ TunablePadding.addTunablePadding(statusBarWindow.findViewById(R.id.keyguard_header),
+ PADDING, padding, FLAG_END);
- FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar);
+ FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBarWindow);
fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG,
new TunablePaddingTagListener(padding, R.id.status_bar));
fragmentHostManager.addTagListener(QS.TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
index aa13fa8..746515a 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@@ -27,6 +27,8 @@
import androidx.annotation.Nullable;
import androidx.core.app.AppComponentFactory;
+import com.android.systemui.dagger.ContextComponentHelper;
+
import javax.inject.Inject;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 91776a3..022bf06 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -34,6 +34,7 @@
import android.util.Log;
import android.util.TimingsTraceLog;
+import com.android.systemui.dagger.ContextComponentHelper;
import com.android.systemui.plugins.OverlayPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.plugins.PluginManager;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 11db481..0a547b6 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -27,6 +27,9 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.dagger.DaggerSystemUIRootComponent;
+import com.android.systemui.dagger.DependencyProvider;
+import com.android.systemui.dagger.SystemUIRootComponent;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -107,7 +110,7 @@
protected SystemUIRootComponent buildSystemUIRootComponent(Context context) {
return DaggerSystemUIRootComponent.builder()
- .dependencyProvider(new com.android.systemui.DependencyProvider())
+ .dependencyProvider(new DependencyProvider())
.contextHolder(new ContextHolder(context))
.build();
}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index ef171d3..f616d57 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -16,8 +16,6 @@
package com.android.systemui.appops;
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
-
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -31,6 +29,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.BgLooper;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -39,7 +38,6 @@
import java.util.Set;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
/**
@@ -79,7 +77,7 @@
};
@Inject
- public AppOpsControllerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper) {
+ public AppOpsControllerImpl(Context context, @BgLooper Looper bgLooper) {
this(context, bgLooper, new PermissionFlagsCache(context));
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index cdc2623..b758731 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -16,6 +16,9 @@
package com.android.systemui.biometrics;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
@@ -27,6 +30,8 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -34,6 +39,7 @@
import android.util.Log;
import android.view.WindowManager;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.systemui.SystemUI;
@@ -229,15 +235,8 @@
}
@Override
- public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
- if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated
- + " reason: " + failureReason);
-
- if (authenticated) {
- mCurrentDialog.onAuthenticationSucceeded();
- } else {
- mCurrentDialog.onAuthenticationFailed(failureReason);
- }
+ public void onBiometricAuthenticated() {
+ mCurrentDialog.onAuthenticationSucceeded();
}
@Override
@@ -247,16 +246,45 @@
mCurrentDialog.onHelp(message);
}
- @Override
- public void onBiometricError(int errorCode, String error) {
- if (DEBUG) Log.d(TAG, "onBiometricError: " + errorCode + ", " + error);
+ private String getErrorString(int modality, int error, int vendorCode) {
+ switch (modality) {
+ case TYPE_FACE:
+ return FaceManager.getErrorString(mContext, error, vendorCode);
- final boolean isLockout = errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
- || errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+ case TYPE_FINGERPRINT:
+ return FingerprintManager.getErrorString(mContext, error, vendorCode);
+
+ default:
+ return "";
+ }
+ }
+
+ @Override
+ public void onBiometricError(int modality, int error, int vendorCode) {
+ if (DEBUG) {
+ Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode));
+ }
+
+ final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
+ || (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
+
+ // TODO(b/141025588): Create separate methods for handling hard and soft errors.
+ final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED
+ || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT);
+
if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
+ if (DEBUG) Log.d(TAG, "onBiometricError, lockout");
mCurrentDialog.animateToCredentialUI();
+ } else if (isSoftError) {
+ final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED)
+ ? mContext.getString(R.string.biometric_not_recognized)
+ : getErrorString(modality, error, vendorCode);
+ if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage);
+ mCurrentDialog.onAuthenticationFailed(errorMessage);
} else {
- mCurrentDialog.onError(error);
+ final String errorMessage = getErrorString(modality, error, vendorCode);
+ if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage);
+ mCurrentDialog.onError(errorMessage);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 5e977b4..ff4711c 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -26,13 +26,12 @@
import android.util.Log
import android.util.SparseArray
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.Dependency.BG_LOOPER_NAME
-import com.android.systemui.Dependency.MAIN_HANDLER_NAME
import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.BgLooper
+import com.android.systemui.dagger.qualifiers.MainHandler
import java.io.FileDescriptor
import java.io.PrintWriter
import javax.inject.Inject
-import javax.inject.Named
import javax.inject.Singleton
data class ReceiverData(
@@ -61,8 +60,8 @@
@Singleton
open class BroadcastDispatcher @Inject constructor (
private val context: Context,
- @Named(MAIN_HANDLER_NAME) private val mainHandler: Handler,
- @Named(BG_LOOPER_NAME) private val bgLooper: Looper
+ @MainHandler private val mainHandler: Handler,
+ @BgLooper private val bgLooper: Looper
) : Dumpable {
// Only modify in BG thread
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index 914258f..db85fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -17,7 +17,6 @@
package com.android.systemui.classifier;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_MANAGER_ENABLED;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
import android.content.Context;
import android.hardware.SensorManager;
@@ -31,6 +30,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.classifier.brightline.BrightLineFalsingManager;
import com.android.systemui.classifier.brightline.FalsingDataProvider;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingPlugin;
import com.android.systemui.plugins.PluginListener;
@@ -41,7 +41,6 @@
import java.io.PrintWriter;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
/**
@@ -62,7 +61,7 @@
@Inject
FalsingManagerProxy(Context context, PluginManager pluginManager,
- @Named(MAIN_HANDLER_NAME) Handler handler,
+ @MainHandler Handler handler,
ProximitySensor proximitySensor,
DeviceConfigProxy deviceConfig) {
mProximitySensor = proximitySensor;
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/ActivityBinder.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/ActivityBinder.java
rename to packages/SystemUI/src/com/android/systemui/dagger/ActivityBinder.java
index 2c8a672..4be610f 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ActivityBinder.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.systemui.dagger;
import android.app.Activity;
+import com.android.systemui.ForegroundServicesDialog;
import com.android.systemui.tuner.TunerActivity;
import dagger.Binds;
diff --git a/packages/SystemUI/src/com/android/systemui/ComponentBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/ComponentBinder.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/ComponentBinder.java
rename to packages/SystemUI/src/com/android/systemui/dagger/ComponentBinder.java
index 3b35c61..4e4c06e 100644
--- a/packages/SystemUI/src/com/android/systemui/ComponentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ComponentBinder.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.systemui.dagger;
import dagger.Binds;
import dagger.Module;
diff --git a/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java
rename to packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java
index 2cf0f8d..d6d1e41 100644
--- a/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.systemui.dagger;
import android.app.Activity;
import android.app.Service;
+import com.android.systemui.SystemUI;
+
/**
* Interface necessary to make Dagger happy. See {@link ContextComponentResolver}.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
rename to packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
index 9952632..d7822c9 100644
--- a/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.systemui.dagger;
import android.app.Activity;
import android.app.Service;
+import com.android.systemui.SystemUI;
+
import java.util.Map;
import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/DependencyBinder.java
rename to packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index f9f0f1b..9032c6f 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.systemui.dagger;
+import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.appops.AppOpsControllerImpl;
import com.android.systemui.classifier.FalsingManagerProxy;
+import com.android.systemui.doze.DozeHost;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.FalsingManager;
@@ -32,6 +34,7 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -240,5 +243,10 @@
/**
*/
@Binds
- public abstract FalsingManager provideFalsingmanager(FalsingManagerProxy falsingManagerImpl);
+ public abstract FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl);
+
+ /**
+ */
+ @Binds
+ public abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
}
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/DependencyProvider.java
rename to packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 8c1f8ac..87434f3 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,51 +14,35 @@
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.systemui.dagger;
-import static com.android.systemui.Dependency.BG_HANDLER_NAME;
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.IActivityManager;
import android.app.INotificationManager;
-import android.app.IWallpaperManager;
-import android.app.WallpaperManager;
import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.SensorPrivacyManager;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.NightDisplayListener;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
-import android.os.PowerManager;
import android.os.Process;
import android.os.ServiceManager;
-import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.view.IWindowManager;
-import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.systemui.dagger.qualifiers.BgHandler;
+import com.android.systemui.dagger.qualifiers.BgLooper;
+import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.MainLooper;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.plugins.PluginInitializerImpl;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
-import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.statusbar.NavigationBarController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.phone.AutoHideController;
@@ -70,11 +54,7 @@
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.util.leak.LeakDetector;
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
import javax.inject.Named;
-import javax.inject.Qualifier;
import javax.inject.Singleton;
import dagger.Module;
@@ -82,16 +62,14 @@
/**
* Provides dependencies for the root component of sysui injection.
+ *
+ * Only SystemUI owned classes and instances should go in here. Other, framework-owned classes
+ * should go in {@link SystemServicesModule}.
+ *
* See SystemUI/docs/dagger.md
*/
@Module
public class DependencyProvider {
- @Qualifier
- @Documented
- @Retention(RUNTIME)
- public @interface MainResources {
- // TODO: use attribute to get other, non-main resources?
- }
@Singleton
@Provides
@@ -104,7 +82,7 @@
@Singleton
@Provides
- @Named(BG_LOOPER_NAME)
+ @BgLooper
public Looper provideBgLooper() {
HandlerThread thread = new HandlerThread("SysUiBg",
Process.THREAD_PRIORITY_BACKGROUND);
@@ -113,29 +91,34 @@
}
/** Main Looper */
- @Singleton
@Provides
- @Named(MAIN_LOOPER_NAME)
+ @MainLooper
public Looper provideMainLooper() {
return Looper.getMainLooper();
}
@Singleton
@Provides
- @Named(BG_HANDLER_NAME)
- public Handler provideBgHandler(@Named(BG_LOOPER_NAME) Looper bgLooper) {
+ @BgHandler
+ public Handler provideBgHandler(@BgLooper Looper bgLooper) {
return new Handler(bgLooper);
}
@Singleton
@Provides
- @Named(MAIN_HANDLER_NAME)
- public Handler provideMainHandler(@Named(MAIN_LOOPER_NAME) Looper mainLooper) {
+ @MainHandler
+ public Handler provideMainHandler(@MainLooper Looper mainLooper) {
return new Handler(mainLooper);
}
/** */
@Provides
+ public AmbientDisplayConfiguration provideAmbientDispalyConfiguration(Context context) {
+ return new AmbientDisplayConfiguration(context);
+ }
+
+ /** */
+ @Provides
public Handler provideHandler() {
return new Handler();
}
@@ -148,23 +131,10 @@
@Singleton
@Provides
- @Nullable
- public LocalBluetoothManager provideLocalBluetoothController(Context context,
- @Named(BG_HANDLER_NAME) Handler bgHandler) {
- return LocalBluetoothManager.create(context, bgHandler,
- UserHandle.ALL);
- }
-
- @Singleton
- @Provides
- public MetricsLogger provideMetricsLogger() {
- return new MetricsLogger();
- }
-
- @Singleton
- @Provides
- public IWindowManager provideIWindowManager() {
- return WindowManagerGlobal.getWindowManagerService();
+ // Single instance of DisplayMetrics, gets updated by StatusBar, but can be used
+ // anywhere it is needed.
+ public DisplayMetrics provideDisplayMetrics() {
+ return new DisplayMetrics();
}
@Singleton
@@ -184,20 +154,6 @@
@Singleton
@Provides
- // Single instance of DisplayMetrics, gets updated by StatusBar, but can be used
- // anywhere it is needed.
- public DisplayMetrics provideDisplayMetrics() {
- return new DisplayMetrics();
- }
-
- @Singleton
- @Provides
- public SensorPrivacyManager provideSensorPrivacyManager(Context context) {
- return context.getSystemService(SensorPrivacyManager.class);
- }
-
- @Singleton
- @Provides
public LeakDetector provideLeakDetector() {
return LeakDetector.create();
@@ -205,8 +161,14 @@
@Singleton
@Provides
+ public MetricsLogger provideMetricsLogger() {
+ return new MetricsLogger();
+ }
+
+ @Singleton
+ @Provides
public NightDisplayListener provideNightDisplayListener(Context context,
- @Named(BG_HANDLER_NAME) Handler bgHandler) {
+ @BgHandler Handler bgHandler) {
return new NightDisplayListener(context, bgHandler);
}
@@ -219,7 +181,7 @@
@Singleton
@Provides
public NavigationBarController provideNavigationBarController(Context context,
- @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
+ @MainHandler Handler mainHandler) {
return new NavigationBarController(context, mainHandler);
}
@@ -232,7 +194,7 @@
@Singleton
@Provides
public AutoHideController provideAutoHideController(Context context,
- @Named(MAIN_HANDLER_NAME) Handler mainHandler,
+ @MainHandler Handler mainHandler,
NotificationRemoteInputManager notificationRemoteInputManager,
IWindowManager iWindowManager) {
return new AutoHideController(context, mainHandler, notificationRemoteInputManager,
@@ -253,25 +215,12 @@
@Singleton
@Provides
- public PackageManagerWrapper providePackageManagerWrapper() {
- return PackageManagerWrapper.getInstance();
- }
-
- @Singleton
- @Provides
public DeviceProvisionedController provideDeviceProvisionedController(Context context,
- @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
+ @MainHandler Handler mainHandler) {
return new DeviceProvisionedControllerImpl(context, mainHandler);
}
/** */
- @Singleton
- @Provides
- public AlarmManager provideAlarmManager(Context context) {
- return context.getSystemService(AlarmManager.class);
- }
-
- /** */
@Provides
public LockPatternUtils provideLockPatternUtils(Context context) {
return new LockPatternUtils(context);
@@ -279,52 +228,7 @@
/** */
@Provides
- public AmbientDisplayConfiguration provideAmbientDispalyConfiguration(Context context) {
- return new AmbientDisplayConfiguration(context);
- }
-
- /** */
- @Provides
public AlwaysOnDisplayPolicy provideAlwaysOnDisplayPolicy(Context context) {
return new AlwaysOnDisplayPolicy(context);
}
-
- /** */
- @Provides
- public PowerManager providePowerManager(Context context) {
- return context.getSystemService(PowerManager.class);
- }
-
- /** */
- @Provides
- @MainResources
- public Resources provideResources(Context context) {
- return context.getResources();
- }
-
- /** */
- @Provides
- @Nullable
- public IWallpaperManager provideIWallpaperManager() {
- return IWallpaperManager.Stub.asInterface(
- ServiceManager.getService(Context.WALLPAPER_SERVICE));
- }
-
- /** */
- @Provides
- public WallpaperManager providesWallpaperManager(Context context) {
- return (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
- }
-
- /** */
- @Provides
- public WindowManager providesWindowManager(Context context) {
- return context.getSystemService(WindowManager.class);
- }
-
- /** */
- @Provides
- public IActivityManager providesIActivityManager() {
- return ActivityManager.getService();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/ServiceBinder.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/ServiceBinder.java
rename to packages/SystemUI/src/com/android/systemui/dagger/ServiceBinder.java
index c11236e..1f2c0a1 100644
--- a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ServiceBinder.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.systemui.dagger;
import android.app.Service;
+import com.android.systemui.ImageWallpaper;
import com.android.systemui.doze.DozeService;
import com.android.systemui.keyguard.KeyguardService;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
new file mode 100644
index 0000000..fffba8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -0,0 +1,130 @@
+/*
+ * 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.systemui.dagger;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.IActivityManager;
+import android.app.IWallpaperManager;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.view.IWindowManager;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.systemui.dagger.qualifiers.BgHandler;
+import com.android.systemui.dagger.qualifiers.MainResources;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides Non-SystemUI, Framework-Owned instances to the dependency graph.
+ */
+@Module
+public class SystemServicesModule {
+
+ @Singleton
+ @Provides
+ static AlarmManager provideAlarmManager(Context context) {
+ return context.getSystemService(AlarmManager.class);
+ }
+
+ @Singleton
+ @Provides
+ static IActivityManager provideIActivityManager() {
+ return ActivityManager.getService();
+ }
+
+ @Provides
+ @Nullable
+ static IWallpaperManager provideIWallPaperManager() {
+ return IWallpaperManager.Stub.asInterface(
+ ServiceManager.getService(Context.WALLPAPER_SERVICE));
+ }
+
+ @Singleton
+ @Provides
+ static IWindowManager provideIWindowManager() {
+ return WindowManagerGlobal.getWindowManagerService();
+ }
+
+ @Singleton
+ @Provides
+ static LatencyTracker provideLatencyTracker(Context context) {
+ return LatencyTracker.getInstance(context);
+ }
+
+ @SuppressLint("MissingPermission")
+ @Singleton
+ @Provides
+ @Nullable
+ static LocalBluetoothManager provideLocalBluetoothController(Context context,
+ @BgHandler Handler bgHandler) {
+ return LocalBluetoothManager.create(context, bgHandler, UserHandle.ALL);
+ }
+
+ @Singleton
+ @Provides
+ static PackageManagerWrapper providePackageManagerWrapper() {
+ return PackageManagerWrapper.getInstance();
+ }
+
+ /** */
+ @Singleton
+ @Provides
+ static PowerManager providePowerManager(Context context) {
+ return context.getSystemService(PowerManager.class);
+ }
+
+ @Provides
+ @MainResources
+ static Resources provideResources(Context context) {
+ return context.getResources();
+ }
+
+ @Singleton
+ @Provides
+ static SensorPrivacyManager provideSensorPrivacyManager(Context context) {
+ return context.getSystemService(SensorPrivacyManager.class);
+ }
+
+ @Provides
+ static WallpaperManager provideWallpaperManager(Context context) {
+ return (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
+ }
+
+ @Singleton
+ @Provides
+ static WindowManager provideWindowManager(Context context) {
+ return context.getSystemService(WindowManager.class);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
rename to packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index a5a5598..738f539 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.systemui.dagger;
+import com.android.systemui.LatencyTester;
+import com.android.systemui.ScreenDecorations;
+import com.android.systemui.SystemUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.pip.PipUI;
import com.android.systemui.power.PowerUI;
@@ -47,6 +50,12 @@
@ClassKey(KeyguardViewMediator.class)
public abstract SystemUI bindKeyguardViewMediator(KeyguardViewMediator sysui);
+ /** Inject into LatencyTests. */
+ @Binds
+ @IntoMap
+ @ClassKey(LatencyTester.class)
+ public abstract SystemUI bindLatencyTester(LatencyTester sysui);
+
/** Inject into PipUI. */
@Binds
@IntoMap
@@ -65,6 +74,12 @@
@ClassKey(Recents.class)
public abstract SystemUI bindRecents(Recents sysui);
+ /** Inject into ScreenDecorations. */
+ @Binds
+ @IntoMap
+ @ClassKey(ScreenDecorations.class)
+ public abstract SystemUI bindScreenDecorations(ScreenDecorations sysui);
+
/** Inject into VolumeUI. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/SystemUIDefaultModule.java
rename to packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index 176bcbf..c95b50b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.systemui.dagger;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME;
import androidx.annotation.Nullable;
+import com.android.systemui.SystemUI;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.power.EnhancedEstimates;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/SystemUIModule.java
rename to packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 4520a1a6..4e60f19 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.systemui.dagger;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.assist.AssistModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -36,7 +37,9 @@
* A dagger module for injecting components of System UI that are not overridden by the System UI
* implementation.
*/
-@Module(includes = {AssistModule.class, ComponentBinder.class, PeopleHubModule.class})
+@Module(includes = {AssistModule.class,
+ ComponentBinder.class,
+ PeopleHubModule.class})
public abstract class SystemUIModule {
@Singleton
@@ -44,11 +47,13 @@
@Nullable
static KeyguardLiftController provideKeyguardLiftController(Context context,
StatusBarStateController statusBarStateController,
- AsyncSensorManager asyncSensorManager) {
+ AsyncSensorManager asyncSensorManager,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
return null;
}
- return new KeyguardLiftController(statusBarStateController, asyncSensorManager);
+ return new KeyguardLiftController(statusBarStateController, asyncSensorManager,
+ keyguardUpdateMonitor);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
rename to packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
index bcbe672..113c9c8 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
@@ -14,12 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.systemui.dagger;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import android.content.ContentProvider;
+import com.android.systemui.Dependency;
+import com.android.systemui.SystemUIAppComponentFactory;
+import com.android.systemui.SystemUIFactory;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.util.InjectionInflationController;
@@ -36,6 +39,7 @@
@Component(modules = {
DependencyProvider.class,
DependencyBinder.class,
+ SystemServicesModule.class,
SystemUIFactory.ContextHolder.class,
SystemUIModule.class,
SystemUIDefaultModule.class})
diff --git a/core/java/android/app/timedetector/TimeSignal.aidl b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgHandler.java
similarity index 61%
copy from core/java/android/app/timedetector/TimeSignal.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgHandler.java
index d2ec357..bc6b83b 100644
--- a/core/java/android/app/timedetector/TimeSignal.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,6 +14,17 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package com.android.systemui.dagger.qualifiers;
-parcelable TimeSignal;
\ No newline at end of file
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface BgHandler {
+}
diff --git a/core/java/android/app/timedetector/TimeSignal.aidl b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgLooper.java
similarity index 61%
copy from core/java/android/app/timedetector/TimeSignal.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgLooper.java
index d2ec357..2aadda1 100644
--- a/core/java/android/app/timedetector/TimeSignal.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgLooper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,6 +14,17 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package com.android.systemui.dagger.qualifiers;
-parcelable TimeSignal;
\ No newline at end of file
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface BgLooper {
+}
diff --git a/core/java/android/app/timedetector/TimeSignal.aidl b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainHandler.java
similarity index 61%
copy from core/java/android/app/timedetector/TimeSignal.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainHandler.java
index d2ec357..79661fa 100644
--- a/core/java/android/app/timedetector/TimeSignal.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,6 +14,17 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package com.android.systemui.dagger.qualifiers;
-parcelable TimeSignal;
\ No newline at end of file
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface MainHandler {
+}
diff --git a/core/java/android/app/timedetector/TimeSignal.aidl b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainLooper.java
similarity index 61%
copy from core/java/android/app/timedetector/TimeSignal.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainLooper.java
index d2ec357..750d7d7 100644
--- a/core/java/android/app/timedetector/TimeSignal.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainLooper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,6 +14,17 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package com.android.systemui.dagger.qualifiers;
-parcelable TimeSignal;
\ No newline at end of file
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface MainLooper {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ComponentBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainResources.java
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/ComponentBinder.java
copy to packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainResources.java
index 3b35c61..3daeda5 100644
--- a/packages/SystemUI/src/com/android/systemui/ComponentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainResources.java
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.systemui.dagger.qualifiers;
-import dagger.Binds;
-import dagger.Module;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
-/**
- * Dagger Module that collects related sub-modules together.
- */
-@Module(includes = {ActivityBinder.class, ServiceBinder.class, SystemUIBinder.class})
-public abstract class ComponentBinder {
- /** */
- @Binds
- public abstract ContextComponentHelper bindComponentHelper(
- ContextComponentResolver componentHelper);
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface MainResources {
+ // TODO: use attribute to get other, non-main resources?
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index a36ff0d..33f68cf 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -57,6 +57,7 @@
private final ProximitySensor mProximitySensor;
private final DelayedWakeLock.Builder mDelayedWakeLockBuilder;
private final Handler mHandler;
+ private final BiometricUnlockController mBiometricUnlockController;
@Inject
public DozeFactory(FalsingManager falsingManager, DozeLog dozeLog,
@@ -65,7 +66,8 @@
WakefulnessLifecycle wakefulnessLifecycle, KeyguardUpdateMonitor keyguardUpdateMonitor,
DockManager dockManager, @Nullable IWallpaperManager wallpaperManager,
ProximitySensor proximitySensor,
- DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler) {
+ DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
+ BiometricUnlockController biometricUnlockController) {
mFalsingManager = falsingManager;
mDozeLog = dozeLog;
mDozeParameters = dozeParameters;
@@ -79,6 +81,7 @@
mProximitySensor = proximitySensor;
mDelayedWakeLockBuilder = delayedWakeLockBuilder;
mHandler = handler;
+ mBiometricUnlockController = biometricUnlockController;
}
/** Creates a DozeMachine with its parts for {@code dozeService}. */
@@ -107,9 +110,7 @@
createDozeScreenBrightness(dozeService, wrappedService, mAsyncSensorManager, host,
mDozeParameters, mHandler),
new DozeWallpaperState(
- mWallpaperManager,
- getBiometricUnlockController(dozeService),
- mDozeParameters),
+ mWallpaperManager, mBiometricUnlockController, mDozeParameters),
new DozeDockHandler(dozeService, machine, host, config, mHandler, mDockManager),
new DozeAuthRemover(dozeService)
});
@@ -149,10 +150,4 @@
final SystemUIApplication app = (SystemUIApplication) appCandidate;
return app.getComponent(DozeHost.class);
}
-
- public static BiometricUnlockController getBiometricUnlockController(DozeService service) {
- Application appCandidate = service.getApplication();
- final SystemUIApplication app = (SystemUIApplication) appCandidate;
- return app.getComponent(BiometricUnlockController.class);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 1a6bd60..d1047e2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -68,6 +68,12 @@
*/
void prepareForGentleSleep(Runnable onDisplayOffCallback);
+ /**
+ * Cancel pending {@code onDisplayOffCallback} callback.
+ * @see #prepareForGentleSleep(Runnable)
+ */
+ void cancelGentleSleep();
+
void onIgnoreTouchWhilePulsing(boolean ignore);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 95c42fc..e1b4f31 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -71,7 +71,7 @@
@Override
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
int screenState = newState.screenState(mParameters);
- mDozeHost.prepareForGentleSleep(null);
+ mDozeHost.cancelGentleSleep();
if (newState == DozeMachine.State.FINISH) {
// Make sure not to apply the screen state after DozeService was destroyed.
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
index b4cc571..1b4857e 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -22,7 +22,7 @@
import com.android.systemui.ConfigurationChangedReceiver;
import com.android.systemui.Dumpable;
-import com.android.systemui.SystemUIRootComponent;
+import com.android.systemui.dagger.SystemUIRootComponent;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.statusbar.phone.NavigationBarFragment;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 5723afd..c452f64 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -90,7 +90,7 @@
public static final int MESSAGE_UPDATE_ACTIONS = 4;
public static final int MESSAGE_UPDATE_DISMISS_FRACTION = 5;
public static final int MESSAGE_ANIMATION_ENDED = 6;
- public static final int MESSAGE_TOUCH_EVENT = 7;
+ public static final int MESSAGE_POINTER_EVENT = 7;
private static final int INITIAL_DISMISS_DELAY = 3500;
private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
@@ -165,9 +165,9 @@
break;
}
- case MESSAGE_TOUCH_EVENT: {
+ case MESSAGE_POINTER_EVENT: {
final MotionEvent ev = (MotionEvent) msg.obj;
- dispatchTouchEvent(ev);
+ dispatchPointerEvent(ev);
break;
}
}
@@ -219,6 +219,9 @@
updateFromIntent(getIntent());
setTitle(R.string.pip_menu_title);
setDisablePreviewScreenshots(true);
+
+ // Hide without an animation.
+ getWindow().setExitTransition(null);
}
@Override
@@ -269,6 +272,17 @@
}
}
+ /**
+ * Dispatch a pointer event from {@link PipTouchHandler}.
+ */
+ private void dispatchPointerEvent(MotionEvent event) {
+ if (event.isTouchEvent()) {
+ dispatchTouchEvent(event);
+ } else {
+ dispatchGenericMotionEvent(event);
+ }
+ }
+
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!mAllowTouches) {
@@ -288,8 +302,6 @@
public void finish() {
notifyActivityCallback(null);
super.finish();
- // Hide without an animation (the menu should already be invisible at this point)
- overridePendingTransition(0, 0);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 62c59e5..b8e0b81 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -508,12 +508,12 @@
}
/**
- * Handles touch event sent from pip input consumer.
+ * Handles a pointer event sent from pip input consumer.
*/
- void handleTouchEvent(MotionEvent ev) {
+ void handlePointerEvent(MotionEvent ev) {
if (mToActivityMessenger != null) {
Message m = Message.obtain();
- m.what = PipMenuActivity.MESSAGE_TOUCH_EVENT;
+ m.what = PipMenuActivity.MESSAGE_POINTER_EVENT;
m.obj = ev;
try {
mToActivityMessenger.send(m);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 1f36d97..f59b372 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -132,6 +132,7 @@
private boolean mSendingHoverAccessibilityEvents;
private boolean mMovementWithinMinimize;
private boolean mMovementWithinDismiss;
+ private PipAccessibilityInteractionConnection mConnection;
// Touch state
private final PipTouchState mTouchState;
@@ -213,9 +214,10 @@
// Register the listener for input consumer touch events
inputConsumerController.setInputListener(this::handleTouchEvent);
inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
- onRegistrationChanged(inputConsumerController.isRegistered());
mPipBoundsHandler = pipBoundsHandler;
+ mConnection = new PipAccessibilityInteractionConnection(mMotionHelper,
+ this::onAccessibilityShowMenu, mHandler);
}
public void setTouchEnabled(boolean enabled) {
@@ -339,9 +341,7 @@
private void onRegistrationChanged(boolean isRegistered) {
mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered
- ? new PipAccessibilityInteractionConnection(mMotionHelper,
- this::onAccessibilityShowMenu, mHandler) : null);
-
+ ? mConnection : null);
if (!isRegistered && mTouchState.isUserInteracting()) {
// If the input consumer is unregistered while the user is interacting, then we may not
// get the final TOUCH_UP event, so clean up the dismiss target as well
@@ -409,27 +409,15 @@
}
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE: {
- if (mAccessibilityManager.isEnabled() && !mSendingHoverAccessibilityEvents) {
- AccessibilityEvent event = AccessibilityEvent.obtain(
- AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
- event.setImportantForAccessibility(true);
- event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID);
- event.setWindowId(
- AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
- mAccessibilityManager.sendAccessibilityEvent(event);
+ if (!shouldDeliverToMenu && !mSendingHoverAccessibilityEvents) {
+ sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
mSendingHoverAccessibilityEvents = true;
}
break;
}
case MotionEvent.ACTION_HOVER_EXIT: {
- if (mAccessibilityManager.isEnabled() && mSendingHoverAccessibilityEvents) {
- AccessibilityEvent event = AccessibilityEvent.obtain(
- AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
- event.setImportantForAccessibility(true);
- event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID);
- event.setWindowId(
- AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
- mAccessibilityManager.sendAccessibilityEvent(event);
+ if (!shouldDeliverToMenu && mSendingHoverAccessibilityEvents) {
+ sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
mSendingHoverAccessibilityEvents = false;
}
break;
@@ -445,12 +433,25 @@
mMenuController.pokeMenu();
}
- mMenuController.handleTouchEvent(cloneEvent);
+ mMenuController.handlePointerEvent(cloneEvent);
}
return true;
}
+ private void sendAccessibilityHoverEvent(int type) {
+ if (!mAccessibilityManager.isEnabled()) {
+ return;
+ }
+
+ AccessibilityEvent event = AccessibilityEvent.obtain(type);
+ event.setImportantForAccessibility(true);
+ event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID);
+ event.setWindowId(
+ AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+
/**
* Updates the appearance of the menu and scrim on top of the PiP while dismissing.
*/
@@ -523,6 +524,10 @@
* Sets the menu visibility.
*/
private void setMenuState(int menuState, boolean resize) {
+ if (mMenuState == menuState && !resize) {
+ return;
+ }
+
if (menuState == MENU_STATE_FULL && mMenuState != MENU_STATE_FULL) {
// Save the current snap fraction and if we do not drag or move the PiP, then
// we store back to this snap fraction. Otherwise, we'll reset the snap
@@ -571,6 +576,9 @@
}
mMenuState = menuState;
updateMovementBounds(menuState);
+ // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip
+ // as well, or it can't handle a11y focus and pip menu can't perform any action.
+ onRegistrationChanged(menuState == MENU_STATE_NONE);
if (menuState != MENU_STATE_CLOSE) {
MetricsLoggerWrapper.logPictureInPictureMenuVisible(mContext, menuState == MENU_STATE_FULL);
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 98f0b2a..f60d9db 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -55,7 +55,11 @@
import java.util.concurrent.Future;
import javax.inject.Inject;
+import javax.inject.Singleton;
+import dagger.Lazy;
+
+@Singleton
public class PowerUI extends SystemUI {
static final String TAG = "PowerUI";
@@ -101,11 +105,14 @@
private IThermalEventListener mSkinThermalEventListener;
private IThermalEventListener mUsbThermalEventListener;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final Lazy<StatusBar> mStatusBarLazy;
@Inject
- public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher) {
+ public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher,
+ Lazy<StatusBar> statusBarLazy) {
super(context);
mBroadcastDispatcher = broadcastDispatcher;
+ mStatusBarLazy = statusBarLazy;
}
public void start() {
@@ -663,8 +670,7 @@
int status = temp.getStatus();
if (status >= Temperature.THROTTLING_EMERGENCY) {
- StatusBar statusBar = getComponent(StatusBar.class);
- if (statusBar != null && !statusBar.isDeviceInVrMode()) {
+ if (!mStatusBarLazy.get().isDeviceInVrMode()) {
mWarnings.showHighTemperatureWarning();
Slog.d(TAG, "SkinThermalEventListener: notifyThrottling was called "
+ ", current skin status = " + status
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index 631b8b7..22fb4c0 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -22,25 +22,20 @@
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
-import android.os.Handler
-import android.os.Looper
-import android.os.Message
-import android.os.UserHandle
-import android.os.UserManager
+import android.os.*
import android.provider.DeviceConfig
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
-import com.android.systemui.Dependency.BG_HANDLER_NAME
-import com.android.systemui.Dependency.MAIN_HANDLER_NAME
+import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.appops.AppOpItem
import com.android.systemui.appops.AppOpsController
-import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.BgHandler
+import com.android.systemui.dagger.qualifiers.MainHandler
import java.io.FileDescriptor
import java.io.PrintWriter
import java.lang.ref.WeakReference
import javax.inject.Inject
-import javax.inject.Named
import javax.inject.Singleton
fun isPermissionsHubEnabled() = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
@@ -50,8 +45,8 @@
class PrivacyItemController @Inject constructor(
val context: Context,
private val appOpsController: AppOpsController,
- @Named(MAIN_HANDLER_NAME) private val uiHandler: Handler,
- @Named(BG_HANDLER_NAME) private val bgHandler: Handler
+ @MainHandler private val uiHandler: Handler,
+ @BgHandler private val bgHandler: Handler
) : Dumpable {
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
new file mode 100644
index 0000000..f710f7f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.systemui.qs
+
+import android.content.Context
+import android.content.res.Configuration
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.R
+import com.android.systemui.qs.TileLayout.exactly
+
+class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTileLayout {
+
+ protected val mRecords = ArrayList<QSPanel.TileRecord>()
+ private var _listening = false
+ private var smallTileSize = 0
+ private val twoLineHeight
+ get() = smallTileSize * 2 + cellMarginVertical
+ private var cellMarginHorizontal = 0
+ private var cellMarginVertical = 0
+
+ init {
+ isFocusableInTouchMode = true
+ clipChildren = false
+ clipToPadding = false
+
+ updateResources()
+ }
+
+ override fun addTile(tile: QSPanel.TileRecord) {
+ mRecords.add(tile)
+ tile.tile.setListening(this, _listening)
+ addTileView(tile)
+ }
+
+ protected fun addTileView(tile: QSPanel.TileRecord) {
+ addView(tile.tileView)
+ }
+
+ override fun removeTile(tile: QSPanel.TileRecord) {
+ mRecords.remove(tile)
+ tile.tile.setListening(this, false)
+ removeView(tile.tileView)
+ }
+
+ override fun removeAllViews() {
+ mRecords.forEach { it.tile.setListening(this, false) }
+ mRecords.clear()
+ super.removeAllViews()
+ }
+
+ override fun getOffsetTop(tile: QSPanel.TileRecord?) = top
+
+ override fun updateResources(): Boolean {
+ with(mContext.resources) {
+ smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size)
+ cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal)
+ cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin)
+ }
+ requestLayout()
+ return false
+ }
+
+ override fun setListening(listening: Boolean) {
+ if (_listening == listening) return
+ _listening = listening
+ for (record in mRecords) {
+ record.tile.setListening(this, listening)
+ }
+ }
+
+ override fun getNumVisibleTiles() = mRecords.size
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ updateResources()
+ }
+
+ override fun onFinishInflate() {
+ updateResources()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ var previousView: View = this
+ var tiles = 0
+
+ mRecords.forEach {
+ val tileView = it.tileView
+ if (tileView.visibility != View.GONE) {
+ tileView.updateAccessibilityOrder(previousView)
+ previousView = tileView
+ tiles++
+ tileView.measure(exactly(smallTileSize), exactly(smallTileSize))
+ }
+ }
+
+ val height = twoLineHeight
+ val columns = tiles / 2
+ val width = paddingStart + paddingEnd +
+ columns * smallTileSize +
+ (columns - 1) * cellMarginHorizontal
+ setMeasuredDimension(width, height)
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ val tiles = mRecords.filter { it.tileView.visibility != View.GONE }
+ tiles.forEachIndexed {
+ index, tile ->
+ val column = index % (tiles.size / 2)
+ val left = getLeftForColumn(column)
+ val top = if (index < tiles.size / 2) 0 else getTopBottomRow()
+ tile.tileView.layout(left, top, left + smallTileSize, top + smallTileSize)
+ }
+ }
+
+ private fun getLeftForColumn(column: Int) = column * (smallTileSize + cellMarginHorizontal)
+
+ private fun getTopBottomRow() = smallTileSize + cellMarginVertical
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 9221b68..a267bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -14,6 +14,7 @@
package com.android.systemui.qs;
+import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
@@ -267,6 +268,17 @@
mAllViews.add(tileView);
count++;
}
+
+
+ int flag = Settings.System.getInt(mQsPanel.getContext().getContentResolver(),
+ "qs_media_player", 0);
+ if (flag == 1) {
+ View qsMediaView = mQsPanel.getMediaPanel();
+ View qqsMediaView = mQuickQsPanel.getMediaPlayer().getView();
+ translationXBuilder.addFloat(qsMediaView, "alpha", 0, 1);
+ translationXBuilder.addFloat(qqsMediaView, "alpha", 1, 0);
+ }
+
if (mAllowFancy) {
// Make brightness appear static position and alpha in through second half.
View brightness = mQsPanel.getBrightnessView();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
index b9f3a7f..5742787 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
@@ -17,9 +17,7 @@
package com.android.systemui.qs;
import static com.android.systemui.Dependency.BG_HANDLER;
-import static com.android.systemui.Dependency.BG_HANDLER_NAME;
import static com.android.systemui.Dependency.MAIN_LOOPER;
-import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
import android.annotation.MainThread;
@@ -41,6 +39,8 @@
import com.android.keyguard.CarrierTextController;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.BgHandler;
+import com.android.systemui.dagger.qualifiers.MainLooper;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -77,8 +77,8 @@
@Inject
public QSCarrierGroup(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
NetworkController networkController, ActivityStarter activityStarter,
- @Named(BG_HANDLER_NAME) Handler handler,
- @Named(MAIN_LOOPER_NAME) Looper looper) {
+ @BgHandler Handler handler,
+ @MainLooper Looper looper) {
super(context, attrs);
mNetworkController = networkController;
mActivityStarter = activityStarter;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
new file mode 100644
index 0000000..af418f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -0,0 +1,301 @@
+/*
+ * 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.systemui.qs;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.Icon;
+import android.graphics.drawable.RippleDrawable;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+
+import com.android.settingslib.media.MediaOutputSliceConstants;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.MediaTransferManager;
+
+/**
+ * Single media player for carousel in QSPanel
+ */
+public class QSMediaPlayer {
+
+ private static final String TAG = "QSMediaPlayer";
+
+ private Context mContext;
+ private LinearLayout mMediaNotifView;
+ private MediaSession.Token mToken;
+ private MediaController mController;
+ private int mWidth;
+ private int mHeight;
+
+ /**
+ *
+ * @param context
+ * @param parent
+ * @param width
+ * @param height
+ */
+ public QSMediaPlayer(Context context, ViewGroup parent, int width, int height) {
+ mContext = context;
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ mMediaNotifView = (LinearLayout) inflater.inflate(R.layout.qs_media_panel, parent, false);
+
+ mWidth = width;
+ mHeight = height;
+ }
+
+ public View getView() {
+ return mMediaNotifView;
+ }
+
+ /**
+ *
+ * @param token token for this media session
+ * @param icon app notification icon
+ * @param iconColor foreground color (for text, icons)
+ * @param bgColor background color
+ * @param actionsContainer a LinearLayout containing the media action buttons
+ * @param notif
+ */
+ public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
+ View actionsContainer, Notification notif) {
+ Log.d(TAG, "got media session: " + token);
+ mToken = token;
+ mController = new MediaController(mContext, token);
+ MediaMetadata mMediaMetadata = mController.getMetadata();
+ Notification.Builder builder = Notification.Builder.recoverBuilder(mContext, notif);
+
+ // Album art
+ addAlbumArtBackground(mMediaMetadata, bgColor, mWidth, mHeight);
+
+ // Reuse notification header instead of reimplementing everything
+ RemoteViews headerRemoteView = builder.makeNotificationHeader();
+ LinearLayout headerView = mMediaNotifView.findViewById(R.id.header);
+ View result = headerRemoteView.apply(mContext, headerView);
+ result.setPadding(0, 0, 0, 0);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, 75);
+ result.setLayoutParams(lp);
+ headerView.removeAllViews();
+ headerView.addView(result);
+
+ View seamless = headerView.findViewById(com.android.internal.R.id.media_seamless);
+ seamless.setVisibility(View.VISIBLE);
+
+ // App icon
+ ImageView appIcon = headerView.findViewById(com.android.internal.R.id.icon);
+ Drawable iconDrawable = icon.loadDrawable(mContext);
+ iconDrawable.setTint(iconColor);
+ appIcon.setImageDrawable(iconDrawable);
+
+ // App title
+ TextView appName = headerView.findViewById(com.android.internal.R.id.app_name_text);
+ String appNameString = builder.loadHeaderAppName();
+ appName.setText(appNameString);
+ appName.setTextColor(iconColor);
+
+ // Action
+ mMediaNotifView.setOnClickListener(v -> {
+ try {
+ notif.contentIntent.send();
+ // Also close shade
+ mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Pending intent was canceled");
+ e.printStackTrace();
+ }
+ });
+
+ // Separator
+ TextView separator = headerView.findViewById(com.android.internal.R.id.header_text_divider);
+ separator.setTextColor(iconColor);
+
+ // Album name
+ TextView albumName = headerView.findViewById(com.android.internal.R.id.header_text);
+ String albumString = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ALBUM);
+ if (!albumString.isEmpty()) {
+ albumName.setText(albumString);
+ albumName.setTextColor(iconColor);
+ albumName.setVisibility(View.VISIBLE);
+ separator.setVisibility(View.VISIBLE);
+ } else {
+ albumName.setVisibility(View.GONE);
+ separator.setVisibility(View.GONE);
+ }
+
+ // Transfer chip
+ MediaTransferManager mediaTransferManager = new MediaTransferManager(mContext);
+ View transferBackgroundView = headerView.findViewById(
+ com.android.internal.R.id.media_seamless);
+ LinearLayout viewLayout = (LinearLayout) transferBackgroundView;
+ RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
+ GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
+ rect.setStroke(2, iconColor);
+ rect.setColor(bgColor);
+ ImageView transferIcon = headerView.findViewById(
+ com.android.internal.R.id.media_seamless_image);
+ transferIcon.setBackgroundColor(bgColor);
+ transferIcon.setImageTintList(ColorStateList.valueOf(iconColor));
+ TextView transferText = headerView.findViewById(
+ com.android.internal.R.id.media_seamless_text);
+ transferText.setTextColor(iconColor);
+
+ ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
+ transferBackgroundView.setOnClickListener(v -> {
+ final Intent intent = new Intent()
+ .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT);
+ mActivityStarter.startActivity(intent, false, true /* dismissShade */,
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ });
+
+ // Artist name
+ TextView artistText = mMediaNotifView.findViewById(R.id.header_title);
+ String artistName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+ artistText.setText(artistName);
+ artistText.setTextColor(iconColor);
+
+ // Song name
+ TextView titleText = mMediaNotifView.findViewById(R.id.header_text);
+ String songName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+ titleText.setText(songName);
+ titleText.setTextColor(iconColor);
+
+ // Media controls
+ LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
+ final int[] actionIds = {
+ R.id.action0,
+ R.id.action1,
+ R.id.action2,
+ R.id.action3,
+ R.id.action4
+ };
+ final int[] notifActionIds = {
+ com.android.internal.R.id.action0,
+ com.android.internal.R.id.action1,
+ com.android.internal.R.id.action2,
+ com.android.internal.R.id.action3,
+ com.android.internal.R.id.action4
+ };
+ for (int i = 0; i < parentActionsLayout.getChildCount() && i < actionIds.length; i++) {
+ ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
+ ImageButton thatBtn = parentActionsLayout.findViewById(notifActionIds[i]);
+ if (thatBtn == null || thatBtn.getDrawable() == null) {
+ thisBtn.setVisibility(View.GONE);
+ continue;
+ }
+
+ Drawable thatIcon = thatBtn.getDrawable();
+ thisBtn.setImageDrawable(thatIcon.mutate());
+ thisBtn.setVisibility(View.VISIBLE);
+ thisBtn.setOnClickListener(v -> {
+ Log.d(TAG, "clicking on other button");
+ thatBtn.performClick();
+ });
+ }
+ }
+
+ public MediaSession.Token getMediaSessionToken() {
+ return mToken;
+ }
+
+ public String getMediaPlayerPackage() {
+ return mController.getPackageName();
+ }
+
+ /**
+ * Check whether the media controlled by this player is currently playing
+ * @return whether it is playing, or false if no controller information
+ */
+ public boolean isPlaying() {
+ if (mController == null) {
+ return false;
+ }
+
+ PlaybackState state = mController.getPlaybackState();
+ if (state == null) {
+ return false;
+ }
+
+ return (state.getState() == PlaybackState.STATE_PLAYING);
+ }
+
+ private void addAlbumArtBackground(MediaMetadata metadata, int bgColor, int width, int height) {
+ Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+ if (albumArt != null) {
+
+ Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
+ Bitmap scaled = scaleBitmap(original, width, height);
+ Canvas canvas = new Canvas(scaled);
+
+ // Add translucent layer over album art to improve contrast
+ Paint p = new Paint();
+ p.setStyle(Paint.Style.FILL);
+ p.setColor(bgColor);
+ p.setAlpha(200);
+ canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), p);
+
+ RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create(
+ mContext.getResources(), scaled);
+ roundedDrawable.setCornerRadius(20);
+
+ mMediaNotifView.setBackground(roundedDrawable);
+ } else {
+ Log.e(TAG, "No album art available");
+ }
+ }
+
+ private Bitmap scaleBitmap(Bitmap original, int width, int height) {
+ Bitmap cropped = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(cropped);
+
+ float scale = (float) cropped.getWidth() / (float) original.getWidth();
+ float dy = (cropped.getHeight() - original.getHeight() * scale) / 2.0f;
+ Matrix transformation = new Matrix();
+ transformation.postTranslate(0, dy);
+ transformation.preScale(scale, scale);
+
+ Paint paint = new Paint();
+ paint.setFilterBitmap(true);
+ canvas.drawBitmap(original, transformation, paint);
+
+ return cropped;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 2e24403..2060059 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -24,16 +24,22 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.media.session.MediaSession;
import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
import android.service.quicksettings.Tile;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
+import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import com.android.internal.logging.MetricsLogger;
@@ -82,6 +88,9 @@
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private final QSTileRevealController mQsTileRevealController;
+ private final LinearLayout mMediaCarousel;
+ private final ArrayList<QSMediaPlayer> mMediaPlayers = new ArrayList<>();
+
protected boolean mExpanded;
protected boolean mListening;
@@ -140,6 +149,27 @@
addDivider();
+ // Add media carousel
+ int flag = Settings.System.getInt(context.getContentResolver(), "qs_media_player", 0);
+ if (flag == 1) {
+ HorizontalScrollView mediaScrollView = new HorizontalScrollView(mContext);
+ mediaScrollView.setHorizontalScrollBarEnabled(false);
+ int playerHeight = (int) getResources().getDimension(R.dimen.qs_media_height);
+ int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
+ LayoutParams lpView = new LayoutParams(LayoutParams.MATCH_PARENT, playerHeight);
+ lpView.setMarginStart(padding);
+ lpView.setMarginEnd(padding);
+ addView(mediaScrollView, lpView);
+
+ LayoutParams lpCarousel = new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT);
+ mMediaCarousel = new LinearLayout(mContext);
+ mMediaCarousel.setOrientation(LinearLayout.HORIZONTAL);
+ mediaScrollView.addView(mMediaCarousel, lpCarousel);
+ } else {
+ mMediaCarousel = null;
+ }
+
mFooter = new QSSecurityFooter(this, context);
addView(mFooter.getView());
@@ -159,6 +189,72 @@
}
+ /**
+ * Add or update a player for the associated media session
+ * @param token
+ * @param icon
+ * @param iconColor
+ * @param bgColor
+ * @param actionsContainer
+ * @param notif
+ */
+ public void addMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
+ View actionsContainer, StatusBarNotification notif) {
+ int flag = Settings.System.getInt(mContext.getContentResolver(), "qs_media_player", 0);
+ if (flag != 1) {
+ // Shouldn't happen, but just in case
+ Log.e(TAG, "Tried to add media session without player!");
+ return;
+ }
+ QSMediaPlayer player = null;
+ String packageName = notif.getPackageName();
+ for (QSMediaPlayer p : mMediaPlayers) {
+ if (p.getMediaSessionToken().equals(token)) {
+ Log.d(TAG, "a player for this session already exists");
+ player = p;
+ break;
+ }
+
+ if (packageName.equals(p.getMediaPlayerPackage())) {
+ Log.d(TAG, "found an old session for this app");
+ player = p;
+ break;
+ }
+ }
+
+ int playerHeight = (int) getResources().getDimension(R.dimen.qs_media_height);
+ int playerWidth = (int) getResources().getDimension(R.dimen.qs_media_width);
+ int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
+ LayoutParams lp = new LayoutParams(playerWidth, ViewGroup.LayoutParams.MATCH_PARENT);
+ lp.setMarginStart(padding);
+ lp.setMarginEnd(padding);
+
+ if (player == null) {
+ Log.d(TAG, "creating new player");
+
+ player = new QSMediaPlayer(mContext, this, playerWidth, playerHeight);
+
+ if (player.isPlaying()) {
+ mMediaCarousel.addView(player.getView(), 0, lp); // add in front
+ } else {
+ mMediaCarousel.addView(player.getView(), lp); // add at end
+ }
+ } else if (player.isPlaying()) {
+ // move it to the front
+ mMediaCarousel.removeView(player.getView());
+ mMediaCarousel.addView(player.getView(), 0, lp);
+ }
+
+ Log.d(TAG, "setting player session");
+ player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer,
+ notif.getNotification());
+ mMediaPlayers.add(player);
+ }
+
+ protected View getMediaPanel() {
+ return mMediaCarousel;
+ }
+
protected void addDivider() {
mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false);
mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 1e763cf..b395c3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -30,11 +30,12 @@
import android.text.TextUtils;
import android.util.Log;
-import com.android.systemui.Dependency;
import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.dagger.qualifiers.BgLooper;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
@@ -61,7 +62,6 @@
import java.util.function.Predicate;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
@@ -94,8 +94,8 @@
public QSTileHost(Context context,
StatusBarIconController iconController,
QSFactoryImpl defaultFactory,
- @Named(Dependency.MAIN_HANDLER_NAME) Handler mainHandler,
- @Named(Dependency.BG_LOOPER_NAME) Looper bgLooper,
+ @MainHandler Handler mainHandler,
+ @BgLooper Looper bgLooper,
PluginManager pluginManager,
TunerService tunerService,
Provider<AutoTileManager> autoTiles,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
new file mode 100644
index 0000000..ae66cd5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -0,0 +1,203 @@
+/*
+ * 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.systemui.qs;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+
+import com.android.systemui.R;
+
+/**
+ * QQS mini media player
+ */
+public class QuickQSMediaPlayer {
+
+ private static final String TAG = "QQSMediaPlayer";
+
+ private Context mContext;
+ private LinearLayout mMediaNotifView;
+ private MediaSession.Token mToken;
+ private MediaController mController;
+
+ /**
+ *
+ * @param context
+ * @param parent
+ */
+ public QuickQSMediaPlayer(Context context, ViewGroup parent) {
+ mContext = context;
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ mMediaNotifView = (LinearLayout) inflater.inflate(R.layout.qqs_media_panel, parent, false);
+ }
+
+ public View getView() {
+ return mMediaNotifView;
+ }
+
+ /**
+ *
+ * @param token token for this media session
+ * @param icon app notification icon
+ * @param iconColor foreground color (for text, icons)
+ * @param bgColor background color
+ * @param actionsContainer a LinearLayout containing the media action buttons
+ */
+ public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
+ View actionsContainer) {
+ Log.d(TAG, "Setting media session: " + token);
+ mToken = token;
+ mController = new MediaController(mContext, token);
+ MediaMetadata mMediaMetadata = mController.getMetadata();
+
+ // Album art
+ addAlbumArtBackground(mMediaMetadata, bgColor);
+
+ // App icon
+ ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
+ Drawable iconDrawable = icon.loadDrawable(mContext);
+ iconDrawable.setTint(iconColor);
+ appIcon.setImageDrawable(iconDrawable);
+
+ // Artist name
+ TextView appText = mMediaNotifView.findViewById(R.id.header_title);
+ String artistName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+ appText.setText(artistName);
+ appText.setTextColor(iconColor);
+
+ // Song name
+ TextView titleText = mMediaNotifView.findViewById(R.id.header_text);
+ String songName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+ titleText.setText(songName);
+ titleText.setTextColor(iconColor);
+
+ // Action buttons
+ LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
+ final int[] actionIds = {R.id.action0, R.id.action1, R.id.action2};
+
+ // TODO some apps choose different buttons to show in compact mode
+ final int[] notifActionIds = {
+ com.android.internal.R.id.action1,
+ com.android.internal.R.id.action2,
+ com.android.internal.R.id.action3
+ };
+ for (int i = 0; i < parentActionsLayout.getChildCount() && i < actionIds.length; i++) {
+ ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
+ ImageButton thatBtn = parentActionsLayout.findViewById(notifActionIds[i]);
+ if (thatBtn == null || thatBtn.getDrawable() == null) {
+ thisBtn.setVisibility(View.GONE);
+ continue;
+ }
+
+ Drawable thatIcon = thatBtn.getDrawable();
+ thisBtn.setImageDrawable(thatIcon.mutate());
+ thisBtn.setVisibility(View.VISIBLE);
+
+ thisBtn.setOnClickListener(v -> {
+ Log.d(TAG, "clicking on other button");
+ thatBtn.performClick();
+ });
+ }
+ }
+
+ public MediaSession.Token getMediaSessionToken() {
+ return mToken;
+ }
+
+ /**
+ * Check whether the media controlled by this player is currently playing
+ * @return whether it is playing, or false if no controller information
+ */
+ public boolean isPlaying() {
+ if (mController == null) {
+ return false;
+ }
+
+ PlaybackState state = mController.getPlaybackState();
+ if (state == null) {
+ return false;
+ }
+
+ return (state.getState() == PlaybackState.STATE_PLAYING);
+ }
+
+ private void addAlbumArtBackground(MediaMetadata metadata, int bgColor) {
+ Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+ if (albumArt != null) {
+ Rect bounds = new Rect();
+ mMediaNotifView.getBoundsOnScreen(bounds);
+ int width = bounds.width();
+ int height = bounds.height();
+
+ Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
+ Bitmap scaled = scaleBitmap(original, width, height);
+ Canvas canvas = new Canvas(scaled);
+
+ // Add translucent layer over album art to improve contrast
+ Paint p = new Paint();
+ p.setStyle(Paint.Style.FILL);
+ p.setColor(bgColor);
+ p.setAlpha(200);
+ canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), p);
+
+ RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create(
+ mContext.getResources(), scaled);
+ roundedDrawable.setCornerRadius(20);
+
+ mMediaNotifView.setBackground(roundedDrawable);
+ } else {
+ Log.e(TAG, "No album art available");
+ }
+ }
+
+ private Bitmap scaleBitmap(Bitmap original, int width, int height) {
+ Bitmap cropped = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(cropped);
+
+ float scale = (float) cropped.getWidth() / (float) original.getWidth();
+ float dy = (cropped.getHeight() - original.getHeight() * scale) / 2.0f;
+ Matrix transformation = new Matrix();
+ transformation.postTranslate(0, dy);
+ transformation.preScale(scale, scale);
+
+ Paint paint = new Paint();
+ paint.setFilterBitmap(true);
+ canvas.drawBitmap(original, transformation, paint);
+
+ return cropped;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 85aafa0..dcd4633 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
@@ -55,6 +56,7 @@
private boolean mDisabledByPolicy;
private int mMaxTiles;
protected QSPanel mFullPanel;
+ private QuickQSMediaPlayer mMediaPlayer;
@Inject
public QuickQSPanel(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
@@ -69,11 +71,43 @@
}
removeView((View) mTileLayout);
}
- sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
- mTileLayout = new HeaderTileLayout(context);
- mTileLayout.setListening(mListening);
- addView((View) mTileLayout, 0 /* Between brightness and footer */);
- super.setPadding(0, 0, 0, 0);
+
+ int flag = Settings.System.getInt(context.getContentResolver(), "qs_media_player", 0);
+ if (flag == 1) {
+ LinearLayout mHorizontalLinearLayout = new LinearLayout(mContext);
+ mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
+ mHorizontalLinearLayout.setClipChildren(false);
+ mHorizontalLinearLayout.setClipToPadding(false);
+
+ LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
+
+ mTileLayout = new DoubleLineTileLayout(context);
+ lp.setMarginEnd(10);
+ lp.setMarginStart(0);
+ mHorizontalLinearLayout.addView((View) mTileLayout, lp);
+
+ mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout);
+
+ lp.setMarginEnd(0);
+ lp.setMarginStart(10);
+ mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp);
+
+ sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
+
+ mTileLayout.setListening(mListening);
+ addView(mHorizontalLinearLayout, 0 /* Between brightness and footer */);
+ super.setPadding(0, 0, 0, 0);
+ } else {
+ sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
+ mTileLayout = new HeaderTileLayout(context);
+ mTileLayout.setListening(mListening);
+ addView((View) mTileLayout, 0 /* Between brightness and footer */);
+ super.setPadding(0, 0, 0, 0);
+ }
+ }
+
+ public QuickQSMediaPlayer getMediaPlayer() {
+ return mMediaPlayer;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 4013586..592e388 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -392,9 +392,15 @@
mSystemIconsView.setLayoutParams(mSystemIconsView.getLayoutParams());
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+
+ int flag = Settings.System.getInt(mContext.getContentResolver(), "qs_media_player", 0);
if (mQsDisabled) {
lp.height = resources.getDimensionPixelSize(
com.android.internal.R.dimen.quick_qs_offset_height);
+ } else if (flag == 1) {
+ lp.height = Math.max(getMinimumHeight(),
+ resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.quick_qs_total_height_with_media));
} else {
lp.height = Math.max(getMinimumHeight(),
resources.getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index c9dc08e..34f5437 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -261,9 +261,9 @@
default void showAuthenticationDialog(Bundle bundle,
IBiometricServiceReceiverInternal receiver, int biometricModality,
boolean requireConfirmation, int userId, String opPackageName) { }
- default void onBiometricAuthenticated(boolean authenticated, String failureReason) { }
+ default void onBiometricAuthenticated() { }
default void onBiometricHelp(String message) { }
- default void onBiometricError(int errorCode, String error) { }
+ default void onBiometricError(int modality, int error, int vendorCode) { }
default void hideAuthenticationDialog() { }
/**
@@ -792,12 +792,9 @@
}
@Override
- public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
+ public void onBiometricAuthenticated() {
synchronized (mLock) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = authenticated;
- args.arg2 = failureReason;
- mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget();
+ mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
}
}
@@ -809,9 +806,13 @@
}
@Override
- public void onBiometricError(int errorCode, String error) {
+ public void onBiometricError(int modality, int error, int vendorCode) {
synchronized (mLock) {
- mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, errorCode, 0, error).sendToTarget();
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = modality;
+ args.argi2 = error;
+ args.argi3 = vendorCode;
+ mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, args).sendToTarget();
}
}
@@ -1098,13 +1099,9 @@
break;
}
case MSG_BIOMETRIC_AUTHENTICATED: {
- SomeArgs someArgs = (SomeArgs) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onBiometricAuthenticated(
- (boolean) someArgs.arg1 /* authenticated */,
- (String) someArgs.arg2 /* failureReason */);
+ mCallbacks.get(i).onBiometricAuthenticated();
}
- someArgs.recycle();
break;
}
case MSG_BIOMETRIC_HELP:
@@ -1113,9 +1110,15 @@
}
break;
case MSG_BIOMETRIC_ERROR:
+ SomeArgs someArgs = (SomeArgs) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onBiometricError(msg.arg1, (String) msg.obj);
+ mCallbacks.get(i).onBiometricError(
+ someArgs.argi1 /* modality */,
+ someArgs.argi2 /* error */,
+ someArgs.argi3 /* vendorCode */
+ );
}
+ someArgs.recycle();
break;
case MSG_BIOMETRIC_HIDE:
for (int i = 0; i < mCallbacks.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 0679595..341c49a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -16,18 +16,17 @@
package com.android.systemui.statusbar;
-import static com.android.systemui.Dependency.BG_HANDLER_NAME;
-
import android.annotation.NonNull;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.provider.DeviceConfig;
import android.util.ArrayMap;
+import com.android.systemui.dagger.qualifiers.BgHandler;
+
import java.util.Map;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
/**
@@ -50,8 +49,7 @@
private final Map<String, Boolean> mCachedDeviceConfigFlags = new ArrayMap<>();
@Inject
- public FeatureFlags(
- @Named(BG_HANDLER_NAME) Handler bgHandler) {
+ public FeatureFlags(@BgHandler Handler bgHandler) {
DeviceConfig.addOnPropertiesChangedListener(
"systemui",
new HandlerExecutor(bgHandler),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index 275475d6..1f38904 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
@@ -18,7 +18,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
import static com.android.systemui.SysUiServiceProvider.getComponent;
import android.content.Context;
@@ -38,6 +37,7 @@
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.systemui.Dependency;
import com.android.systemui.assist.AssistHandleViewController;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.phone.AutoHideController;
@@ -48,7 +48,6 @@
import com.android.systemui.statusbar.policy.BatteryController;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
@@ -67,7 +66,7 @@
SparseArray<NavigationBarFragment> mNavigationBars = new SparseArray<>();
@Inject
- public NavigationBarController(Context context, @Named(MAIN_HANDLER_NAME) Handler handler) {
+ public NavigationBarController(Context context, @MainHandler Handler handler) {
mContext = context;
mHandler = handler;
mDisplayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 23968d5..c838ac5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -17,8 +17,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -52,6 +50,7 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -69,7 +68,6 @@
import java.util.Set;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Lazy;
@@ -262,7 +260,7 @@
NotificationEntryManager notificationEntryManager,
Lazy<ShadeController> shadeController,
StatusBarStateController statusBarStateController,
- @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
+ @MainHandler Handler mainHandler) {
mContext = context;
mLockscreenUserManager = lockscreenUserManager;
mSmartReplyController = smartReplyController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index c2bb5b7..d6b87af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -16,19 +16,19 @@
package com.android.systemui.statusbar;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -47,7 +47,6 @@
import java.util.Stack;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Lazy;
@@ -88,6 +87,7 @@
private final BubbleController mBubbleController;
private final DynamicPrivacyController mDynamicPrivacyController;
private final KeyguardBypassController mBypassController;
+ private final Context mContext;
private NotificationPresenter mPresenter;
private NotificationListContainer mListContainer;
@@ -99,8 +99,7 @@
private boolean mIsHandleDynamicPrivacyChangeScheduled;
@Inject
- public NotificationViewHierarchyManager(Context context,
- @Named(MAIN_HANDLER_NAME) Handler mainHandler,
+ public NotificationViewHierarchyManager(Context context, @MainHandler Handler mainHandler,
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotificationGroupManager groupManager,
VisualStabilityManager visualStabilityManager,
@@ -110,6 +109,7 @@
KeyguardBypassController bypassController,
BubbleController bubbleController,
DynamicPrivacyController privacyController) {
+ mContext = context;
mHandler = mainHandler;
mLockscreenUserManager = notificationLockscreenUserManager;
mBypassController = bypassController;
@@ -146,7 +146,11 @@
final int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
NotificationEntry ent = activeNotifications.get(i);
+ int flag = Settings.System.getInt(mContext.getContentResolver(),
+ "qs_media_player", 0);
+ boolean hideMedia = (flag == 1);
if (ent.isRowDismissed() || ent.isRowRemoved()
+ || (ent.isMediaNotification() && hideMedia)
|| mBubbleController.isBubbleNotificationSuppressedFromShade(ent.getKey())) {
// we don't want to update removed notifications because they could
// temporarily become children if they were isolated before.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 6e464f4..b4dc538 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -138,6 +138,14 @@
mNotificationEntryListeners.add(listener);
}
+ /**
+ * Removes a {@link NotificationEntryListener} previously registered via
+ * {@link #addNotificationEntryListener(NotificationEntryListener)}.
+ */
+ public void removeNotificationEntryListener(NotificationEntryListener listener) {
+ mNotificationEntryListeners.remove(listener);
+ }
+
/** Sets the {@link NotificationRemoveInterceptor}. */
public void setNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) {
mRemoveInterceptor = interceptor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index 6fe4abe..1daf484 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
import android.os.Handler;
import android.os.SystemClock;
import android.view.View;
@@ -25,6 +23,7 @@
import androidx.collection.ArraySet;
import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -35,7 +34,6 @@
import java.util.ArrayList;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
/**
@@ -64,8 +62,7 @@
@Inject
public VisualStabilityManager(
- NotificationEntryManager notificationEntryManager,
- @Named(MAIN_HANDLER_NAME) Handler handler) {
+ NotificationEntryManager notificationEntryManager, @MainHandler Handler handler) {
mHandler = handler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
index 7e398bb..7d0ce5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
@@ -37,6 +37,7 @@
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.logging.NotifEvent;
import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -76,12 +77,16 @@
private final Ranking mTmpRanking = new Ranking();
private final boolean mUsePeopleFiltering;
private final NotifLog mNotifLog;
+ private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Inject
- public NotificationData(NotificationSectionsFeatureManager sectionsFeatureManager,
- NotifLog notifLog) {
+ public NotificationData(
+ NotificationSectionsFeatureManager sectionsFeatureManager,
+ NotifLog notifLog,
+ PeopleNotificationIdentifier peopleNotificationIdentifier) {
mUsePeopleFiltering = sectionsFeatureManager.isFilteringEnabled();
mNotifLog = notifLog;
+ mPeopleNotificationIdentifier = peopleNotificationIdentifier;
}
public void setHeadsUpManager(HeadsUpManager headsUpManager) {
@@ -460,8 +465,7 @@
}
private boolean isPeopleNotification(NotificationEntry e) {
- return e.getSbn().getNotification().getNotificationStyle()
- == Notification.MessagingStyle.class;
+ return mPeopleNotificationIdentifier.isPeopleNotification(e.getSbn());
}
public void dump(PrintWriter pw, String indent) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
index 8c067b7..4570989 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
@@ -24,14 +24,24 @@
@Binds
abstract fun peopleHubSectionFooterViewController(
- viewAdapter: PeopleHubSectionFooterViewAdapterImpl
+ impl: PeopleHubSectionFooterViewAdapterImpl
): PeopleHubSectionFooterViewAdapter
@Binds
- abstract fun peopleHubDataSource(s: PeopleHubDataSourceImpl): DataSource<PeopleHubModel>
+ abstract fun peopleHubDataSource(impl: PeopleHubDataSourceImpl): DataSource<PeopleHubModel>
@Binds
abstract fun peopleHubViewModelFactoryDataSource(
- dataSource: PeopleHubViewModelFactoryDataSourceImpl
+ impl: PeopleHubViewModelFactoryDataSourceImpl
): DataSource<PeopleHubViewModelFactory>
+
+ @Binds
+ abstract fun peopleNotificationIdentifier(
+ impl: PeopleNotificationIdentifierImpl
+ ): PeopleNotificationIdentifier
+
+ @Binds
+ abstract fun notificationPersonExtractor(
+ pluginImpl: NotificationPersonExtractorPluginBoundary
+ ): NotificationPersonExtractor
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index 90a860a..fe257d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -17,11 +17,14 @@
package com.android.systemui.statusbar.notification.people
import android.app.Notification
+import android.content.Context
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.PixelFormat
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.service.notification.StatusBarNotification
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
@@ -30,59 +33,112 @@
import com.android.internal.widget.MessagingGroup
import com.android.launcher3.icons.BaseIconFactory
import com.android.systemui.R
+import com.android.systemui.plugins.NotificationPersonExtractorPlugin
import com.android.systemui.statusbar.notification.NotificationEntryListener
import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.policy.ExtensionController
import java.util.ArrayDeque
import javax.inject.Inject
import javax.inject.Singleton
private const val MAX_STORED_INACTIVE_PEOPLE = 10
-@Singleton
-class PeopleHubDataSourceImpl @Inject constructor(
- notificationEntryManager: NotificationEntryManager,
- private val peopleHubManager: PeopleHubManager
-) : DataSource<PeopleHubModel> {
+interface NotificationPersonExtractor {
+ fun extractPerson(sbn: StatusBarNotification): PersonModel?
+ fun extractPersonKey(sbn: StatusBarNotification): String?
+}
- private var dataListener: DataListener<PeopleHubModel>? = null
+@Singleton
+class NotificationPersonExtractorPluginBoundary @Inject constructor(
+ extensionController: ExtensionController,
+ private val context: Context
+) : NotificationPersonExtractor {
+
+ private var plugin: NotificationPersonExtractorPlugin? = null
init {
- notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
- override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) =
- addVisibleEntry(entry)
-
- override fun onEntryReinflated(entry: NotificationEntry) = addVisibleEntry(entry)
-
- override fun onPostEntryUpdated(entry: NotificationEntry) = addVisibleEntry(entry)
-
- override fun onEntryRemoved(
- entry: NotificationEntry,
- visibility: NotificationVisibility?,
- removedByUser: Boolean
- ) = removeVisibleEntry(entry)
- })
+ plugin = extensionController
+ .newExtension(NotificationPersonExtractorPlugin::class.java)
+ .withPlugin(NotificationPersonExtractorPlugin::class.java)
+ .withCallback { extractor ->
+ plugin = extractor
+ }
+ .build()
+ .get()
}
- private fun removeVisibleEntry(entry: NotificationEntry?) {
- if (entry?.extractPersonKey()?.let(peopleHubManager::removeActivePerson) == true) {
+ override fun extractPerson(sbn: StatusBarNotification) =
+ plugin?.extractPerson(sbn)?.let { data ->
+ val badged = addBadgeToDrawable(data.avatar, context, sbn.packageName, sbn.user)
+ PersonModel(data.key, data.name, badged, data.clickIntent)
+ }
+
+ override fun extractPersonKey(sbn: StatusBarNotification) = plugin?.extractPersonKey(sbn)
+}
+
+@Singleton
+class PeopleHubDataSourceImpl @Inject constructor(
+ private val notificationEntryManager: NotificationEntryManager,
+ private val peopleHubManager: PeopleHubManager,
+ private val extractor: NotificationPersonExtractor
+) : DataSource<PeopleHubModel> {
+
+ private val dataListeners = mutableListOf<DataListener<PeopleHubModel>>()
+
+ private val notificationEntryListener = object : NotificationEntryListener {
+ override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) =
+ addVisibleEntry(entry)
+
+ override fun onEntryReinflated(entry: NotificationEntry) = addVisibleEntry(entry)
+
+ override fun onPostEntryUpdated(entry: NotificationEntry) = addVisibleEntry(entry)
+
+ override fun onEntryRemoved(
+ entry: NotificationEntry,
+ visibility: NotificationVisibility?,
+ removedByUser: Boolean
+ ) = removeVisibleEntry(entry)
+ }
+
+ private fun removeVisibleEntry(entry: NotificationEntry) {
+ val key = extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey()
+ if (key?.let(peopleHubManager::removeActivePerson) == true) {
updateUi()
}
}
- private fun addVisibleEntry(entry: NotificationEntry?) {
- if (entry?.extractPerson()?.let(peopleHubManager::addActivePerson) == true) {
+ private fun addVisibleEntry(entry: NotificationEntry) {
+ val personModel = extractor.extractPerson(entry.sbn) ?: entry.extractPerson()
+ if (personModel?.let(peopleHubManager::addActivePerson) == true) {
updateUi()
}
}
- override fun setListener(listener: DataListener<PeopleHubModel>) {
- this.dataListener = listener
- updateUi()
+ override fun registerListener(listener: DataListener<PeopleHubModel>): Subscription {
+ val registerWithNotificationEntryManager = dataListeners.isEmpty()
+ dataListeners.add(listener)
+ if (registerWithNotificationEntryManager) {
+ notificationEntryManager.addNotificationEntryListener(notificationEntryListener)
+ } else {
+ listener.onDataChanged(peopleHubManager.getPeopleHubModel())
+ }
+ return object : Subscription {
+ override fun unsubscribe() {
+ dataListeners.remove(listener)
+ if (dataListeners.isEmpty()) {
+ notificationEntryManager
+ .removeNotificationEntryListener(notificationEntryListener)
+ }
+ }
+ }
}
private fun updateUi() {
- dataListener?.onDataChanged(peopleHubManager.getPeopleHubModel())
+ val model = peopleHubManager.getPeopleHubModel()
+ for (listener in dataListeners) {
+ listener.onDataChanged(model)
+ }
}
}
@@ -124,19 +180,26 @@
if (!isMessagingNotification()) {
return null
}
-
val clickIntent = sbn.notification.contentIntent
+ ?: return null
val extras = sbn.notification.extras
val name = extras.getString(Notification.EXTRA_CONVERSATION_TITLE)
?: extras.getString(Notification.EXTRA_TITLE)
?: return null
val drawable = extractAvatarFromRow(this) ?: return null
+ val badgedAvatar = addBadgeToDrawable(drawable, row.context, sbn.packageName, sbn.user)
+ return PersonModel(key, name, badgedAvatar, clickIntent)
+}
- val context = row.context
+private fun addBadgeToDrawable(
+ drawable: Drawable,
+ context: Context,
+ packageName: String,
+ user: UserHandle
+): Drawable {
val pm = context.packageManager
- val appInfo = pm.getApplicationInfo(sbn.packageName, 0)
-
- val badgedAvatar = object : Drawable() {
+ val appInfo = pm.getApplicationInfoAsUser(packageName, 0, user)
+ return object : Drawable() {
override fun draw(canvas: Canvas) {
val iconBounds = getBounds()
val factory = object : BaseIconFactory(
@@ -146,7 +209,7 @@
true) {}
val badge = factory.createBadgedIconBitmap(
appInfo.loadIcon(pm),
- sbn.user,
+ user,
true,
appInfo.isInstantApp,
null)
@@ -156,7 +219,7 @@
colorFilter = drawable.colorFilter
val badgeWidth = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
- 16f,
+ 15f,
context.resources.displayMetrics
).toInt()
setBounds(
@@ -181,8 +244,6 @@
@PixelFormat.Opacity
override fun getOpacity(): Int = PixelFormat.OPAQUE
}
-
- return PersonModel(key, name, badgedAvatar, clickIntent)
}
private fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
index 8d1253b..5c35408 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
@@ -60,8 +60,9 @@
private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubViewModelFactory>
) : PeopleHubSectionFooterViewAdapter {
- override fun bindView(viewBoundary: PeopleHubSectionFooterViewBoundary) =
- dataSource.setListener(PeopleHubDataListenerImpl(viewBoundary))
+ override fun bindView(viewBoundary: PeopleHubSectionFooterViewBoundary) {
+ dataSource.registerListener(PeopleHubDataListenerImpl(viewBoundary))
+ }
}
private class PeopleHubDataListenerImpl(
@@ -92,8 +93,8 @@
private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel>
) : DataSource<PeopleHubViewModelFactory> {
- override fun setListener(listener: DataListener<PeopleHubViewModelFactory>) =
- dataSource.setListener(PeopleHubModelListenerImpl(activityStarter, listener))
+ override fun registerListener(listener: DataListener<PeopleHubViewModelFactory>) =
+ dataSource.registerListener(PeopleHubModelListenerImpl(activityStarter, listener))
}
private class PeopleHubModelListenerImpl(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
new file mode 100644
index 0000000..bfd4070
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -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 com.android.systemui.statusbar.notification.people
+
+import android.app.Notification
+import android.service.notification.StatusBarNotification
+import javax.inject.Inject
+import javax.inject.Singleton
+
+interface PeopleNotificationIdentifier {
+ fun isPeopleNotification(sbn: StatusBarNotification): Boolean
+}
+
+@Singleton
+class PeopleNotificationIdentifierImpl @Inject constructor(
+ private val personExtractor: NotificationPersonExtractor
+) : PeopleNotificationIdentifier {
+
+ override fun isPeopleNotification(sbn: StatusBarNotification) =
+ sbn.notification.notificationStyle == Notification.MessagingStyle::class.java ||
+ personExtractor.extractPersonKey(sbn) != null
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt
index 33e3bb8..3ca3792 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt
@@ -28,10 +28,17 @@
/** Boundary between a View and data pipeline, as seen by the View. */
interface DataSource<out T> {
- fun setListener(listener: DataListener<T>)
+ fun registerListener(listener: DataListener<T>): Subscription
+}
+
+/** Represents a registration with a [DataSource]. */
+interface Subscription {
+ /** Removes the previously registered [DataListener] from the [DataSource] */
+ fun unsubscribe()
}
/** Transform all data coming out of this [DataSource] using the given [mapper]. */
fun <S, T> DataSource<S>.map(mapper: (S) -> T): DataSource<T> = object : DataSource<T> {
- override fun setListener(listener: DataListener<T>) = setListener(listener.contraMap(mapper))
+ override fun registerListener(listener: DataListener<T>) =
+ registerListener(listener.contraMap(mapper))
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 5d5c09e..9bc0ca4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -28,6 +28,7 @@
import android.media.session.PlaybackState;
import android.metrics.LogMaker;
import android.os.Handler;
+import android.provider.Settings;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
@@ -41,9 +42,12 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.widget.MediaNotificationView;
import com.android.systemui.Dependency;
+import com.android.systemui.qs.QSPanel;
+import com.android.systemui.qs.QuickQSPanel;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.phone.StatusBarWindowController;
import java.util.Timer;
import java.util.TimerTask;
@@ -178,6 +182,26 @@
final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras
.getParcelable(Notification.EXTRA_MEDIA_SESSION);
+ int flag = Settings.System.getInt(mContext.getContentResolver(), "qs_media_player", 0);
+ if (flag == 1) {
+ StatusBarWindowController ctrl = Dependency.get(StatusBarWindowController.class);
+ QuickQSPanel panel = ctrl.getStatusBarView().findViewById(
+ com.android.systemui.R.id.quick_qs_panel);
+ panel.getMediaPlayer().setMediaSession(token,
+ mRow.getStatusBarNotification().getNotification().getSmallIcon(),
+ getNotificationHeader().getOriginalIconColor(),
+ mRow.getCurrentBackgroundTint(),
+ mActions);
+ QSPanel bigPanel = ctrl.getStatusBarView().findViewById(
+ com.android.systemui.R.id.quick_settings_panel);
+ bigPanel.addMediaSession(token,
+ mRow.getStatusBarNotification().getNotification().getSmallIcon(),
+ getNotificationHeader().getOriginalIconColor(),
+ mRow.getCurrentBackgroundTint(),
+ mActions,
+ mRow.getStatusBarNotification());
+ }
+
boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar();
if (token == null || (COMPACT_MEDIA_TAG.equals(mView.getTag()) && !showCompactSeekbar)) {
if (mSeekBarView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index bd87d77..54d4066 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -131,6 +131,7 @@
}
mInitialized = true;
reinflateViews(layoutInflater);
+ mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
mConfigurationController.addCallback(mConfigurationListener);
}
@@ -172,10 +173,6 @@
if (oldPeopleHubPos != -1) {
mParent.addView(mPeopleHubView, oldPeopleHubPos);
}
-
- if (!mInitialized) {
- mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
- }
}
/** Listener for when the "clear all" buttton is clciked on the gentle notification header. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index 008464e..f9b9367 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;
@@ -25,10 +23,10 @@
import android.view.IWindowManager;
import android.view.MotionEvent;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import javax.inject.Inject;
-import javax.inject.Named;
/** A controller to control all auto-hide things. */
public class AutoHideController {
@@ -54,7 +52,7 @@
};
@Inject
- public AutoHideController(Context context, @Named(MAIN_HANDLER_NAME) Handler handler,
+ public AutoHideController(Context context, @MainHandler Handler handler,
NotificationRemoteInputManager notificationRemoteInputManager,
IWindowManager iWindowManager) {
mHandler = handler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 007c50c..837517e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -21,7 +21,7 @@
import android.provider.Settings.Secure;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
+import com.android.systemui.dagger.qualifiers.BgHandler;
import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.SecureSetting;
@@ -33,7 +33,6 @@
import com.android.systemui.statusbar.policy.HotspotController.Callback;
import javax.inject.Inject;
-import javax.inject.Named;
/**
* Manages which tiles should be automatically added to QS.
@@ -58,7 +57,7 @@
@Inject
public AutoTileManager(Context context, AutoAddTracker autoAddTracker, QSTileHost host,
- @Named(Dependency.BG_HANDLER_NAME) Handler handler,
+ @BgHandler Handler handler,
HotspotController hotspotController,
DataSaverController dataSaverController,
ManagedProfileController managedProfileController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 548afd5..bd3d848 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.biometrics.BiometricSourceType;
import android.metrics.LogMaker;
import android.os.Handler;
@@ -34,6 +35,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dependency;
+import com.android.systemui.dagger.qualifiers.MainResources;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -44,15 +46,17 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import javax.inject.Inject;
+
/**
* Controller which coordinates all the biometric unlocking actions with the UI.
*/
public class BiometricUnlockController extends KeyguardUpdateMonitorCallback {
- private static final String TAG = "BiometricUnlockController";
+ private static final String TAG = "BiometricUnlockCtrl";
private static final boolean DEBUG_BIO_WAKELOCK = KeyguardConstants.DEBUG_BIOMETRIC_WAKELOCK;
private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
- private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock wakelock";
+ private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
@IntDef(prefix = { "MODE_" }, value = {
MODE_NONE,
@@ -128,7 +132,7 @@
private final KeyguardBypassController mKeyguardBypassController;
private PowerManager.WakeLock mWakeLock;
private final KeyguardUpdateMonitor mUpdateMonitor;
- private final DozeParameters mDozeParameters;
+ private DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
private final StatusBarWindowController mStatusBarWindowController;
private final Context mContext;
@@ -145,31 +149,16 @@
private boolean mHasScreenTurnedOnSinceAuthenticating;
private boolean mFadedAwayAfterWakeAndUnlock;
- private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+ private final MetricsLogger mMetricsLogger;
- public BiometricUnlockController(
- Context context,
- DozeScrimController dozeScrimController,
- KeyguardViewMediator keyguardViewMediator,
- ScrimController scrimController,
- StatusBar statusBar,
- KeyguardStateController keyguardStateController, Handler handler,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardBypassController keyguardBypassController,
- DozeParameters dozeParameters) {
- this(context, dozeScrimController, keyguardViewMediator, scrimController, statusBar,
- keyguardStateController, handler, keyguardUpdateMonitor,
- context.getResources()
- .getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze),
- keyguardBypassController, dozeParameters);
- }
-
- @VisibleForTesting
- protected BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
+ @Inject
+ public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
StatusBar statusBar, KeyguardStateController keyguardStateController, Handler handler,
- KeyguardUpdateMonitor keyguardUpdateMonitor, int wakeUpDelay,
- KeyguardBypassController keyguardBypassController, DozeParameters dozeParameters) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ @MainResources Resources resources,
+ KeyguardBypassController keyguardBypassController, DozeParameters dozeParameters,
+ MetricsLogger metricsLogger) {
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
mUpdateMonitor = keyguardUpdateMonitor;
@@ -185,9 +174,10 @@
mStatusBar = statusBar;
mKeyguardStateController = keyguardStateController;
mHandler = handler;
- mWakeUpDelay = wakeUpDelay;
+ mWakeUpDelay = resources.getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze);
mKeyguardBypassController = keyguardBypassController;
mKeyguardBypassController.setUnlockController(this);
+ mMetricsLogger = metricsLogger;
}
public void setStatusBarKeyguardViewManager(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 28dac87..bc48235 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -24,9 +24,8 @@
import android.provider.Settings;
import android.util.MathUtils;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.DependencyProvider;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.MainResources;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
import com.android.systemui.tuner.TunerService;
@@ -59,7 +58,7 @@
@Inject
protected DozeParameters(
- @DependencyProvider.MainResources Resources resources,
+ @MainResources Resources resources,
AmbientDisplayConfiguration ambientDisplayConfiguration,
AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
PowerManager powerManager,
@@ -188,12 +187,7 @@
return;
}
mControlScreenOffAnimation = controlScreenOffAnimation;
- getPowerManager().setDozeAfterScreenOff(!controlScreenOffAnimation);
- }
-
- @VisibleForTesting
- protected PowerManager getPowerManager() {
- return mPowerManager;
+ mPowerManager.setDozeAfterScreenOff(!controlScreenOffAnimation);
}
private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index fe3c04e..1ecc489 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -29,10 +29,12 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import javax.inject.Inject;
+import javax.inject.Singleton;
/**
* Controller which handles all the doze animations of the scrims.
*/
+@Singleton
public class DozeScrimController implements StateListener {
private static final String TAG = "DozeScrimController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
new file mode 100644
index 0000000..2854355
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -0,0 +1,465 @@
+/*
+ * 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.systemui.statusbar.phone;
+
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.doze.DozeEvent;
+import com.android.systemui.doze.DozeHost;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
+/**
+ * Implementation of DozeHost for SystemUI.
+ */
+@Singleton
+public final class DozeServiceHost implements DozeHost {
+ private static final String TAG = "DozeServiceHost";
+ private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+ private final DozeLog mDozeLog;
+ private final PowerManager mPowerManager;
+ private boolean mAnimateWakeup;
+ private boolean mAnimateScreenOff;
+ private boolean mIgnoreTouchWhilePulsing;
+ private Runnable mPendingScreenOffCallback;
+ @VisibleForTesting
+ boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean(
+ "persist.sysui.wake_performs_auth", true);
+ private boolean mDozingRequested;
+ private boolean mDozing;
+ private boolean mPulsing;
+ private WakefulnessLifecycle mWakefulnessLifecycle;
+ private final SysuiStatusBarStateController mStatusBarStateController;
+ private final DeviceProvisionedController mDeviceProvisionedController;
+ private final HeadsUpManagerPhone mHeadsUpManagerPhone;
+ private final BatteryController mBatteryController;
+ private final ScrimController mScrimController;
+ private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
+ private BiometricUnlockController mBiometricUnlockController;
+ private final KeyguardViewMediator mKeyguardViewMediator;
+ private final AssistManager mAssistManager;
+ private final DozeScrimController mDozeScrimController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final VisualStabilityManager mVisualStabilityManager;
+ private final PulseExpansionHandler mPulseExpansionHandler;
+ private final StatusBarWindowController mStatusBarWindowController;
+ private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
+ private NotificationIconAreaController mNotificationIconAreaController;
+ private StatusBarWindowViewController mStatusBarWindowViewController;
+ private StatusBarWindowView mStatusBarWindow;
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private NotificationPanelView mNotificationPanel;
+ private View mAmbientIndicationContainer;
+ private StatusBar mStatusBar;
+
+ @Inject
+ public DozeServiceHost(DozeLog dozeLog, PowerManager powerManager,
+ WakefulnessLifecycle wakefulnessLifecycle,
+ SysuiStatusBarStateController statusBarStateController,
+ DeviceProvisionedController deviceProvisionedController,
+ HeadsUpManagerPhone headsUpManagerPhone, BatteryController batteryController,
+ ScrimController scrimController,
+ Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+ KeyguardViewMediator keyguardViewMediator,
+ AssistManager assistManager,
+ DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor,
+ VisualStabilityManager visualStabilityManager,
+ PulseExpansionHandler pulseExpansionHandler,
+ StatusBarWindowController statusBarWindowController,
+ NotificationWakeUpCoordinator notificationWakeUpCoordinator) {
+ super();
+ mDozeLog = dozeLog;
+ mPowerManager = powerManager;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
+ mStatusBarStateController = statusBarStateController;
+ mDeviceProvisionedController = deviceProvisionedController;
+ mHeadsUpManagerPhone = headsUpManagerPhone;
+ mBatteryController = batteryController;
+ mScrimController = scrimController;
+ mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
+ mKeyguardViewMediator = keyguardViewMediator;
+ mAssistManager = assistManager;
+ mDozeScrimController = dozeScrimController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mVisualStabilityManager = visualStabilityManager;
+ mPulseExpansionHandler = pulseExpansionHandler;
+ mStatusBarWindowController = statusBarWindowController;
+ mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
+ }
+
+ // TODO: we should try to not pass status bar in here if we can avoid it.
+
+ /**
+ * Initialize instance with objects only available later during execution.
+ */
+ public void initialize(StatusBar statusBar,
+ NotificationIconAreaController notificationIconAreaController,
+ StatusBarWindowViewController statusBarWindowViewController,
+ StatusBarWindowView statusBarWindow,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ NotificationPanelView notificationPanel, View ambientIndicationContainer) {
+ mStatusBar = statusBar;
+ mNotificationIconAreaController = notificationIconAreaController;
+ mStatusBarWindowViewController = statusBarWindowViewController;
+ mStatusBarWindow = statusBarWindow;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mNotificationPanel = notificationPanel;
+ mAmbientIndicationContainer = ambientIndicationContainer;
+ mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
+ }
+
+ @Override
+ public String toString() {
+ return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]";
+ }
+
+ void firePowerSaveChanged(boolean active) {
+ for (Callback callback : mCallbacks) {
+ callback.onPowerSaveChanged(active);
+ }
+ }
+
+ void fireNotificationPulse(NotificationEntry entry) {
+ Runnable pulseSuppressedListener = () -> {
+ entry.setPulseSuppressed(true);
+ mNotificationIconAreaController.updateAodNotificationIcons();
+ };
+ for (Callback callback : mCallbacks) {
+ callback.onNotificationAlerted(pulseSuppressedListener);
+ }
+ }
+
+ boolean getDozingRequested() {
+ return mDozingRequested;
+ }
+
+ boolean isPulsing() {
+ return mPulsing;
+ }
+
+
+ @Override
+ public void addCallback(@NonNull Callback callback) {
+ mCallbacks.add(callback);
+ }
+
+ @Override
+ public void removeCallback(@NonNull Callback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ @Override
+ public void startDozing() {
+ if (!mDozingRequested) {
+ mDozingRequested = true;
+ mDozeLog.traceDozing(mDozing);
+ updateDozing();
+ mStatusBar.updateIsKeyguard();
+ }
+ }
+
+ void updateDozing() {
+ // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
+ boolean
+ dozing =
+ mDozingRequested && mStatusBarStateController.getState() == StatusBarState.KEYGUARD
+ || mBiometricUnlockController.getMode()
+ == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+ // When in wake-and-unlock we may not have received a change to StatusBarState
+ // but we still should not be dozing, manually set to false.
+ if (mBiometricUnlockController.getMode()
+ == BiometricUnlockController.MODE_WAKE_AND_UNLOCK) {
+ dozing = false;
+ }
+
+
+ mStatusBarStateController.setIsDozing(dozing);
+ }
+
+ @Override
+ public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
+ if (reason == DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS) {
+ mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+ "com.android.systemui:LONG_PRESS");
+ mAssistManager.startAssist(new Bundle());
+ return;
+ }
+
+ if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+ mScrimController.setWakeLockScreenSensorActive(true);
+ }
+
+ if (reason == DozeEvent.PULSE_REASON_DOCKING && mStatusBarWindow != null) {
+ mStatusBarWindowViewController.suppressWakeUpGesture(true);
+ }
+
+ boolean passiveAuthInterrupt = reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
+ && mWakeLockScreenPerformsAuth;
+ // Set the state to pulsing, so ScrimController will know what to do once we ask it to
+ // execute the transition. The pulse callback will then be invoked when the scrims
+ // are black, indicating that StatusBar is ready to present the rest of the UI.
+ mPulsing = true;
+ mDozeScrimController.pulse(new PulseCallback() {
+ @Override
+ public void onPulseStarted() {
+ callback.onPulseStarted();
+ mStatusBar.updateNotificationPanelTouchState();
+ setPulsing(true);
+ }
+
+ @Override
+ public void onPulseFinished() {
+ mPulsing = false;
+ callback.onPulseFinished();
+ mStatusBar.updateNotificationPanelTouchState();
+ mScrimController.setWakeLockScreenSensorActive(false);
+ if (mStatusBarWindow != null) {
+ mStatusBarWindowViewController.suppressWakeUpGesture(false);
+ }
+ setPulsing(false);
+ }
+
+ private void setPulsing(boolean pulsing) {
+ mStatusBarStateController.setPulsing(pulsing);
+ mStatusBarKeyguardViewManager.setPulsing(pulsing);
+ mKeyguardViewMediator.setPulsing(pulsing);
+ mNotificationPanel.setPulsing(pulsing);
+ mVisualStabilityManager.setPulsing(pulsing);
+ mStatusBarWindowViewController.setPulsing(pulsing);
+ mIgnoreTouchWhilePulsing = false;
+ if (mKeyguardUpdateMonitor != null && passiveAuthInterrupt) {
+ mKeyguardUpdateMonitor.onAuthInterruptDetected(pulsing /* active */);
+ }
+ mStatusBar.updateScrimController();
+ mPulseExpansionHandler.setPulsing(pulsing);
+ mNotificationWakeUpCoordinator.setPulsing(pulsing);
+ }
+ }, reason);
+ // DozeScrimController is in pulse state, now let's ask ScrimController to start
+ // pulsing and draw the black frame, if necessary.
+ mStatusBar.updateScrimController();
+ }
+
+ @Override
+ public void stopDozing() {
+ if (mDozingRequested) {
+ mDozingRequested = false;
+ mDozeLog.traceDozing(mDozing);
+ updateDozing();
+ }
+ }
+
+ @Override
+ public void onIgnoreTouchWhilePulsing(boolean ignore) {
+ if (ignore != mIgnoreTouchWhilePulsing) {
+ mDozeLog.tracePulseTouchDisabledByProx(ignore);
+ }
+ mIgnoreTouchWhilePulsing = ignore;
+ if (mDozing && ignore) {
+ mStatusBarWindowViewController.cancelCurrentTouch();
+ }
+ }
+
+ @Override
+ public void dozeTimeTick() {
+ mNotificationPanel.dozeTimeTick();
+ if (mAmbientIndicationContainer instanceof DozeReceiver) {
+ ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
+ }
+ }
+
+ @Override
+ public boolean isPowerSaveActive() {
+ return mBatteryController.isAodPowerSave();
+ }
+
+ @Override
+ public boolean isPulsingBlocked() {
+ return mBiometricUnlockController.getMode()
+ == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+ }
+
+ @Override
+ public boolean isProvisioned() {
+ return mDeviceProvisionedController.isDeviceProvisioned()
+ && mDeviceProvisionedController.isCurrentUserSetup();
+ }
+
+ @Override
+ public boolean isBlockingDoze() {
+ if (mBiometricUnlockController.hasPendingAuthentication()) {
+ Log.i(StatusBar.TAG, "Blocking AOD because fingerprint has authenticated");
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void extendPulse(int reason) {
+ if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+ mScrimController.setWakeLockScreenSensorActive(true);
+ }
+ if (mDozeScrimController.isPulsing() && mHeadsUpManagerPhone.hasNotifications()) {
+ mHeadsUpManagerPhone.extendHeadsUp();
+ } else {
+ mDozeScrimController.extendPulse();
+ }
+ }
+
+ @Override
+ public void stopPulsing() {
+ if (mDozeScrimController.isPulsing()) {
+ mDozeScrimController.pulseOutNow();
+ }
+ }
+
+ @Override
+ public void setAnimateWakeup(boolean animateWakeup) {
+ if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE
+ || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING) {
+ // Too late to change the wakeup animation.
+ return;
+ }
+ mAnimateWakeup = animateWakeup;
+ }
+
+ @Override
+ public void setAnimateScreenOff(boolean animateScreenOff) {
+ mAnimateScreenOff = animateScreenOff;
+ }
+
+ @Override
+ public void onSlpiTap(float screenX, float screenY) {
+ if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
+ && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
+ int[] locationOnScreen = new int[2];
+ mAmbientIndicationContainer.getLocationOnScreen(locationOnScreen);
+ float viewX = screenX - locationOnScreen[0];
+ float viewY = screenY - locationOnScreen[1];
+ if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth()
+ && 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) {
+
+ // Dispatch a tap
+ long now = SystemClock.elapsedRealtime();
+ MotionEvent ev = MotionEvent.obtain(
+ now, now, MotionEvent.ACTION_DOWN, screenX, screenY, 0);
+ mAmbientIndicationContainer.dispatchTouchEvent(ev);
+ ev.recycle();
+ ev = MotionEvent.obtain(
+ now, now, MotionEvent.ACTION_UP, screenX, screenY, 0);
+ mAmbientIndicationContainer.dispatchTouchEvent(ev);
+ ev.recycle();
+ }
+ }
+ }
+
+ @Override
+ public void setDozeScreenBrightness(int value) {
+ mStatusBarWindowController.setDozeScreenBrightness(value);
+ }
+
+ @Override
+ public void setAodDimmingScrim(float scrimOpacity) {
+ mScrimController.setAodFrontScrimAlpha(scrimOpacity);
+ }
+
+
+
+ @Override
+ public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
+ if (mPendingScreenOffCallback != null) {
+ Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one.");
+ }
+ mPendingScreenOffCallback = onDisplayOffCallback;
+ mStatusBar.updateScrimController();
+ }
+
+ @Override
+ public void cancelGentleSleep() {
+ mPendingScreenOffCallback = null;
+ if (mScrimController.getState() == ScrimState.OFF) {
+ mStatusBar.updateScrimController();
+ }
+ }
+
+ /**
+ * When the dozing host is waiting for scrims to fade out to change the display state.
+ */
+ boolean hasPendingScreenOffCallback() {
+ return mPendingScreenOffCallback != null;
+ }
+
+ /**
+ * Executes an nullifies the pending display state callback.
+ *
+ * @see #hasPendingScreenOffCallback()
+ * @see #prepareForGentleSleep(Runnable)
+ */
+ void executePendingScreenOffCallback() {
+ if (mPendingScreenOffCallback == null) {
+ return;
+ }
+ mPendingScreenOffCallback.run();
+ mPendingScreenOffCallback = null;
+ }
+
+ boolean shouldAnimateWakeup() {
+ return mAnimateWakeup;
+ }
+
+ boolean shouldAnimateScreenOff() {
+ return mAnimateScreenOff;
+ }
+
+ public void setDozing(boolean dozing) {
+ mDozing = dozing;
+ }
+
+ boolean getIgnoreTouchWhilePulsing() {
+ return mIgnoreTouchWhilePulsing;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index c9c38a0..1c8e832 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -54,9 +54,7 @@
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.WindowManagerWrapper;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -70,15 +68,6 @@
private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
"gestures.back_timeout", 250);
- private final PinnedStackListener mImeChangedListener = new PinnedStackListener() {
- @Override
- public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
- // No need to thread jump, assignments are atomic
- mImeHeight = imeVisible ? imeHeight : 0;
- // TODO: Probably cancel any existing gesture
- }
- };
-
private ISystemGestureExclusionListener mGestureExclusionListener =
new ISystemGestureExclusionListener.Stub() {
@Override
@@ -126,8 +115,6 @@
private boolean mInRejectedExclusion = false;
private boolean mIsOnLeftEdge;
- private int mImeHeight = 0;
-
private boolean mIsAttached;
private boolean mIsGesturalModeEnabled;
private boolean mIsEnabled;
@@ -227,7 +214,6 @@
}
if (!mIsEnabled) {
- WindowManagerWrapper.getInstance().removePinnedStackListener(mImeChangedListener);
mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
try {
@@ -244,7 +230,6 @@
mContext.getMainThreadHandler());
try {
- WindowManagerWrapper.getInstance().addPinnedStackListener(mImeChangedListener);
WindowManagerGlobal.getWindowManagerService()
.registerSystemGestureExclusionListener(
mGestureExclusionListener, mDisplayId);
@@ -301,11 +286,6 @@
}
private boolean isWithinTouchRegion(int x, int y) {
- // Disallow if over the IME
- if (y > (mDisplaySize.y - Math.max(mImeHeight, mNavBarHeight))) {
- return false;
- }
-
// Disallow if too far from the edge
if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) {
return false;
@@ -483,7 +463,6 @@
pw.println(" mInRejectedExclusion" + mInRejectedExclusion);
pw.println(" mExcludeRegion=" + mExcludeRegion);
pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion);
- pw.println(" mImeHeight=" + mImeHeight);
pw.println(" mIsAttached=" + mIsAttached);
pw.println(" mEdgeWidth=" + mEdgeWidth);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index d9de59e..bf88704 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -21,17 +21,16 @@
import android.hardware.TriggerEventListener
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.Dependency
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.Assert
import com.android.systemui.util.sensors.AsyncSensorManager
class KeyguardLiftController constructor(
private val statusBarStateController: StatusBarStateController,
- private val asyncSensorManager: AsyncSensorManager
+ private val asyncSensorManager: AsyncSensorManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor
) : StatusBarStateController.StateListener, KeyguardUpdateMonitorCallback() {
- private val keyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor::class.java)
private val pickupSensor = asyncSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE)
private var isListening = false
private var bouncerVisible = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 89051cd..30fe68a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -770,6 +770,11 @@
int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
int topMargin =
res.getDimensionPixelOffset(com.android.internal.R.dimen.quick_qs_total_height);
+ int flag = Settings.System.getInt(mContext.getContentResolver(), "qs_media_player", 0);
+ if (flag == 1) {
+ topMargin = res.getDimensionPixelOffset(
+ com.android.internal.R.dimen.quick_qs_total_height_with_media);
+ }
lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
|| lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index c50b1bf..35039a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -42,10 +42,10 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.DejankUtils;
-import com.android.systemui.DependencyProvider;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.MainResources;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -60,11 +60,13 @@
import java.util.function.Consumer;
import javax.inject.Inject;
+import javax.inject.Singleton;
/**
* Controls both the scrim behind the notifications and in front of the notifications (when a
* security method gets shown).
*/
+@Singleton
public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnColorsChangedListener,
Dumpable {
@@ -188,7 +190,7 @@
@Inject
public ScrimController(LightBarController lightBarController, DozeParameters dozeParameters,
AlarmManager alarmManager, KeyguardStateController keyguardStateController,
- @DependencyProvider.MainResources Resources resources,
+ @MainResources Resources resources,
DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
KeyguardUpdateMonitor keyguardUpdateMonitor, SysuiColorExtractor sysuiColorExtractor) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index e0597159..13055ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -39,15 +39,20 @@
@Override
public void prepare(ScrimState previousState) {
mFrontTint = Color.BLACK;
- mBehindTint = previousState.mBehindTint;
+ mBehindTint = Color.BLACK;
mBubbleTint = previousState.mBubbleTint;
mFrontAlpha = 1f;
- mBehindAlpha = previousState.mBehindAlpha;
+ mBehindAlpha = 1f;
mBubbleAlpha = previousState.mBubbleAlpha;
mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
}
+
+ @Override
+ public boolean isLowPowerState() {
+ return true;
+ }
},
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 2963c94..b8adfea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -50,7 +50,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -157,10 +156,8 @@
import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.doze.DozeEvent;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
-import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.fragments.ExtensionFragmentListener;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
@@ -347,7 +344,7 @@
/**
* The {@link StatusBarState} of the status bar.
*/
- protected int mState;
+ protected int mState; // TODO: remove this. Just use StatusBarStateController
protected boolean mBouncerShowing;
private PhoneStatusBarPolicy mIconPolicy;
@@ -356,7 +353,7 @@
private VolumeComponent mVolumeComponent;
private BrightnessMirrorController mBrightnessMirrorController;
private boolean mBrightnessMirrorVisible;
- protected BiometricUnlockController mBiometricUnlockController;
+ private BiometricUnlockController mBiometricUnlockController;
private final LightBarController mLightBarController;
private final Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
protected LockscreenWallpaper mLockscreenWallpaper;
@@ -373,7 +370,7 @@
protected StatusBarWindowController mStatusBarWindowController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@VisibleForTesting
- DozeServiceHost mDozeServiceHost = new DozeServiceHost();
+ DozeServiceHost mDozeServiceHost;
private boolean mWakeUpComingFromTouch;
private PointF mWakeUpTouchLocation;
@@ -398,6 +395,7 @@
private final StatusBarWindowViewController.Builder mStatusBarWindowViewControllerBuilder;
private final NotifLog mNotifLog;
private final DozeParameters mDozeParameters;
+ private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
// expanded notifications
protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
@@ -492,7 +490,6 @@
private final UiOffloadThread mUiOffloadThread;
protected boolean mDozing;
- private boolean mDozingRequested;
private final NotificationMediaManager mMediaManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
@@ -611,7 +608,6 @@
private ActivityLaunchAnimator mActivityLaunchAnimator;
protected StatusBarNotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
- private boolean mPulsing;
private final BubbleController mBubbleController;
private final BubbleController.BubbleExpandListener mBubbleExpandListener;
@@ -691,7 +687,11 @@
NotifLog notifLog,
DozeParameters dozeParameters,
ScrimController scrimController,
- Lazy<LockscreenWallpaper> lockscreenWallpaperLazy) {
+ Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
+ Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+ DozeServiceHost dozeServiceHost,
+ PowerManager powerManager,
+ DozeScrimController dozeScrimController) {
super(context);
mFeatureFlags = featureFlags;
mLightBarController = lightBarController;
@@ -748,9 +748,13 @@
mStatusBarWindowController = statusBarWindowController;
mStatusBarWindowViewControllerBuilder = statusBarWindowViewControllerBuilder;
mNotifLog = notifLog;
+ mDozeServiceHost = dozeServiceHost;
+ mPowerManager = powerManager;
mDozeParameters = dozeParameters;
mScrimController = scrimController;
mLockscreenWallpaperLazy = lockscreenWallpaperLazy;
+ mDozeScrimController = dozeScrimController;
+ mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
mBubbleExpandListener =
(isExpanding, key) -> {
@@ -802,7 +806,6 @@
mAccessibilityManager = (AccessibilityManager)
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -902,6 +905,9 @@
startKeyguard();
mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
+ mDozeServiceHost.initialize(this, mNotificationIconAreaController,
+ mStatusBarWindowViewController, mStatusBarWindow, mStatusBarKeyguardViewManager,
+ mNotificationPanel, mAmbientIndicationContainer);
putComponent(DozeHost.class, mDozeServiceHost);
mScreenPinningRequest = new ScreenPinningRequest(mContext);
@@ -1066,7 +1072,6 @@
mNotificationPanel.initDependencies(this, mGroupManager, mNotificationShelf,
mHeadsUpManager, mNotificationIconAreaController, mScrimController);
- mDozeScrimController = new DozeScrimController(mDozeParameters, mDozeLog);
BackDropView backdrop = mStatusBarWindow.findViewById(R.id.backdrop);
mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
@@ -1370,11 +1375,7 @@
protected void startKeyguard() {
Trace.beginSection("StatusBar#startKeyguard");
- mBiometricUnlockController = new BiometricUnlockController(mContext,
- mDozeScrimController, mKeyguardViewMediator,
- mScrimController, this, mKeyguardStateController, new Handler(),
- mKeyguardUpdateMonitor, mKeyguardBypassController, mDozeParameters);
- putComponent(BiometricUnlockController.class, mBiometricUnlockController);
+ mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
mStatusBarKeyguardViewManager = mKeyguardViewMediator.registerStatusBar(this,
getBouncerContainer(), mNotificationPanel, mBiometricUnlockController,
mStatusBarWindow.findViewById(R.id.lock_icon_container), mStackScroller,
@@ -1731,7 +1732,7 @@
if (isDozing() && isHeadsUp) {
entry.setPulseSuppressed(false);
mDozeServiceHost.fireNotificationPulse(entry);
- if (mPulsing) {
+ if (mDozeServiceHost.isPulsing()) {
mDozeScrimController.cancelPendingPulseTimeout();
}
}
@@ -1763,7 +1764,7 @@
}
public boolean isPulsing() {
- return mPulsing;
+ return mDozeServiceHost.isPulsing();
}
public boolean hideStatusBarIconsWhenExpanded() {
@@ -2826,7 +2827,7 @@
if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP
&& mKeyguardStateController.canDismissLockScreen()
&& !mStatusBarStateController.leaveOpenOnKeyguardHide()
- && isPulsing()) {
+ && mDozeServiceHost.isPulsing()) {
// Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a pulse.
// TODO: Factor this transition out of BiometricUnlockController.
mBiometricUnlockController.startWakeAndUnlock(
@@ -3157,7 +3158,7 @@
return mState == StatusBarState.FULLSCREEN_USER_SWITCHER;
}
- private boolean updateIsKeyguard() {
+ boolean updateIsKeyguard() {
boolean wakeAndUnlocking = mBiometricUnlockController.getMode()
== BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -3165,8 +3166,8 @@
// there's no surface we can show to the user. Note that the device goes fully interactive
// late in the transition, so we also allow the device to start dozing once the screen has
// turned off fully.
- boolean keyguardForDozing = mDozingRequested &&
- (!mDeviceInteractive || isGoingToSleep() && (isScreenFullyOff() || mIsKeyguard));
+ boolean keyguardForDozing = mDozeServiceHost.getDozingRequested()
+ && (!mDeviceInteractive || isGoingToSleep() && (isScreenFullyOff() || mIsKeyguard));
boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested()
|| keyguardForDozing) && !wakeAndUnlocking;
if (keyguardForDozing) {
@@ -3582,7 +3583,7 @@
public void onStateChanged(int newState) {
mState = newState;
updateReportRejectedTouchVisibility();
- updateDozing();
+ mDozeServiceHost.updateDozing();
updateTheme();
mNavigationBarController.touchAutoDim(mDisplayId);
Trace.beginSection("StatusBar#updateKeyguardState");
@@ -3620,36 +3621,23 @@
public void onDozingChanged(boolean isDozing) {
Trace.beginSection("StatusBar#updateDozing");
mDozing = isDozing;
+ mDozeServiceHost.setDozing(mDozing);
// Collapse the notification panel if open
- boolean dozingAnimated = mDozingRequested && mDozeParameters.shouldControlScreenOff();
+ boolean dozingAnimated = mDozeServiceHost.getDozingRequested()
+ && mDozeParameters.shouldControlScreenOff();
mNotificationPanel.resetViews(dozingAnimated);
updateQsExpansionEnabled();
mKeyguardViewMediator.setDozing(mDozing);
mEntryManager.updateNotifications("onDozingChanged");
- updateDozingState();
+ mDozeServiceHost.updateDozing();
updateScrimController();
updateReportRejectedTouchVisibility();
Trace.endSection();
}
- private void updateDozing() {
- // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
- boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD
- || mBiometricUnlockController.getMode()
- == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
- // When in wake-and-unlock we may not have received a change to mState
- // but we still should not be dozing, manually set to false.
- if (mBiometricUnlockController.getMode() ==
- BiometricUnlockController.MODE_WAKE_AND_UNLOCK) {
- dozing = false;
- }
-
- mStatusBarStateController.setIsDozing(dozing);
- }
-
private void updateKeyguardState() {
mKeyguardStateController.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
mStatusBarKeyguardViewManager.isOccluded());
@@ -3874,10 +3862,11 @@
* collapse the panel after we expanded it, and thus we would end up with a blank
* Keyguard.
*/
- private void updateNotificationPanelTouchState() {
+ void updateNotificationPanelTouchState() {
boolean goingToSleepWithoutAnimation = isGoingToSleep()
&& !mDozeParameters.shouldControlScreenOff();
- boolean disabled = (!mDeviceInteractive && !mPulsing) || goingToSleepWithoutAnimation;
+ boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing())
+ || goingToSleepWithoutAnimation;
mNotificationPanel.setTouchAndAnimationDisabled(disabled);
mNotificationIconAreaController.setAnimationsEnabled(!disabled);
}
@@ -4027,7 +4016,7 @@
}
public void notifyBiometricAuthModeChanged() {
- updateDozing();
+ mDozeServiceHost.updateDozing();
updateScrimController();
mStatusBarWindowViewController.onBiometricAuthModeChanged(
mBiometricUnlockController.isWakeAndUnlock(),
@@ -4063,7 +4052,7 @@
mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
} else if (mBrightnessMirrorVisible) {
mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
- } else if (isPulsing()) {
+ } else if (mDozeServiceHost.isPulsing()) {
mScrimController.transitionTo(ScrimState.PULSING,
mDozeScrimController.getScrimCallback());
} else if (mDozeServiceHost.hasPendingScreenOffCallback()) {
@@ -4093,287 +4082,8 @@
return mStatusBarKeyguardViewManager.isShowing();
}
- @VisibleForTesting
- final class DozeServiceHost implements DozeHost {
- private final ArrayList<Callback> mCallbacks = new ArrayList<>();
- private boolean mAnimateWakeup;
- private boolean mAnimateScreenOff;
- private boolean mIgnoreTouchWhilePulsing;
- private Runnable mPendingScreenOffCallback;
- @VisibleForTesting
- boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean(
- "persist.sysui.wake_performs_auth", true);
-
- @Override
- public String toString() {
- return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]";
- }
-
- public void firePowerSaveChanged(boolean active) {
- for (Callback callback : mCallbacks) {
- callback.onPowerSaveChanged(active);
- }
- }
-
- public void fireNotificationPulse(NotificationEntry entry) {
- Runnable pulseSupressedListener = () -> {
- entry.setPulseSuppressed(true);
- mNotificationIconAreaController.updateAodNotificationIcons();
- };
- for (Callback callback : mCallbacks) {
- callback.onNotificationAlerted(pulseSupressedListener);
- }
- }
-
- @Override
- public void addCallback(@NonNull Callback callback) {
- mCallbacks.add(callback);
- }
-
- @Override
- public void removeCallback(@NonNull Callback callback) {
- mCallbacks.remove(callback);
- }
-
- @Override
- public void startDozing() {
- if (!mDozingRequested) {
- mDozingRequested = true;
- mDozeLog.traceDozing(mDozing);
- updateDozing();
- updateIsKeyguard();
- }
- }
-
- @Override
- public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
- if (reason == DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS) {
- mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
- "com.android.systemui:LONG_PRESS");
- startAssist(new Bundle());
- return;
- }
-
- if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
- mScrimController.setWakeLockScreenSensorActive(true);
- }
-
- if (reason == DozeEvent.PULSE_REASON_DOCKING && mStatusBarWindow != null) {
- mStatusBarWindowViewController.suppressWakeUpGesture(true);
- }
-
- boolean passiveAuthInterrupt = reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
- && mWakeLockScreenPerformsAuth;
- // Set the state to pulsing, so ScrimController will know what to do once we ask it to
- // execute the transition. The pulse callback will then be invoked when the scrims
- // are black, indicating that StatusBar is ready to present the rest of the UI.
- mPulsing = true;
- mDozeScrimController.pulse(new PulseCallback() {
- @Override
- public void onPulseStarted() {
- callback.onPulseStarted();
- updateNotificationPanelTouchState();
- setPulsing(true);
- }
-
- @Override
- public void onPulseFinished() {
- mPulsing = false;
- callback.onPulseFinished();
- updateNotificationPanelTouchState();
- mScrimController.setWakeLockScreenSensorActive(false);
- if (mStatusBarWindow != null) {
- mStatusBarWindowViewController.suppressWakeUpGesture(false);
- }
- setPulsing(false);
- }
-
- private void setPulsing(boolean pulsing) {
- mStatusBarStateController.setPulsing(pulsing);
- mStatusBarKeyguardViewManager.setPulsing(pulsing);
- mKeyguardViewMediator.setPulsing(pulsing);
- mNotificationPanel.setPulsing(pulsing);
- mVisualStabilityManager.setPulsing(pulsing);
- mStatusBarWindowViewController.setPulsing(pulsing);
- mIgnoreTouchWhilePulsing = false;
- if (mKeyguardUpdateMonitor != null && passiveAuthInterrupt) {
- mKeyguardUpdateMonitor.onAuthInterruptDetected(pulsing /* active */);
- }
- updateScrimController();
- mPulseExpansionHandler.setPulsing(pulsing);
- mWakeUpCoordinator.setPulsing(pulsing);
- }
- }, reason);
- // DozeScrimController is in pulse state, now let's ask ScrimController to start
- // pulsing and draw the black frame, if necessary.
- updateScrimController();
- }
-
- @Override
- public void stopDozing() {
- if (mDozingRequested) {
- mDozingRequested = false;
- mDozeLog.traceDozing(mDozing);
- updateDozing();
- }
- }
-
- @Override
- public void onIgnoreTouchWhilePulsing(boolean ignore) {
- if (ignore != mIgnoreTouchWhilePulsing) {
- mDozeLog.tracePulseTouchDisabledByProx(ignore);
- }
- mIgnoreTouchWhilePulsing = ignore;
- if (isDozing() && ignore) {
- mStatusBarWindowViewController.cancelCurrentTouch();
- }
- }
-
- @Override
- public void dozeTimeTick() {
- mNotificationPanel.dozeTimeTick();
- if (mAmbientIndicationContainer instanceof DozeReceiver) {
- ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
- }
- }
-
- @Override
- public boolean isPowerSaveActive() {
- return mBatteryController.isAodPowerSave();
- }
-
- @Override
- public boolean isPulsingBlocked() {
- return mBiometricUnlockController.getMode()
- == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
- }
-
- @Override
- public boolean isProvisioned() {
- return mDeviceProvisionedController.isDeviceProvisioned()
- && mDeviceProvisionedController.isCurrentUserSetup();
- }
-
- @Override
- public boolean isBlockingDoze() {
- if (mBiometricUnlockController.hasPendingAuthentication()) {
- Log.i(TAG, "Blocking AOD because fingerprint has authenticated");
- return true;
- }
- return false;
- }
-
- @Override
- public void extendPulse(int reason) {
- if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
- mScrimController.setWakeLockScreenSensorActive(true);
- }
- if (mDozeScrimController.isPulsing() && mHeadsUpManager.hasNotifications()) {
- mHeadsUpManager.extendHeadsUp();
- } else {
- mDozeScrimController.extendPulse();
- }
- }
-
- @Override
- public void stopPulsing() {
- if (mDozeScrimController.isPulsing()) {
- mDozeScrimController.pulseOutNow();
- }
- }
-
- @Override
- public void setAnimateWakeup(boolean animateWakeup) {
- if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE
- || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING) {
- // Too late to change the wakeup animation.
- return;
- }
- mAnimateWakeup = animateWakeup;
- }
-
- @Override
- public void setAnimateScreenOff(boolean animateScreenOff) {
- mAnimateScreenOff = animateScreenOff;
- }
-
- @Override
- public void onSlpiTap(float screenX, float screenY) {
- if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
- && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
- mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2);
- float viewX = screenX - mTmpInt2[0];
- float viewY = screenY - mTmpInt2[1];
- if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth()
- && 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) {
- dispatchTap(mAmbientIndicationContainer, viewX, viewY);
- }
- }
- }
-
- @Override
- public void setDozeScreenBrightness(int value) {
- mStatusBarWindowController.setDozeScreenBrightness(value);
- }
-
- @Override
- public void setAodDimmingScrim(float scrimOpacity) {
- mScrimController.setAodFrontScrimAlpha(scrimOpacity);
- }
-
- @Override
- public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
- if (onDisplayOffCallback != null) {
- Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one.");
- }
- mPendingScreenOffCallback = onDisplayOffCallback;
- updateScrimController();
- }
-
- /**
- * When the dozing host is waiting for scrims to fade out to change the display state.
- */
- boolean hasPendingScreenOffCallback() {
- return mPendingScreenOffCallback != null;
- }
-
- /**
- * Executes an nullifies the pending display state callback.
- *
- * @see #hasPendingScreenOffCallback()
- * @see #prepareForGentleSleep(Runnable)
- */
- void executePendingScreenOffCallback() {
- if (mPendingScreenOffCallback == null) {
- return;
- }
- mPendingScreenOffCallback.run();
- mPendingScreenOffCallback = null;
- }
-
- private void dispatchTap(View view, float x, float y) {
- long now = SystemClock.elapsedRealtime();
- dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_DOWN);
- dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_UP);
- }
-
- private void dispatchTouchEvent(View view, float x, float y, long now, int action) {
- MotionEvent ev = MotionEvent.obtain(now, now, action, x, y, 0 /* meta */);
- view.dispatchTouchEvent(ev);
- ev.recycle();
- }
-
- private boolean shouldAnimateWakeup() {
- return mAnimateWakeup;
- }
-
- public boolean shouldAnimateScreenOff() {
- return mAnimateScreenOff;
- }
- }
-
public boolean shouldIgnoreTouch() {
- return isDozing() && mDozeServiceHost.mIgnoreTouchWhilePulsing;
+ return isDozing() && mDozeServiceHost.getIgnoreTouchWhilePulsing();
}
// Begin Extra BaseStatusBar methods.
@@ -4400,7 +4110,7 @@
private boolean mVisibleToUser;
protected DevicePolicyManager mDevicePolicyManager;
- protected PowerManager mPowerManager;
+ private final PowerManager mPowerManager;
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
protected KeyguardManager mKeyguardManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 0a2fb2e..76683b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -16,9 +16,6 @@
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
-import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
-
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
@@ -36,6 +33,8 @@
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.systemui.dagger.qualifiers.BgLooper;
+import com.android.systemui.dagger.qualifiers.MainLooper;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -46,7 +45,6 @@
import java.util.WeakHashMap;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
/**
@@ -74,9 +72,8 @@
/**
*/
@Inject
- public BluetoothControllerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper,
- @Named(MAIN_LOOPER_NAME) Looper mainLooper,
- @Nullable LocalBluetoothManager localBluetoothManager) {
+ public BluetoothControllerImpl(Context context, @BgLooper Looper bgLooper,
+ @MainLooper Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager) {
mLocalBluetoothManager = localBluetoothManager;
mBgHandler = new Handler(bgLooper);
mHandler = new H(mainLooper);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
index 089d5c9..0a40b3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
@@ -14,8 +14,6 @@
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,12 +24,12 @@
import android.provider.Settings.Secure;
import android.util.Log;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.settings.CurrentUserTracker;
import java.util.ArrayList;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
/**
@@ -51,8 +49,7 @@
/**
*/
@Inject
- public DeviceProvisionedControllerImpl(Context context,
- @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
+ public DeviceProvisionedControllerImpl(Context context, @MainHandler Handler mainHandler) {
super(context);
mContext = context;
mContentResolver = context.getContentResolver();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
index cade5dc..3ce6239 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
@@ -57,7 +57,7 @@
ExtensionBuilder<T> withCallback(Consumer<T> callback);
ExtensionBuilder<T> withUiMode(int mode, Supplier<T> def);
ExtensionBuilder<T> withFeature(String feature, Supplier<T> def);
- Extension build();
+ Extension<T> build();
}
public interface PluginConverter<T, P> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
index fd030d1..eeef726 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
@@ -135,7 +135,7 @@
}
@Override
- public ExtensionController.Extension build() {
+ public ExtensionController.Extension<T> build() {
// Sort items in ascending order
Collections.sort(mExtension.mProducers, Comparator.comparingInt(Item::sortOrder));
mExtension.notifyChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 1c6d12f..8f201c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
import android.app.ActivityManager;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -26,12 +24,13 @@
import android.os.UserManager;
import android.util.Log;
+import com.android.systemui.dagger.qualifiers.MainHandler;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
/**
@@ -56,7 +55,7 @@
/**
*/
@Inject
- public HotspotControllerImpl(Context context, @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
+ public HotspotControllerImpl(Context context, @MainHandler Handler mainHandler) {
mContext = context;
mConnectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 683cdbb..5a97eed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.policy;
import static com.android.settingslib.Utils.updateLocationEnabled;
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
import android.app.ActivityManager;
import android.app.AppOpsManager;
@@ -36,13 +35,13 @@
import androidx.annotation.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.BgLooper;
import com.android.systemui.util.Utils;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
/**
@@ -66,7 +65,7 @@
private final H mHandler = new H();
@Inject
- public LocationControllerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper) {
+ public LocationControllerImpl(Context context, @BgLooper Looper bgLooper) {
mContext = context;
// Register to listen for changes in location settings.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 7b5d489..60784c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -24,7 +24,6 @@
import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -65,6 +64,7 @@
import com.android.systemui.DemoMode;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.BgLooper;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.MobileSignalController.MobileIconGroup;
@@ -81,7 +81,6 @@
import java.util.Map;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
/** Platform implementation of the network controller. **/
@@ -170,7 +169,7 @@
* Construct this controller object and register for updates.
*/
@Inject
- public NetworkControllerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper,
+ public NetworkControllerImpl(Context context, @BgLooper Looper bgLooper,
DeviceProvisionedController deviceProvisionedController) {
this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 7b4a7d2..502a9bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -679,8 +679,8 @@
}
};
- InputConnection ic = InputConnectionCompat.createWrapper(
- inputConnection, outAttrs, callback);
+ InputConnection ic = inputConnection == null ? null :
+ InputConnectionCompat.createWrapper(inputConnection, outAttrs, callback);
Context userContext = null;
try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index d88ae78..6edd75b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -15,8 +15,6 @@
*/
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.Dependency.BG_HANDLER_NAME;
-
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -50,6 +48,7 @@
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.BgHandler;
import com.android.systemui.settings.CurrentUserTracker;
import java.io.FileDescriptor;
@@ -57,7 +56,6 @@
import java.util.ArrayList;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
/**
@@ -102,7 +100,7 @@
/**
*/
@Inject
- public SecurityControllerImpl(Context context, @Named(BG_HANDLER_NAME) Handler bgHandler) {
+ public SecurityControllerImpl(Context context, @BgHandler Handler bgHandler) {
this(context, bgHandler, null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 655c29c..347d300 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
import android.app.RemoteInput;
import android.content.Context;
import android.content.res.Resources;
@@ -30,9 +28,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
@Singleton
@@ -67,7 +65,7 @@
private final KeyValueListParser mParser = new KeyValueListParser(',');
@Inject
- public SmartReplyConstants(@Named(MAIN_HANDLER_NAME) Handler handler, Context context) {
+ public SmartReplyConstants(@MainHandler Handler handler, Context context) {
mHandler = handler;
mContext = context;
final Resources resources = mContext.getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index bcfbdac..f2d2fae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -18,7 +18,6 @@
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
import android.app.ActivityManager;
import android.app.Dialog;
@@ -58,6 +57,7 @@
import com.android.systemui.Prefs.Key;
import com.android.systemui.R;
import com.android.systemui.SystemUISecondaryUserService;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.qs.tiles.UserDetailView;
@@ -70,7 +70,6 @@
import java.util.List;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
/**
@@ -110,7 +109,7 @@
@Inject
public UserSwitcherController(Context context, KeyguardStateController keyguardStateController,
- @Named(MAIN_HANDLER_NAME) Handler handler, ActivityStarter activityStarter) {
+ @MainHandler Handler handler, ActivityStarter activityStarter) {
mContext = context;
if (!UserManager.isGuestUserEphemeral()) {
mGuestResumeSessionReceiver.register(context);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 29c42d2..1c7a195 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.NotificationManager;
@@ -41,6 +39,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.qs.GlobalSetting;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.util.Utils;
@@ -51,7 +50,6 @@
import java.util.Objects;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
/** Platform implementation of the zen mode controller. **/
@@ -79,7 +77,7 @@
private NotificationManager.Policy mConsolidatedNotificationPolicy;
@Inject
- public ZenModeControllerImpl(Context context, @Named(MAIN_HANDLER_NAME) Handler handler) {
+ public ZenModeControllerImpl(Context context, @MainHandler Handler handler) {
super(context);
mContext = context;
mModeSetting = new GlobalSetting(mContext, handler, Global.ZEN_MODE) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index a55e2cf..2d6027c 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -15,8 +15,6 @@
*/
package com.android.systemui.tuner;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -36,6 +34,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.systemui.DejankUtils;
import com.android.systemui.DemoMode;
+import com.android.systemui.dagger.qualifiers.MainHandler;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -46,7 +45,6 @@
import java.util.Set;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
@@ -83,7 +81,7 @@
/**
*/
@Inject
- public TunerServiceImpl(Context context, @Named(MAIN_HANDLER_NAME) Handler mainHandler,
+ public TunerServiceImpl(Context context, @MainHandler Handler mainHandler,
LeakDetector leakDetector) {
mContext = context;
mContentResolver = mContext.getContentResolver();
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 7e801da..5ed027d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -26,7 +26,7 @@
import com.android.keyguard.KeyguardClockSwitch;
import com.android.keyguard.KeyguardMessageArea;
import com.android.keyguard.KeyguardSliceView;
-import com.android.systemui.SystemUIRootComponent;
+import com.android.systemui.dagger.SystemUIRootComponent;
import com.android.systemui.qs.QSCarrierGroup;
import com.android.systemui.qs.QSFooterImpl;
import com.android.systemui.qs.QSPanel;
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index 63db755..bff405c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -20,7 +20,6 @@
import static android.telephony.ims.feature.ImsFeature.STATE_UNAVAILABLE;
import static com.android.internal.logging.MetricsLogger.VIEW_UNKNOWN;
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -48,6 +47,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.qualifiers.BgLooper;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSHost;
@@ -59,7 +59,6 @@
import java.util.List;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Singleton;
/**
@@ -110,7 +109,7 @@
@Inject
public GarbageMonitor(
Context context,
- @Named(BG_LOOPER_NAME) Looper bgLooper,
+ @BgLooper Looper bgLooper,
LeakDetector leakDetector,
LeakReporter leakReporter) {
mContext = context.getApplicationContext();
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index fdffc43..a96977a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -25,8 +25,8 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.DependencyProvider;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.MainResources;
import java.util.ArrayList;
import java.util.List;
@@ -65,7 +65,7 @@
};
@Inject
- public ProximitySensor(@DependencyProvider.MainResources Resources resources,
+ public ProximitySensor(@MainResources Resources resources,
AsyncSensorManager sensorManager) {
mSensorManager = sensorManager;
Sensor sensor = findBrightnessSensor(resources);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index c338d70..7359fdce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -61,9 +61,13 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.Collections;
+import dagger.Lazy;
+
@RunWithLooper
@RunWith(AndroidTestingRunner.class)
@SmallTest
@@ -71,16 +75,19 @@
private TestableLooper mTestableLooper;
private ScreenDecorations mScreenDecorations;
- private StatusBar mStatusBar;
+ @Mock private StatusBar mStatusBar;
private WindowManager mWindowManager;
private FragmentService mFragmentService;
private FragmentHostManager mFragmentHostManager;
private TunerService mTunerService;
private StatusBarWindowView mView;
private TunablePaddingService mTunablePaddingService;
+ @Mock private Lazy<StatusBar> mStatusBarLazy;
@Before
public void setup() {
+ MockitoAnnotations.initMocks(this);
+
mTestableLooper = TestableLooper.get(this);
mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
new Handler(mTestableLooper.getLooper()));
@@ -88,11 +95,10 @@
mTunerService = mDependency.injectMockDependency(TunerService.class);
mFragmentService = mDependency.injectMockDependency(FragmentService.class);
- mStatusBar = mock(StatusBar.class);
mWindowManager = mock(WindowManager.class);
mView = spy(new StatusBarWindowView(mContext, null));
+ when(mStatusBarLazy.get()).thenReturn(mStatusBar);
when(mStatusBar.getStatusBarWindow()).thenReturn(mView);
- mContext.putComponent(StatusBar.class, mStatusBar);
Display display = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
when(mWindowManager.getDefaultDisplay()).thenReturn(display);
@@ -102,7 +108,7 @@
when(mFragmentService.getFragmentHostManager(any())).thenReturn(mFragmentHostManager);
- mScreenDecorations = new ScreenDecorations(mContext) {
+ mScreenDecorations = new ScreenDecorations(mContext, mStatusBarLazy) {
@Override
public void start() {
super.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index e1eb3b0..b089b74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -38,15 +38,18 @@
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.hardware.biometrics.Authenticator;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.hardware.face.FaceManager;
import android.os.Bundle;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper.RunWithLooper;
+import com.android.internal.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -89,9 +92,9 @@
when(context.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE))
- .thenReturn(true);
+ .thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
- .thenReturn(true);
+ .thenReturn(true);
when(mDialog1.getOpPackageName()).thenReturn("Dialog1");
when(mDialog2.getOpPackageName()).thenReturn("Dialog2");
@@ -170,20 +173,34 @@
@Test
public void testOnAuthenticationSucceededInvoked_whenSystemRequested() {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
- mAuthController.onBiometricAuthenticated(true, null /* failureReason */);
+ mAuthController.onBiometricAuthenticated();
verify(mDialog1).onAuthenticationSucceeded();
}
@Test
- public void testOnAuthenticationFailedInvoked_whenSystemRequested() {
+ public void testOnAuthenticationFailedInvoked_whenBiometricRejected() {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
- final String failureReason = "failure reason";
- mAuthController.onBiometricAuthenticated(false, failureReason);
+ mAuthController.onBiometricError(BiometricAuthenticator.TYPE_NONE,
+ BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
+ 0 /* vendorCode */);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onAuthenticationFailed(captor.capture());
- assertEquals(captor.getValue(), failureReason);
+ assertEquals(captor.getValue(), mContext.getString(R.string.biometric_not_recognized));
+ }
+
+ @Test
+ public void testOnAuthenticationFailedInvoked_whenBiometricTimedOut() {
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ final int error = BiometricConstants.BIOMETRIC_ERROR_TIMEOUT;
+ final int vendorCode = 0;
+ mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
+
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ verify(mDialog1).onAuthenticationFailed(captor.capture());
+
+ assertEquals(captor.getValue(), FaceManager.getErrorString(mContext, error, vendorCode));
}
@Test
@@ -199,27 +216,27 @@
}
@Test
- public void testOnErrorInvoked_whenSystemRequested() {
+ public void testOnErrorInvoked_whenSystemRequested() throws Exception {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = 1;
- final String errMessage = "error message";
- mAuthController.onBiometricError(error, errMessage);
+ final int vendorCode = 0;
+ mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onError(captor.capture());
- assertEquals(captor.getValue(), errMessage);
+ assertEquals(captor.getValue(), FaceManager.getErrorString(mContext, error, vendorCode));
}
@Test
public void testErrorLockout_whenCredentialAllowed_AnimatesToCredentialUI() {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
- final String errorString = "lockout";
+ final int vendorCode = 0;
when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
- mAuthController.onBiometricError(error, errorString);
+ mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
verify(mDialog1, never()).onError(anyString());
verify(mDialog1).animateToCredentialUI();
}
@@ -228,11 +245,11 @@
public void testErrorLockoutPermanent_whenCredentialAllowed_AnimatesToCredentialUI() {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
- final String errorString = "lockout_permanent";
+ final int vendorCode = 0;
when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
- mAuthController.onBiometricError(error, errorString);
+ mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
verify(mDialog1, never()).onError(anyString());
verify(mDialog1).animateToCredentialUI();
}
@@ -241,12 +258,12 @@
public void testErrorLockout_whenCredentialNotAllowed_sendsOnError() {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
- final String errorString = "lockout";
+ final int vendorCode = 0;
when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
- mAuthController.onBiometricError(error, errorString);
- verify(mDialog1).onError(eq(errorString));
+ mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
+ verify(mDialog1).onError(eq(FaceManager.getErrorString(mContext, error, vendorCode)));
verify(mDialog1, never()).animateToCredentialUI();
}
@@ -254,12 +271,12 @@
public void testErrorLockoutPermanent_whenCredentialNotAllowed_sendsOnError() {
showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
- final String errorString = "lockout_permanent";
+ final int vendorCode = 0;
when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
- mAuthController.onBiometricError(error, errorString);
- verify(mDialog1).onError(eq(errorString));
+ mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
+ verify(mDialog1).onError(eq(FaceManager.getErrorString(mContext, error, vendorCode)));
verify(mDialog1, never()).animateToCredentialUI();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 8f4de3f..47b35fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -22,7 +22,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.anyObject;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -60,6 +59,8 @@
import java.time.Duration;
import java.util.concurrent.TimeUnit;
+import dagger.Lazy;
+
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
@@ -86,6 +87,8 @@
private IThermalEventListener mUsbThermalEventListener;
private IThermalEventListener mSkinThermalEventListener;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock private Lazy<StatusBar> mStatusBarLazy;
+ @Mock private StatusBar mStatusBar;
@Before
public void setup() {
@@ -93,7 +96,8 @@
mMockWarnings = mDependency.injectMockDependency(WarningsUI.class);
mEnhancedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class);
- mContext.putComponent(StatusBar.class, mock(StatusBar.class));
+ when(mStatusBarLazy.get()).thenReturn(mStatusBar);
+
mContext.addMockSystemService(Context.POWER_SERVICE, mPowerManager);
createPowerUi();
@@ -682,7 +686,7 @@
}
private void createPowerUi() {
- mPowerUI = new PowerUI(mContext, mBroadcastDispatcher);
+ mPowerUI = new PowerUI(mContext, mBroadcastDispatcher, mStatusBarLazy);
mPowerUI.mComponents = mContext.getComponents();
mPowerUI.mThermalService = mThermalServiceMock;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index eb198c6..8c9ae71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -418,10 +418,9 @@
@Test
public void testOnBiometricAuthenticated() {
- String failureReason = "test_failure_reason";
- mCommandQueue.onBiometricAuthenticated(true /* authenticated */, failureReason);
+ mCommandQueue.onBiometricAuthenticated();
waitForIdleSync();
- verify(mCallbacks).onBiometricAuthenticated(eq(true), eq(failureReason));
+ verify(mCallbacks).onBiometricAuthenticated();
}
@Test
@@ -434,11 +433,12 @@
@Test
public void testOnBiometricError() {
- final int errorCode = 1;
- String errorMessage = "test_error_message";
- mCommandQueue.onBiometricError(errorCode, errorMessage);
+ final int modality = 1;
+ final int error = 2;
+ final int vendorCode = 3;
+ mCommandQueue.onBiometricError(modality, error, vendorCode);
waitForIdleSync();
- verify(mCallbacks).onBiometricError(eq(errorCode), eq(errorMessage));
+ verify(mCallbacks).onBiometricError(eq(modality), eq(error), eq(vendorCode));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index ebdf851..bde7ef9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -82,6 +82,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -148,8 +149,12 @@
private final CountDownLatch mCountDownLatch;
TestableNotificationEntryManager() {
- super(new NotificationData(mock(NotificationSectionsFeatureManager.class),
- mock(NotifLog.class)), mock(NotifLog.class));
+ super(
+ new NotificationData(
+ mock(NotificationSectionsFeatureManager.class),
+ mock(NotifLog.class),
+ mock(PeopleNotificationIdentifier.class)),
+ mock(NotifLog.class));
mCountDownLatch = new CountDownLatch(1);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
index 9202c51..2435bb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -78,8 +79,10 @@
// TODO: Remove this once EntryManager no longer needs to be mocked
private NotificationData mNotificationData =
- new NotificationData(new NotificationSectionsFeatureManager(
- new DeviceConfigProxyFake(), mContext), mock(NotifLog.class));
+ new NotificationData(
+ new NotificationSectionsFeatureManager(new DeviceConfigProxyFake(), mContext),
+ mock(NotifLog.class),
+ mock(PeopleNotificationIdentifier.class));
private int mNextNotifId = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
index 640984b..dba0174 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
@@ -81,6 +81,7 @@
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
@@ -639,7 +640,10 @@
public static class TestableNotificationData extends NotificationData {
public TestableNotificationData(NotificationSectionsFeatureManager sectionsFeatureManager) {
- super(sectionsFeatureManager, mock(NotifLog.class));
+ super(
+ sectionsFeatureManager,
+ mock(NotifLog.class),
+ mock(PeopleNotificationIdentifier.class));
}
public static final String OVERRIDE_RANK = "r";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 72bea56..4a0b371 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -34,7 +34,9 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import android.testing.TestableResources;
+import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -78,11 +80,14 @@
private KeyguardBypassController mKeyguardBypassController;
@Mock
private DozeParameters mDozeParameters;
+ @Mock
+ private MetricsLogger mMetricsLogger;
private BiometricUnlockController mBiometricUnlockController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ TestableResources res = getContext().getOrCreateTestableResources();
when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
@@ -92,10 +97,11 @@
mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
mDependency.injectTestDependency(StatusBarWindowController.class,
mStatusBarWindowController);
+ res.addOverride(com.android.internal.R.integer.config_wakeUpDelayDoze, 0);
mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController,
mKeyguardViewMediator, mScrimController, mStatusBar, mKeyguardStateController,
- mHandler, mUpdateMonitor, 0 /* wakeUpDelay */, mKeyguardBypassController,
- mDozeParameters);
+ mHandler, mUpdateMonitor, res.getResources(), mKeyguardBypassController,
+ mDozeParameters, mMetricsLogger);
mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
new file mode 100644
index 0000000..b05172c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.systemui.statusbar.phone;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+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.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.PowerManager;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.doze.DozeEvent;
+import com.android.systemui.doze.DozeHost;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.StatusBarStateControllerImpl;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+
+import dagger.Lazy;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DozeServiceHostTest extends SysuiTestCase {
+
+ private DozeServiceHost mDozeServiceHost;
+
+ @Mock private HeadsUpManagerPhone mHeadsUpManager;
+ @Mock private ScrimController mScrimController;
+ @Mock private DozeScrimController mDozeScrimController;
+ @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
+ @Mock private VisualStabilityManager mVisualStabilityManager;
+ @Mock private KeyguardViewMediator mKeyguardViewMediator;
+ @Mock private StatusBarStateControllerImpl mStatusBarStateController;
+ @Mock private BatteryController mBatteryController;
+ @Mock private DeviceProvisionedController mDeviceProvisionedController;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock private AssistManager mAssistManager;
+ @Mock private DozeLog mDozeLog;
+ @Mock private PulseExpansionHandler mPulseExpansionHandler;
+ @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
+ @Mock private StatusBarWindowController mStatusBarWindowController;
+ @Mock private PowerManager mPowerManager;
+ @Mock private WakefulnessLifecycle mWakefullnessLifecycle;
+ @Mock private StatusBar mStatusBar;
+ @Mock private NotificationIconAreaController mNotificationIconAreaController;
+ @Mock private StatusBarWindowViewController mStatusBarWindowViewController;
+ @Mock private StatusBarWindowView mStatusBarWindow;
+ @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock private NotificationPanelView mNotificationPanel;
+ @Mock private View mAmbientIndicationContainer;
+ @Mock private BiometricUnlockController mBiometricUnlockController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
+ mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle,
+ mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
+ mBatteryController, mScrimController, mBiometricUnlockControllerLazy,
+ mKeyguardViewMediator, mAssistManager, mDozeScrimController, mKeyguardUpdateMonitor,
+ mVisualStabilityManager, mPulseExpansionHandler, mStatusBarWindowController,
+ mNotificationWakeUpCoordinator);
+
+ mDozeServiceHost.initialize(mStatusBar, mNotificationIconAreaController,
+ mStatusBarWindowViewController, mStatusBarWindow, mStatusBarKeyguardViewManager,
+ mNotificationPanel, mAmbientIndicationContainer);
+ }
+
+ @Test
+ public void testStartStopDozing() {
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
+ when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
+
+ assertFalse(mDozeServiceHost.getDozingRequested());
+
+ mDozeServiceHost.startDozing();
+ verify(mStatusBarStateController).setIsDozing(eq(true));
+ verify(mStatusBar).updateIsKeyguard();
+
+ mDozeServiceHost.stopDozing();
+ verify(mStatusBarStateController).setIsDozing(eq(false));
+ }
+
+
+ @Test
+ public void testPulseWhileDozing_updatesScrimController() {
+ mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+ mStatusBar.showKeyguardImpl();
+
+ // Keep track of callback to be able to stop the pulse
+// DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
+// doAnswer(invocation -> {
+// pulseCallback[0] = invocation.getArgument(0);
+// return null;
+// }).when(mDozeScrimController).pulse(any(), anyInt());
+
+ // Starting a pulse should change the scrim controller to the pulsing state
+ mDozeServiceHost.pulseWhileDozing(new DozeHost.PulseCallback() {
+ @Override
+ public void onPulseStarted() {
+ }
+
+ @Override
+ public void onPulseFinished() {
+ }
+ }, DozeEvent.PULSE_REASON_NOTIFICATION);
+
+ ArgumentCaptor<DozeHost.PulseCallback> pulseCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(DozeHost.PulseCallback.class);
+
+ verify(mDozeScrimController).pulse(
+ pulseCallbackArgumentCaptor.capture(), eq(DozeEvent.PULSE_REASON_NOTIFICATION));
+ verify(mStatusBar).updateScrimController();
+ reset(mStatusBar);
+
+ pulseCallbackArgumentCaptor.getValue().onPulseFinished();
+ assertFalse(mDozeScrimController.isPulsing());
+ verify(mStatusBar).updateScrimController();
+ }
+
+
+ @Test
+ public void testPulseWhileDozingWithDockingReason_suppressWakeUpGesture() {
+ // Keep track of callback to be able to stop the pulse
+ final DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
+ doAnswer(invocation -> {
+ pulseCallback[0] = invocation.getArgument(0);
+ return null;
+ }).when(mDozeScrimController).pulse(any(), anyInt());
+
+ // Starting a pulse while docking should suppress wakeup gesture
+ mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class),
+ DozeEvent.PULSE_REASON_DOCKING);
+ verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(true));
+
+ // Ending a pulse should restore wakeup gesture
+ pulseCallback[0].onPulseFinished();
+ verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(false));
+ }
+
+ @Test
+ public void testPulseWhileDozing_notifyAuthInterrupt() {
+ HashSet<Integer> reasonsWantingAuth = new HashSet<>(
+ Collections.singletonList(DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN));
+ HashSet<Integer> reasonsSkippingAuth = new HashSet<>(
+ Arrays.asList(DozeEvent.PULSE_REASON_INTENT,
+ DozeEvent.PULSE_REASON_NOTIFICATION,
+ DozeEvent.PULSE_REASON_SENSOR_SIGMOTION,
+ DozeEvent.REASON_SENSOR_PICKUP,
+ DozeEvent.REASON_SENSOR_DOUBLE_TAP,
+ DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS,
+ DozeEvent.PULSE_REASON_DOCKING,
+ DozeEvent.REASON_SENSOR_WAKE_UP,
+ DozeEvent.REASON_SENSOR_TAP));
+ HashSet<Integer> reasonsThatDontPulse = new HashSet<>(
+ Arrays.asList(DozeEvent.REASON_SENSOR_PICKUP,
+ DozeEvent.REASON_SENSOR_DOUBLE_TAP,
+ DozeEvent.REASON_SENSOR_TAP));
+
+ doAnswer(invocation -> {
+ DozeHost.PulseCallback callback = invocation.getArgument(0);
+ callback.onPulseStarted();
+ return null;
+ }).when(mDozeScrimController).pulse(any(), anyInt());
+
+ mDozeServiceHost.mWakeLockScreenPerformsAuth = true;
+ for (int i = 0; i < DozeEvent.TOTAL_REASONS; i++) {
+ reset(mKeyguardUpdateMonitor);
+ mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class), i);
+ if (reasonsWantingAuth.contains(i)) {
+ verify(mKeyguardUpdateMonitor).onAuthInterruptDetected(eq(true));
+ } else if (reasonsSkippingAuth.contains(i) || reasonsThatDontPulse.contains(i)) {
+ verify(mKeyguardUpdateMonitor, never()).onAuthInterruptDetected(eq(true));
+ } else {
+ throw new AssertionError("Reason " + i + " isn't specified as wanting or skipping"
+ + " passive auth. Please consider how this pulse reason should behave.");
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index cff6635..4853f20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -57,6 +57,7 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -236,8 +237,11 @@
mock(PluginManager.class),
mock(ShadeController.class),
mock(NotificationLockscreenUserManager.class),
- new NotificationEntryManager(new NotificationData(mock(
- NotificationSectionsFeatureManager.class), mock(NotifLog.class)),
+ new NotificationEntryManager(
+ new NotificationData(
+ mock(NotificationSectionsFeatureManager.class),
+ mock(NotifLog.class),
+ mock(PeopleNotificationIdentifier.class)),
mock(NotifLog.class)),
mock(KeyguardStateController.class),
statusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 1b132b9..85c247e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -64,6 +64,7 @@
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -249,7 +250,7 @@
finishAnimationsImmediately();
assertScrimAlpha(OPAQUE /* front */,
- SEMI_TRANSPARENT /* back */,
+ OPAQUE /* back */,
TRANSPARENT /* bubble */);
assertScrimTint(true /* front */,
@@ -858,6 +859,22 @@
mScrimForBubble.getDefaultFocusHighlightEnabled());
}
+ @Test
+ public void testIsLowPowerMode() {
+ HashSet<ScrimState> lowPowerModeStates = new HashSet<>(Arrays.asList(
+ ScrimState.OFF, ScrimState.AOD, ScrimState.PULSING));
+ HashSet<ScrimState> regularStates = new HashSet<>(Arrays.asList(
+ ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, ScrimState.BOUNCER,
+ ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR, ScrimState.UNLOCKED,
+ ScrimState.BUBBLE_EXPANDED));
+
+ for (ScrimState state : ScrimState.values()) {
+ if (!lowPowerModeStates.contains(state) && !regularStates.contains(state)) {
+ Assert.fail("Scrim state not whitelisted nor blacklisted as low power mode");
+ }
+ }
+ }
+
private void assertScrimTint(boolean front, boolean behind, boolean bubble) {
Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
+ " with scrim: " + getScrimName(mScrimInFront) + " and tint: "
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 03d0cea..66c01ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -61,7 +61,9 @@
import android.testing.TestableLooper.RunWithLooper;
import android.util.DisplayMetrics;
import android.util.SparseArray;
+import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
@@ -70,6 +72,7 @@
import com.android.internal.logging.testing.FakeMetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.Dependency;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.InitController;
@@ -82,8 +85,6 @@
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.doze.DozeEvent;
-import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
@@ -142,9 +143,6 @@
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
import dagger.Lazy;
@@ -173,6 +171,7 @@
@Mock private ScrimController mScrimController;
@Mock private DozeScrimController mDozeScrimController;
@Mock private ArrayList<NotificationEntry> mNotificationList;
+ @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
@Mock private BiometricUnlockController mBiometricUnlockController;
@Mock private NotificationData mNotificationData;
@Mock private NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor;
@@ -227,11 +226,15 @@
@Mock private DozeParameters mDozeParameters;
@Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
@Mock private LockscreenWallpaper mLockscreenWallpaper;
+ @Mock private DozeServiceHost mDozeServiceHost;
+ @Mock private LinearLayout mLockIconContainer;
+ @Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
mDependency.injectTestDependency(NotificationFilter.class, mNotificationFilter);
+ mDependency.injectMockDependency(KeyguardDismissUtil.class);
IPowerManager powerManagerService = mock(IPowerManager.class);
mPowerManager = new PowerManager(mContext, powerManagerService,
@@ -293,6 +296,7 @@
.thenReturn(mStatusBarWindowViewController);
when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
+ when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
mStatusBar = new StatusBar(
mContext,
@@ -356,23 +360,37 @@
mNotifLog,
mDozeParameters,
mScrimController,
- mLockscreenWallpaperLazy);
+ mLockscreenWallpaperLazy,
+ mBiometricUnlockControllerLazy,
+ mDozeServiceHost,
+ mPowerManager,
+ mDozeScrimController);
+
+ when(mStatusBarWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
+ mLockIconContainer);
+
+ when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class),
+ any(NotificationPanelView.class), any(BiometricUnlockController.class),
+ any(ViewGroup.class), any(ViewGroup.class), any(KeyguardBypassController.class)))
+ .thenReturn(mStatusBarKeyguardViewManager);
+
+ when(mKeyguardViewMediator.getViewMediatorCallback()).thenReturn(
+ mKeyguardVieMediatorCallback);
+
// TODO: we should be able to call mStatusBar.start() and have all the below values
// initialized automatically.
mStatusBar.mComponents = mContext.getComponents();
- mStatusBar.mStatusBarKeyguardViewManager = mStatusBarKeyguardViewManager;
mStatusBar.mStatusBarWindow = mStatusBarWindowView;
- mStatusBar.mBiometricUnlockController = mBiometricUnlockController;
mStatusBar.mNotificationPanel = mNotificationPanelView;
mStatusBar.mCommandQueue = mCommandQueue;
mStatusBar.mDozeScrimController = mDozeScrimController;
mStatusBar.mNotificationIconAreaController = mNotificationIconAreaController;
mStatusBar.mPresenter = mNotificationPresenter;
mStatusBar.mKeyguardIndicationController = mKeyguardIndicationController;
- mStatusBar.mPowerManager = mPowerManager;
mStatusBar.mBarService = mBarService;
mStatusBar.mStackScroller = mStackScroller;
mStatusBar.mStatusBarWindowViewController = mStatusBarWindowViewController;
+ mStatusBar.startKeyguard();
mStatusBar.putComponent(StatusBar.class, mStatusBar);
Dependency.get(InitController.class).executePostInitTasks();
entryManager.setUpForTest(mock(NotificationPresenter.class), mStackScroller,
@@ -737,83 +755,18 @@
mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
mStatusBar.showKeyguardImpl();
- // Keep track of callback to be able to stop the pulse
- DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
- doAnswer(invocation -> {
- pulseCallback[0] = invocation.getArgument(0);
- return null;
- }).when(mDozeScrimController).pulse(any(), anyInt());
-
// Starting a pulse should change the scrim controller to the pulsing state
- mStatusBar.mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class),
- DozeEvent.PULSE_REASON_NOTIFICATION);
+ when(mDozeServiceHost.isPulsing()).thenReturn(true);
+ mStatusBar.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.PULSING), any());
// Ending a pulse should take it back to keyguard state
- pulseCallback[0].onPulseFinished();
+ when(mDozeServiceHost.isPulsing()).thenReturn(false);
+ mStatusBar.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
}
@Test
- public void testPulseWhileDozing_notifyAuthInterrupt() {
- HashSet<Integer> reasonsWantingAuth = new HashSet<>(
- Collections.singletonList(DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN));
- HashSet<Integer> reasonsSkippingAuth = new HashSet<>(
- Arrays.asList(DozeEvent.PULSE_REASON_INTENT,
- DozeEvent.PULSE_REASON_NOTIFICATION,
- DozeEvent.PULSE_REASON_SENSOR_SIGMOTION,
- DozeEvent.REASON_SENSOR_PICKUP,
- DozeEvent.REASON_SENSOR_DOUBLE_TAP,
- DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS,
- DozeEvent.PULSE_REASON_DOCKING,
- DozeEvent.REASON_SENSOR_WAKE_UP,
- DozeEvent.REASON_SENSOR_TAP));
- HashSet<Integer> reasonsThatDontPulse = new HashSet<>(
- Arrays.asList(DozeEvent.REASON_SENSOR_PICKUP,
- DozeEvent.REASON_SENSOR_DOUBLE_TAP,
- DozeEvent.REASON_SENSOR_TAP));
-
- doAnswer(invocation -> {
- DozeHost.PulseCallback callback = invocation.getArgument(0);
- callback.onPulseStarted();
- return null;
- }).when(mDozeScrimController).pulse(any(), anyInt());
-
- mStatusBar.mDozeServiceHost.mWakeLockScreenPerformsAuth = true;
- for (int i = 0; i < DozeEvent.TOTAL_REASONS; i++) {
- reset(mKeyguardUpdateMonitor);
- mStatusBar.mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class), i);
- if (reasonsWantingAuth.contains(i)) {
- verify(mKeyguardUpdateMonitor).onAuthInterruptDetected(eq(true));
- } else if (reasonsSkippingAuth.contains(i) || reasonsThatDontPulse.contains(i)) {
- verify(mKeyguardUpdateMonitor, never()).onAuthInterruptDetected(eq(true));
- } else {
- throw new AssertionError("Reason " + i + " isn't specified as wanting or skipping"
- + " passive auth. Please consider how this pulse reason should behave.");
- }
- }
- }
-
- @Test
- public void testPulseWhileDozingWithDockingReason_suppressWakeUpGesture() {
- // Keep track of callback to be able to stop the pulse
- final DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
- doAnswer(invocation -> {
- pulseCallback[0] = invocation.getArgument(0);
- return null;
- }).when(mDozeScrimController).pulse(any(), anyInt());
-
- // Starting a pulse while docking should suppress wakeup gesture
- mStatusBar.mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class),
- DozeEvent.PULSE_REASON_DOCKING);
- verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(true));
-
- // Ending a pulse should restore wakeup gesture
- pulseCallback[0].onPulseFinished();
- verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(false));
- }
-
- @Test
public void testSetState_changesIsFullScreenUserSwitcherState() {
mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
assertFalse(mStatusBar.isFullScreenUserSwitcherState());
@@ -839,27 +792,17 @@
}
@Test
- public void testStartStopDozing() {
- mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
- when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
-
- mStatusBar.mDozeServiceHost.startDozing();
- verify(mStatusBarStateController).setIsDozing(eq(true));
-
- mStatusBar.mDozeServiceHost.stopDozing();
- verify(mStatusBarStateController).setIsDozing(eq(false));
- }
-
- @Test
public void testOnStartedWakingUp_isNotDozing() {
mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
- mStatusBar.mDozeServiceHost.startDozing();
- verify(mStatusBarStateController).setIsDozing(eq(true));
+ when(mDozeServiceHost.getDozingRequested()).thenReturn(true);
+ mStatusBar.updateIsKeyguard();
+ // TODO: mNotificationPanelView.expand(false) gets called twice. Should be once.
+ verify(mNotificationPanelView, times(2)).expand(eq(false));
clearInvocations(mNotificationPanelView);
mStatusBar.mWakefulnessObserver.onStartedWakingUp();
- verify(mStatusBarStateController).setIsDozing(eq(false));
+ verify(mDozeServiceHost).stopDozing();
verify(mNotificationPanelView).expand(eq(false));
}
@@ -867,7 +810,8 @@
public void testOnStartedWakingUp_doesNotDismissBouncer_whenPulsing() {
mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
- mStatusBar.mDozeServiceHost.startDozing();
+ when(mDozeServiceHost.getDozingRequested()).thenReturn(true);
+ mStatusBar.updateIsKeyguard();
clearInvocations(mNotificationPanelView);
mStatusBar.setBouncerShowing(true);
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 5d6d1c9..5712566 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -253,6 +253,8 @@
NOTE_NETWORK_LOGGED_IN = 744;
// A partial connectivity network was detected during network validation
NOTE_NETWORK_PARTIAL_CONNECTIVITY = 745;
+ // Private DNS is broken in strict mode
+ NOTE_NETWORK_PRIVATE_DNS_BROKEN = 746;
// Notify the user that their work profile has been deleted
// Package: android
diff --git a/services/art-profile b/services/art-profile
index 8b911a2..a0338d5 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -21148,7 +21148,6 @@
HPLcom/android/server/wm/RemoteAnimationController;->lambda$goodToGo$1$RemoteAnimationController([Landroid/view/RemoteAnimationTarget;[Landroid/view/RemoteAnimationTarget;)V
HPLcom/android/server/wm/RunningTasks;->getTasks(ILjava/util/List;IILjava/util/ArrayList;IZZLandroid/util/ArraySet;)V
HPLcom/android/server/wm/SplashScreenStartingData;->createStartingSurface(Lcom/android/server/wm/ActivityRecord;)Lcom/android/server/policy/WindowManagerPolicy$StartingSurface;
-HPLcom/android/server/wm/SurfaceAnimator$Animatable;->shouldDeferAnimationFinish(Ljava/lang/Runnable;)Z
HPLcom/android/server/wm/SurfaceAnimator;->lambda$getFinishedCallback$0$SurfaceAnimator(Lcom/android/server/wm/AnimationAdapter;Ljava/lang/Runnable;)V
HPLcom/android/server/wm/Task;->positionChildAt(ILcom/android/server/wm/ActivityRecord;Z)V
HPLcom/android/server/wm/TaskRecord;->handlesOrientationChangeFromDescendant()Z
@@ -21191,7 +21190,6 @@
HPLcom/android/server/wm/RemoteAnimationController;->createRemoteAnimationRecord(Lcom/android/server/wm/ActivityRecord;Landroid/graphics/Point;Landroid/graphics/Rect;Landroid/graphics/Rect;)Lcom/android/server/wm/RemoteAnimationController$RemoteAnimationRecord;
HPLcom/android/server/wm/RemoteAnimationController;->linkToDeathOfRunner()V
HPLcom/android/server/wm/RemoteAnimationController;->writeStartDebugStatement()V
-HPLcom/android/server/wm/SurfaceAnimator$Animatable;->shouldDeferAnimationFinish(Ljava/lang/Runnable;)Z
HPLcom/android/server/wm/TaskRecord;->setMinDimensions(Landroid/content/pm/ActivityInfo;)V
HPLcom/android/server/wm/WallpaperAnimationAdapter;->startWallpaperAnimations(Lcom/android/server/wm/WindowManagerService;JJLjava/util/function/Consumer;Ljava/util/ArrayList;)[Landroid/view/RemoteAnimationTarget;
HPLcom/android/server/wm/WindowStateAnimator;->finishDrawingLocked(Landroid/view/SurfaceControl$Transaction;)Z
@@ -21206,13 +21204,152 @@
HPLcom/android/server/wm/AnimationAdapter;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
HPLcom/android/server/wm/RemoteAnimationController$RemoteAnimationAdapterWrapper;->dump(Ljava/io/PrintWriter;Ljava/lang/String;)V
HPLcom/android/server/wm/RemoteAnimationController$RemoteAnimationRecord;-><init>(Lcom/android/server/wm/RemoteAnimationController;Lcom/android/server/wm/ActivityRecord;Landroid/graphics/Point;Landroid/graphics/Rect;Landroid/graphics/Rect;)V
-HPLcom/android/server/wm/SurfaceAnimator$Animatable;->shouldDeferAnimationFinish(Ljava/lang/Runnable;)Z
HPLcom/android/server/wm/WindowProcessController;->setBoundClientUids(Landroid/util/ArraySet;)V
HPLcom/google/android/startop/iorap/IorapForwardingService$AppLaunchObserver;->lambda$onActivityLaunched$2$IorapForwardingService$AppLaunchObserver([BILcom/google/android/startop/iorap/IIorap;)V
HPLcom/google/android/startop/iorap/IorapForwardingService$AppLaunchObserver;->lambda$onActivityLaunchFinished$4$IorapForwardingService$AppLaunchObserver([BJLcom/google/android/startop/iorap/IIorap;)V
HPLcom/google/android/startop/iorap/IorapForwardingService$AppLaunchObserver;->lambda$onIntentStarted$0$IorapForwardingService$AppLaunchObserver(Landroid/content/Intent;JLcom/google/android/startop/iorap/IIorap;)V
HPLcom/android/server/statusbar/StatusBarManagerService;->updateUiVisibilityLocked(IIIIILandroid/graphics/Rect;Landroid/graphics/Rect;Z)V
HPLcom/android/server/wm/AnimationAdapter;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
-HPLcom/android/server/wm/SurfaceAnimator$Animatable;->shouldDeferAnimationFinish(Ljava/lang/Runnable;)Z
HPLcom/google/android/startop/iorap/IIorap$Stub$Proxy;->onAppLaunchEvent(Lcom/google/android/startop/iorap/RequestId;Lcom/google/android/startop/iorap/AppLaunchEvent;)V
HPLcom/google/android/startop/iorap/RequestId;->nextValueForSequence()Lcom/google/android/startop/iorap/RequestId;
+HPLcom/android/server/AlarmManagerService;->decrementAlarmCount(II)V
+HPLcom/android/server/am/ActivityManagerService$4;->newArray(I)[Landroid/content/IntentFilter;
+HPLcom/android/server/am/ActivityManagerService$PidMap;->remove(Lcom/android/server/am/ProcessRecord;)V
+HPLcom/android/server/am/ActivityManagerService;->updateOomAdjLocked(Ljava/lang/String;)V
+HPLcom/android/server/am/ActivityManagerShellCommand$1;-><init>(Lcom/android/server/am/ActivityManagerShellCommand;)V
+HPLcom/android/server/am/ActivityManagerShellCommand;-><init>(Lcom/android/server/am/ActivityManagerService;Z)V
+HPLcom/android/server/am/ActivityManagerShellCommand;->runStartActivity(Ljava/io/PrintWriter;)I
+HPLcom/android/server/am/HostingRecord;-><init>(Ljava/lang/String;Landroid/content/ComponentName;I)V
+HPLcom/android/server/am/PendingIntentController;->registerIntentSenderCancelListener(Landroid/content/IIntentSender;Lcom/android/internal/os/IResultReceiver;)V
+HPLcom/android/server/appprediction/-$$Lambda$AppPredictionManagerService$PredictionManagerServiceStub$vSY20eQq5y5FXrxhhqOTcEmezTs;-><init>(Landroid/app/prediction/AppPredictionSessionId;)V
+HPLcom/android/server/appprediction/-$$Lambda$RemoteAppPredictionService$9DCowUTEF8fYuBlWGxOmP5hTAWA;-><init>(Landroid/app/prediction/AppPredictionSessionId;)V
+HPLcom/android/server/appprediction/RemoteAppPredictionService;->lambda$requestPredictionUpdate$6(Landroid/app/prediction/AppPredictionSessionId;Landroid/service/appprediction/IPredictionService;)V
+HPLcom/android/server/autofill/-$$Lambda$AutofillManagerService$1$1-WNu3tTkxodB_LsZ7dGIlvrPN0;->visit(Ljava/lang/Object;)V
+HPLcom/android/server/autofill/ui/-$$Lambda$AutoFillUI$56AC3ykfo4h_e2LSjdkJ3XQn370;-><init>(Lcom/android/server/autofill/ui/AutoFillUI;Lcom/android/server/autofill/ui/AutoFillUI$AutoFillUiCallback;)V
+HPLcom/android/server/contentsuggestions/-$$Lambda$RemoteContentSuggestionsService$Enqw46SYVKFK9F2xX4qUcIu5_3I;->run(Landroid/os/IInterface;)V
+HPLcom/android/server/contentsuggestions/-$$Lambda$RemoteContentSuggestionsService$eoGnQ2MDLLnW1UBX6wxNE1VBLAk;->run(Landroid/os/IInterface;)V
+HPLcom/android/server/contentsuggestions/-$$Lambda$RemoteContentSuggestionsService$VKh1DoMPNSPjPfnVGdsInmxuqzc;->run(Landroid/os/IInterface;)V
+HPLcom/android/server/contentsuggestions/-$$Lambda$RemoteContentSuggestionsService$yUTbcaYlZCYTmagCkNJ3i2VCkY4;->run(Landroid/os/IInterface;)V
+HPLcom/android/server/contentsuggestions/RemoteContentSuggestionsService;->getServiceInterface(Landroid/os/IBinder;)Landroid/os/IInterface;
+HPLcom/android/server/display/AutomaticBrightnessController;->updateAutoBrightness(ZZ)V
+HPLcom/android/server/display/DisplayPowerController$BrightnessReason;->setModifier(I)V
+HPLcom/android/server/display/DisplayPowerController$BrightnessReason;->setReason(I)V
+HPLcom/android/server/display/DisplayPowerController$BrightnessReason;->toString(I)Ljava/lang/String;
+HPLcom/android/server/display/DisplayPowerController$BrightnessReason;->toString()Ljava/lang/String;
+HPLcom/android/server/media/projection/MediaProjectionManagerService$1;->onProcessDied(II)V
+HPLcom/android/server/net/NetworkPolicyManagerService;->removeUidStateUL(I)Z
+HPLcom/android/server/pm/permission/PermissionManagerService;->addOnPermissionsChangeListener(Landroid/permission/IOnPermissionsChangeListener;)V
+HPLcom/android/server/protolog/ProtoLogImpl;->getSingleInstance()Lcom/android/server/protolog/ProtoLogImpl;
+HPLcom/android/server/soundtrigger/SoundTriggerHelper$PowerSaveModeListener;-><init>(Lcom/android/server/soundtrigger/SoundTriggerHelper;)V
+HPLcom/android/server/statusbar/-$$Lambda$StatusBarManagerService$uF0ibEnnXe7Lxunxb98QQLJjgZM;->run()V
+HPLcom/android/server/statusbar/StatusBarManagerService$UiState;->setSystemUiState(IIILandroid/graphics/Rect;Landroid/graphics/Rect;Z)V
+HPLcom/android/server/statusbar/StatusBarManagerService$UiState;->systemUiStateEquals(IIILandroid/graphics/Rect;Landroid/graphics/Rect;Z)Z
+HPLcom/android/server/usage/UsageStatsDatabase;->filterStats(Lcom/android/server/usage/IntervalStats;)V
+HPLcom/android/server/VibratorService$VibrationInfo;-><init>(JLandroid/os/VibrationEffect;Landroid/os/VibrationEffect;Landroid/media/AudioAttributes;ILjava/lang/String;Ljava/lang/String;)V
+HPLcom/android/server/VibratorService;->getAppOpMode(Lcom/android/server/VibratorService$Vibration;)I
+HPLcom/android/server/VibratorService;->getCurrentIntensityLocked(Lcom/android/server/VibratorService$Vibration;)I
+HPLcom/android/server/VibratorService;->isAllowedToVibrateLocked(Lcom/android/server/VibratorService$Vibration;)Z
+HPLcom/android/server/VibratorService;->vibrate(ILjava/lang/String;Landroid/os/VibrationEffect;Landroid/media/AudioAttributes;Ljava/lang/String;Landroid/os/IBinder;)V
+HPLcom/android/server/wm/-$$Lambda$ActivityRecord$g49I60MBbnNkxHlgA-NR7ALwWTQ;->apply(Ljava/lang/Object;)Z
+HPLcom/android/server/wm/-$$Lambda$ActivityRecord$pBc6yUdDV5IrUd9vt6oCz6QzpiE;->accept(Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$BEx3OWenCvYAaV5h_J2ZkZXhEcY;->accept(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$DisplayPolicy$Zbxkj4wIhcDki6VwBh1kWmSmxqM;-><init>(Lcom/android/server/wm/DisplayPolicy;IIILandroid/graphics/Rect;Landroid/graphics/Rect;ZLcom/android/server/wm/WindowState;I[Lcom/android/internal/view/AppearanceRegion;ZZ)V
+HPLcom/android/server/wm/-$$Lambda$DisplayPolicy$Zbxkj4wIhcDki6VwBh1kWmSmxqM;->run()V
+HPLcom/android/server/wm/-$$Lambda$TaskChangeNotificationController$UexNbaqPy0mc3VxTw2coCctHho8;->accept(Landroid/app/ITaskStackListener;Landroid/os/Message;)V
+HPLcom/android/server/wm/ActivityMetricsLogger;->allWindowsDrawn()Z
+HPLcom/android/server/wm/ActivityRecord;->createAnimationBoundsLayer(Landroid/view/SurfaceControl$Transaction;)Landroid/view/SurfaceControl;
+HPLcom/android/server/wm/ActivityRecord;->destroyed(Ljava/lang/String;)V
+HPLcom/android/server/wm/ActivityRecord;->forAllWindowsUnchecked(Lcom/android/internal/util/ToBooleanFunction;Z)Z
+HPLcom/android/server/wm/ActivityRecord;->getTopFullscreenWindow()Lcom/android/server/wm/WindowState;
+HPLcom/android/server/wm/ActivityRecord;->loadAnimation(Landroid/view/WindowManager$LayoutParams;IZZ)Landroid/view/animation/Animation;
+HPLcom/android/server/wm/ActivityRecord;->notifyAppStopped()V
+HPLcom/android/server/wm/ActivityRecord;->onFirstWindowDrawn(Lcom/android/server/wm/WindowState;Lcom/android/server/wm/WindowStateAnimator;)V
+HPLcom/android/server/wm/ActivityRecord;->onRemovedFromDisplay()V
+HPLcom/android/server/wm/ActivityRecord;->removeDeadWindows()V
+HPLcom/android/server/wm/ActivityRecord;->setClientHidden(Z)V
+HPLcom/android/server/wm/ActivityRecord;->stopFreezingScreenLocked(Z)V
+HPLcom/android/server/wm/ActivityStack;->removeTimeoutsForActivity(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/ActivityStarter;->recycleTask(Lcom/android/server/wm/TaskRecord;Lcom/android/server/wm/ActivityRecord;Lcom/android/server/wm/ActivityRecord;[Lcom/android/server/wm/ActivityRecord;)I
+HPLcom/android/server/wm/ActivityTaskManagerService;->getTaskSnapshot(IZZ)Landroid/app/ActivityManager$TaskSnapshot;
+HPLcom/android/server/wm/AnimationAdapter;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
+HPLcom/android/server/wm/DisplayContent;->getWindowCornerRadius()F
+HPLcom/android/server/wm/DisplayPolicy;->convertNonDecorInsetsToStableInsets(Landroid/graphics/Rect;I)V
+HPLcom/android/server/wm/DisplayPolicy;->getInsetsPolicy()Lcom/android/server/wm/InsetsPolicy;
+HPLcom/android/server/wm/DisplayPolicy;->updateLightStatusBarAppearanceLw(ILcom/android/server/wm/WindowState;Lcom/android/server/wm/WindowState;)I
+HPLcom/android/server/wm/InsetsPolicy;->getInsetsForDispatch(Lcom/android/server/wm/WindowState;)Landroid/view/InsetsState;
+HPLcom/android/server/wm/InsetsPolicy;->isHidden(I)Z
+HPLcom/android/server/wm/InsetsPolicy;->updateBarControlTarget(Lcom/android/server/wm/WindowState;)V
+HPLcom/android/server/wm/Task;->getTopFullscreenActivity()Lcom/android/server/wm/ActivityRecord;
+HPLcom/android/server/wm/TaskRecord;->removeTaskActivitiesLocked(Ljava/lang/String;)V
+HPLcom/android/server/wm/Task;->removeChild(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/TaskSnapshotCache$CacheEntry;-><init>(Landroid/app/ActivityManager$TaskSnapshot;Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/TaskSnapshotController;->createTaskSnapshot(Lcom/android/server/wm/Task;F)Landroid/view/SurfaceControl$ScreenshotGraphicBuffer;
+HPLcom/android/server/wm/TaskSnapshotController;->findAppTokenForSnapshot(Lcom/android/server/wm/Task;)Lcom/android/server/wm/ActivityRecord;
+HPLcom/android/server/wm/TaskSnapshotPersister$DeleteWriteQueueItem;-><init>(Lcom/android/server/wm/TaskSnapshotPersister;II)V
+HPLcom/android/server/wm/TaskSnapshotPersister$StoreWriteQueueItem;-><init>(Lcom/android/server/wm/TaskSnapshotPersister;IILandroid/app/ActivityManager$TaskSnapshot;)V
+HPLcom/android/server/wm/TaskSnapshotPersister$StoreWriteQueueItem;->onQueuedLocked()V
+HPLcom/android/server/wm/WallpaperAnimationAdapter;->lambda$startWallpaperAnimations$0(JJLjava/util/function/Consumer;Ljava/util/ArrayList;Ljava/util/ArrayList;Lcom/android/server/wm/WallpaperWindowToken;)V
+HPLcom/android/server/wm/WallpaperAnimationAdapter;->startWallpaperAnimations(Lcom/android/server/wm/WindowManagerService;JJLjava/util/function/Consumer;Ljava/util/ArrayList;)[Landroid/view/RemoteAnimationTarget;
+HPLcom/android/server/wm/WindowAnimationSpec;->findTranslateAnimation(Landroid/view/animation/Animation;)Landroid/view/animation/TranslateAnimation;
+HPLcom/android/server/wm/WindowProcessControllerMap;->removeProcessFromUidMap(Lcom/android/server/wm/WindowProcessController;)V
+HPLcom/android/server/wm/WindowProcessController;->updateProcessInfo(ZZZ)V
+HPLcom/android/server/wm/WindowState$DeathRecipient;-><init>(Lcom/android/server/wm/WindowState;Lcom/android/server/wm/WindowState$1;)V
+HPLcom/android/server/wm/WindowState$WindowId;-><init>(Lcom/android/server/wm/WindowState;Lcom/android/server/wm/WindowState$1;)V
+HPLcom/google/android/startop/iorap/AppLaunchEvent$ActivityLaunched;->writeToParcelImpl(Landroid/os/Parcel;I)V
+HPLcom/google/android/startop/iorap/AppLaunchEvent$ActivityLaunchFinished;->writeToParcelImpl(Landroid/os/Parcel;I)V
+HPLcom/google/android/startop/iorap/AppLaunchEvent$IntentStarted;->writeToParcelImpl(Landroid/os/Parcel;I)V
+HPLcom/google/android/startop/iorap/AppLaunchEvent;->getTypeIndex()I
+HPLcom/android/server/pm/PackageManagerService;->access$7600(Lcom/android/server/pm/PackageManagerService;Landroid/content/Intent;Ljava/lang/String;IIZI)Landroid/content/pm/ResolveInfo;
+HPLcom/android/server/pm/PackageManagerService;->access$8100(Lcom/android/server/pm/PackageManagerService;II)Z
+HPLcom/android/server/wm/ActivityRecord$Token;->access$100(Lcom/android/server/wm/ActivityRecord$Token;Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/RemoteAnimationController$RemoteAnimationAdapterWrapper;->access$000(Lcom/android/server/wm/RemoteAnimationController$RemoteAnimationAdapterWrapper;)Lcom/android/server/wm/SurfaceAnimator$OnAnimationFinishedCallback;
+HPLcom/android/server/wm/RemoteAnimationController$RemoteAnimationAdapterWrapper;->access$400(Lcom/android/server/wm/RemoteAnimationController$RemoteAnimationAdapterWrapper;)Landroid/graphics/Point;
+HPLcom/android/server/wm/RemoteAnimationController$RemoteAnimationAdapterWrapper;->access$500(Lcom/android/server/wm/RemoteAnimationController$RemoteAnimationAdapterWrapper;)Landroid/graphics/Rect;
+HPLcom/android/server/wm/RemoteAnimationController;->access$100(Lcom/android/server/wm/RemoteAnimationController;)V
+HPLcom/android/server/wm/RemoteAnimationController;->access$300(Lcom/android/server/wm/RemoteAnimationController;)Landroid/view/RemoteAnimationAdapter;
+HPLcom/android/server/wm/TaskPersister$TaskWriteQueueItem;->access$200(Lcom/android/server/wm/TaskPersister$TaskWriteQueueItem;)Lcom/android/server/wm/TaskRecord;
+HPLcom/android/server/wm/TaskPersister;->access$000(I)Ljava/io/File;
+HPLcom/android/server/wm/WindowContainer;->access$100(Lcom/android/server/wm/WindowContainer;)Landroid/util/Pools$SynchronizedPool;
+HPLcom/android/server/wm/WindowManagerService;->access$1600(Lcom/android/server/wm/WindowManagerService;Landroid/os/IBinder;)V
+HPLcom/android/server/pm/ShortcutUser;->logSharingShortcutStats(Lcom/android/internal/logging/MetricsLogger;)V
+HPLcom/android/server/statusbar/StatusBarManagerService;->lambda$topAppWindowChanged$1$StatusBarManagerService(IZZ)V
+HPLcom/android/server/wm/ActivityRecord$Token;->attach(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/AnimationAdapter;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
+HPLcom/android/server/wm/DisplayPolicy;->lambda$updateSystemUiVisibilityLw$10$DisplayPolicy(IIILandroid/graphics/Rect;Landroid/graphics/Rect;ZLcom/android/server/wm/WindowState;I[Lcom/android/internal/view/AppearanceRegion;ZZ)V
+HPLcom/android/server/wm/InsetsSourceProvider;->hasWindow()Z
+HPLcom/android/server/wm/InsetsStateController;->onBarControlTargetChanged(Lcom/android/server/wm/InsetsControlTarget;Lcom/android/server/wm/InsetsControlTarget;Lcom/android/server/wm/InsetsControlTarget;Lcom/android/server/wm/InsetsControlTarget;)V
+HPLcom/android/server/wm/InsetsStateController;->peekSourceProvider(I)Lcom/android/server/wm/InsetsSourceProvider;
+HPLcom/android/server/wm/TaskPersister$TaskWriteQueueItem;->access$200(Lcom/android/server/wm/TaskPersister$TaskWriteQueueItem;)Lcom/android/server/wm/TaskRecord;
+HPLcom/android/server/wm/WindowContainer;->access$100(Lcom/android/server/wm/WindowContainer;)Landroid/util/Pools$SynchronizedPool;
+HPLcom/android/server/-$$Lambda$AlarmManagerService$2$Eo-D98J-N9R2METkD-12gPs320c;-><init>(Lcom/android/server/AlarmManagerService$2;Landroid/app/IAlarmCompleteListener;)V
+HPLcom/android/server/AlarmManagerService;->access$100(Lcom/android/server/AlarmManagerService;)Lcom/android/server/AlarmManagerService$Injector;
+HPLcom/android/server/AlarmManagerService;->access$2102(Lcom/android/server/AlarmManagerService;J)J
+HPLcom/android/server/AlarmManagerService;->access$2202(Lcom/android/server/AlarmManagerService;J)J
+HPLcom/android/server/AlarmManagerService;->access$2300(Lcom/android/server/AlarmManagerService;)V
+HPLcom/android/server/AlarmManagerService;->access$2400(Lcom/android/server/AlarmManagerService;Lcom/android/server/AlarmManagerService$Alarm;)Z
+HPLcom/android/server/AlarmManagerService;->isExemptFromAppStandby(Lcom/android/server/AlarmManagerService$Alarm;)Z
+HPLcom/android/server/appwidget/AppWidgetServiceImpl$HostId;-><init>(IILjava/lang/String;)V
+HPLcom/android/server/content/ContentService$ObserverCall;->run()V
+HPLcom/android/server/statusbar/-$$Lambda$StatusBarManagerService$uF0ibEnnXe7Lxunxb98QQLJjgZM;-><init>(Lcom/android/server/statusbar/StatusBarManagerService;IZZ)V
+HPLcom/android/server/statusbar/StatusBarManagerService$1;->topAppWindowChanged(IZZ)V
+HPLcom/android/server/statusbar/StatusBarManagerService$UiState;->access$1600(Lcom/android/server/statusbar/StatusBarManagerService$UiState;Z)V
+HPLcom/android/server/statusbar/StatusBarManagerService$UiState;->access$1700(Lcom/android/server/statusbar/StatusBarManagerService$UiState;Z)V
+HPLcom/android/server/statusbar/StatusBarManagerService$UiState;->setFullscreen(Z)V
+HPLcom/android/server/statusbar/StatusBarManagerService$UiState;->setImmersive(Z)V
+HPLcom/android/server/statusbar/StatusBarManagerService;->access$600(Lcom/android/server/statusbar/StatusBarManagerService;IZZ)V
+HPLcom/android/server/statusbar/StatusBarManagerService;->enforceStatusBar()V
+HPLcom/android/server/statusbar/StatusBarManagerService;->getUiState(I)Lcom/android/server/statusbar/StatusBarManagerService$UiState;
+HPLcom/android/server/statusbar/StatusBarManagerService;->topAppWindowChanged(IZZ)V
+HPLcom/android/server/wm/InsetsStateController;->onControlFakeTargetChanged(ILcom/android/server/wm/InsetsControlTarget;)V
+HPLcom/android/server/wm/LocalAnimationAdapter$AnimationSpec;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
+HPLcom/android/server/wm/LocalAnimationAdapter;->writeToProto(Landroid/util/proto/ProtoOutputStream;)V
+HPLcom/android/server/wm/PersisterQueue;->access$100(Lcom/android/server/wm/PersisterQueue;)Ljava/util/ArrayList;
+HPLcom/android/server/wm/PersisterQueue;->access$200(Lcom/android/server/wm/PersisterQueue;)Ljava/util/ArrayList;
+HPLcom/android/server/wm/PersisterQueue;->access$300(Lcom/android/server/wm/PersisterQueue;)V
+HPLcom/android/server/wm/TaskSnapshotPersister;->access$100(Lcom/android/server/wm/TaskSnapshotPersister;)Ljava/lang/Object;
+HPLcom/android/server/wm/TaskSnapshotPersister;->access$200(Lcom/android/server/wm/TaskSnapshotPersister;)Z
+HPLcom/android/server/wm/TaskSnapshotPersister;->access$300(Lcom/android/server/wm/TaskSnapshotPersister;)Ljava/util/ArrayDeque;
+HPLcom/android/server/wm/TaskSnapshotPersister;->access$402(Lcom/android/server/wm/TaskSnapshotPersister;Z)Z
+HPLcom/android/server/wm/WindowAnimationSpec;->findTranslateAnimation(Landroid/view/animation/Animation;)Landroid/view/animation/TranslateAnimation;
+HPLcom/android/server/wm/WindowAnimationSpec;->writeToProtoInner(Landroid/util/proto/ProtoOutputStream;)V
+HPLcom/android/server/wm/WindowContainer;->access$100(Lcom/android/server/wm/WindowContainer;)Landroid/util/Pools$SynchronizedPool;
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 7d129ea..1fc4751 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -197,8 +197,20 @@
if ((type & SaveInfo.SAVE_DATA_TYPE_ADDRESS) != 0) {
types.add(context.getString(R.string.autofill_save_type_address));
}
- if ((type & SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD) != 0) {
+
+ // fallback to generic card type if set multiple types
+ final int cardTypeMask = SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD
+ | SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD
+ | SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD;
+ final int count = Integer.bitCount(type & cardTypeMask);
+ if (count > 1 || (type & SaveInfo.SAVE_DATA_TYPE_GENERIC_CARD) != 0) {
+ types.add(context.getString(R.string.autofill_save_type_generic_card));
+ } else if ((type & SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD) != 0) {
+ types.add(context.getString(R.string.autofill_save_type_payment_card));
+ } else if ((type & SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD) != 0) {
types.add(context.getString(R.string.autofill_save_type_credit_card));
+ } else if ((type & SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD) != 0) {
+ types.add(context.getString(R.string.autofill_save_type_debit_card));
}
if ((type & SaveInfo.SAVE_DATA_TYPE_USERNAME) != 0) {
types.add(context.getString(R.string.autofill_save_type_username));
@@ -269,7 +281,9 @@
noButton.setOnClickListener((v) -> mListener.onCancel(info.getNegativeActionListener()));
final TextView yesButton = view.findViewById(R.id.autofill_save_yes);
- if (isUpdate) {
+ if (info.getPositiveActionStyle() == SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE) {
+ yesButton.setText(R.string.autofill_continue_yes);
+ } else if (isUpdate) {
yesButton.setText(R.string.autofill_update_yes);
}
yesButton.setOnClickListener((v) -> mListener.onSave());
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index d18b4f6..a33fcd5 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -720,6 +720,8 @@
event.putInt(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
event.putInt(BatteryManager.EXTRA_PLUGGED, mPlugType);
event.putInt(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.batteryVoltage);
+ event.putInt(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.batteryTemperature);
+ event.putInt(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounter);
event.putLong(BatteryManager.EXTRA_EVENT_TIMESTAMP, now);
boolean queueWasEmpty = mBatteryLevelsEventQueue.isEmpty();
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 0bb72cb..ce0e9e7 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -26,6 +26,7 @@
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
@@ -528,6 +529,15 @@
private static final int EVENT_SET_ACCEPT_PARTIAL_CONNECTIVITY = 45;
/**
+ * Event for NetworkMonitor to inform ConnectivityService that the probe status has changed.
+ * Both of the arguments are bitmasks, and the value of bits come from
+ * INetworkMonitor.NETWORK_VALIDATION_PROBE_*.
+ * arg1 = A bitmask to describe which probes are completed.
+ * arg2 = A bitmask to describe which probes are successful.
+ */
+ public static final int EVENT_PROBE_STATUS_CHANGED = 46;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -2663,6 +2673,41 @@
switch (msg.what) {
default:
return false;
+ case EVENT_PROBE_STATUS_CHANGED: {
+ final Integer netId = (Integer) msg.obj;
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
+ if (nai == null) {
+ break;
+ }
+ final boolean probePrivateDnsCompleted =
+ ((msg.arg1 & NETWORK_VALIDATION_PROBE_PRIVDNS) != 0);
+ final boolean privateDnsBroken =
+ ((msg.arg2 & NETWORK_VALIDATION_PROBE_PRIVDNS) == 0);
+ if (probePrivateDnsCompleted) {
+ if (nai.networkCapabilities.isPrivateDnsBroken() != privateDnsBroken) {
+ nai.networkCapabilities.setPrivateDnsBroken(privateDnsBroken);
+ final int oldScore = nai.getCurrentScore();
+ updateCapabilities(oldScore, nai, nai.networkCapabilities);
+ }
+ // Only show the notification when the private DNS is broken and the
+ // PRIVATE_DNS_BROKEN notification hasn't shown since last valid.
+ if (privateDnsBroken && !nai.networkMisc.hasShownBroken) {
+ showNetworkNotification(nai, NotificationType.PRIVATE_DNS_BROKEN);
+ }
+ nai.networkMisc.hasShownBroken = privateDnsBroken;
+ } else if (nai.networkCapabilities.isPrivateDnsBroken()) {
+ // If probePrivateDnsCompleted is false but nai.networkCapabilities says
+ // private DNS is broken, it means this network is being reevaluated.
+ // Either probing private DNS is not necessary any more or it hasn't been
+ // done yet. In either case, the networkCapabilities should be updated to
+ // reflect the new status.
+ nai.networkCapabilities.setPrivateDnsBroken(false);
+ final int oldScore = nai.getCurrentScore();
+ updateCapabilities(oldScore, nai, nai.networkCapabilities);
+ nai.networkMisc.hasShownBroken = false;
+ }
+ break;
+ }
case EVENT_NETWORK_TESTED: {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
if (nai == null) break;
@@ -2705,14 +2750,20 @@
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
if (valid) {
handleFreshlyValidatedNetwork(nai);
- // Clear NO_INTERNET, PARTIAL_CONNECTIVITY and LOST_INTERNET
- // notifications if network becomes valid.
+ // Clear NO_INTERNET, PRIVATE_DNS_BROKEN, PARTIAL_CONNECTIVITY and
+ // LOST_INTERNET notifications if network becomes valid.
mNotifier.clearNotification(nai.network.netId,
NotificationType.NO_INTERNET);
mNotifier.clearNotification(nai.network.netId,
NotificationType.LOST_INTERNET);
mNotifier.clearNotification(nai.network.netId,
NotificationType.PARTIAL_CONNECTIVITY);
+ mNotifier.clearNotification(nai.network.netId,
+ NotificationType.PRIVATE_DNS_BROKEN);
+ // If network becomes valid, the hasShownBroken should be reset for
+ // that network so that the notification will be fired when the private
+ // DNS is broken again.
+ nai.networkMisc.hasShownBroken = false;
}
} else if (partialConnectivityChanged) {
updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
@@ -2863,6 +2914,13 @@
}
@Override
+ public void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) {
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+ EVENT_PROBE_STATUS_CHANGED,
+ probesCompleted, probesSucceeded, new Integer(mNetId)));
+ }
+
+ @Override
public void showProvisioningNotification(String action, String packageName) {
final Intent intent = new Intent(action);
intent.setPackage(packageName);
@@ -3679,6 +3737,11 @@
// High priority because it is only displayed for explicitly selected networks.
highPriority = true;
break;
+ case PRIVATE_DNS_BROKEN:
+ action = Settings.ACTION_WIRELESS_SETTINGS;
+ // High priority because we should let user know why there is no internet.
+ highPriority = true;
+ break;
case LOST_INTERNET:
action = ConnectivityManager.ACTION_PROMPT_LOST_VALIDATION;
// High priority because it could help the user avoid unexpected data usage.
@@ -3696,7 +3759,7 @@
}
Intent intent = new Intent(action);
- if (type != NotificationType.LOGGED_IN) {
+ if (type != NotificationType.LOGGED_IN && type != NotificationType.PRIVATE_DNS_BROKEN) {
intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName("com.android.settings",
@@ -5162,6 +5225,13 @@
ns.assertValidFromUid(Binder.getCallingUid());
}
+ private void ensureValid(NetworkCapabilities nc) {
+ ensureValidNetworkSpecifier(nc);
+ if (nc.isPrivateDnsBroken()) {
+ throw new IllegalArgumentException("Can't request broken private DNS");
+ }
+ }
+
@Override
public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
@@ -5195,7 +5265,7 @@
if (timeoutMs < 0) {
throw new IllegalArgumentException("Bad timeout specified");
}
- ensureValidNetworkSpecifier(networkCapabilities);
+ ensureValid(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
nextNetworkRequestId(), type);
@@ -5337,7 +5407,7 @@
// There is no need to do this for requests because an app without CHANGE_NETWORK_STATE
// can't request networks.
restrictBackgroundRequestForCaller(nc);
- ensureValidNetworkSpecifier(nc);
+ ensureValid(nc);
NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
NetworkRequest.Type.LISTEN);
@@ -5355,7 +5425,7 @@
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
}
- ensureValidNetworkSpecifier(networkCapabilities);
+ ensureValid(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), Binder.getCallingUid());
@@ -5841,6 +5911,7 @@
} else {
newNc.removeCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
}
+ newNc.setPrivateDnsBroken(nai.networkCapabilities.isPrivateDnsBroken());
return newNc;
}
diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java
index 44a8234..274e2f1 100644
--- a/services/core/java/com/android/server/GnssManagerService.java
+++ b/services/core/java/com/android/server/GnssManagerService.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.Context;
@@ -300,7 +301,7 @@
* @return true if callback is successfully added, false otherwise
*/
public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
- @NonNull String listenerIdentity) {
+ @Nullable String featureId, @NonNull String listenerIdentity) {
mContext.enforceCallingPermission(
android.Manifest.permission.LOCATION_HARDWARE,
"Location Hardware permission not granted to access hardware batching");
@@ -319,7 +320,7 @@
CallerIdentity callerIdentity =
new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName,
- listenerIdentity);
+ featureId, listenerIdentity);
synchronized (mGnssBatchingLock) {
mGnssBatchingCallback = callback;
mGnssBatchingDeathCallback =
@@ -497,6 +498,7 @@
private <TListener extends IInterface> boolean addGnssDataListenerLocked(
TListener listener,
String packageName,
+ @Nullable String featureId,
@NonNull String listenerIdentifier,
RemoteListenerHelper<TListener> gnssDataProvider,
ArrayMap<IBinder,
@@ -517,7 +519,7 @@
CallerIdentity callerIdentity =
new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName,
- listenerIdentifier);
+ featureId, listenerIdentifier);
LinkedListener<TListener> linkedListener =
new LocationManagerServiceUtils.LinkedListener<>(
listener, listenerIdentifier, callerIdentity, binderDeathCallback);
@@ -605,11 +607,13 @@
* @param packageName name of requesting package
* @return true if listener is successfully registered, false otherwise
*/
- public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName) {
+ public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
+ @Nullable String featureId) {
synchronized (mGnssStatusListeners) {
return addGnssDataListenerLocked(
listener,
packageName,
+ featureId,
"Gnss status",
mGnssStatusProvider,
mGnssStatusListeners,
@@ -636,12 +640,13 @@
* @return true if listener is successfully added, false otherwise
*/
public boolean addGnssMeasurementsListener(
- IGnssMeasurementsListener listener, String packageName,
+ IGnssMeasurementsListener listener, String packageName, @Nullable String featureId,
@NonNull String listenerIdentifier) {
synchronized (mGnssMeasurementsListeners) {
return addGnssDataListenerLocked(
listener,
packageName,
+ featureId,
listenerIdentifier,
mGnssMeasurementsProvider,
mGnssMeasurementsListeners,
@@ -695,11 +700,12 @@
*/
public boolean addGnssNavigationMessageListener(
IGnssNavigationMessageListener listener, String packageName,
- @NonNull String listenerIdentifier) {
+ @Nullable String featureId, @NonNull String listenerIdentifier) {
synchronized (mGnssNavigationMessageListeners) {
return addGnssDataListenerLocked(
listener,
packageName,
+ featureId,
listenerIdentifier,
mGnssNavigationMessageProvider,
mGnssNavigationMessageListeners,
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 1d22b82..0a63bf8 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1000,6 +1000,12 @@
@Override
public void onReportLocation(Location location) {
+ // likelihood of a 0,0 bug is far greater than this being a valid location
+ if (!isMock() && location.getLatitude() == 0 && location.getLongitude() == 0) {
+ Slog.w(TAG, "blocking 0,0 location from " + mName + " provider");
+ return;
+ }
+
synchronized (mLock) {
handleLocationChangedLocked(location, this);
}
@@ -1227,9 +1233,9 @@
PowerManager.WakeLock mWakeLock;
private Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid,
- String packageName, WorkSource workSource, boolean hideFromAppOps,
- @NonNull String listenerIdentifier) {
- super(new CallerIdentity(uid, pid, packageName, listenerIdentifier),
+ String packageName, @Nullable String featureId, WorkSource workSource,
+ boolean hideFromAppOps, @NonNull String listenerIdentifier) {
+ super(new CallerIdentity(uid, pid, packageName, featureId, listenerIdentifier),
"LocationListener");
mListener = listener;
mPendingIntent = intent;
@@ -1354,7 +1360,8 @@
if (!currentlyMonitoring) {
if (allowMonitoring) {
return mAppOps.startOpNoThrow(op, mCallerIdentity.mUid,
- mCallerIdentity.mPackageName) == AppOpsManager.MODE_ALLOWED;
+ mCallerIdentity.mPackageName, false, mCallerIdentity.mFeatureId, null)
+ == AppOpsManager.MODE_ALLOWED;
}
} else {
if (!allowMonitoring
@@ -1539,11 +1546,11 @@
@Override
public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
- String listenerIdentifier) {
+ String featureId, String listenerIdentifier) {
Preconditions.checkNotNull(listenerIdentifier);
return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback(
- callback, packageName, listenerIdentifier);
+ callback, packageName, featureId, listenerIdentifier);
}
@Override
@@ -1706,10 +1713,10 @@
}
private boolean reportLocationAccessNoThrow(int pid, int uid, String packageName,
- int allowedResolutionLevel, @Nullable String message) {
+ @Nullable String featureId, int allowedResolutionLevel, @Nullable String message) {
int op = resolutionLevelToOp(allowedResolutionLevel);
if (op >= 0) {
- if (mAppOps.noteOpNoThrow(op, uid, packageName, null, message)
+ if (mAppOps.noteOpNoThrow(op, uid, packageName, featureId, message)
!= AppOpsManager.MODE_ALLOWED) {
return false;
}
@@ -2143,12 +2150,12 @@
@GuardedBy("mLock")
private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
- String packageName, WorkSource workSource, boolean hideFromAppOps,
- @NonNull String listenerIdentifier) {
+ String packageName, @Nullable String featureId, WorkSource workSource,
+ boolean hideFromAppOps, @NonNull String listenerIdentifier) {
IBinder binder = listener.asBinder();
Receiver receiver = mReceivers.get(binder);
if (receiver == null) {
- receiver = new Receiver(listener, null, pid, uid, packageName, workSource,
+ receiver = new Receiver(listener, null, pid, uid, packageName, featureId, workSource,
hideFromAppOps, listenerIdentifier);
if (!receiver.linkToListenerDeathNotificationLocked(
receiver.getListener().asBinder())) {
@@ -2161,10 +2168,11 @@
@GuardedBy("mLock")
private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName,
- WorkSource workSource, boolean hideFromAppOps, @NonNull String listenerIdentifier) {
+ @Nullable String featureId, WorkSource workSource, boolean hideFromAppOps,
+ @NonNull String listenerIdentifier) {
Receiver receiver = mReceivers.get(intent);
if (receiver == null) {
- receiver = new Receiver(null, intent, pid, uid, packageName, workSource,
+ receiver = new Receiver(null, intent, pid, uid, packageName, featureId, workSource,
hideFromAppOps, listenerIdentifier);
mReceivers.put(intent, receiver);
}
@@ -2227,7 +2235,8 @@
@Override
public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
- PendingIntent intent, String packageName, String listenerIdentifier) {
+ PendingIntent intent, String packageName, String featureId,
+ String listenerIdentifier) {
Preconditions.checkNotNull(listenerIdentifier);
synchronized (mLock) {
@@ -2283,11 +2292,11 @@
Receiver receiver;
if (intent != null) {
- receiver = getReceiverLocked(intent, pid, uid, packageName, workSource,
- hideFromAppOps, listenerIdentifier);
+ receiver = getReceiverLocked(intent, pid, uid, packageName, featureId,
+ workSource, hideFromAppOps, listenerIdentifier);
} else {
- receiver = getReceiverLocked(listener, pid, uid, packageName, workSource,
- hideFromAppOps, listenerIdentifier);
+ receiver = getReceiverLocked(listener, pid, uid, packageName, featureId,
+ workSource, hideFromAppOps, listenerIdentifier);
}
requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName);
} finally {
@@ -2356,9 +2365,10 @@
synchronized (mLock) {
Receiver receiver;
if (intent != null) {
- receiver = getReceiverLocked(intent, pid, uid, packageName, null, false, "");
+ receiver = getReceiverLocked(intent, pid, uid, packageName, null, null, false, "");
} else {
- receiver = getReceiverLocked(listener, pid, uid, packageName, null, false, "");
+ receiver = getReceiverLocked(listener, pid, uid, packageName, null, null, false,
+ "");
}
long identity = Binder.clearCallingIdentity();
@@ -2402,7 +2412,7 @@
}
@Override
- public Location getLastLocation(LocationRequest r, String packageName) {
+ public Location getLastLocation(LocationRequest r, String packageName, String featureId) {
synchronized (mLock) {
LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST;
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
@@ -2477,8 +2487,8 @@
}
// Don't report location access if there is no last location to deliver.
if (lastLocation != null) {
- if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel,
- null)) {
+ if (!reportLocationAccessNoThrow(pid, uid, packageName, featureId,
+ allowedResolutionLevel, null)) {
if (D) {
Log.d(TAG, "not returning last loc for no op app: " + packageName);
}
@@ -2495,9 +2505,9 @@
@Override
public boolean getCurrentLocation(LocationRequest locationRequest,
ICancellationSignal remoteCancellationSignal, ILocationListener listener,
- String packageName, String listenerIdentifier) {
+ String packageName, String featureId, String listenerIdentifier) {
// side effect of validating locationRequest and packageName
- Location lastLocation = getLastLocation(locationRequest, packageName);
+ Location lastLocation = getLastLocation(locationRequest, packageName, featureId);
if (lastLocation != null) {
long locationAgeMs = TimeUnit.NANOSECONDS.toMillis(
SystemClock.elapsedRealtimeNanos() - lastLocation.getElapsedRealtimeNanos());
@@ -2532,7 +2542,8 @@
}
}
- requestLocationUpdates(locationRequest, listener, null, packageName, listenerIdentifier);
+ requestLocationUpdates(locationRequest, listener, null, packageName, featureId,
+ listenerIdentifier);
CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
remoteCancellationSignal);
if (cancellationSignal != null) {
@@ -2582,7 +2593,7 @@
@Override
public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent,
- String packageName, String listenerIdentifier) {
+ String packageName, String featureId, String listenerIdentifier) {
Preconditions.checkNotNull(listenerIdentifier);
if (request == null) request = DEFAULT_LOCATION_REQUEST;
@@ -2630,7 +2641,7 @@
}
mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
- uid, packageName, listenerIdentifier);
+ uid, packageName, featureId, listenerIdentifier);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -2666,9 +2677,10 @@
}
@Override
- public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName) {
+ public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
+ String featureId) {
return mGnssManagerService == null ? false : mGnssManagerService.registerGnssStatusCallback(
- listener, packageName);
+ listener, packageName, featureId);
}
@Override
@@ -2678,12 +2690,12 @@
@Override
public boolean addGnssMeasurementsListener(IGnssMeasurementsListener listener,
- String packageName, String listenerIdentifier) {
+ String packageName, String featureId, String listenerIdentifier) {
Preconditions.checkNotNull(listenerIdentifier);
return mGnssManagerService == null ? false
- : mGnssManagerService.addGnssMeasurementsListener(listener, packageName,
- listenerIdentifier);
+ : mGnssManagerService.addGnssMeasurementsListener(listener, packageName, featureId,
+ listenerIdentifier);
}
@Override
@@ -2711,12 +2723,12 @@
@Override
public boolean addGnssNavigationMessageListener(IGnssNavigationMessageListener listener,
- String packageName, String listenerIdentifier) {
+ String packageName, String featureId, String listenerIdentifier) {
Preconditions.checkNotNull(listenerIdentifier);
return mGnssManagerService == null ? false
: mGnssManagerService.addGnssNavigationMessageListener(listener, packageName,
- listenerIdentifier);
+ featureId, listenerIdentifier);
}
@Override
@@ -3022,6 +3034,7 @@
receiver.mCallerIdentity.mPid,
receiver.mCallerIdentity.mUid,
receiver.mCallerIdentity.mPackageName,
+ receiver.mCallerIdentity.mFeatureId,
receiver.mAllowedResolutionLevel,
"Location sent to " + receiver.mCallerIdentity.mListenerIdentifier)) {
if (D) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index a6ac17d..0d493b8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1018,7 +1018,14 @@
synchronized (mAudioPolicies) {
for (AudioPolicyProxy policy : mAudioPolicies.values()) {
- policy.connectMixes();
+ final int status = policy.connectMixes();
+ if (status != AudioSystem.SUCCESS) {
+ // note that PERMISSION_DENIED may also indicate trouble getting to APService
+ Log.e(TAG, "onAudioServerDied: error "
+ + AudioSystem.audioSystemErrorToString(status)
+ + " when connecting mixes for policy " + policy.toLogFriendlyString());
+ policy.release();
+ }
}
}
@@ -7019,16 +7026,8 @@
}
public void binderDied() {
- synchronized (mAudioPolicies) {
- Log.i(TAG, "audio policy " + mPolicyCallback + " died");
- release();
- mAudioPolicies.remove(mPolicyCallback.asBinder());
- }
- if (mIsVolumeController) {
- synchronized (mExtVolumeControllerLock) {
- mExtVolumeController = null;
- }
- }
+ Log.i(TAG, "audio policy " + mPolicyCallback + " died");
+ release();
}
String getRegistrationId() {
@@ -7052,9 +7051,20 @@
Log.e(TAG, "Fail to unregister Audiopolicy callback from MediaProjection");
}
}
+ if (mIsVolumeController) {
+ synchronized (mExtVolumeControllerLock) {
+ mExtVolumeController = null;
+ }
+ }
final long identity = Binder.clearCallingIdentity();
AudioSystem.registerPolicyMixes(mMixes, false);
Binder.restoreCallingIdentity(identity);
+ synchronized (mAudioPolicies) {
+ mAudioPolicies.remove(mPolicyCallback.asBinder());
+ }
+ try {
+ mPolicyCallback.notifyUnregistration();
+ } catch (RemoteException e) { }
}
boolean hasMixAffectingUsage(int usage, int excludedFlags) {
@@ -7105,7 +7115,7 @@
}
}
- int connectMixes() {
+ @AudioSystem.AudioSystemError int connectMixes() {
final long identity = Binder.clearCallingIdentity();
int status = AudioSystem.registerPolicyMixes(mMixes, true);
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 4f1db3c..44c81fc 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -37,13 +37,12 @@
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
-import android.hardware.face.FaceManager;
import android.hardware.face.IFaceService;
-import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IFingerprintService;
import android.net.Uri;
import android.os.Binder;
@@ -68,6 +67,8 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.statusbar.IStatusBarService;
import com.android.server.SystemService;
+import com.android.server.biometrics.face.FaceAuthenticator;
+import com.android.server.biometrics.fingerprint.FingerprintAuthenticator;
import java.util.ArrayList;
import java.util.HashMap;
@@ -95,11 +96,6 @@
private static final int MSG_CANCEL_AUTHENTICATION = 10;
private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 11;
private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12;
- private static final int[] FEATURE_ID = {
- TYPE_FINGERPRINT,
- TYPE_IRIS,
- TYPE_FACE
- };
/**
* Authentication either just called and we have not transitioned to the CALLED state, or
@@ -172,7 +168,7 @@
byte[] mTokenEscrow;
// Waiting for SystemUI to complete animation
int mErrorEscrow;
- String mErrorStringEscrow;
+ int mVendorCodeEscrow;
// Timestamp when authentication started
private long mStartTimeMs;
@@ -219,19 +215,15 @@
private final Injector mInjector;
@VisibleForTesting
final IBiometricService.Stub mImpl;
+ private final boolean mHasFeatureFace;
private final boolean mHasFeatureFingerprint;
private final boolean mHasFeatureIris;
- private final boolean mHasFeatureFace;
@VisibleForTesting
final SettingObserver mSettingObserver;
private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
private final Random mRandom = new Random();
@VisibleForTesting
- IFingerprintService mFingerprintService;
- @VisibleForTesting
- IFaceService mFaceService;
- @VisibleForTesting
IStatusBarService mStatusBarService;
@VisibleForTesting
KeyStore mKeyStore;
@@ -262,7 +254,7 @@
}
case MSG_ON_AUTHENTICATION_REJECTED: {
- handleAuthenticationRejected((String) msg.obj /* failureReason */);
+ handleAuthenticationRejected();
break;
}
@@ -270,8 +262,9 @@
SomeArgs args = (SomeArgs) msg.obj;
handleOnError(
args.argi1 /* cookie */,
- args.argi2 /* error */,
- (String) args.arg1 /* message */);
+ args.argi2 /* modality */,
+ args.argi3 /* error */,
+ args.argi4 /* vendorCode */);
args.recycle();
break;
}
@@ -331,7 +324,12 @@
}
case MSG_ON_AUTHENTICATION_TIMED_OUT: {
- handleAuthenticationTimedOut((String) msg.obj /* errorMessage */);
+ SomeArgs args = (SomeArgs) msg.obj;
+ handleAuthenticationTimedOut(
+ args.argi1 /* modality */,
+ args.argi2 /* error */,
+ args.argi3 /* vendorCode */);
+ args.recycle();
break;
}
@@ -347,21 +345,23 @@
}
};
- private final class AuthenticatorWrapper {
- final int mType;
- final BiometricAuthenticator mAuthenticator;
+ /**
+ * Wraps IBiometricAuthenticator implementation and stores information about the authenticator.
+ * TODO(b/141025588): Consider refactoring the tests to not rely on this implementation detail.
+ */
+ @VisibleForTesting
+ public static final class AuthenticatorWrapper {
+ public final int id;
+ public final int strength;
+ public final int modality;
+ public final IBiometricAuthenticator impl;
- AuthenticatorWrapper(int type, BiometricAuthenticator authenticator) {
- mType = type;
- mAuthenticator = authenticator;
- }
-
- int getType() {
- return mType;
- }
-
- BiometricAuthenticator getAuthenticator() {
- return mAuthenticator;
+ AuthenticatorWrapper(int id, int strength, int modality,
+ IBiometricAuthenticator impl) {
+ this.id = id;
+ this.strength = strength;
+ this.modality = modality;
+ this.impl = impl;
}
}
@@ -521,23 +521,28 @@
@Override
public void onAuthenticationFailed()
throws RemoteException {
- String failureReason = getContext().getString(R.string.biometric_not_recognized);
- Slog.v(TAG, "onAuthenticationFailed: " + failureReason);
- mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED, failureReason).sendToTarget();
+ Slog.v(TAG, "onAuthenticationFailed");
+ mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED).sendToTarget();
}
@Override
- public void onError(int cookie, int error, String message) throws RemoteException {
+ public void onError(int cookie, int modality, int error, int vendorCode)
+ throws RemoteException {
// Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are
// soft errors and we should allow the user to try authenticating again instead of
// dismissing BiometricPrompt.
if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) {
- mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, message).sendToTarget();
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = modality;
+ args.argi2 = error;
+ args.argi3 = vendorCode;
+ mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, args).sendToTarget();
} else {
SomeArgs args = SomeArgs.obtain();
args.argi1 = cookie;
- args.argi2 = error;
- args.arg1 = message;
+ args.argi2 = modality;
+ args.argi3 = error;
+ args.argi4 = vendorCode;
mHandler.obtainMessage(MSG_ON_ERROR, args).sendToTarget();
}
}
@@ -666,7 +671,8 @@
final long ident = Binder.clearCallingIdentity();
int error;
try {
- final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
+ final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId,
+ opPackageName);
error = result.second;
} finally {
Binder.restoreCallingIdentity(ident);
@@ -675,22 +681,30 @@
}
@Override
- public boolean hasEnrolledBiometrics(int userId) {
+ public boolean hasEnrolledBiometrics(int userId, String opPackageName) {
checkInternalPermission();
final long ident = Binder.clearCallingIdentity();
try {
- for (int i = 0; i < mAuthenticators.size(); i++) {
- if (mAuthenticators.get(i).mAuthenticator.hasEnrolledTemplates(userId)) {
+ for (AuthenticatorWrapper authenticator : mAuthenticators) {
+ if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
return true;
}
}
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
} finally {
Binder.restoreCallingIdentity(ident);
}
return false;
}
+ @Override
+ public void registerAuthenticator(int id, int strength, int modality,
+ IBiometricAuthenticator authenticator) {
+ mAuthenticators.add(new AuthenticatorWrapper(id, strength, modality, authenticator));
+ }
+
@Override // Binder call
public void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback)
throws RemoteException {
@@ -710,9 +724,11 @@
checkInternalPermission();
final long ident = Binder.clearCallingIdentity();
try {
- for (int i = 0; i < mAuthenticators.size(); i++) {
- mAuthenticators.get(i).getAuthenticator().setActiveUser(userId);
+ for (AuthenticatorWrapper authenticator : mAuthenticators) {
+ authenticator.impl.setActiveUser(userId);
}
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -723,11 +739,8 @@
checkInternalPermission();
final long ident = Binder.clearCallingIdentity();
try {
- if (mFingerprintService != null) {
- mFingerprintService.resetTimeout(token);
- }
- if (mFaceService != null) {
- mFaceService.resetLockout(token);
+ for (AuthenticatorWrapper authenticator : mAuthenticators) {
+ authenticator.impl.resetLockout(token);
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
@@ -750,40 +763,69 @@
}
}
+ /**
+ * Class for injecting dependencies into BiometricService.
+ * TODO(b/141025588): Replace with a dependency injection framework (e.g. Guice, Dagger).
+ */
@VisibleForTesting
- static class Injector {
- IActivityManager getActivityManagerService() {
+ public static class Injector {
+
+ @VisibleForTesting
+ public IActivityManager getActivityManagerService() {
return ActivityManager.getService();
}
- IStatusBarService getStatusBarService() {
+ @VisibleForTesting
+ public IStatusBarService getStatusBarService() {
return IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
}
- IFingerprintService getFingerprintService() {
- return IFingerprintService.Stub.asInterface(
- ServiceManager.getService(Context.FINGERPRINT_SERVICE));
+ /**
+ * Allows to mock FaceAuthenticator for testing.
+ */
+ @VisibleForTesting
+ public IBiometricAuthenticator getFingerprintAuthenticator() {
+ return new FingerprintAuthenticator(IFingerprintService.Stub.asInterface(
+ ServiceManager.getService(Context.FINGERPRINT_SERVICE)));
}
- IFaceService getFaceService() {
- return IFaceService.Stub.asInterface(ServiceManager.getService(Context.FACE_SERVICE));
+ /**
+ * Allows to mock FaceAuthenticator for testing.
+ */
+ @VisibleForTesting
+ public IBiometricAuthenticator getFaceAuthenticator() {
+ return new FaceAuthenticator(
+ IFaceService.Stub.asInterface(ServiceManager.getService(Context.FACE_SERVICE)));
}
- SettingObserver getSettingObserver(Context context, Handler handler,
+ /**
+ * Allows to mock SettingObserver for testing.
+ */
+ @VisibleForTesting
+ public SettingObserver getSettingObserver(Context context, Handler handler,
List<EnabledOnKeyguardCallback> callbacks) {
return new SettingObserver(context, handler, callbacks);
}
- KeyStore getKeyStore() {
+ @VisibleForTesting
+ public KeyStore getKeyStore() {
return KeyStore.getInstance();
}
- boolean isDebugEnabled(Context context, int userId) {
+ /**
+ * Allows to enable/disable debug logs.
+ */
+ @VisibleForTesting
+ public boolean isDebugEnabled(Context context, int userId) {
return Utils.isDebugEnabled(context, userId);
}
- void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
+ /**
+ * Allows to stub publishBinderService(...) for testing.
+ */
+ @VisibleForTesting
+ public void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
service.publishBinderService(Context.BIOMETRIC_SERVICE, impl);
}
}
@@ -812,9 +854,9 @@
mEnabledOnKeyguardCallbacks);
final PackageManager pm = context.getPackageManager();
+ mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
mHasFeatureIris = pm.hasSystemFeature(PackageManager.FEATURE_IRIS);
- mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
try {
injector.getActivityManagerService().registerUserSwitchObserver(
@@ -833,26 +875,30 @@
@Override
public void onStart() {
- // TODO: maybe get these on-demand
- if (mHasFeatureFingerprint) {
- mFingerprintService = mInjector.getFingerprintService();
- }
- if (mHasFeatureFace) {
- mFaceService = mInjector.getFaceService();
+ // TODO(b/141025588): remove this code block once AuthService is integrated.
+ {
+ if (mHasFeatureFace) {
+ try {
+ mImpl.registerAuthenticator(0, 0, TYPE_FACE, mInjector.getFaceAuthenticator());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+ if (mHasFeatureFingerprint) {
+ try {
+ mImpl.registerAuthenticator(0, 0, TYPE_FINGERPRINT,
+ mInjector.getFingerprintAuthenticator());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+ if (mHasFeatureIris) {
+ Slog.e(TAG, "Iris is not supported");
+ }
}
mKeyStore = mInjector.getKeyStore();
mStatusBarService = mInjector.getStatusBarService();
-
- // Cache the authenticators
- for (int featureId : FEATURE_ID) {
- if (hasFeature(featureId)) {
- AuthenticatorWrapper authenticator =
- new AuthenticatorWrapper(featureId, getAuthenticator(featureId));
- mAuthenticators.add(authenticator);
- }
- }
-
mInjector.publishBinderService(this, mImpl);
}
@@ -868,7 +914,7 @@
* {@link BiometricAuthenticator#TYPE_FACE}
* and the error containing one of the {@link BiometricConstants} errors.
*/
- private Pair<Integer, Integer> checkAndGetBiometricModality(int userId) {
+ private Pair<Integer, Integer> checkAndGetBiometricModality(int userId, String opPackageName) {
// No biometric features, send error
if (mAuthenticators.isEmpty()) {
return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
@@ -886,23 +932,26 @@
int modality = TYPE_NONE;
int firstHwAvailable = TYPE_NONE;
- for (AuthenticatorWrapper authenticatorWrapper : mAuthenticators) {
- modality = authenticatorWrapper.getType();
- BiometricAuthenticator authenticator = authenticatorWrapper.getAuthenticator();
- if (authenticator.isHardwareDetected()) {
- isHardwareDetected = true;
- if (firstHwAvailable == TYPE_NONE) {
- // Store the first one since we want to return the error in correct priority
- // order.
- firstHwAvailable = modality;
- }
- if (authenticator.hasEnrolledTemplates(userId)) {
- hasTemplatesEnrolled = true;
- if (isEnabledForApp(modality, userId)) {
- enabledForApps = true;
- break;
+ for (AuthenticatorWrapper authenticator : mAuthenticators) {
+ modality = authenticator.modality;
+ try {
+ if (authenticator.impl.isHardwareDetected(opPackageName)) {
+ isHardwareDetected = true;
+ if (firstHwAvailable == TYPE_NONE) {
+ // Store the first one since we want to return the error in correct priority
+ // order.
+ firstHwAvailable = modality;
+ }
+ if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
+ hasTemplatesEnrolled = true;
+ if (isEnabledForApp(modality, userId)) {
+ enabledForApps = true;
+ break;
+ }
}
}
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
}
}
@@ -926,7 +975,7 @@
}
private boolean isEnabledForApp(int modality, int userId) {
- switch(modality) {
+ switch (modality) {
case TYPE_FINGERPRINT:
return true;
case TYPE_IRIS:
@@ -939,49 +988,6 @@
}
}
- private String getErrorString(int type, int error, int vendorCode) {
- switch (type) {
- case TYPE_FINGERPRINT:
- return FingerprintManager.getErrorString(getContext(), error, vendorCode);
- case TYPE_IRIS:
- Slog.w(TAG, "Modality not supported");
- return null; // not supported
- case TYPE_FACE:
- return FaceManager.getErrorString(getContext(), error, vendorCode);
- default:
- Slog.w(TAG, "Unable to get error string for modality: " + type);
- return null;
- }
- }
-
- private BiometricAuthenticator getAuthenticator(int type) {
- switch (type) {
- case TYPE_FINGERPRINT:
- return (FingerprintManager)
- getContext().getSystemService(Context.FINGERPRINT_SERVICE);
- case TYPE_IRIS:
- return null;
- case TYPE_FACE:
- return (FaceManager)
- getContext().getSystemService(Context.FACE_SERVICE);
- default:
- return null;
- }
- }
-
- private boolean hasFeature(int type) {
- switch (type) {
- case TYPE_FINGERPRINT:
- return mHasFeatureFingerprint;
- case TYPE_IRIS:
- return mHasFeatureIris;
- case TYPE_FACE:
- return mHasFeatureFace;
- default:
- return false;
- }
- }
-
private void logDialogDismissed(int reason) {
if (reason == BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED) {
// Explicit auth, authentication confirmed.
@@ -1081,14 +1087,14 @@
// Notify SysUI that the biometric has been authenticated. SysUI already knows
// the implicit/explicit state and will react accordingly.
- mStatusBarService.onBiometricAuthenticated(true, null /* failureReason */);
+ mStatusBarService.onBiometricAuthenticated();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
- private void handleAuthenticationRejected(String failureReason) {
- Slog.v(TAG, "handleAuthenticationRejected: " + failureReason);
+ private void handleAuthenticationRejected() {
+ Slog.v(TAG, "handleAuthenticationRejected()");
try {
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
@@ -1097,7 +1103,8 @@
return;
}
- mStatusBarService.onBiometricAuthenticated(false, failureReason);
+ mStatusBarService.onBiometricError(TYPE_NONE,
+ BiometricConstants.BIOMETRIC_PAUSED_REJECTED, 0 /* vendorCode */);
// TODO: This logic will need to be updated if BP is multi-modal
if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
@@ -1112,8 +1119,9 @@
}
}
- private void handleAuthenticationTimedOut(String message) {
- Slog.v(TAG, "handleAuthenticationTimedOut: " + message);
+ private void handleAuthenticationTimedOut(int modality, int error, int vendorCode) {
+ Slog.v(TAG, String.format("handleAuthenticationTimedOut(%d, %d, %d)", modality, error,
+ vendorCode));
try {
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
@@ -1122,14 +1130,14 @@
return;
}
- mStatusBarService.onBiometricAuthenticated(false, message);
+ mStatusBarService.onBiometricError(modality, error, vendorCode);
mCurrentAuthSession.mState = STATE_AUTH_PAUSED;
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
- private void handleOnError(int cookie, int error, String message) {
+ private void handleOnError(int cookie, int modality, int error, int vendorCode) {
Slog.d(TAG, "handleOnError: " + error + " cookie: " + cookie);
// Errors can either be from the current auth session or the pending auth session.
// The pending auth session may receive errors such as ERROR_LOCKOUT before
@@ -1140,7 +1148,7 @@
try {
if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) {
mCurrentAuthSession.mErrorEscrow = error;
- mCurrentAuthSession.mErrorStringEscrow = message;
+ mCurrentAuthSession.mVendorCodeEscrow = vendorCode;
if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
@@ -1148,20 +1156,20 @@
if (mCurrentAuthSession.isAllowDeviceCredential() && errorLockout) {
// SystemUI handles transition from biometric to device credential.
mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
- mStatusBarService.onBiometricError(error, message);
+ mStatusBarService.onBiometricError(modality, error, vendorCode);
} else {
mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI;
if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
mStatusBarService.hideAuthenticationDialog();
} else {
- mStatusBarService.onBiometricError(error, message);
+ mStatusBarService.onBiometricError(modality, error, vendorCode);
}
}
} else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED) {
// In the "try again" state, we should forward canceled errors to
// the client and and clean up. The only error we should get here is
// ERROR_CANCELED due to another client kicking us out.
- mCurrentAuthSession.mClientReceiver.onError(error, message);
+ mCurrentAuthSession.mClientReceiver.onError(modality, error, vendorCode);
mStatusBarService.hideAuthenticationDialog();
mCurrentAuthSession = null;
} else if (mCurrentAuthSession.mState == STATE_SHOWING_DEVICE_CREDENTIAL) {
@@ -1197,7 +1205,7 @@
mCurrentAuthSession.mUserId,
mCurrentAuthSession.mOpPackageName);
} else {
- mPendingAuthSession.mClientReceiver.onError(error, message);
+ mPendingAuthSession.mClientReceiver.onError(modality, error, vendorCode);
mPendingAuthSession = null;
}
} else {
@@ -1261,8 +1269,10 @@
case BiometricPrompt.DISMISSED_REASON_USER_CANCEL:
mCurrentAuthSession.mClientReceiver.onError(
+ mCurrentAuthSession.mModality,
BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
- getContext().getString(R.string.biometric_error_user_canceled));
+ 0 /* vendorCode */
+ );
// Cancel authentication. Skip the token/package check since we are cancelling
// from system server. The interface is permission protected so this is fine.
cancelInternal(null /* token */, null /* package */, false /* fromClient */);
@@ -1270,8 +1280,11 @@
case BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED:
case BiometricPrompt.DISMISSED_REASON_ERROR:
- mCurrentAuthSession.mClientReceiver.onError(mCurrentAuthSession.mErrorEscrow,
- mCurrentAuthSession.mErrorStringEscrow);
+ mCurrentAuthSession.mClientReceiver.onError(
+ mCurrentAuthSession.mModality,
+ mCurrentAuthSession.mErrorEscrow,
+ mCurrentAuthSession.mVendorCodeEscrow
+ );
break;
default:
@@ -1354,30 +1367,35 @@
mPendingAuthSession = null;
mCurrentAuthSession.mState = STATE_AUTH_STARTED;
- try {
- int modality = TYPE_NONE;
- it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
- if (pair.getKey() == TYPE_FINGERPRINT) {
- mFingerprintService.startPreparedClient(pair.getValue());
- } else if (pair.getKey() == TYPE_IRIS) {
- Slog.e(TAG, "Iris unsupported");
- } else if (pair.getKey() == TYPE_FACE) {
- mFaceService.startPreparedClient(pair.getValue());
- } else {
- Slog.e(TAG, "Unknown modality: " + pair.getKey());
+ int modality = TYPE_NONE;
+ it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
+ boolean foundAuthenticator = false;
+ for (AuthenticatorWrapper authenticator : mAuthenticators) {
+ if (authenticator.modality == pair.getKey()) {
+ foundAuthenticator = true;
+ try {
+ authenticator.impl.startPreparedClient(pair.getValue());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
}
- modality |= pair.getKey();
}
+ if (!foundAuthenticator) {
+ Slog.e(TAG, "Unknown modality: " + pair.getKey());
+ }
+ modality |= pair.getKey();
+ }
- if (!continuing) {
+ if (!continuing) {
+ try {
mStatusBarService.showAuthenticationDialog(mCurrentAuthSession.mBundle,
mInternalReceiver, modality, requireConfirmation, userId,
mCurrentAuthSession.mOpPackageName);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
}
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
}
}
}
@@ -1387,7 +1405,8 @@
int callingUid, int callingPid, int callingUserId) {
mHandler.post(() -> {
- final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
+ final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId,
+ opPackageName);
final int modality = result.first;
final int error = result.second;
@@ -1400,23 +1419,7 @@
} else if (error != BiometricConstants.BIOMETRIC_SUCCESS) {
// Check for errors, notify callback, and return
try {
- final String hardwareUnavailable =
- getContext().getString(R.string.biometric_error_hw_unavailable);
- switch (error) {
- case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
- receiver.onError(error, hardwareUnavailable);
- break;
- case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE:
- receiver.onError(error, hardwareUnavailable);
- break;
- case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS:
- receiver.onError(error,
- getErrorString(modality, error, 0 /* vendorCode */));
- break;
- default:
- Slog.e(TAG, "Unhandled error");
- break;
- }
+ receiver.onError(modality, error, 0 /* vendorCode */);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send error", e);
}
@@ -1435,41 +1438,41 @@
* modality/modalities to start authenticating with. authenticateInternal() should only be
* used for:
* 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is,
- * invoked, shortly after which BiometricPrompt is shown and authentication starts
+ * invoked, shortly after which BiometricPrompt is shown and authentication starts
* 2) Preparing <Biometric>Services for authentication when BiometricPrompt is already shown
- * and the user has pressed "try again"
+ * and the user has pressed "try again"
*/
private void authenticateInternal(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
int callingUid, int callingPid, int callingUserId, int modality) {
+ boolean requireConfirmation = bundle.getBoolean(
+ BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
+ if ((modality & TYPE_FACE) != 0) {
+ // Check if the user has forced confirmation to be required in Settings.
+ requireConfirmation = requireConfirmation
+ || mSettingObserver.getFaceAlwaysRequireConfirmation(userId);
+ }
+ // Generate random cookies to pass to the services that should prepare to start
+ // authenticating. Store the cookie here and wait for all services to "ack"
+ // with the cookie. Once all cookies are received, we can show the prompt
+ // and let the services start authenticating. The cookie should be non-zero.
+ final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
+ final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
+ Slog.d(TAG, "Creating auth session. Modality: " + modality
+ + ", cookie: " + cookie
+ + ", authenticators: " + authenticators);
+ final HashMap<Integer, Integer> modalities = new HashMap<>();
+
+ // If it's only device credential, we don't need to wait - LockSettingsService is
+ // always ready to check credential (SystemUI invokes that path).
+ if ((authenticators & ~Authenticator.TYPE_CREDENTIAL) != 0) {
+ modalities.put(modality, cookie);
+ }
+ mPendingAuthSession = new AuthSession(modalities, token, sessionId, userId,
+ receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
+ modality, requireConfirmation);
+
try {
- boolean requireConfirmation = bundle.getBoolean(
- BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
- if ((modality & TYPE_FACE) != 0) {
- // Check if the user has forced confirmation to be required in Settings.
- requireConfirmation = requireConfirmation
- || mSettingObserver.getFaceAlwaysRequireConfirmation(userId);
- }
- // Generate random cookies to pass to the services that should prepare to start
- // authenticating. Store the cookie here and wait for all services to "ack"
- // with the cookie. Once all cookies are received, we can show the prompt
- // and let the services start authenticating. The cookie should be non-zero.
- final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
- final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
- Slog.d(TAG, "Creating auth session. Modality: " + modality
- + ", cookie: " + cookie
- + ", authenticators: " + authenticators);
- final HashMap<Integer, Integer> modalities = new HashMap<>();
-
- // If it's only device credential, we don't need to wait - LockSettingsService is
- // always ready to check credential (SystemUI invokes that path).
- if ((authenticators & ~Authenticator.TYPE_CREDENTIAL) != 0) {
- modalities.put(modality, cookie);
- }
- mPendingAuthSession = new AuthSession(modalities, token, sessionId, userId,
- receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
- modality, requireConfirmation);
-
if (authenticators == Authenticator.TYPE_CREDENTIAL) {
mPendingAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mCurrentAuthSession = mPendingAuthSession;
@@ -1484,19 +1487,10 @@
mCurrentAuthSession.mOpPackageName);
} else {
mPendingAuthSession.mState = STATE_AUTH_CALLED;
- // No polymorphism :(
- if ((modality & TYPE_FINGERPRINT) != 0) {
- mFingerprintService.prepareForAuthentication(token, sessionId, userId,
- mInternalReceiver, opPackageName, cookie,
- callingUid, callingPid, callingUserId);
- }
- if ((modality & TYPE_IRIS) != 0) {
- Slog.w(TAG, "Iris unsupported");
- }
- if ((modality & TYPE_FACE) != 0) {
- mFaceService.prepareForAuthentication(requireConfirmation,
- token, sessionId, userId, mInternalReceiver, opPackageName,
- cookie, callingUid, callingPid, callingUserId);
+ for (AuthenticatorWrapper authenticator : mAuthenticators) {
+ authenticator.impl.prepareForAuthentication(requireConfirmation, token,
+ sessionId, userId, mInternalReceiver, opPackageName, cookie, callingUid,
+ callingPid, callingUserId);
}
}
} catch (RemoteException e) {
@@ -1517,11 +1511,10 @@
try {
// Send error to client
mCurrentAuthSession.mClientReceiver.onError(
+ mCurrentAuthSession.mModality,
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
- getContext().getString(
- com.android.internal.R.string.biometric_error_user_canceled)
+ 0 /* vendorCode */
);
-
mCurrentAuthSession = null;
mStatusBarService.hideAuthenticationDialog();
} catch (RemoteException e) {
@@ -1537,30 +1530,25 @@
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
- try {
- if (mCurrentAuthSession == null) {
- Slog.w(TAG, "Skipping cancelInternal");
- return;
- } else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
- Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState);
- return;
- }
+ if (mCurrentAuthSession == null) {
+ Slog.w(TAG, "Skipping cancelInternal");
+ return;
+ } else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
+ Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState);
+ return;
+ }
- // TODO: For multiple modalities, send a single ERROR_CANCELED only when all
- // drivers have canceled authentication.
- if ((mCurrentAuthSession.mModality & TYPE_FINGERPRINT) != 0) {
- mFingerprintService.cancelAuthenticationFromService(token, opPackageName,
- callingUid, callingPid, callingUserId, fromClient);
+ // TODO: For multiple modalities, send a single ERROR_CANCELED only when all
+ // drivers have canceled authentication.
+ for (AuthenticatorWrapper authenticator : mAuthenticators) {
+ if ((authenticator.modality & mCurrentAuthSession.mModality) != 0) {
+ try {
+ authenticator.impl.cancelAuthenticationFromService(token, opPackageName,
+ callingUid, callingPid, callingUserId, fromClient);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to cancel authentication");
+ }
}
- if ((mCurrentAuthSession.mModality & TYPE_IRIS) != 0) {
- Slog.w(TAG, "Iris unsupported");
- }
- if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
- mFaceService.cancelAuthenticationFromService(token, opPackageName,
- callingUid, callingPid, callingUserId, fromClient);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to cancel authentication");
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/face/FaceAuthenticator.java
new file mode 100644
index 0000000..5df5b29d
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/face/FaceAuthenticator.java
@@ -0,0 +1,75 @@
+/*
+ * 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.biometrics.face;
+
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.hardware.face.IFaceService;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * TODO(b/141025588): Add JavaDoc.
+ */
+public final class FaceAuthenticator extends IBiometricAuthenticator.Stub {
+ private final IFaceService mFaceService;
+
+ public FaceAuthenticator(IFaceService faceService) {
+ mFaceService = faceService;
+ }
+
+ @Override
+ public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
+ long sessionId, int userId, IBiometricServiceReceiverInternal wrapperReceiver,
+ String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId)
+ throws RemoteException {
+ mFaceService.prepareForAuthentication(requireConfirmation, token, sessionId, userId,
+ wrapperReceiver, opPackageName, cookie, callingUid, callingPid, callingUserId);
+ }
+
+ @Override
+ public void startPreparedClient(int cookie) throws RemoteException {
+ mFaceService.startPreparedClient(cookie);
+ }
+
+ @Override
+ public void cancelAuthenticationFromService(IBinder token, String opPackageName, int callingUid,
+ int callingPid, int callingUserId, boolean fromClient) throws RemoteException {
+ mFaceService.cancelAuthenticationFromService(token, opPackageName, callingUid, callingPid,
+ callingUserId, fromClient);
+ }
+
+ @Override
+ public boolean isHardwareDetected(String opPackageName) throws RemoteException {
+ return mFaceService.isHardwareDetected(opPackageName);
+ }
+
+ @Override
+ public boolean hasEnrolledTemplates(int userId, String opPackageName) throws RemoteException {
+ return mFaceService.hasEnrolledFaces(userId, opPackageName);
+ }
+
+ @Override
+ public void resetLockout(byte[] token) throws RemoteException {
+ mFaceService.resetLockout(token);
+ }
+
+ @Override
+ public void setActiveUser(int uid) throws RemoteException {
+ mFaceService.setActiveUser(uid);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 1b13212..a0c8e23 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.RESET_FACE_LOCKOUT;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import android.app.ActivityManager;
import android.app.AppOpsManager;
@@ -538,7 +539,7 @@
// TODO: refactor out common code here
@Override // Binder call
- public boolean isHardwareDetected(long deviceId, String opPackageName) {
+ public boolean isHardwareDetected(String opPackageName) {
checkPermission(USE_BIOMETRIC_INTERNAL);
if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
Binder.getCallingUid(), Binder.getCallingPid(),
@@ -752,8 +753,7 @@
public void onError(long deviceId, int error, int vendorCode, int cookie)
throws RemoteException {
if (getWrapperReceiver() != null) {
- getWrapperReceiver().onError(cookie, error,
- FaceManager.getErrorString(getContext(), error, vendorCode));
+ getWrapperReceiver().onError(cookie, TYPE_FACE, error, vendorCode);
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java
new file mode 100644
index 0000000..6150de1
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java
@@ -0,0 +1,75 @@
+/*
+ * 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.biometrics.fingerprint;
+
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.hardware.fingerprint.IFingerprintService;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * TODO(b/141025588): Add JavaDoc.
+ */
+public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub {
+ private final IFingerprintService mFingerprintService;
+
+ public FingerprintAuthenticator(IFingerprintService fingerprintService) {
+ mFingerprintService = fingerprintService;
+ }
+
+ @Override
+ public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
+ long sessionId, int userId, IBiometricServiceReceiverInternal wrapperReceiver,
+ String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId)
+ throws RemoteException {
+ mFingerprintService.prepareForAuthentication(token, sessionId, userId, wrapperReceiver,
+ opPackageName, cookie, callingUid, callingPid, callingUserId);
+ }
+
+ @Override
+ public void startPreparedClient(int cookie) throws RemoteException {
+ mFingerprintService.startPreparedClient(cookie);
+ }
+
+ @Override
+ public void cancelAuthenticationFromService(IBinder token, String opPackageName, int callingUid,
+ int callingPid, int callingUserId, boolean fromClient) throws RemoteException {
+ mFingerprintService.cancelAuthenticationFromService(token, opPackageName, callingUid,
+ callingPid, callingUserId, fromClient);
+ }
+
+ @Override
+ public boolean isHardwareDetected(String opPackageName) throws RemoteException {
+ return mFingerprintService.isHardwareDetected(opPackageName);
+ }
+
+ @Override
+ public boolean hasEnrolledTemplates(int userId, String opPackageName) throws RemoteException {
+ return mFingerprintService.hasEnrolledFingerprints(userId, opPackageName);
+ }
+
+ @Override
+ public void resetLockout(byte[] token) throws RemoteException {
+ mFingerprintService.resetTimeout(token);
+ }
+
+ @Override
+ public void setActiveUser(int uid) throws RemoteException {
+ mFingerprintService.setActiveUser(uid);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index d85af2e..44797ad 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -22,6 +22,7 @@
import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import android.app.ActivityManager;
import android.app.AlarmManager;
@@ -350,7 +351,7 @@
// TODO: refactor out common code here
@Override // Binder call
- public boolean isHardwareDetected(long deviceId, String opPackageName) {
+ public boolean isHardwareDetected(String opPackageName) {
if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
Binder.getCallingUid(), Binder.getCallingPid(),
UserHandle.getCallingUserId())) {
@@ -480,8 +481,7 @@
public void onError(long deviceId, int error, int vendorCode, int cookie)
throws RemoteException {
if (getWrapperReceiver() != null) {
- getWrapperReceiver().onError(cookie, error,
- FingerprintManager.getErrorString(getContext(), error, vendorCode));
+ getWrapperReceiver().onError(cookie, TYPE_FINGERPRINT, error, vendorCode);
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/iris/IrisAuthenticator.java
new file mode 100644
index 0000000..c44b8e7
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/iris/IrisAuthenticator.java
@@ -0,0 +1,68 @@
+/*
+ * 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.biometrics.iris;
+
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.hardware.iris.IIrisService;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * TODO(b/141025588): Add JavaDoc.
+ */
+public final class IrisAuthenticator extends IBiometricAuthenticator.Stub {
+ private final IIrisService mIrisService;
+
+ public IrisAuthenticator(IIrisService irisService) {
+ mIrisService = irisService;
+ }
+
+ @Override
+ public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
+ long sessionId, int userId, IBiometricServiceReceiverInternal wrapperReceiver,
+ String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId)
+ throws RemoteException {
+ }
+
+ @Override
+ public void startPreparedClient(int cookie) throws RemoteException {
+ }
+
+ @Override
+ public void cancelAuthenticationFromService(IBinder token, String opPackageName, int callingUid,
+ int callingPid, int callingUserId, boolean fromClient) throws RemoteException {
+ }
+
+ @Override
+ public boolean isHardwareDetected(String opPackageName) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean hasEnrolledTemplates(int userId, String opPackageName) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void resetLockout(byte[] token) throws RemoteException {
+ }
+
+ @Override
+ public void setActiveUser(int uid) throws RemoteException {
+ }
+}
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 854f16a..9ac9955 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -19,7 +19,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.os.Process;
+import android.os.UserHandle;
import android.util.Slog;
import android.util.StatsLog;
@@ -54,8 +54,8 @@
}
@Override
- public void reportChangeByPackageName(long changeId, String packageName) {
- ApplicationInfo appInfo = getApplicationInfo(packageName);
+ public void reportChangeByPackageName(long changeId, String packageName, int userId) {
+ ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
if (appInfo == null) {
return;
}
@@ -80,8 +80,8 @@
}
@Override
- public boolean isChangeEnabledByPackageName(long changeId, String packageName) {
- ApplicationInfo appInfo = getApplicationInfo(packageName);
+ public boolean isChangeEnabledByPackageName(long changeId, String packageName, int userId) {
+ ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
if (appInfo == null) {
return true;
}
@@ -96,7 +96,8 @@
}
boolean enabled = true;
for (String packageName : packages) {
- enabled = enabled && isChangeEnabledByPackageName(changeId, packageName);
+ enabled = enabled && isChangeEnabledByPackageName(changeId, packageName,
+ UserHandle.getUserId(uid));
}
return enabled;
}
@@ -127,10 +128,9 @@
mChangeReporter.resetReportedChanges(appInfo.uid);
}
- private ApplicationInfo getApplicationInfo(String packageName) {
+ private ApplicationInfo getApplicationInfo(String packageName, int userId) {
try {
- return mContext.getPackageManager().getApplicationInfoAsUser(packageName, 0,
- Process.myUid());
+ return mContext.getPackageManager().getApplicationInfoAsUser(packageName, 0, userId);
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "No installed package " + packageName);
}
diff --git a/services/core/java/com/android/server/compat/PlatformCompatNative.java b/services/core/java/com/android/server/compat/PlatformCompatNative.java
index 8399671..85dfbf4 100644
--- a/services/core/java/com/android/server/compat/PlatformCompatNative.java
+++ b/services/core/java/com/android/server/compat/PlatformCompatNative.java
@@ -29,8 +29,8 @@
}
@Override
- public void reportChangeByPackageName(long changeId, String packageName) {
- mPlatformCompat.reportChangeByPackageName(changeId, packageName);
+ public void reportChangeByPackageName(long changeId, String packageName, int userId) {
+ mPlatformCompat.reportChangeByPackageName(changeId, packageName, userId);
}
@Override
@@ -39,8 +39,8 @@
}
@Override
- public boolean isChangeEnabledByPackageName(long changeId, String packageName) {
- return mPlatformCompat.isChangeEnabledByPackageName(changeId, packageName);
+ public boolean isChangeEnabledByPackageName(long changeId, String packageName, int userId) {
+ return mPlatformCompat.isChangeEnabledByPackageName(changeId, packageName, userId);
}
@Override
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 077c405..d13e675 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -53,7 +53,8 @@
NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET),
LOGGED_IN(SystemMessage.NOTE_NETWORK_LOGGED_IN),
PARTIAL_CONNECTIVITY(SystemMessage.NOTE_NETWORK_PARTIAL_CONNECTIVITY),
- SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN);
+ SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN),
+ PRIVATE_DNS_BROKEN(SystemMessage.NOTE_NETWORK_PRIVATE_DNS_BROKEN);
public final int eventId;
@@ -175,13 +176,23 @@
}
Resources r = Resources.getSystem();
- CharSequence title;
- CharSequence details;
+ final CharSequence title;
+ final CharSequence details;
int icon = getIcon(transportType, notifyType);
if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
title = r.getString(R.string.wifi_no_internet,
WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID()));
details = r.getString(R.string.wifi_no_internet_detailed);
+ } else if (notifyType == NotificationType.PRIVATE_DNS_BROKEN) {
+ if (transportType == TRANSPORT_CELLULAR) {
+ title = r.getString(R.string.mobile_no_internet);
+ } else if (transportType == TRANSPORT_WIFI) {
+ title = r.getString(R.string.wifi_no_internet,
+ WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID()));
+ } else {
+ title = r.getString(R.string.other_networks_no_internet);
+ }
+ details = r.getString(R.string.private_dns_broken_detailed);
} else if (notifyType == NotificationType.PARTIAL_CONNECTIVITY
&& transportType == TRANSPORT_WIFI) {
title = r.getString(R.string.network_partial_connectivity,
@@ -357,8 +368,10 @@
}
switch (t) {
case SIGN_IN:
- return 5;
+ return 6;
case PARTIAL_CONNECTIVITY:
+ return 5;
+ case PRIVATE_DNS_BROKEN:
return 4;
case NO_INTERNET:
return 3;
diff --git a/services/core/java/com/android/server/location/CallerIdentity.java b/services/core/java/com/android/server/location/CallerIdentity.java
index 61e5d1f..75ba5b8 100644
--- a/services/core/java/com/android/server/location/CallerIdentity.java
+++ b/services/core/java/com/android/server/location/CallerIdentity.java
@@ -17,6 +17,7 @@
package com.android.server.location;
import android.annotation.NonNull;
+import android.annotation.Nullable;
/**
* Represents the calling process's uid, pid, and package name.
@@ -25,13 +26,15 @@
public final int mUid;
public final int mPid;
public final String mPackageName;
+ public final @Nullable String mFeatureId;
public final @NonNull String mListenerIdentifier;
- public CallerIdentity(int uid, int pid, String packageName,
+ public CallerIdentity(int uid, int pid, String packageName, @Nullable String featureId,
@NonNull String listenerIdentifier) {
mUid = uid;
mPid = pid;
mPackageName = packageName;
+ mFeatureId = featureId;
mListenerIdentifier = listenerIdentifier;
}
}
diff --git a/services/core/java/com/android/server/location/GeofenceManager.java b/services/core/java/com/android/server/location/GeofenceManager.java
index 4f10a71..17a2169 100644
--- a/services/core/java/com/android/server/location/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/GeofenceManager.java
@@ -17,6 +17,7 @@
package com.android.server.location;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
@@ -152,7 +153,7 @@
}
public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
- int allowedResolutionLevel, int uid, String packageName,
+ int allowedResolutionLevel, int uid, String packageName, @Nullable String featureId,
@NonNull String listenerIdentifier) {
if (D) {
Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence
@@ -160,8 +161,8 @@
}
GeofenceState state = new GeofenceState(geofence,
- request.getExpireAt(), allowedResolutionLevel, uid, packageName, listenerIdentifier,
- intent);
+ request.getExpireAt(), allowedResolutionLevel, uid, packageName, featureId,
+ listenerIdentifier, intent);
synchronized (mLock) {
// first make sure it doesn't already exist
for (int i = mFences.size() - 1; i >= 0; i--) {
@@ -304,7 +305,7 @@
int op = LocationManagerService.resolutionLevelToOp(state.mAllowedResolutionLevel);
if (op >= 0) {
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, state.mUid,
- state.mPackageName, null, state.mListenerIdentifier)
+ state.mPackageName, state.mFeatureId, state.mListenerIdentifier)
!= AppOpsManager.MODE_ALLOWED) {
if (D) {
Slog.d(TAG, "skipping geofence processing for no op app: "
diff --git a/services/core/java/com/android/server/location/GeofenceState.java b/services/core/java/com/android/server/location/GeofenceState.java
index fe0719d..a91a1dc 100644
--- a/services/core/java/com/android/server/location/GeofenceState.java
+++ b/services/core/java/com/android/server/location/GeofenceState.java
@@ -18,6 +18,7 @@
package com.android.server.location;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.location.Geofence;
import android.location.Location;
@@ -39,6 +40,7 @@
public final int mAllowedResolutionLevel;
public final int mUid;
public final String mPackageName;
+ public final @Nullable String mFeatureId;
public final @NonNull String mListenerIdentifier;
public final PendingIntent mIntent;
@@ -46,7 +48,8 @@
double mDistanceToCenter; // current distance to center of fence
public GeofenceState(Geofence fence, long expireAt, int allowedResolutionLevel, int uid,
- String packageName, @NonNull String listenerIdentifier, PendingIntent intent) {
+ String packageName, @Nullable String featureId, @NonNull String listenerIdentifier,
+ PendingIntent intent) {
mState = STATE_UNKNOWN;
mDistanceToCenter = Double.MAX_VALUE;
@@ -55,6 +58,7 @@
mAllowedResolutionLevel = allowedResolutionLevel;
mUid = uid;
mPackageName = packageName;
+ mFeatureId = featureId;
mListenerIdentifier = listenerIdentifier;
mIntent = intent;
diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java
index 25b544f..60ce1f4 100644
--- a/services/core/java/com/android/server/location/RemoteListenerHelper.java
+++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java
@@ -182,7 +182,7 @@
}
return mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, callerIdentity.mUid,
- callerIdentity.mPackageName, null,
+ callerIdentity.mPackageName, callerIdentity.mFeatureId,
"Location sent to " + callerIdentity.mListenerIdentifier)
== AppOpsManager.MODE_ALLOWED;
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 34fb641..9bbe628 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -23,8 +23,11 @@
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
+import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -101,7 +104,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -116,7 +118,6 @@
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockPatternUtils.CredentialType;
import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -178,7 +179,6 @@
private static final int PROFILE_KEY_IV_SIZE = 12;
private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge";
- private static final int SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT = 1;
private static final String PREV_SYNTHETIC_PASSWORD_HANDLE_KEY = "prev-sp-handle";
private static final String SYNTHETIC_PASSWORD_UPDATE_TIME_KEY = "sp-handle-ts";
@@ -317,14 +317,34 @@
}
}
+ private LockscreenCredential generateRandomProfilePassword() {
+ byte[] randomLockSeed = new byte[] {};
+ try {
+ randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
+ char[] newPasswordChars = HexEncoding.encode(randomLockSeed);
+ byte[] newPassword = new byte[newPasswordChars.length];
+ for (int i = 0; i < newPasswordChars.length; i++) {
+ newPassword[i] = (byte) newPasswordChars[i];
+ }
+ LockscreenCredential credential =
+ LockscreenCredential.createManagedPassword(newPassword);
+ Arrays.fill(newPasswordChars, '\u0000');
+ Arrays.fill(newPassword, (byte) 0);
+ Arrays.fill(randomLockSeed, (byte) 0);
+ return credential;
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("Fail to generate profile password", e);
+ }
+ }
+
/**
* Tie managed profile to primary profile if it is in unified mode and not tied before.
*
* @param managedUserId Managed profile user Id
* @param managedUserPassword Managed profile original password (when it has separated lock).
- * NULL when it does not have a separated lock before.
*/
- public void tieManagedProfileLockIfNecessary(int managedUserId, byte[] managedUserPassword) {
+ public void tieManagedProfileLockIfNecessary(int managedUserId,
+ LockscreenCredential managedUserPassword) {
if (DEBUG) Slog.v(TAG, "Check child profile lock for user: " + managedUserId);
// Only for managed profile
if (!mUserManager.getUserInfo(managedUserId).isManagedProfile()) {
@@ -356,27 +376,10 @@
return;
}
if (DEBUG) Slog.v(TAG, "Tie managed profile to parent now!");
- byte[] randomLockSeed = new byte[] {};
- try {
- randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
- char[] newPasswordChars = HexEncoding.encode(randomLockSeed);
- byte[] newPassword = new byte[newPasswordChars.length];
- for (int i = 0; i < newPasswordChars.length; i++) {
- newPassword[i] = (byte) newPasswordChars[i];
- }
- Arrays.fill(newPasswordChars, '\u0000');
- final int quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
- setLockCredentialInternal(newPassword, CREDENTIAL_TYPE_PASSWORD, managedUserPassword,
- quality, managedUserId, false, /* isLockTiedToParent= */ true);
- // We store a private credential for the managed user that's unlocked by the primary
- // account holder's credential. As such, the user will never be prompted to enter this
- // password directly, so we always store a password.
- setLong(LockPatternUtils.PASSWORD_TYPE_KEY, quality, managedUserId);
- tieProfileLockToParent(managedUserId, newPassword);
- Arrays.fill(newPassword, (byte) 0);
- } catch (NoSuchAlgorithmException e) {
- Slog.e(TAG, "Fail to tie managed profile", e);
- // Nothing client can do to fix this issue, so we do not throw exception out
+ try (LockscreenCredential unifiedProfilePassword = generateRandomProfilePassword()) {
+ setLockCredentialInternal(unifiedProfilePassword, managedUserPassword, managedUserId,
+ false, /* isLockTiedToParent= */ true);
+ tieProfileLockToParent(managedUserId, unifiedProfilePassword);
}
}
@@ -512,6 +515,10 @@
}
}
+ public int settingsGlobalGetInt(ContentResolver contentResolver, String keyName,
+ int defaultValue) {
+ return Settings.Global.getInt(contentResolver, keyName, defaultValue);
+ }
}
public LockSettingsService(Context context) {
@@ -682,7 +689,7 @@
hideEncryptionNotification(new UserHandle(userId));
if (mUserManager.getUserInfo(userId).isManagedProfile()) {
- tieManagedProfileLockIfNecessary(userId, null);
+ tieManagedProfileLockIfNecessary(userId, LockscreenCredential.createNone());
}
// If the user doesn't have a credential, try and derive their secret for the
@@ -704,10 +711,9 @@
}
final long handle = getSyntheticPasswordHandleLocked(userId);
- final byte[] noCredential = null;
AuthenticationResult result =
- mSpManager.unwrapPasswordBasedSyntheticPassword(
- getGateKeeperService(), handle, noCredential, userId, null);
+ mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
+ handle, LockscreenCredential.createNone(), userId, null);
if (result.authToken != null) {
Slog.i(TAG, "Retrieved auth token for user " + userId);
onAuthTokenKnownForUser(userId, result.authToken);
@@ -870,25 +876,6 @@
final List<UserInfo> users = mUserManager.getUsers();
for (int i = 0; i < users.size(); i++) {
final UserInfo userInfo = users.get(i);
- if (userInfo.isManagedProfile() && mStorage.hasChildProfileLock(userInfo.id)) {
- // When managed profile has a unified lock, the password quality stored has 2
- // possibilities only.
- // 1). PASSWORD_QUALITY_UNSPECIFIED, which is upgraded from dp2, and we are
- // going to set it back to PASSWORD_QUALITY_ALPHANUMERIC.
- // 2). PASSWORD_QUALITY_ALPHANUMERIC, which is the actual password quality for
- // unified lock.
- final long quality = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
- if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
- // Only possible when it's upgraded from nyc dp3
- Slog.i(TAG, "Migrated tied profile lock type");
- setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userInfo.id);
- } else if (quality != DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) {
- // It should not happen
- Slog.e(TAG, "Invalid tied profile lock type: " + quality);
- }
- }
try {
final String alias = LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userInfo.id;
java.security.KeyStore keyStore =
@@ -1031,21 +1018,22 @@
@Override
public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
- byte[] managedUserPassword) {
+ LockscreenCredential managedUserPassword) {
checkWritePermission(userId);
if (!mLockPatternUtils.hasSecureLockScreen()) {
throw new UnsupportedOperationException(
"This operation requires secure lock screen feature.");
}
synchronized (mSeparateChallengeLock) {
- setSeparateProfileChallengeEnabledLocked(userId, enabled, managedUserPassword);
+ setSeparateProfileChallengeEnabledLocked(userId, enabled, managedUserPassword != null
+ ? managedUserPassword : LockscreenCredential.createNone());
}
notifySeparateProfileChallengeChanged(userId);
}
@GuardedBy("mSeparateChallengeLock")
private void setSeparateProfileChallengeEnabledLocked(@UserIdInt int userId,
- boolean enabled, byte[] managedUserPassword) {
+ boolean enabled, LockscreenCredential managedUserPassword) {
final boolean old = getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
try {
@@ -1083,6 +1071,10 @@
@Override
public void setLong(String key, long value, int userId) {
checkWritePermission(userId);
+ setLongUnchecked(key, value, userId);
+ }
+
+ private void setLongUnchecked(String key, long value, int userId) {
setStringUnchecked(key, userId, Long.toString(value));
}
@@ -1112,6 +1104,10 @@
@Override
public long getLong(String key, long defaultValue, int userId) {
checkReadPermission(key, userId);
+ return getLongUnchecked(key, defaultValue, userId);
+ }
+
+ private long getLongUnchecked(String key, long defaultValue, int userId) {
String value = getStringUnchecked(key, null, userId);
return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
}
@@ -1122,7 +1118,7 @@
return getStringUnchecked(key, defaultValue, userId);
}
- public String getStringUnchecked(String key, String defaultValue, int userId) {
+ private String getStringUnchecked(String key, String defaultValue, int userId) {
if (Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)) {
long ident = Binder.clearCallingIdentity();
try {
@@ -1131,9 +1127,8 @@
Binder.restoreCallingIdentity(ident);
}
}
-
if (userId == USER_FRP) {
- return getFrpStringUnchecked(key);
+ return null;
}
if (LockPatternUtils.LEGACY_LOCK_PATTERN_ENABLED.equals(key)) {
@@ -1143,51 +1138,81 @@
return mStorage.readKeyValue(key, defaultValue, userId);
}
- private String getFrpStringUnchecked(String key) {
- if (LockPatternUtils.PASSWORD_TYPE_KEY.equals(key)) {
- return String.valueOf(readFrpPasswordQuality());
- }
- return null;
+ private void setKeyguardStoredQuality(int quality, int userId) {
+ if (DEBUG) Slog.d(TAG, "setKeyguardStoredQuality: user=" + userId + " quality=" + quality);
+ setLongUnchecked(LockPatternUtils.PASSWORD_TYPE_KEY, quality, userId);
}
- private int readFrpPasswordQuality() {
- return mStorage.readPersistentDataBlock().qualityForUi;
+ private int getKeyguardStoredQuality(int userId) {
+ return (int) getLongUnchecked(LockPatternUtils.PASSWORD_TYPE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
}
@Override
- public boolean havePassword(int userId) {
+ public int getCredentialType(int userId) {
checkPasswordHavePermission(userId);
- synchronized (mSpManager) {
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- final long handle = getSyntheticPasswordHandleLocked(userId);
- return mSpManager.getCredentialType(handle, userId) == CREDENTIAL_TYPE_PASSWORD;
- }
- }
- // Do we need a permissions check here?
- return mStorage.hasPassword(userId);
+ return getCredentialTypeInternal(userId);
}
- @Override
- public boolean havePattern(int userId) {
- checkPasswordHavePermission(userId);
+ // TODO: this is a hot path, can we optimize it?
+ /**
+ * Returns the credential type of the user, can be one of {@link #CREDENTIAL_TYPE_NONE},
+ * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} and
+ * {@link #CREDENTIAL_TYPE_PASSWORD}
+ */
+ public int getCredentialTypeInternal(int userId) {
+ if (userId == USER_FRP) {
+ return getFrpCredentialType();
+ }
synchronized (mSpManager) {
if (isSyntheticPasswordBasedCredentialLocked(userId)) {
final long handle = getSyntheticPasswordHandleLocked(userId);
- return mSpManager.getCredentialType(handle, userId) == CREDENTIAL_TYPE_PATTERN;
+ int rawType = mSpManager.getCredentialType(handle, userId);
+ if (rawType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
+ return rawType;
+ }
+ return pinOrPasswordQualityToCredentialType(getKeyguardStoredQuality(userId));
}
}
- // Do we need a permissions check here?
- return mStorage.hasPattern(userId);
+ // Intentional duplication of the getKeyguardStoredQuality() call above since this is a
+ // unlikely code path (device with pre-synthetic password credential). We want to skip
+ // calling getKeyguardStoredQuality whenever possible.
+ final int savedQuality = getKeyguardStoredQuality(userId);
+ if (savedQuality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ && mStorage.hasPattern(userId)) {
+ return CREDENTIAL_TYPE_PATTERN;
+ }
+ if (savedQuality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ && mStorage.hasPassword(userId)) {
+ return pinOrPasswordQualityToCredentialType(savedQuality);
+ }
+ return CREDENTIAL_TYPE_NONE;
+ }
+
+ private int getFrpCredentialType() {
+ PersistentData data = mStorage.readPersistentDataBlock();
+ if (data.type != PersistentData.TYPE_SP && data.type != PersistentData.TYPE_SP_WEAVER) {
+ return CREDENTIAL_TYPE_NONE;
+ }
+ int credentialType = SyntheticPasswordManager.getFrpCredentialType(data.payload);
+ if (credentialType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
+ return credentialType;
+ }
+ return pinOrPasswordQualityToCredentialType(data.qualityForUi);
+ }
+
+ private static int pinOrPasswordQualityToCredentialType(int quality) {
+ if (LockPatternUtils.isQualityAlphabeticPassword(quality)) {
+ return CREDENTIAL_TYPE_PASSWORD;
+ }
+ if (LockPatternUtils.isQualityNumericPin(quality)) {
+ return CREDENTIAL_TYPE_PIN;
+ }
+ throw new IllegalArgumentException("Quality is neither Pin nor password: " + quality);
}
private boolean isUserSecure(int userId) {
- synchronized (mSpManager) {
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- final long handle = getSyntheticPasswordHandleLocked(userId);
- return mSpManager.getCredentialType(handle, userId) != CREDENTIAL_TYPE_NONE;
- }
- }
- return mStorage.hasCredential(userId);
+ return getCredentialTypeInternal(userId) != CREDENTIAL_TYPE_NONE;
}
private void setKeystorePassword(byte[] password, int userHandle) {
@@ -1205,8 +1230,8 @@
ks.unlock(userHandle, passwordString);
}
- @VisibleForTesting
- protected byte[] getDecryptedPasswordForTiedProfile(int userId)
+ @VisibleForTesting /** Note: this method is overridden in unit tests */
+ protected LockscreenCredential getDecryptedPasswordForTiedProfile(int userId)
throws KeyStoreException, UnrecoverableKeyException,
NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException,
@@ -1230,7 +1255,10 @@
cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv));
decryptionResult = cipher.doFinal(encryptedPassword);
- return decryptionResult;
+ LockscreenCredential credential = LockscreenCredential.createManagedPassword(
+ decryptionResult);
+ Arrays.fill(decryptionResult, (byte) 0);
+ return credential;
}
private void unlockChildProfile(int profileHandle, boolean ignoreUserNotAuthenticated,
@@ -1238,7 +1266,6 @@
@Nullable ArrayList<PendingResetLockout> resetLockouts) {
try {
doVerifyCredential(getDecryptedPasswordForTiedProfile(profileHandle),
- CREDENTIAL_TYPE_PASSWORD,
challengeType, challenge, profileHandle, null /* progressCallback */,
resetLockouts);
} catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
@@ -1276,17 +1303,17 @@
final IProgressListener listener = new IProgressListener.Stub() {
@Override
public void onStarted(int id, Bundle extras) throws RemoteException {
- Log.d(TAG, "unlockUser started");
+ Slog.d(TAG, "unlockUser started");
}
@Override
public void onProgress(int id, int progress, Bundle extras) throws RemoteException {
- Log.d(TAG, "unlockUser progress " + progress);
+ Slog.d(TAG, "unlockUser progress " + progress);
}
@Override
public void onFinished(int id, Bundle extras) throws RemoteException {
- Log.d(TAG, "unlockUser finished");
+ Slog.d(TAG, "unlockUser finished");
latch.countDown();
}
};
@@ -1351,11 +1378,11 @@
&& mUserManager.isUserRunning(userInfo.id);
}
- private Map<Integer, byte[]> getDecryptedPasswordsForAllTiedProfiles(int userId) {
+ private Map<Integer, LockscreenCredential> getDecryptedPasswordsForAllTiedProfiles(int userId) {
if (mUserManager.getUserInfo(userId).isManagedProfile()) {
return null;
}
- Map<Integer, byte[]> result = new ArrayMap<Integer, byte[]>();
+ Map<Integer, LockscreenCredential> result = new ArrayMap<>();
final List<UserInfo> profiles = mUserManager.getProfiles(userId);
final int size = profiles.size();
for (int i = 0; i < size; i++) {
@@ -1393,7 +1420,7 @@
* terminates when the user is a managed profile.
*/
private void synchronizeUnifiedWorkChallengeForProfiles(int userId,
- Map<Integer, byte[]> profilePasswordMap) {
+ Map<Integer, LockscreenCredential> profilePasswordMap) {
if (mUserManager.getUserInfo(userId).isManagedProfile()) {
return;
}
@@ -1408,20 +1435,23 @@
continue;
}
if (isSecure) {
- tieManagedProfileLockIfNecessary(managedUserId, null);
+ tieManagedProfileLockIfNecessary(managedUserId,
+ LockscreenCredential.createNone());
} else {
// We use cached work profile password computed before clearing the parent's
// credential, otherwise they get lost
- if (profilePasswordMap != null && profilePasswordMap.containsKey(managedUserId)) {
- setLockCredentialInternal(null, CREDENTIAL_TYPE_NONE,
+ if (profilePasswordMap != null
+ && profilePasswordMap.containsKey(managedUserId)) {
+ setLockCredentialInternal(LockscreenCredential.createNone(),
profilePasswordMap.get(managedUserId),
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, managedUserId,
+ managedUserId,
false, /* isLockTiedToParent= */ true);
} else {
Slog.wtf(TAG, "clear tied profile challenges, but no password supplied.");
- // Supplying null here would lead to untrusted credential change
- setLockCredentialInternal(null, CREDENTIAL_TYPE_NONE, null,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, managedUserId,
+ // Attempt an untrusted reset by supplying an empty credential.
+ setLockCredentialInternal(LockscreenCredential.createNone(),
+ LockscreenCredential.createNone(),
+ managedUserId,
true, /* isLockTiedToParent= */ true);
}
mStorage.removeChildProfileLock(managedUserId);
@@ -1445,8 +1475,7 @@
* Send credentials for user {@code userId} to {@link RecoverableKeyStoreManager} during an
* unlock operation.
*/
- private void sendCredentialsOnUnlockIfRequired(
- int credentialType, @NonNull byte[] credential, int userId) {
+ private void sendCredentialsOnUnlockIfRequired(LockscreenCredential credential, int userId) {
// Don't send credentials during the factory reset protection flow.
if (userId == USER_FRP) {
return;
@@ -1459,10 +1488,12 @@
return;
}
+ // RecoverableKeyStoreManager expects null for empty credential.
+ final byte[] secret = credential.isNone() ? null : credential.getCredential();
// Send credentials for the user and any child profiles that share its lock screen.
for (int profileId : getProfilesWithSameLockScreen(userId)) {
mRecoverableKeyStoreManager.lockScreenSecretAvailable(
- credentialType, credential, profileId);
+ credential.getType(), secret, profileId);
}
}
@@ -1471,7 +1502,7 @@
* credentials are set/changed.
*/
private void sendCredentialsOnChangeIfRequired(
- int credentialType, byte[] credential, int userId, boolean isLockTiedToParent) {
+ LockscreenCredential credential, int userId, boolean isLockTiedToParent) {
// A profile whose lock screen is being tied to its parent's will either have a randomly
// generated credential (creation) or null (removal). We rely on the parent to send its
// credentials for the profile in both cases as it stores the unified lock credential.
@@ -1479,10 +1510,12 @@
return;
}
+ // RecoverableKeyStoreManager expects null for empty credential.
+ final byte[] secret = credential.isNone() ? null : credential.getCredential();
// Send credentials for the user and any child profiles that share its lock screen.
for (int profileId : getProfilesWithSameLockScreen(userId)) {
mRecoverableKeyStoreManager.lockScreenSecretChanged(
- credentialType, credential, profileId);
+ credential.getType(), secret, profileId);
}
}
@@ -1505,9 +1538,8 @@
// This method should be called by LockPatternUtil only, all internal methods in this class
// should call setLockCredentialInternal.
@Override
- public boolean setLockCredential(byte[] credential, int type,
- byte[] savedCredential, int requestedQuality, int userId,
- boolean allowUntrustedChange) {
+ public boolean setLockCredential(LockscreenCredential credential,
+ LockscreenCredential savedCredential, int userId, boolean allowUntrustedChange) {
if (!mLockPatternUtils.hasSecureLockScreen()) {
throw new UnsupportedOperationException(
@@ -1515,11 +1547,11 @@
}
checkWritePermission(userId);
synchronized (mSeparateChallengeLock) {
- if (!setLockCredentialInternal(credential, type, savedCredential, requestedQuality,
+ if (!setLockCredentialInternal(credential, savedCredential,
userId, allowUntrustedChange, /* isLockTiedToParent= */ false)) {
return false;
}
- setSeparateProfileChallengeEnabledLocked(userId, true, null);
+ setSeparateProfileChallengeEnabledLocked(userId, true, /* unused */ null);
notifyPasswordChanged(userId);
}
if (mUserManager.getUserInfo(userId).isManagedProfile()) {
@@ -1531,51 +1563,44 @@
}
/**
+ * @param savedCredential if the user is a managed profile with unified challenge and
+ * savedCredential is empty, LSS will try to re-derive the profile password internally.
+ * TODO (b/80170828): Fix this so profile password is always passed in.
* @param isLockTiedToParent is {@code true} if {@code userId} is a profile and its new
* credentials are being tied to its parent's credentials.
*/
- private boolean setLockCredentialInternal(byte[] credential, @CredentialType int credentialType,
- byte[] savedCredential, int requestedQuality, int userId, boolean allowUntrustedChange,
- boolean isLockTiedToParent) {
- // Normalize savedCredential and credential such that empty string is always represented
- // as null.
- if (savedCredential == null || savedCredential.length == 0) {
- savedCredential = null;
- }
- if (credential == null || credential.length == 0) {
- credential = null;
- }
+ private boolean setLockCredentialInternal(LockscreenCredential credential,
+ LockscreenCredential savedCredential, int userId,
+ boolean allowUntrustedChange, boolean isLockTiedToParent) {
+ Preconditions.checkNotNull(credential);
+ Preconditions.checkNotNull(savedCredential);
synchronized (mSpManager) {
if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- return spBasedSetLockCredentialInternalLocked(credential, credentialType,
- savedCredential, requestedQuality, userId, allowUntrustedChange,
- isLockTiedToParent);
+ return spBasedSetLockCredentialInternalLocked(credential, savedCredential, userId,
+ allowUntrustedChange, isLockTiedToParent);
}
}
- if (credentialType == CREDENTIAL_TYPE_NONE) {
- if (credential != null) {
- Slog.wtf(TAG, "CredentialType is none, but credential is non-null.");
- }
+ if (credential.isNone()) {
clearUserKeyProtection(userId);
gateKeeperClearSecureUserId(userId);
mStorage.writeCredentialHash(CredentialHash.createEmptyHash(), userId);
+ // Still update PASSWORD_TYPE_KEY if we are running in pre-synthetic password code path,
+ // since it forms part of the state that determines the credential type
+ // @see getCredentialTypeInternal
+ setKeyguardStoredQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
setKeystorePassword(null, userId);
fixateNewestUserKeyAuth(userId);
synchronizeUnifiedWorkChallengeForProfiles(userId, null);
- setUserPasswordMetrics(CREDENTIAL_TYPE_NONE, null, userId);
- sendCredentialsOnChangeIfRequired(
- credentialType, credential, userId, isLockTiedToParent);
+ setUserPasswordMetrics(LockscreenCredential.createNone(), userId);
+ sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
return true;
}
- if (credential == null) {
- throw new IllegalArgumentException("Null credential with mismatched credential type");
- }
CredentialHash currentHandle = mStorage.readCredentialHash(userId);
if (isManagedProfileWithUnifiedLock(userId)) {
// get credential from keystore when managed profile has unified lock
- if (savedCredential == null) {
+ if (savedCredential.isNone()) {
try {
savedCredential = getDecryptedPasswordForTiedProfile(userId);
} catch (FileNotFoundException e) {
@@ -1589,47 +1614,50 @@
}
} else {
if (currentHandle.hash == null) {
- if (savedCredential != null) {
+ if (!savedCredential.isNone()) {
Slog.w(TAG, "Saved credential provided, but none stored");
}
- savedCredential = null;
+ savedCredential.close();
+ savedCredential = LockscreenCredential.createNone();
}
}
synchronized (mSpManager) {
if (shouldMigrateToSyntheticPasswordLocked(userId)) {
- initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential,
- currentHandle.type, requestedQuality, userId);
- return spBasedSetLockCredentialInternalLocked(credential, credentialType,
- savedCredential, requestedQuality, userId, allowUntrustedChange,
- isLockTiedToParent);
+ initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential, userId);
+ return spBasedSetLockCredentialInternalLocked(credential, savedCredential, userId,
+ allowUntrustedChange, isLockTiedToParent);
}
}
if (DEBUG) Slog.d(TAG, "setLockCredentialInternal: user=" + userId);
- byte[] enrolledHandle = enrollCredential(currentHandle.hash, savedCredential, credential,
- userId);
+ byte[] enrolledHandle = enrollCredential(currentHandle.hash,
+ savedCredential.getCredential(), credential.getCredential(), userId);
if (enrolledHandle == null) {
Slog.w(TAG, String.format("Failed to enroll %s: incorrect credential",
- credentialType == CREDENTIAL_TYPE_PASSWORD ? "password" : "pattern"));
+ credential.isPattern() ? "pattern" : "password"));
return false;
}
- CredentialHash willStore = CredentialHash.create(enrolledHandle, credentialType);
+ CredentialHash willStore = CredentialHash.create(enrolledHandle, credential.getType());
mStorage.writeCredentialHash(willStore, userId);
+ // Still update PASSWORD_TYPE_KEY if we are running in pre-synthetic password code path,
+ // since it forms part of the state that determines the credential type
+ // @see getCredentialTypeInternal
+ setKeyguardStoredQuality(
+ LockPatternUtils.credentialTypeToPasswordQuality(credential.getType()), userId);
// push new secret and auth token to vold
GateKeeperResponse gkResponse;
try {
gkResponse = getGateKeeperService().verifyChallenge(userId, 0, willStore.hash,
- credential);
+ credential.getCredential());
} catch (RemoteException e) {
throw new IllegalStateException("Failed to verify current credential", e);
}
setUserKeyProtection(userId, credential, convertResponse(gkResponse));
fixateNewestUserKeyAuth(userId);
// Refresh the auth token
- doVerifyCredential(credential, credentialType, CHALLENGE_FROM_CALLER, 0, userId,
+ doVerifyCredential(credential, CHALLENGE_FROM_CALLER, 0, userId,
null /* progressCallback */);
synchronizeUnifiedWorkChallengeForProfiles(userId, null);
- sendCredentialsOnChangeIfRequired(
- credentialType, credential, userId, isLockTiedToParent);
+ sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
return true;
}
@@ -1637,8 +1665,8 @@
return VerifyCredentialResponse.fromGateKeeperResponse(gateKeeperResponse);
}
- @VisibleForTesting
- protected void tieProfileLockToParent(int userId, byte[] password) {
+ @VisibleForTesting /** Note: this method is overridden in unit tests */
+ protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
byte[] encryptionResult;
byte[] iv;
@@ -1673,7 +1701,7 @@
KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
+ KeyProperties.ENCRYPTION_PADDING_NONE);
cipher.init(Cipher.ENCRYPT_MODE, keyStoreEncryptionKey);
- encryptionResult = cipher.doFinal(password);
+ encryptionResult = cipher.doFinal(password.getCredential());
iv = cipher.getIV();
} finally {
// The original key can now be discarded.
@@ -1728,7 +1756,8 @@
addUserKeyAuth(userId, null, key);
}
- private void setUserKeyProtection(int userId, byte[] credential, VerifyCredentialResponse vcr) {
+ private void setUserKeyProtection(int userId, LockscreenCredential credential,
+ VerifyCredentialResponse vcr) {
if (DEBUG) Slog.d(TAG, "setUserKeyProtection: user=" + userId);
if (vcr == null) {
throw new IllegalArgumentException("Null response verifying a credential we just set");
@@ -1749,7 +1778,7 @@
addUserKeyAuth(userId, null, null);
}
- private static byte[] secretFromCredential(byte[] credential) {
+ private static byte[] secretFromCredential(LockscreenCredential credential) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-512");
// Personalize the hash
@@ -1757,7 +1786,7 @@
// Pad it to the block size of the hash function
personalization = Arrays.copyOf(personalization, 128);
digest.update(personalization);
- digest.update(credential);
+ digest.update(credential.getCredential());
return digest.digest();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("NoSuchAlgorithmException for SHA-512");
@@ -1768,7 +1797,7 @@
try {
return mStorageManager.isUserKeyUnlocked(userId);
} catch (RemoteException e) {
- Log.e(TAG, "failed to check user key locked state", e);
+ Slog.e(TAG, "failed to check user key locked state", e);
return false;
}
}
@@ -1815,7 +1844,7 @@
checkWritePermission(userId);
if (DEBUG) Slog.v(TAG, "Reset keystore for user: " + userId);
int managedUserId = -1;
- byte[] managedUserDecryptedPassword = null;
+ LockscreenCredential managedUserDecryptedPassword = null;
final List<UserInfo> profiles = mUserManager.getProfiles(userId);
for (UserInfo pi : profiles) {
// Unlock managed profile with unified lock
@@ -1852,30 +1881,30 @@
tieProfileLockToParent(managedUserId, managedUserDecryptedPassword);
}
}
- if (managedUserDecryptedPassword != null && managedUserDecryptedPassword.length > 0) {
- Arrays.fill(managedUserDecryptedPassword, (byte) 0);
+ if (managedUserDecryptedPassword != null) {
+ managedUserDecryptedPassword.zeroize();
}
}
@Override
- public VerifyCredentialResponse checkCredential(byte[] credential, int type, int userId,
+ public VerifyCredentialResponse checkCredential(LockscreenCredential credential, int userId,
ICheckCredentialProgressCallback progressCallback) {
checkPasswordReadPermission(userId);
- return doVerifyCredential(credential, type, CHALLENGE_NONE, 0, userId, progressCallback);
+ return doVerifyCredential(credential, CHALLENGE_NONE, 0, userId, progressCallback);
}
@Override
- public VerifyCredentialResponse verifyCredential(byte[] credential, int type, long challenge,
- int userId) {
+ public VerifyCredentialResponse verifyCredential(LockscreenCredential credential,
+ long challenge, int userId) {
checkPasswordReadPermission(userId);
- return doVerifyCredential(credential, type, CHALLENGE_FROM_CALLER, challenge, userId,
+ return doVerifyCredential(credential, CHALLENGE_FROM_CALLER, challenge, userId,
null /* progressCallback */);
}
- private VerifyCredentialResponse doVerifyCredential(byte[] credential, int credentialType,
+ private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
@ChallengeType int challengeType, long challenge, int userId,
ICheckCredentialProgressCallback progressCallback) {
- return doVerifyCredential(credential, credentialType, challengeType, challenge, userId,
+ return doVerifyCredential(credential, challengeType, challenge, userId,
progressCallback, null /* resetLockouts */);
}
@@ -1883,25 +1912,25 @@
* Verify user credential and unlock the user. Fix pattern bug by deprecating the old base zero
* format.
*/
- private VerifyCredentialResponse doVerifyCredential(byte[] credential, int credentialType,
+ private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
@ChallengeType int challengeType, long challenge, int userId,
ICheckCredentialProgressCallback progressCallback,
@Nullable ArrayList<PendingResetLockout> resetLockouts) {
- if (credential == null || credential.length == 0) {
+ if (credential == null || credential.isNone()) {
throw new IllegalArgumentException("Credential can't be null or empty");
}
- if (userId == USER_FRP && Settings.Global.getInt(mContext.getContentResolver(),
+ if (userId == USER_FRP && mInjector.settingsGlobalGetInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
return VerifyCredentialResponse.ERROR;
}
VerifyCredentialResponse response = null;
- response = spBasedDoVerifyCredential(credential, credentialType, challengeType, challenge,
+ response = spBasedDoVerifyCredential(credential, challengeType, challenge,
userId, progressCallback, resetLockouts);
// The user employs synthetic password based credential.
if (response != null) {
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
- sendCredentialsOnUnlockIfRequired(credentialType, credential, userId);
+ sendCredentialsOnUnlockIfRequired(credential, userId);
}
return response;
}
@@ -1912,9 +1941,9 @@
}
final CredentialHash storedHash = mStorage.readCredentialHash(userId);
- if (storedHash.type != credentialType) {
+ if (!credential.checkAgainstStoredType(storedHash.type)) {
Slog.wtf(TAG, "doVerifyCredential type mismatch with stored credential??"
- + " stored: " + storedHash.type + " passed in: " + credentialType);
+ + " stored: " + storedHash.type + " passed in: " + credential.getType());
return VerifyCredentialResponse.ERROR;
}
@@ -1929,7 +1958,7 @@
}
@Override
- public VerifyCredentialResponse verifyTiedProfileChallenge(byte[] credential, int type,
+ public VerifyCredentialResponse verifyTiedProfileChallenge(LockscreenCredential credential,
long challenge, int userId) {
checkPasswordReadPermission(userId);
if (!isManagedProfileWithUnifiedLock(userId)) {
@@ -1939,7 +1968,6 @@
// Unlock parent by using parent's challenge
final VerifyCredentialResponse parentResponse = doVerifyCredential(
credential,
- type,
CHALLENGE_FROM_CALLER,
challenge,
parentProfileId,
@@ -1952,7 +1980,6 @@
try {
// Unlock work profile, and work profile with unified lock must use password only
return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId),
- CREDENTIAL_TYPE_PASSWORD,
CHALLENGE_FROM_CALLER,
challenge,
userId, null /* progressCallback */);
@@ -1971,15 +1998,14 @@
* hash to GK.
*/
private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash,
- byte[] credential, @ChallengeType int challengeType, long challenge,
+ LockscreenCredential credential, @ChallengeType int challengeType, long challenge,
ICheckCredentialProgressCallback progressCallback) {
- if ((storedHash == null || storedHash.hash.length == 0)
- && (credential == null || credential.length == 0)) {
+ if ((storedHash == null || storedHash.hash.length == 0) && credential.isNone()) {
// don't need to pass empty credentials to GateKeeper
return VerifyCredentialResponse.OK;
}
- if (storedHash == null || credential == null || credential.length == 0) {
+ if (storedHash == null || storedHash.hash.length == 0 || credential.isNone()) {
return VerifyCredentialResponse.ERROR;
}
@@ -1989,8 +2015,8 @@
GateKeeperResponse gateKeeperResponse;
try {
- gateKeeperResponse = getGateKeeperService()
- .verifyChallenge(userId, challenge, storedHash.hash, credential);
+ gateKeeperResponse = getGateKeeperService().verifyChallenge(
+ userId, challenge, storedHash.hash, credential.getCredential());
} catch (RemoteException e) {
Slog.e(TAG, "gatekeeper verify failed", e);
gateKeeperResponse = GateKeeperResponse.ERROR;
@@ -2006,11 +2032,11 @@
try {
progressCallback.onCredentialVerified();
} catch (RemoteException e) {
- Log.w(TAG, "progressCallback throws exception", e);
+ Slog.w(TAG, "progressCallback throws exception", e);
}
}
- setUserPasswordMetrics(storedHash.type, credential, userId);
- unlockKeystore(credential, userId);
+ setUserPasswordMetrics(credential, userId);
+ unlockKeystore(credential.getCredential(), userId);
Slog.i(TAG, "Unlocking user " + userId + " with token length "
+ response.getPayload().length);
@@ -2019,27 +2045,22 @@
if (isManagedProfileWithSeparatedLock(userId)) {
setDeviceUnlockedForUser(userId);
}
- int reEnrollQuality = storedHash.type == CREDENTIAL_TYPE_PATTERN
- ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
- : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
- /* TODO(roosa): keep the same password quality */;
if (shouldReEnroll) {
- setLockCredentialInternal(credential, storedHash.type, credential,
- reEnrollQuality, userId, false, /* isLockTiedToParent= */ false);
+ setLockCredentialInternal(credential, credential,
+ userId, false, /* isLockTiedToParent= */ false);
} else {
// Now that we've cleared of all required GK migration, let's do the final
// migration to synthetic password.
synchronized (mSpManager) {
if (shouldMigrateToSyntheticPasswordLocked(userId)) {
AuthenticationToken auth = initializeSyntheticPasswordLocked(
- storedHash.hash, credential, storedHash.type, reEnrollQuality,
- userId);
+ storedHash.hash, credential, userId);
activateEscrowTokens(auth, userId);
}
}
}
// Use credentials to create recoverable keystore snapshot.
- sendCredentialsOnUnlockIfRequired(storedHash.type, credential, userId);
+ sendCredentialsOnUnlockIfRequired(credential, userId);
} else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
if (response.getTimeout() > 0) {
@@ -2055,12 +2076,9 @@
* when the user is authenticating or when a new password is being set. In comparison,
* {@link #notifyPasswordChanged} only needs to be called when the user changes the password.
*/
- private void setUserPasswordMetrics(@CredentialType int credentialType, byte[] password,
- @UserIdInt int userHandle) {
+ private void setUserPasswordMetrics(LockscreenCredential password, @UserIdInt int userHandle) {
synchronized (this) {
- mUserPasswordMetrics.put(userHandle,
- PasswordMetrics.computeForCredential(
- LockscreenCredential.createRaw(credentialType, password)));
+ mUserPasswordMetrics.put(userHandle, PasswordMetrics.computeForCredential(password));
}
}
@@ -2089,6 +2107,14 @@
});
}
+ private LockscreenCredential createPattern(String patternString) {
+ final byte[] patternBytes = patternString.getBytes();
+ LockscreenCredential pattern = LockscreenCredential.createPattern(
+ LockPatternUtils.byteArrayToPattern(patternBytes));
+ Arrays.fill(patternBytes, (byte) 0);
+ return pattern;
+ }
+
@Override
public boolean checkVoldPassword(int userId) {
if (!mFirstCallToVold) {
@@ -2119,30 +2145,34 @@
} finally {
Binder.restoreCallingIdentity(identity);
}
- if (password == null) {
+ if (TextUtils.isEmpty(password)) {
return false;
}
try {
- if (mLockPatternUtils.isLockPatternEnabled(userId)) {
- if (checkCredential(password.getBytes(), CREDENTIAL_TYPE_PATTERN,
- userId, null /* progressCallback */)
- .getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
- return true;
- }
+ final LockscreenCredential credential;
+ switch (getCredentialTypeInternal(userId)) {
+ case CREDENTIAL_TYPE_PATTERN:
+ credential = createPattern(password);
+ break;
+ case CREDENTIAL_TYPE_PIN:
+ credential = LockscreenCredential.createPin(password);
+ break;
+ case CREDENTIAL_TYPE_PASSWORD:
+ credential = LockscreenCredential.createPassword(password);
+ break;
+ default:
+ credential = null;
+ Slog.e(TAG, "Unknown credential type");
}
- } catch (Exception e) {
- }
- try {
- if (mLockPatternUtils.isLockPasswordEnabled(userId)) {
- if (checkCredential(password.getBytes(), CREDENTIAL_TYPE_PASSWORD,
- userId, null /* progressCallback */)
+ if (credential != null
+ && checkCredential(credential, userId, null /* progressCallback */)
.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
- return true;
- }
+ return true;
}
} catch (Exception e) {
+ Slog.e(TAG, "checkVoldPassword failed: ", e);
}
return false;
@@ -2524,7 +2554,7 @@
@GuardedBy("mSpManager")
@VisibleForTesting
protected AuthenticationToken initializeSyntheticPasswordLocked(byte[] credentialHash,
- byte[] credential, int credentialType, int requestedQuality, int userId) {
+ LockscreenCredential credential, int userId) {
Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
final AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(
getGateKeeperService(), credentialHash, credential, userId);
@@ -2534,8 +2564,8 @@
return null;
}
long handle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
- credential, credentialType, auth, requestedQuality, userId);
- if (credential != null) {
+ credential, auth, userId);
+ if (!credential.isNone()) {
if (credentialHash == null) {
// Since when initializing SP, we didn't provide an existing password handle
// for it to migrate SID, we need to create a new SID for the user.
@@ -2567,6 +2597,13 @@
}
+ @VisibleForTesting
+ boolean isSyntheticPasswordBasedCredential(int userId) {
+ synchronized (mSpManager) {
+ return isSyntheticPasswordBasedCredentialLocked(userId);
+ }
+ }
+
private boolean isSyntheticPasswordBasedCredentialLocked(int userId) {
if (userId == USER_FRP) {
final int type = mStorage.readPersistentDataBlock().type;
@@ -2592,8 +2629,8 @@
setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM);
}
- private VerifyCredentialResponse spBasedDoVerifyCredential(byte[] userCredential,
- @CredentialType int credentialType, @ChallengeType int challengeType, long challenge,
+ private VerifyCredentialResponse spBasedDoVerifyCredential(LockscreenCredential userCredential,
+ @ChallengeType int challengeType, long challenge,
int userId, ICheckCredentialProgressCallback progressCallback,
@Nullable ArrayList<PendingResetLockout> resetLockouts) {
@@ -2601,9 +2638,6 @@
Slog.d(TAG, "spBasedDoVerifyCredential: user=" + userId + " challengeType=" + challengeType
+ " hasEnrolledBiometrics=" + hasEnrolledBiometrics);
- if (credentialType == CREDENTIAL_TYPE_NONE) {
- userCredential = null;
- }
final PackageManager pm = mContext.getPackageManager();
// TODO: When lockout is handled under the HAL for all biometrics (fingerprint),
@@ -2625,17 +2659,13 @@
}
if (userId == USER_FRP) {
return mSpManager.verifyFrpCredential(getGateKeeperService(),
- userCredential, credentialType, progressCallback);
+ userCredential, progressCallback);
}
long handle = getSyntheticPasswordHandleLocked(userId);
authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
getGateKeeperService(), handle, userCredential, userId, progressCallback);
- if (authResult.credentialType != credentialType) {
- Slog.e(TAG, "Credential type mismatch.");
- return VerifyCredentialResponse.ERROR;
- }
response = authResult.gkResponse;
// credential has matched
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
@@ -2653,7 +2683,7 @@
}
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
- setUserPasswordMetrics(credentialType, userCredential, userId);
+ setUserPasswordMetrics(userCredential, userId);
unlockKeystore(authResult.authToken.deriveKeyStorePassword(), userId);
// Do resetLockout / revokeChallenge when all profiles are unlocked
@@ -2700,14 +2730,13 @@
* added back when new password is set in future.
*/
@GuardedBy("mSpManager")
- private long setLockCredentialWithAuthTokenLocked(byte[] credential,
- @CredentialType int credentialType, AuthenticationToken auth, int requestedQuality,
- int userId) {
+ private long setLockCredentialWithAuthTokenLocked(LockscreenCredential credential,
+ AuthenticationToken auth, int userId) {
if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId);
long newHandle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
- credential, credentialType, auth, requestedQuality, userId);
- final Map<Integer, byte[]> profilePasswords;
- if (credential != null) {
+ credential, auth, userId);
+ final Map<Integer, LockscreenCredential> profilePasswords;
+ if (!credential.isNone()) {
// not needed by synchronizeUnifiedWorkChallengeForProfiles()
profilePasswords = null;
@@ -2748,11 +2777,11 @@
setSyntheticPasswordHandleLocked(newHandle, userId);
synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
- setUserPasswordMetrics(credentialType, credential, userId);
+ setUserPasswordMetrics(credential, userId);
if (profilePasswords != null) {
- for (Map.Entry<Integer, byte[]> entry : profilePasswords.entrySet()) {
- Arrays.fill(entry.getValue(), (byte) 0);
+ for (Map.Entry<Integer, LockscreenCredential> entry : profilePasswords.entrySet()) {
+ entry.getValue().zeroize();
}
}
@@ -2838,12 +2867,17 @@
};
}
+ /**
+ * @param savedCredential if the user is a managed profile with unified challenge and
+ * savedCredential is empty, LSS will try to re-derive the profile password internally.
+ * TODO (b/80170828): Fix this so profile password is always passed in.
+ */
@GuardedBy("mSpManager")
- private boolean spBasedSetLockCredentialInternalLocked(byte[] credential, int credentialType,
- byte[] savedCredential, int requestedQuality, int userId,
+ private boolean spBasedSetLockCredentialInternalLocked(LockscreenCredential credential,
+ LockscreenCredential savedCredential, int userId,
boolean allowUntrustedChange, boolean isLockTiedToParent) {
if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId);
- if (isManagedProfileWithUnifiedLock(userId)) {
+ if (savedCredential.isNone() && isManagedProfileWithUnifiedLock(userId)) {
// get credential from keystore when managed profile has unified lock
try {
savedCredential = getDecryptedPasswordForTiedProfile(userId);
@@ -2863,9 +2897,8 @@
AuthenticationToken auth = authResult.authToken;
// If existing credential is provided, the existing credential must match.
- if (savedCredential != null && auth == null) {
- Slog.w(TAG, String.format("Failed to enroll %s: incorrect credential",
- credentialType == CREDENTIAL_TYPE_PASSWORD ? "password" : "pattern"));
+ if (!savedCredential.isNone() && auth == null) {
+ Slog.w(TAG, "Failed to enroll: incorrect credential");
return false;
}
boolean untrustedReset = false;
@@ -2898,8 +2931,7 @@
// setLockCredentialWithAuthTokenLocked next
mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
}
- setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, requestedQuality,
- userId);
+ setLockCredentialWithAuthTokenLocked(credential, auth, userId);
mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
} else {
throw new IllegalStateException(
@@ -2908,7 +2940,7 @@
// requestedQuality, userId) instead if we still allow untrusted reset that changes
// synthetic password. That would invalidate existing escrow tokens though.
}
- sendCredentialsOnChangeIfRequired(credentialType, credential, userId, isLockTiedToParent);
+ sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
return true;
}
@@ -2919,11 +2951,8 @@
* If user is a managed profile with unified challenge, currentCredential is ignored.
*/
@Override
- public byte[] getHashFactor(byte[] currentCredential, int userId) {
+ public byte[] getHashFactor(LockscreenCredential currentCredential, int userId) {
checkPasswordReadPermission(userId);
- if (currentCredential == null || currentCredential.length == 0) {
- currentCredential = null;
- }
if (isManagedProfileWithUnifiedLock(userId)) {
try {
currentCredential = getDecryptedPasswordForTiedProfile(userId);
@@ -2957,13 +2986,12 @@
AuthenticationToken auth = null;
if (!isUserSecure(userId)) {
if (shouldMigrateToSyntheticPasswordLocked(userId)) {
- auth = initializeSyntheticPasswordLocked(null, null,
- CREDENTIAL_TYPE_NONE,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
+ auth = initializeSyntheticPasswordLocked(
+ /* credentialHash */ null, LockscreenCredential.createNone(), userId);
} else /* isSyntheticPasswordBasedCredentialLocked(userId) */ {
long pwdHandle = getSyntheticPasswordHandleLocked(userId);
auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
- pwdHandle, null, userId, null).authToken;
+ pwdHandle, LockscreenCredential.createNone(), userId, null).authToken;
}
}
if (isSyntheticPasswordBasedCredentialLocked(userId)) {
@@ -3023,21 +3051,21 @@
}
}
- private boolean setLockCredentialWithToken(byte[] credential, int type, long tokenHandle,
- byte[] token, int requestedQuality, int userId) {
+ private boolean setLockCredentialWithToken(LockscreenCredential credential, long tokenHandle,
+ byte[] token, int userId) {
boolean result;
synchronized (mSpManager) {
if (!mSpManager.hasEscrowData(userId)) {
throw new SecurityException("Escrow token is disabled on the current user");
}
- result = setLockCredentialWithTokenInternalLocked(credential, type, tokenHandle, token,
- requestedQuality, userId);
+ result = setLockCredentialWithTokenInternalLocked(
+ credential, tokenHandle, token, userId);
}
if (result) {
synchronized (mSeparateChallengeLock) {
- setSeparateProfileChallengeEnabledLocked(userId, true, null);
+ setSeparateProfileChallengeEnabledLocked(userId, true, /* unused */ null);
}
- if (credential == null) {
+ if (credential.isNone()) {
// If clearing credential, unlock the user manually in order to progress user start
// Call unlockUser() on a handler thread so no lock is held (either by LSS or by
// the caller like DPMS), otherwise it can lead to deadlock.
@@ -3050,8 +3078,8 @@
}
@GuardedBy("mSpManager")
- private boolean setLockCredentialWithTokenInternalLocked(byte[] credential, int type,
- long tokenHandle, byte[] token, int requestedQuality, int userId) {
+ private boolean setLockCredentialWithTokenInternalLocked(LockscreenCredential credential,
+ long tokenHandle, byte[] token, int userId) {
final AuthenticationResult result;
result = mSpManager.unwrapTokenBasedSyntheticPassword(
getGateKeeperService(), tokenHandle, token, userId);
@@ -3066,11 +3094,8 @@
+ "verification.");
return false;
}
- // TODO: refactor usage of PASSWORD_TYPE_KEY b/65239740
- setLong(LockPatternUtils.PASSWORD_TYPE_KEY, requestedQuality, userId);
long oldHandle = getSyntheticPasswordHandleLocked(userId);
- setLockCredentialWithAuthTokenLocked(credential, type, result.authToken,
- requestedQuality, userId);
+ setLockCredentialWithAuthTokenLocked(credential, result.authToken, userId);
mSpManager.destroyPasswordBasedSyntheticPassword(oldHandle, userId);
onAuthTokenKnownForUser(userId, result.authToken);
@@ -3132,11 +3157,10 @@
}
// It's OK to dump the password type since anyone with physical access can just
// observe it from the keyguard directly.
- pw.println("PasswordType: " + getLong(LockPatternUtils.PASSWORD_TYPE_KEY, 0, userId));
- pw.println("hasPassword: " + havePassword(userId));
- pw.println("hasPattern: " + havePattern(userId)); // print raw credential type instead?
+ pw.println("Quality: " + getKeyguardStoredQuality(userId));
+ pw.println("CredentialType: " + getCredentialTypeInternal(userId));
pw.println("SeparateChallenge: " + getSeparateProfileChallengeEnabled(userId));
- pw.println(String.format("metrics: %s",
+ pw.println(String.format("Metrics: %s",
getUserPasswordMetrics(userId) != null ? "known" : "unknown"));
pw.decreaseIndent();
}
@@ -3298,14 +3322,14 @@
}
@Override
- public boolean setLockCredentialWithToken(byte[] credential, int type,
- long tokenHandle, byte[] token, int requestedQuality, int userId) {
+ public boolean setLockCredentialWithToken(LockscreenCredential credential, long tokenHandle,
+ byte[] token, int userId) {
if (!mLockPatternUtils.hasSecureLockScreen()) {
throw new UnsupportedOperationException(
"This operation requires secure lock screen feature.");
}
- return LockSettingsService.this.setLockCredentialWithToken(credential, type,
- tokenHandle, token, requestedQuality, userId);
+ return LockSettingsService.this.setLockCredentialWithToken(
+ credential, tokenHandle, token, userId);
}
@Override
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 0a8e5bd..0f8561e 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -16,14 +16,22 @@
package com.android.server.locksettings;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
import android.app.ActivityManager;
+import android.app.admin.PasswordMetrics;
import android.os.ShellCommand;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.PasswordValidationError;
import java.io.PrintWriter;
+import java.util.List;
class LockSettingsShellCommand extends ShellCommand {
@@ -70,18 +78,19 @@
if (!checkCredential()) {
return -1;
}
+ boolean success = true;
switch (cmd) {
case COMMAND_SET_PATTERN:
- runSetPattern();
+ success = runSetPattern();
break;
case COMMAND_SET_PASSWORD:
- runSetPassword();
+ success = runSetPassword();
break;
case COMMAND_SET_PIN:
- runSetPin();
+ success = runSetPin();
break;
case COMMAND_CLEAR:
- runClear();
+ success = runClear();
break;
case COMMAND_SP:
runChangeSp();
@@ -102,7 +111,7 @@
getErrPrintWriter().println("Unknown command: " + cmd);
break;
}
- return 0;
+ return success ? 0 : -1;
} catch (Exception e) {
getErrPrintWriter().println("Error while executing command: " + cmd);
e.printStackTrace(getErrPrintWriter());
@@ -201,34 +210,66 @@
}
}
- private void runSetPattern() {
- mLockPatternUtils.setLockCredential(
- LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
- mNew.getBytes())),
- getOldCredential(),
- mCurrentUserId);
+ private boolean runSetPattern() {
+ final LockscreenCredential pattern = LockscreenCredential.createPattern(
+ LockPatternUtils.byteArrayToPattern(mNew.getBytes()));
+ if (!isNewCredentialSufficient(pattern)) {
+ return false;
+ }
+ mLockPatternUtils.setLockCredential(pattern, getOldCredential(), mCurrentUserId);
getOutPrintWriter().println("Pattern set to '" + mNew + "'");
+ return true;
}
- private void runSetPassword() {
- mLockPatternUtils.setLockCredential(LockscreenCredential.createPassword(mNew),
- getOldCredential(),
- mCurrentUserId);
+ private boolean runSetPassword() {
+ final LockscreenCredential password = LockscreenCredential.createPassword(mNew);
+ if (!isNewCredentialSufficient(password)) {
+ return false;
+ }
+ mLockPatternUtils.setLockCredential(password, getOldCredential(), mCurrentUserId);
getOutPrintWriter().println("Password set to '" + mNew + "'");
+ return true;
}
- private void runSetPin() {
- mLockPatternUtils.setLockCredential(LockscreenCredential.createPin(mNew),
- getOldCredential(),
- mCurrentUserId);
+ private boolean runSetPin() {
+ final LockscreenCredential pin = LockscreenCredential.createPin(mNew);
+ if (!isNewCredentialSufficient(pin)) {
+ return false;
+ }
+ mLockPatternUtils.setLockCredential(pin, getOldCredential(), mCurrentUserId);
getOutPrintWriter().println("Pin set to '" + mNew + "'");
+ return true;
}
- private void runClear() {
- mLockPatternUtils.setLockCredential(LockscreenCredential.createNone(),
- getOldCredential(),
- mCurrentUserId);
+ private boolean runClear() {
+ LockscreenCredential none = LockscreenCredential.createNone();
+ if (!isNewCredentialSufficient(none)) {
+ return false;
+ }
+ mLockPatternUtils.setLockCredential(none, getOldCredential(), mCurrentUserId);
getOutPrintWriter().println("Lock credential cleared");
+ return true;
+ }
+
+ private boolean isNewCredentialSufficient(LockscreenCredential credential) {
+ final PasswordMetrics requiredMetrics =
+ mLockPatternUtils.getRequestedPasswordMetrics(mCurrentUserId);
+ final List<PasswordValidationError> errors;
+ if (credential.isPassword() || credential.isPin()) {
+ errors = PasswordMetrics.validatePassword(requiredMetrics, PASSWORD_COMPLEXITY_NONE,
+ credential.isPin(), credential.getCredential());
+ } else {
+ PasswordMetrics metrics = new PasswordMetrics(
+ credential.isPattern() ? CREDENTIAL_TYPE_PATTERN : CREDENTIAL_TYPE_NONE);
+ errors = PasswordMetrics.validatePasswordMetrics(
+ requiredMetrics, PASSWORD_COMPLEXITY_NONE, false /* isPin */, metrics);
+ }
+ if (!errors.isEmpty()) {
+ getOutPrintWriter().println(
+ "New credential doesn't satisfy admin policies: " + errors.get(0));
+ return false;
+ }
+ return true;
}
private void runSetDisabled() {
@@ -243,9 +284,7 @@
}
private boolean checkCredential() {
- final boolean havePassword = mLockPatternUtils.isLockPasswordEnabled(mCurrentUserId);
- final boolean havePattern = mLockPatternUtils.isLockPatternEnabled(mCurrentUserId);
- if (havePassword || havePattern) {
+ if (mLockPatternUtils.isSecure(mCurrentUserId)) {
if (mLockPatternUtils.isManagedProfileWithUnifiedChallenge(mCurrentUserId)) {
getOutPrintWriter().println("Profile uses unified challenge");
return false;
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index f4cad63..3dab3ce 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -30,7 +30,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
-import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -214,7 +213,7 @@
private CredentialHash readPasswordHashIfExists(int userId) {
byte[] stored = readFile(getLockPasswordFilename(userId));
if (!ArrayUtils.isEmpty(stored)) {
- return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
+ return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN);
}
return null;
}
@@ -270,10 +269,6 @@
return hasFile(getLockPatternFilename(userId));
}
- public boolean hasCredential(int userId) {
- return hasPassword(userId) || hasPattern(userId);
- }
-
private boolean hasFile(String name) {
byte[] contents = readFile(name);
return contents != null && contents.length > 0;
@@ -360,11 +355,15 @@
public void writeCredentialHash(CredentialHash hash, int userId) {
byte[] patternHash = null;
byte[] passwordHash = null;
-
- if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+ if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN
+ || hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+ || hash.type == LockPatternUtils.CREDENTIAL_TYPE_PIN) {
passwordHash = hash.hash;
} else if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
patternHash = hash.hash;
+ } else {
+ Preconditions.checkArgument(hash.type == LockPatternUtils.CREDENTIAL_TYPE_NONE,
+ "Unknown credential type");
}
writeFile(getLockPasswordFilename(userId), passwordHash);
writeFile(getLockPatternFilename(userId), patternHash);
@@ -523,8 +522,8 @@
mCache.clear();
}
- @Nullable
- public PersistentDataBlockManagerInternal getPersistentDataBlock() {
+ @Nullable @VisibleForTesting
+ PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
if (mPersistentDataBlockManagerInternal == null) {
mPersistentDataBlockManagerInternal =
LocalServices.getService(PersistentDataBlockManagerInternal.class);
@@ -534,7 +533,7 @@
public void writePersistentDataBlock(int persistentType, int userId, int qualityForUi,
byte[] payload) {
- PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlock();
+ PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager();
if (persistentDataBlock == null) {
return;
}
@@ -543,7 +542,7 @@
}
public PersistentData readPersistentDataBlock() {
- PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlock();
+ PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager();
if (persistentDataBlock == null) {
return PersistentData.NONE;
}
@@ -699,7 +698,7 @@
}
if (upgradeVersion != DATABASE_VERSION) {
- Log.w(TAG, "Failed to upgrade database!");
+ Slog.w(TAG, "Failed to upgrade database!");
}
}
}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 955a9aa..53d922b 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.weaver.V1_0.IWeaver;
@@ -35,13 +34,13 @@
import android.service.gatekeeper.GateKeeperResponse;
import android.service.gatekeeper.IGateKeeperService;
import android.util.ArrayMap;
-import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
@@ -138,7 +137,6 @@
static class AuthenticationResult {
public AuthenticationToken authToken;
public VerifyCredentialResponse gkResponse;
- public int credentialType;
}
static class AuthenticationToken {
@@ -220,7 +218,7 @@
byte scryptN;
byte scryptR;
byte scryptP;
- public int passwordType;
+ public int credentialType;
byte[] salt;
// For GateKeeper-based credential, this is the password handle returned by GK,
// for weaver-based credential, this is empty.
@@ -231,7 +229,7 @@
result.scryptN = PASSWORD_SCRYPT_N;
result.scryptR = PASSWORD_SCRYPT_R;
result.scryptP = PASSWORD_SCRYPT_P;
- result.passwordType = passwordType;
+ result.credentialType = passwordType;
result.salt = secureRandom(PASSWORD_SALT_LENGTH);
return result;
}
@@ -241,7 +239,7 @@
ByteBuffer buffer = ByteBuffer.allocate(data.length);
buffer.put(data, 0, data.length);
buffer.flip();
- result.passwordType = buffer.getInt();
+ result.credentialType = buffer.getInt();
result.scryptN = buffer.get();
result.scryptR = buffer.get();
result.scryptP = buffer.get();
@@ -263,7 +261,7 @@
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + 3 * Byte.BYTES
+ Integer.BYTES + salt.length + Integer.BYTES +
(passwordHandle != null ? passwordHandle.length : 0));
- buffer.putInt(passwordType);
+ buffer.putInt(credentialType);
buffer.put(scryptN);
buffer.put(scryptR);
buffer.put(scryptP);
@@ -366,11 +364,11 @@
try {
int writeStatus = mWeaver.write(slot, toByteArrayList(key), toByteArrayList(value));
if (writeStatus != WeaverStatus.OK) {
- Log.e(TAG, "weaver write failed, slot: " + slot + " status: " + writeStatus);
+ Slog.e(TAG, "weaver write failed, slot: " + slot + " status: " + writeStatus);
return null;
}
} catch (RemoteException e) {
- Log.e(TAG, "weaver write failed", e);
+ Slog.e(TAG, "weaver write failed", e);
return null;
}
return value;
@@ -401,31 +399,31 @@
break;
case WeaverReadStatus.THROTTLE:
response[0] = new VerifyCredentialResponse(readResponse.timeout);
- Log.e(TAG, "weaver read failed (THROTTLE), slot: " + slot);
+ Slog.e(TAG, "weaver read failed (THROTTLE), slot: " + slot);
break;
case WeaverReadStatus.INCORRECT_KEY:
if (readResponse.timeout == 0) {
response[0] = VerifyCredentialResponse.ERROR;
- Log.e(TAG, "weaver read failed (INCORRECT_KEY), slot: " + slot);
+ Slog.e(TAG, "weaver read failed (INCORRECT_KEY), slot: " + slot);
} else {
response[0] = new VerifyCredentialResponse(readResponse.timeout);
- Log.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: "
+ Slog.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: "
+ slot);
}
break;
case WeaverReadStatus.FAILED:
response[0] = VerifyCredentialResponse.ERROR;
- Log.e(TAG, "weaver read failed (FAILED), slot: " + slot);
+ Slog.e(TAG, "weaver read failed (FAILED), slot: " + slot);
break;
default:
response[0] = VerifyCredentialResponse.ERROR;
- Log.e(TAG, "weaver read unknown status " + status + ", slot: " + slot);
+ Slog.e(TAG, "weaver read unknown status " + status + ", slot: " + slot);
break;
}
});
} catch (RemoteException e) {
response[0] = VerifyCredentialResponse.ERROR;
- Log.e(TAG, "weaver read failed, slot: " + slot, e);
+ Slog.e(TAG, "weaver read failed, slot: " + slot, e);
}
return response[0];
}
@@ -437,13 +435,20 @@
}
}
- public int getCredentialType(long handle, int userId) {
+ int getCredentialType(long handle, int userId) {
byte[] passwordData = loadState(PASSWORD_DATA_NAME, handle, userId);
if (passwordData == null) {
- Log.w(TAG, "getCredentialType: encountered empty password data for user " + userId);
+ Slog.w(TAG, "getCredentialType: encountered empty password data for user " + userId);
return LockPatternUtils.CREDENTIAL_TYPE_NONE;
}
- return PasswordData.fromBytes(passwordData).passwordType;
+ return PasswordData.fromBytes(passwordData).credentialType;
+ }
+
+ static int getFrpCredentialType(byte[] payload) {
+ if (payload == null) {
+ return LockPatternUtils.CREDENTIAL_TYPE_NONE;
+ }
+ return PasswordData.fromBytes(payload).credentialType;
}
/**
@@ -469,17 +474,18 @@
*
*/
public AuthenticationToken newSyntheticPasswordAndSid(IGateKeeperService gatekeeper,
- byte[] hash, byte[] credential, int userId) {
+ byte[] hash, LockscreenCredential credential, int userId) {
AuthenticationToken result = AuthenticationToken.create();
GateKeeperResponse response;
if (hash != null) {
try {
- response = gatekeeper.enroll(userId, hash, credential, result.deriveGkPassword());
+ response = gatekeeper.enroll(userId, hash, credential.getCredential(),
+ result.deriveGkPassword());
} catch (RemoteException e) {
throw new IllegalStateException("Failed to enroll credential duing SP init", e);
}
if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
- Log.w(TAG, "Fail to migrate SID, assuming no SID, user " + userId);
+ Slog.w(TAG, "Fail to migrate SID, assuming no SID, user " + userId);
clearSidForUser(userId);
} else {
saveSyntheticPasswordHandle(response.getPayload(), userId);
@@ -504,7 +510,7 @@
throw new IllegalStateException("Failed to create new SID for user", e);
}
if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
- Log.e(TAG, "Fail to create new SID for user " + userId);
+ Slog.e(TAG, "Fail to create new SID for user " + userId);
return;
}
saveSyntheticPasswordHandle(response.getPayload(), userId);
@@ -561,7 +567,7 @@
buffer.put(data, 0, data.length);
buffer.flip();
if (buffer.get() != WEAVER_VERSION) {
- Log.e(TAG, "Invalid weaver slot version of handle " + handle);
+ Slog.e(TAG, "Invalid weaver slot version of handle " + handle);
return INVALID_WEAVER_SLOT;
}
return buffer.getInt();
@@ -580,11 +586,11 @@
if (slot != INVALID_WEAVER_SLOT) {
Set<Integer> usedSlots = getUsedWeaverSlots();
if (!usedSlots.contains(slot)) {
- Log.i(TAG, "Destroy weaver slot " + slot + " for user " + userId);
+ Slog.i(TAG, "Destroy weaver slot " + slot + " for user " + userId);
weaverEnroll(slot, null, null);
mPasswordSlotManager.markSlotDeleted(slot);
} else {
- Log.w(TAG, "Skip destroying reused weaver slot " + slot + " for user " + userId);
+ Slog.w(TAG, "Skip destroying reused weaver slot " + slot + " for user " + userId);
}
}
}
@@ -638,15 +644,9 @@
* @throw IllegalStateException if creation fails.
*/
public long createPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
- byte[] credential, int credentialType, AuthenticationToken authToken,
- int requestedQuality, int userId) {
- if (credential == null || credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
- credentialType = LockPatternUtils.CREDENTIAL_TYPE_NONE;
- credential = DEFAULT_PASSWORD;
- }
-
+ LockscreenCredential credential, AuthenticationToken authToken, int userId) {
long handle = generateHandle();
- PasswordData pwd = PasswordData.create(credentialType);
+ PasswordData pwd = PasswordData.create(credential.getType());
byte[] pwdToken = computePasswordToken(credential, pwd);
final long sid;
final byte[] applicationId;
@@ -654,7 +654,7 @@
if (isWeaverAvailable()) {
// Weaver based user password
int weaverSlot = getNextAvailableWeaverSlot();
- Log.i(TAG, "Weaver enroll password to slot " + weaverSlot + " for user " + userId);
+ Slog.i(TAG, "Weaver enroll password to slot " + weaverSlot + " for user " + userId);
byte[] weaverSecret = weaverEnroll(weaverSlot, passwordTokenToWeaverKey(pwdToken),
null);
if (weaverSecret == null) {
@@ -663,7 +663,8 @@
}
saveWeaverSlot(weaverSlot, handle, userId);
mPasswordSlotManager.markSlotInUse(weaverSlot);
- synchronizeWeaverFrpPassword(pwd, requestedQuality, userId, weaverSlot);
+ // No need to pass in quality since the credential type already encodes sufficient info
+ synchronizeWeaverFrpPassword(pwd, 0, userId, weaverSlot);
pwd.passwordHandle = null;
sid = GateKeeper.INVALID_SECURE_USER_ID;
@@ -674,7 +675,7 @@
try {
gatekeeper.clearSecureUserId(fakeUid(userId));
} catch (RemoteException ignore) {
- Log.w(TAG, "Failed to clear SID from gatekeeper");
+ Slog.w(TAG, "Failed to clear SID from gatekeeper");
}
// GateKeeper based user password
GateKeeperResponse response;
@@ -692,7 +693,8 @@
sid = sidFromPasswordHandle(pwd.passwordHandle);
applicationId = transformUnderSecdiscardable(pwdToken,
createSecdiscardable(handle, userId));
- synchronizeFrpPassword(pwd, requestedQuality, userId);
+ // No need to pass in quality since the credential type already encodes sufficient info
+ synchronizeFrpPassword(pwd, 0, userId);
}
saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
@@ -702,7 +704,7 @@
}
public VerifyCredentialResponse verifyFrpCredential(IGateKeeperService gatekeeper,
- byte[] userCredential, int credentialType,
+ LockscreenCredential userCredential,
ICheckCredentialProgressCallback progressCallback) {
PersistentData persistentData = mStorage.readPersistentDataBlock();
if (persistentData.type == PersistentData.TYPE_SP) {
@@ -714,7 +716,7 @@
response = gatekeeper.verifyChallenge(fakeUid(persistentData.userId),
0 /* challenge */, pwd.passwordHandle, passwordTokenToGkInput(pwdToken));
} catch (RemoteException e) {
- Log.e(TAG, "FRP verifyChallenge failed", e);
+ Slog.e(TAG, "FRP verifyChallenge failed", e);
return VerifyCredentialResponse.ERROR;
}
return VerifyCredentialResponse.fromGateKeeperResponse(response);
@@ -725,7 +727,7 @@
return weaverVerify(weaverSlot, passwordTokenToWeaverKey(pwdToken)).stripPayload();
} else {
- Log.e(TAG, "persistentData.type must be TYPE_SP or TYPE_SP_WEAVER, but is "
+ Slog.e(TAG, "persistentData.type must be TYPE_SP or TYPE_SP_WEAVER, but is "
+ persistentData.type);
return VerifyCredentialResponse.ERROR;
}
@@ -733,11 +735,11 @@
public void migrateFrpPasswordLocked(long handle, UserInfo userInfo, int requestedQuality) {
- if (mStorage.getPersistentDataBlock() != null
+ if (mStorage.getPersistentDataBlockManager() != null
&& LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) {
PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle,
userInfo.id));
- if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
int weaverSlot = loadWeaverSlot(handle, userInfo.id);
if (weaverSlot != INVALID_WEAVER_SLOT) {
synchronizeWeaverFrpPassword(pwd, requestedQuality, userInfo.id, weaverSlot);
@@ -750,10 +752,10 @@
private void synchronizeFrpPassword(PasswordData pwd,
int requestedQuality, int userId) {
- if (mStorage.getPersistentDataBlock() != null
+ if (mStorage.getPersistentDataBlockManager() != null
&& LockPatternUtils.userOwnsFrpCredential(mContext,
mUserManager.getUserInfo(userId))) {
- if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
pwd.toBytes());
} else {
@@ -764,10 +766,10 @@
private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId,
int weaverSlot) {
- if (mStorage.getPersistentDataBlock() != null
+ if (mStorage.getPersistentDataBlockManager() != null
&& LockPatternUtils.userOwnsFrpCredential(mContext,
mUserManager.getUserInfo(userId))) {
- if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
requestedQuality, pwd.toBytes());
} else {
@@ -829,14 +831,14 @@
return false;
}
if (!loadEscrowData(authToken, userId)) {
- Log.w(TAG, "User is not escrowable");
+ Slog.w(TAG, "User is not escrowable");
return false;
}
if (isWeaverAvailable()) {
int slot = getNextAvailableWeaverSlot();
- Log.i(TAG, "Weaver enroll token to slot " + slot + " for user " + userId);
+ Slog.i(TAG, "Weaver enroll token to slot " + slot + " for user " + userId);
if (weaverEnroll(slot, null, tokenData.weaverSecret) == null) {
- Log.e(TAG, "Failed to enroll weaver secret when activating token");
+ Slog.e(TAG, "Failed to enroll weaver secret when activating token");
return false;
}
saveWeaverSlot(slot, handle, userId);
@@ -881,18 +883,20 @@
* Decrypt a synthetic password by supplying the user credential and corresponding password
* blob handle generated previously. If the decryption is successful, initiate a GateKeeper
* verification to referesh the SID & Auth token maintained by the system.
- * Note: the credential type is not validated here since there are call sites where the type is
- * unknown. Caller might choose to validate it by examining AuthenticationResult.credentialType
*/
public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
- long handle, byte[] credential, int userId,
+ long handle, @NonNull LockscreenCredential credential, int userId,
ICheckCredentialProgressCallback progressCallback) {
- if (credential == null) {
- credential = DEFAULT_PASSWORD;
- }
AuthenticationResult result = new AuthenticationResult();
PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle, userId));
- result.credentialType = pwd.passwordType;
+
+ if (!credential.checkAgainstStoredType(pwd.credentialType)) {
+ Slog.e(TAG, String.format("Credential type mismatch: expected %d actual %d",
+ pwd.credentialType, credential.getType()));
+ result.gkResponse = VerifyCredentialResponse.ERROR;
+ return result;
+ }
+
byte[] pwdToken = computePasswordToken(credential, pwd);
final byte[] applicationId;
@@ -901,7 +905,7 @@
if (weaverSlot != INVALID_WEAVER_SLOT) {
// Weaver based user password
if (!isWeaverAvailable()) {
- Log.e(TAG, "No weaver service to unwrap password based SP");
+ Slog.e(TAG, "No weaver service to unwrap password based SP");
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
}
@@ -918,7 +922,7 @@
response = gatekeeper.verifyChallenge(fakeUid(userId), 0L,
pwd.passwordHandle, gkPwdToken);
} catch (RemoteException e) {
- Log.e(TAG, "gatekeeper verify failed", e);
+ Slog.e(TAG, "gatekeeper verify failed", e);
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
}
@@ -931,21 +935,19 @@
reenrollResponse = gatekeeper.enroll(fakeUid(userId),
pwd.passwordHandle, gkPwdToken, gkPwdToken);
} catch (RemoteException e) {
- Log.w(TAG, "Fail to invoke gatekeeper.enroll", e);
+ Slog.w(TAG, "Fail to invoke gatekeeper.enroll", e);
reenrollResponse = GateKeeperResponse.ERROR;
// continue the flow anyway
}
if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
pwd.passwordHandle = reenrollResponse.getPayload();
+ // Use the reenrollment opportunity to update credential type
+ // (getting rid of CREDENTIAL_TYPE_PASSWORD_OR_PIN)
+ pwd.credentialType = credential.getType();
saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
- synchronizeFrpPassword(pwd,
- pwd.passwordType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN
- ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
- : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
- /* TODO(roosa): keep the same password quality */,
- userId);
+ synchronizeFrpPassword(pwd, 0, userId);
} else {
- Log.w(TAG, "Fail to re-enroll user password for user " + userId);
+ Slog.w(TAG, "Fail to re-enroll user password for user " + userId);
// continue the flow anyway
}
}
@@ -966,7 +968,7 @@
try {
progressCallback.onCredentialVerified();
} catch (RemoteException e) {
- Log.w(TAG, "progressCallback throws exception", e);
+ Slog.w(TAG, "progressCallback throws exception", e);
}
}
result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
@@ -989,14 +991,14 @@
int slotId = loadWeaverSlot(handle, userId);
if (slotId != INVALID_WEAVER_SLOT) {
if (!isWeaverAvailable()) {
- Log.e(TAG, "No weaver service to unwrap token based SP");
+ Slog.e(TAG, "No weaver service to unwrap token based SP");
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
}
VerifyCredentialResponse response = weaverVerify(slotId, null);
if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK ||
response.getPayload() == null) {
- Log.e(TAG, "Failed to retrieve weaver secret when unwrapping token");
+ Slog.e(TAG, "Failed to retrieve weaver secret when unwrapping token");
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
}
@@ -1043,13 +1045,13 @@
Arrays.copyOfRange(blob, 2, blob.length), applicationId);
}
if (secret == null) {
- Log.e(TAG, "Fail to decrypt SP for user " + userId);
+ Slog.e(TAG, "Fail to decrypt SP for user " + userId);
return null;
}
AuthenticationToken result = new AuthenticationToken(version);
if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
if (!loadEscrowData(result, userId)) {
- Log.e(TAG, "User is not escrowable: " + userId);
+ Slog.e(TAG, "User is not escrowable: " + userId);
return null;
}
result.recreate(secret);
@@ -1057,7 +1059,7 @@
result.syntheticPassword = new String(secret);
}
if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
- Log.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type);
+ Slog.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type);
createSyntheticPasswordBlob(handle, type, result, applicationId, sid, userId);
}
return result;
@@ -1084,7 +1086,7 @@
response = gatekeeper.verifyChallenge(userId, challenge,
spHandle, auth.deriveGkPassword());
} catch (RemoteException e) {
- Log.e(TAG, "Fail to verify with gatekeeper " + userId, e);
+ Slog.e(TAG, "Fail to verify with gatekeeper " + userId, e);
return VerifyCredentialResponse.ERROR;
}
int responseCode = response.getResponseCode();
@@ -1095,7 +1097,7 @@
response = gatekeeper.enroll(userId, spHandle, spHandle,
auth.deriveGkPassword());
} catch (RemoteException e) {
- Log.e(TAG, "Failed to invoke gatekeeper.enroll", e);
+ Slog.e(TAG, "Failed to invoke gatekeeper.enroll", e);
response = GateKeeperResponse.ERROR;
}
if (response.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
@@ -1105,7 +1107,7 @@
return verifyChallenge(gatekeeper, auth, challenge, userId);
} else {
// Fall through, return result from the previous verification attempt.
- Log.w(TAG, "Fail to re-enroll SP handle for user " + userId);
+ Slog.w(TAG, "Fail to re-enroll SP handle for user " + userId);
}
}
return result;
@@ -1225,7 +1227,8 @@
return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, handle);
}
- private byte[] computePasswordToken(byte[] password, PasswordData data) {
+ private byte[] computePasswordToken(LockscreenCredential credential, PasswordData data) {
+ final byte[] password = credential.isNone() ? DEFAULT_PASSWORD : credential.getCredential();
return scrypt(password, data.salt, 1 << data.scryptN, 1 << data.scryptR, 1 << data.scryptP,
PASSWORD_TOKEN_LENGTH);
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 5676da2..29338ba0 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -18,7 +18,6 @@
import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_LOCKSCREEN;
-import android.annotation.Nullable;
import android.content.Context;
import android.os.RemoteException;
import android.security.Scrypt;
@@ -193,6 +192,7 @@
private boolean isCustomLockScreen() {
return mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE
&& mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+ && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PIN
&& mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
}
@@ -345,7 +345,7 @@
}
KeyChainProtectionParams keyChainProtectionParams = new KeyChainProtectionParams.Builder()
.setUserSecretType(TYPE_LOCKSCREEN)
- .setLockScreenUiFormat(getUiFormat(mCredentialType, mCredential))
+ .setLockScreenUiFormat(getUiFormat(mCredentialType))
.setKeyDerivationParams(keyDerivationParams)
.setSecret(new byte[0])
.build();
@@ -449,11 +449,10 @@
* @return The format - either pattern, pin, or password.
*/
@VisibleForTesting
- @KeyChainProtectionParams.LockScreenUiFormat static int getUiFormat(
- int credentialType, byte[] credential) {
+ @KeyChainProtectionParams.LockScreenUiFormat static int getUiFormat(int credentialType) {
if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
return KeyChainProtectionParams.UI_FORMAT_PATTERN;
- } else if (isPin(credential)) {
+ } else if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PIN) {
return KeyChainProtectionParams.UI_FORMAT_PIN;
} else {
return KeyChainProtectionParams.UI_FORMAT_PASSWORD;
@@ -472,23 +471,6 @@
}
/**
- * Returns {@code true} if {@code credential} looks like a pin.
- */
- @VisibleForTesting
- static boolean isPin(@Nullable byte[] credential) {
- if (credential == null) {
- return false;
- }
- int length = credential.length;
- for (int i = 0; i < length; i++) {
- if (!Character.isDigit((char) credential[i])) {
- return false;
- }
- }
- return true;
- }
-
- /**
* Hashes {@code credentials} with the given {@code salt}.
*
* @return The SHA-256 hash.
@@ -541,6 +523,7 @@
}
private boolean shouldUseScryptToHashCredential() {
- return mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+ return mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+ || mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_PIN;
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java
index 90a36723..c963f79 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java
@@ -97,7 +97,8 @@
if (credential == null) {
return false;
}
- if (credentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+ if (credentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+ && credentialType != LockPatternUtils.CREDENTIAL_TYPE_PIN) {
return false;
}
byte[] insecurePasswordPrefixBytes =
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 714bbb9..9a1b30d 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -25,7 +25,6 @@
import android.os.BugreportParams;
import android.os.IDumpstate;
import android.os.IDumpstateListener;
-import android.os.IDumpstateToken;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -64,13 +63,6 @@
@Override
@RequiresPermission(android.Manifest.permission.DUMP)
- public IDumpstateToken setListener(String name, IDumpstateListener listener,
- boolean getSectionDetails) {
- throw new UnsupportedOperationException("setListener is not allowed on this service");
- }
-
- @Override
- @RequiresPermission(android.Manifest.permission.DUMP)
public void startBugreport(int callingUidUnused, String callingPackage,
FileDescriptor bugreportFd, FileDescriptor screenshotFd,
int bugreportMode, IDumpstateListener listener) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 00c0566..ed2bb3d5 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -259,7 +259,7 @@
// Don't hold mSessions lock when calling restoreSession, since it might trigger an APK
// atomic install which needs to query sessions, which requires lock on mSessions.
for (PackageInstallerSession session : stagedSessionsToRestore) {
- if (mPm.isDeviceUpgrading()) {
+ if (mPm.isDeviceUpgrading() && !session.isStagedAndInTerminalState()) {
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
"Build fingerprint has changed");
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0a4415b..104ce1c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1562,8 +1562,8 @@
}
// Send broadcasts
for (int i = 0; i < size; i++) {
- sendPackageChangedBroadcast(packages[i], true, components[i], uids[i],
- null);
+ sendPackageChangedBroadcast(packages[i], true /* dontKillApp */,
+ components[i], uids[i], null /* reason */);
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
break;
@@ -2049,7 +2049,7 @@
for (int i = 0; i < res.libraryConsumers.size(); i++) {
PackageParser.Package pkg = res.libraryConsumers.get(i);
// send broadcast that all consumers of the static shared library have changed
- sendPackageChangedBroadcast(pkg.packageName, false /*killFlag*/,
+ sendPackageChangedBroadcast(pkg.packageName, false /* dontKillApp */,
new ArrayList<>(Collections.singletonList(pkg.packageName)),
pkg.applicationInfo.uid, null);
}
@@ -19999,7 +19999,7 @@
}
private void sendPackageChangedBroadcast(String packageName,
- boolean killFlag, ArrayList<String> componentNames, int packageUid,
+ boolean dontKillApp, ArrayList<String> componentNames, int packageUid,
String reason) {
if (DEBUG_INSTALL)
Log.v(TAG, "Sending package changed: package=" + packageName + " components="
@@ -20009,7 +20009,7 @@
String nameList[] = new String[componentNames.size()];
componentNames.toArray(nameList);
extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
- extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag);
+ extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, dontKillApp);
extras.putInt(Intent.EXTRA_UID, packageUid);
if (reason != null) {
extras.putString(Intent.EXTRA_REASON, reason);
@@ -20282,7 +20282,7 @@
return;
}
sendPackageChangedBroadcast(pkg.packageName,
- false /* killFlag */,
+ true /* dontKillApp */,
new ArrayList<>(Collections.singletonList(pkg.packageName)),
pkg.applicationInfo.uid,
Intent.ACTION_OVERLAY_CHANGED);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 9e88d0c..bb3388c 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -53,7 +53,6 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
-import android.app.AppOpsManager;
import android.app.ApplicationPackageManager;
import android.app.IActivityManager;
import android.content.Context;
@@ -1542,7 +1541,6 @@
}
};
- final AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
for (int i = 0; i < permissionCount; i++) {
final String permName = pkg.requestedPermissions.get(i);
final BasePermission bp;
@@ -1608,16 +1606,9 @@
// If this permission was granted by default, make sure it is.
if ((oldFlags & FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0) {
+ // PermissionPolicyService will handle the app op for runtime permissions later.
grantRuntimePermissionInternal(permName, packageName, false,
Process.SYSTEM_UID, userId, delayingPermCallback);
- // Allow app op later as we are holding mPackages
- // PermissionPolicyService will handle the app op for foreground/background
- // permissions.
- String appOp = AppOpsManager.permissionToOp(permName);
- if (appOp != null) {
- mHandler.post(() -> appOpsManager.setUidMode(appOp, uid,
- AppOpsManager.MODE_ALLOWED));
- }
// If permission review is enabled the permissions for a legacy apps
// are represented as constantly granted runtime ones, so don't revoke.
} else if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
@@ -4233,8 +4224,8 @@
}
@Override
- public @NonNull ArrayList<PermissionInfo> getAllPermissionWithProtectionLevel(
- @PermissionInfo.Protection int protectionLevel) {
+ public @NonNull ArrayList<PermissionInfo> getAllPermissionWithProtection(
+ @PermissionInfo.Protection int protection) {
ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>();
synchronized (PermissionManagerService.this.mLock) {
@@ -4244,7 +4235,7 @@
BasePermission bp = mSettings.mPermissions.valueAt(i);
if (bp.perm != null && bp.perm.info != null
- && bp.protectionLevel == protectionLevel) {
+ && bp.perm.info.getProtection() == protection) {
matchingPermissions.add(bp.perm.info);
}
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 04ec5ba..a807a7e 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -291,8 +291,8 @@
public abstract @Nullable BasePermission getPermissionTEMP(@NonNull String permName);
/** Get all permission that have a certain protection level */
- public abstract @NonNull ArrayList<PermissionInfo> getAllPermissionWithProtectionLevel(
- @PermissionInfo.Protection int protectionLevel);
+ public abstract @NonNull ArrayList<PermissionInfo> getAllPermissionWithProtection(
+ @PermissionInfo.Protection int protection);
/**
* Returns the delegate used to influence permission checking.
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index b892360..9b9f93f 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -48,12 +48,12 @@
import android.permission.PermissionControllerManager;
import android.provider.Telephony;
import android.telecom.TelecomManager;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.LongSparseLongArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsCallback;
@@ -67,6 +67,7 @@
import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback;
import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
@@ -144,7 +145,7 @@
};
final ArrayList<PermissionInfo> dangerousPerms =
- permManagerInternal.getAllPermissionWithProtectionLevel(
+ permManagerInternal.getAllPermissionWithProtection(
PermissionInfo.PROTECTION_DANGEROUS);
try {
@@ -152,7 +153,7 @@
for (int i = 0; i < numDangerousPerms; i++) {
PermissionInfo perm = dangerousPerms.get(i);
- if (perm.isRestricted() || perm.backgroundPermission != null) {
+ if (perm.isRuntime()) {
appOpsService.startWatchingMode(getSwitchOp(perm.name), null, appOpsListener);
}
if (perm.isSoftRestricted()) {
@@ -389,8 +390,7 @@
private final @NonNull PackageManager mPackageManager;
private final @NonNull AppOpsManager mAppOpsManager;
- /** All uid that need to be synchronized */
- private final @NonNull SparseIntArray mAllUids = new SparseIntArray();
+ private final @NonNull ArrayMap<String, PermissionInfo> mRuntimePermissionInfos;
/**
* All ops that need to be flipped to allow.
@@ -428,6 +428,18 @@
mContext = context;
mPackageManager = context.getPackageManager();
mAppOpsManager = context.getSystemService(AppOpsManager.class);
+
+ mRuntimePermissionInfos = new ArrayMap<>();
+ PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService(
+ PermissionManagerServiceInternal.class);
+ List<PermissionInfo> permissionInfos =
+ permissionManagerInternal.getAllPermissionWithProtection(
+ PermissionInfo.PROTECTION_DANGEROUS);
+ int permissionInfosSize = permissionInfos.size();
+ for (int i = 0; i < permissionInfosSize; i++) {
+ PermissionInfo permissionInfo = permissionInfos.get(i);
+ mRuntimePermissionInfos.put(permissionInfo.name, permissionInfo);
+ }
}
/**
@@ -489,7 +501,7 @@
* Note: Called with the package lock held. Do <u>not</u> call into app-op manager.
*/
private void addAppOps(@NonNull PackageInfo packageInfo, @NonNull String permissionName) {
- PermissionInfo permissionInfo = getPermissionInfo(permissionName);
+ PermissionInfo permissionInfo = mRuntimePermissionInfos.get(permissionName);
if (permissionInfo == null) {
return;
}
@@ -499,8 +511,7 @@
private void addPermissionAppOp(@NonNull PackageInfo packageInfo,
@NonNull PermissionInfo permissionInfo) {
- // TODO: Sync all permissions in the future.
- if (!permissionInfo.isRestricted() && permissionInfo.backgroundPermission == null) {
+ if (!permissionInfo.isRuntime()) {
return;
}
@@ -525,7 +536,7 @@
boolean shouldGrantAppOp = shouldGrantAppOp(packageInfo, permissionInfo);
if (shouldGrantAppOp) {
if (permissionInfo.backgroundPermission != null) {
- PermissionInfo backgroundPermissionInfo = getPermissionInfo(
+ PermissionInfo backgroundPermissionInfo = mRuntimePermissionInfos.get(
permissionInfo.backgroundPermission);
boolean shouldGrantBackgroundAppOp = backgroundPermissionInfo != null
&& shouldGrantAppOp(packageInfo, backgroundPermissionInfo);
@@ -552,15 +563,6 @@
}
}
- @Nullable
- private PermissionInfo getPermissionInfo(@NonNull String permissionName) {
- try {
- return mPackageManager.getPermissionInfo(permissionName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- }
-
private boolean shouldGrantAppOp(@NonNull PackageInfo packageInfo,
@NonNull PermissionInfo permissionInfo) {
String permissionName = permissionInfo.name;
@@ -638,8 +640,6 @@
return;
}
- mAllUids.put(pkg.applicationInfo.uid, pkg.applicationInfo.uid);
-
if (pkg.requestedPermissions == null) {
return;
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2593c38..5e1d93f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -386,6 +386,7 @@
BurnInProtectionHelper mBurnInProtectionHelper;
private DisplayFoldController mDisplayFoldController;
AppOpsManager mAppOpsManager;
+ PackageManager mPackageManager;
private boolean mHasFeatureAuto;
private boolean mHasFeatureWatch;
private boolean mHasFeatureLeanback;
@@ -1555,10 +1556,9 @@
private void launchAllAppsAction() {
Intent intent = new Intent(Intent.ACTION_ALL_APPS);
if (mHasFeatureLeanback) {
- final PackageManager pm = mContext.getPackageManager();
Intent intentLauncher = new Intent(Intent.ACTION_MAIN);
intentLauncher.addCategory(Intent.CATEGORY_HOME);
- ResolveInfo resolveInfo = pm.resolveActivityAsUser(intentLauncher,
+ ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(intentLauncher,
PackageManager.MATCH_SYSTEM_ONLY,
mCurrentUserId);
if (resolveInfo != null) {
@@ -1753,10 +1753,11 @@
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mDisplayManager = mContext.getSystemService(DisplayManager.class);
- mHasFeatureWatch = mContext.getPackageManager().hasSystemFeature(FEATURE_WATCH);
- mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK);
- mHasFeatureAuto = mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE);
- mHasFeatureHdmiCec = mContext.getPackageManager().hasSystemFeature(FEATURE_HDMI_CEC);
+ mPackageManager = mContext.getPackageManager();
+ mHasFeatureWatch = mPackageManager.hasSystemFeature(FEATURE_WATCH);
+ mHasFeatureLeanback = mPackageManager.hasSystemFeature(FEATURE_LEANBACK);
+ mHasFeatureAuto = mPackageManager.hasSystemFeature(FEATURE_AUTOMOTIVE);
+ mHasFeatureHdmiCec = mPackageManager.hasSystemFeature(FEATURE_HDMI_CEC);
mAccessibilityShortcutController =
new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
mLogger = new MetricsLogger();
@@ -1994,7 +1995,7 @@
}
mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_NOTHING;
- if (mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
+ if (mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
}
}
@@ -2138,7 +2139,7 @@
ApplicationInfo appInfo;
try {
- appInfo = mContext.getPackageManager().getApplicationInfoAsUser(
+ appInfo = mPackageManager.getApplicationInfoAsUser(
attrs.packageName,
0 /* flags */,
UserHandle.getUserId(callingUid));
@@ -4914,7 +4915,7 @@
@Override public void run() {
if (mBootMsgDialog == null) {
int theme;
- if (mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK)) {
+ if (mPackageManager.hasSystemFeature(FEATURE_LEANBACK)) {
theme = com.android.internal.R.style.Theme_Leanback_Dialog_Alert;
} else {
theme = 0;
@@ -4943,7 +4944,7 @@
return true;
}
};
- if (mContext.getPackageManager().isDeviceUpgrading()) {
+ if (mPackageManager.isDeviceUpgrading()) {
mBootMsgDialog.setTitle(R.string.android_upgrading_title);
} else {
mBootMsgDialog.setTitle(R.string.android_start_title);
@@ -5203,7 +5204,7 @@
}
ActivityInfo ai = null;
- ResolveInfo info = mContext.getPackageManager().resolveActivityAsUser(
+ ResolveInfo info = mPackageManager.resolveActivityAsUser(
intent,
PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA,
mCurrentUserId);
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index edf0cbf..b67d9b2 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -94,16 +94,16 @@
private static final int MSG_PROFILE_TIMED_OUT = 5;
private static final int MSG_WIRED_CHARGING_STARTED = 6;
- private static final long[] WIRELESS_VIBRATION_TIME = {
+ private static final long[] CHARGING_VIBRATION_TIME = {
40, 40, 40, 40, 40, 40, 40, 40, 40, // ramp-up sampling rate = 40ms
40, 40, 40, 40, 40, 40, 40 // ramp-down sampling rate = 40ms
};
- private static final int[] WIRELESS_VIBRATION_AMPLITUDE = {
+ private static final int[] CHARGING_VIBRATION_AMPLITUDE = {
1, 4, 11, 25, 44, 67, 91, 114, 123, // ramp-up amplitude (from 0 to 50%)
103, 79, 55, 34, 17, 7, 2 // ramp-up amplitude
};
- private static final VibrationEffect WIRELESS_CHARGING_VIBRATION_EFFECT =
- VibrationEffect.createWaveform(WIRELESS_VIBRATION_TIME, WIRELESS_VIBRATION_AMPLITUDE,
+ private static final VibrationEffect CHARGING_VIBRATION_EFFECT =
+ VibrationEffect.createWaveform(CHARGING_VIBRATION_TIME, CHARGING_VIBRATION_AMPLITUDE,
-1);
private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -130,6 +130,10 @@
// True if the device should suspend when the screen is off due to proximity.
private final boolean mSuspendWhenScreenOffDueToProximityConfig;
+ // True if the device should show the wireless charging animation when the device
+ // begins charging wirelessly
+ private final boolean mShowWirelessChargingAnimationConfig;
+
// The current interactive state. This is set as soon as an interactive state
// transition begins so as to capture the reason that it happened. At some point
// this state will propagate to the pending state then eventually to the
@@ -182,6 +186,8 @@
mSuspendWhenScreenOffDueToProximityConfig = context.getResources().getBoolean(
com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity);
+ mShowWirelessChargingAnimationConfig = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim);
// Initialize interactive state for battery stats.
try {
@@ -755,35 +761,45 @@
}
};
- /**
- * If enabled, plays a sound and/or vibration when wireless or non-wireless charging has started
- */
- private void playChargingStartedFeedback(@UserIdInt int userId) {
- playChargingStartedVibration(userId);
+ private void playChargingStartedFeedback(@UserIdInt int userId, boolean wireless) {
+ if (!isChargingFeedbackEnabled(userId)) {
+ return;
+ }
+
+ // vibrate
+ final boolean vibrate = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0;
+ if (vibrate) {
+ mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, VIBRATION_ATTRIBUTES);
+ }
+
+ // play sound
final String soundPath = Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.CHARGING_STARTED_SOUND);
- if (isChargingFeedbackEnabled(userId) && soundPath != null) {
- final Uri soundUri = Uri.parse("file://" + soundPath);
- if (soundUri != null) {
- final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
- if (sfx != null) {
- sfx.setStreamType(AudioManager.STREAM_SYSTEM);
- sfx.play();
- }
+ wireless ? Settings.Global.WIRELESS_CHARGING_STARTED_SOUND
+ : Settings.Global.CHARGING_STARTED_SOUND);
+ final Uri soundUri = Uri.parse("file://" + soundPath);
+ if (soundUri != null) {
+ final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
+ if (sfx != null) {
+ sfx.setStreamType(AudioManager.STREAM_SYSTEM);
+ sfx.play();
}
}
}
private void showWirelessChargingStarted(int batteryLevel, @UserIdInt int userId) {
- playChargingStartedFeedback(userId);
- if (mStatusBarManagerInternal != null) {
+ // play sounds + haptics
+ playChargingStartedFeedback(userId, true /* wireless */);
+
+ // show animation
+ if (mShowWirelessChargingAnimationConfig && mStatusBarManagerInternal != null) {
mStatusBarManagerInternal.showChargingAnimation(batteryLevel);
}
mSuspendBlocker.release();
}
private void showWiredChargingStarted(@UserIdInt int userId) {
- playChargingStartedFeedback(userId);
+ playChargingStartedFeedback(userId, false /* wireless */);
mSuspendBlocker.release();
}
@@ -791,14 +807,6 @@
mTrustManager.setDeviceLockedForUser(userId, true /*locked*/);
}
- private void playChargingStartedVibration(@UserIdInt int userId) {
- final boolean vibrateEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0;
- if (vibrateEnabled && isChargingFeedbackEnabled(userId)) {
- mVibrator.vibrate(WIRELESS_CHARGING_VIBRATION_EFFECT, VIBRATION_ATTRIBUTES);
- }
- }
-
private boolean isChargingFeedbackEnabled(@UserIdInt int userId) {
final boolean enabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.CHARGING_SOUNDS_ENABLED, 1, userId) != 0;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 3c1a6af..489c343 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -655,11 +655,11 @@
}
@Override
- public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
+ public void onBiometricAuthenticated() {
enforceBiometricDialog();
if (mBar != null) {
try {
- mBar.onBiometricAuthenticated(authenticated, failureReason);
+ mBar.onBiometricAuthenticated();
} catch (RemoteException ex) {
}
}
@@ -677,11 +677,11 @@
}
@Override
- public void onBiometricError(int errorCode, String error) {
+ public void onBiometricError(int modality, int error, int vendorCode) {
enforceBiometricDialog();
if (mBar != null) {
try {
- mBar.onBiometricError(errorCode, error);
+ mBar.onBiometricError(modality, error, vendorCode);
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
index 7bdc8a3..9dbbf16 100644
--- a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
-import android.app.timedetector.TimeSignal;
+import android.app.timedetector.PhoneTimeSuggestion;
import android.content.Intent;
import android.util.Slog;
import android.util.TimestampedValue;
@@ -48,9 +48,8 @@
// @NonNull after initialize()
private Callback mCallback;
- // NITZ state.
- @Nullable private TimestampedValue<Long> mLastNitzTime;
-
+ // Last phone suggestion.
+ @Nullable private PhoneTimeSuggestion mLastPhoneSuggestion;
// Information about the last time signal received: Used when toggling auto-time.
@Nullable private TimestampedValue<Long> mLastSystemClockTime;
@@ -65,46 +64,40 @@
}
@Override
- public void suggestTime(@NonNull TimeSignal timeSignal) {
- if (!TimeSignal.SOURCE_ID_NITZ.equals(timeSignal.getSourceId())) {
- Slog.w(TAG, "Ignoring signal from unsupported source: " + timeSignal);
- return;
- }
-
+ public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
// NITZ logic
- TimestampedValue<Long> newNitzUtcTime = timeSignal.getUtcTime();
- boolean nitzTimeIsValid = validateNewNitzTime(newNitzUtcTime, mLastNitzTime);
- if (!nitzTimeIsValid) {
+ boolean timeSuggestionIsValid =
+ validateNewPhoneSuggestion(timeSuggestion, mLastPhoneSuggestion);
+ if (!timeSuggestionIsValid) {
return;
}
// Always store the last NITZ value received, regardless of whether we go on to use it to
// update the system clock. This is so that we can validate future NITZ signals.
- mLastNitzTime = newNitzUtcTime;
+ mLastPhoneSuggestion = timeSuggestion;
// System clock update logic.
// Historically, Android has sent a telephony broadcast only when setting the time using
// NITZ.
- final boolean sendNetworkBroadcast =
- TimeSignal.SOURCE_ID_NITZ.equals(timeSignal.getSourceId());
+ final boolean sendNetworkBroadcast = true;
- final TimestampedValue<Long> newUtcTime = newNitzUtcTime;
+ final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
setSystemClockIfRequired(newUtcTime, sendNetworkBroadcast);
}
- private static boolean validateNewNitzTime(TimestampedValue<Long> newNitzUtcTime,
- TimestampedValue<Long> lastNitzTime) {
+ private static boolean validateNewPhoneSuggestion(@NonNull PhoneTimeSuggestion newSuggestion,
+ @Nullable PhoneTimeSuggestion lastSuggestion) {
- if (lastNitzTime != null) {
- long referenceTimeDifference =
- TimestampedValue.referenceTimeDifference(newNitzUtcTime, lastNitzTime);
+ if (lastSuggestion != null) {
+ long referenceTimeDifference = TimestampedValue.referenceTimeDifference(
+ newSuggestion.getUtcTime(), lastSuggestion.getUtcTime());
if (referenceTimeDifference < 0 || referenceTimeDifference > Integer.MAX_VALUE) {
// Out of order or bogus.
Slog.w(TAG, "validateNewNitzTime: Bad NITZ signal received."
+ " referenceTimeDifference=" + referenceTimeDifference
- + " lastNitzTime=" + lastNitzTime
- + " newNitzUtcTime=" + newNitzUtcTime);
+ + " lastSuggestion=" + lastSuggestion
+ + " newSuggestion=" + newSuggestion);
return false;
}
}
@@ -182,7 +175,7 @@
@Override
public void dump(@NonNull PrintWriter pw, @Nullable String[] args) {
- pw.println("mLastNitzTime=" + mLastNitzTime);
+ pw.println("mLastPhoneSuggestion=" + mLastPhoneSuggestion);
pw.println("mLastSystemClockTimeSet=" + mLastSystemClockTimeSet);
pw.println("mLastSystemClockTime=" + mLastSystemClockTime);
pw.println("mLastSystemClockTimeSendNetworkBroadcast="
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 9c83000..ee42279 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.timedetector.ITimeDetectorService;
-import android.app.timedetector.TimeSignal;
+import android.app.timedetector.PhoneTimeSuggestion;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -96,14 +96,14 @@
}
@Override
- public void suggestTime(@NonNull TimeSignal timeSignal) {
+ public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSignal) {
enforceSetTimePermission();
Objects.requireNonNull(timeSignal);
long idToken = Binder.clearCallingIdentity();
try {
synchronized (mStrategyLock) {
- mTimeDetectorStrategy.suggestTime(timeSignal);
+ mTimeDetectorStrategy.suggestPhoneTime(timeSignal);
}
} finally {
Binder.restoreCallingIdentity(idToken);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index e050865..7c2a945 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.timedetector.TimeSignal;
+import android.app.timedetector.PhoneTimeSuggestion;
import android.content.Intent;
import android.util.TimestampedValue;
@@ -72,7 +72,7 @@
void initialize(@NonNull Callback callback);
/** Process the suggested time. */
- void suggestTime(@NonNull TimeSignal timeSignal);
+ void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion);
/** Handle the auto-time setting being toggled on or off. */
void handleAutoTimeDetectionToggle(boolean enabled);
diff --git a/services/core/java/com/android/server/twilight/TwilightService.java b/services/core/java/com/android/server/twilight/TwilightService.java
index e4cb19e..e72ba8d 100644
--- a/services/core/java/com/android/server/twilight/TwilightService.java
+++ b/services/core/java/com/android/server/twilight/TwilightService.java
@@ -17,6 +17,7 @@
package com.android.server.twilight;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -160,8 +161,13 @@
// Request the device's location immediately if a previous location isn't available.
if (mLocationManager.getLastLocation() == null) {
if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
- mLocationManager.requestSingleUpdate(
- LocationManager.NETWORK_PROVIDER, this, Looper.getMainLooper());
+ mLocationManager.getCurrentLocation(
+ LocationManager.NETWORK_PROVIDER, null, getContext().getMainExecutor(),
+ this::onLocationChanged);
+ } else if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+ mLocationManager.getCurrentLocation(
+ LocationManager.GPS_PROVIDER, null, getContext().getMainExecutor(),
+ this::onLocationChanged);
}
}
@@ -218,12 +224,7 @@
for (int i = mListeners.size() - 1; i >= 0; --i) {
final TwilightListener listener = mListeners.keyAt(i);
final Handler handler = mListeners.valueAt(i);
- handler.post(new Runnable() {
- @Override
- public void run() {
- listener.onTwilightStateChanged(state);
- }
- });
+ handler.post(() -> listener.onTwilightStateChanged(state));
}
}
}
@@ -243,12 +244,8 @@
}
@Override
- public void onLocationChanged(Location location) {
- // Location providers may erroneously return (0.0, 0.0) when they fail to determine the
- // device's location. These location updates can be safely ignored since the chance of a
- // user actually being at these coordinates is quite low.
- if (location != null
- && !(location.getLongitude() == 0.0 && location.getLatitude() == 0.0)) {
+ public void onLocationChanged(@Nullable Location location) {
+ if (location != null) {
Slog.d(TAG, "onLocationChanged:"
+ " provider=" + location.getProvider()
+ " accuracy=" + location.getAccuracy()
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index e6c6b12e..9d41d97 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -187,9 +187,22 @@
private int startingWindowDelayMs = INVALID_DELAY;
private int bindApplicationDelayMs = INVALID_DELAY;
private int reason = APP_TRANSITION_TIMEOUT;
- private boolean loggedWindowsDrawn;
+ // TODO(b/132736359) The number may need to consider the visibility change.
+ private int numUndrawnActivities = 1;
private boolean loggedStartingWindowDrawn;
private boolean launchTraceActive;
+
+ /**
+ * Remembers the latest launched activity to represent the final transition. This also
+ * increments the number of activities that should be drawn, so a consecutive launching
+ * sequence can be coalesced as one event.
+ */
+ void setLatestLaunchedActivity(ActivityRecord r) {
+ if (launchedActivity == r) {
+ return;
+ }
+ launchedActivity = r;
+ }
}
final class WindowingModeTransitionInfoSnapshot {
@@ -400,7 +413,7 @@
// the other attributes.
// Coalesce multiple (trampoline) activities from a single sequence together.
- info.launchedActivity = launchedActivity;
+ info.setLatestLaunchedActivity(launchedActivity);
return;
}
@@ -422,7 +435,7 @@
// A new launch sequence [with the windowingMode] has begun.
// Start tracking it.
final WindowingModeTransitionInfo newInfo = new WindowingModeTransitionInfo();
- newInfo.launchedActivity = launchedActivity;
+ newInfo.setLatestLaunchedActivity(launchedActivity);
newInfo.currentTransitionProcessRunning = processRunning;
newInfo.startResult = resultCode;
mWindowingModeTransitionInfo.put(windowingMode, newInfo);
@@ -448,11 +461,11 @@
if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn windowingMode=" + windowingMode);
final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(windowingMode);
- if (info == null || info.loggedWindowsDrawn) {
+ if (info == null || info.numUndrawnActivities == 0) {
return null;
}
info.windowsDrawnDelayMs = calculateDelay(timestampNs);
- info.loggedWindowsDrawn = true;
+ info.numUndrawnActivities--;
final WindowingModeTransitionInfoSnapshot infoSnapshot =
new WindowingModeTransitionInfoSnapshot(info);
if (allWindowsDrawn() && mLoggedTransitionStarting) {
@@ -594,9 +607,10 @@
}
}
- private boolean allWindowsDrawn() {
+ @VisibleForTesting
+ boolean allWindowsDrawn() {
for (int index = mWindowingModeTransitionInfo.size() - 1; index >= 0; index--) {
- if (!mWindowingModeTransitionInfo.valueAt(index).loggedWindowsDrawn) {
+ if (mWindowingModeTransitionInfo.valueAt(index).numUndrawnActivities != 0) {
return false;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 1bc36bb..887ece5 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -2086,7 +2086,6 @@
final TaskRecord task = r.getTaskRecord();
final ActivityStack stack = task.getStack();
- r.mLaunchTaskBehind = false;
mRecentTasks.add(task);
mService.getTaskChangeNotificationController().notifyTaskStackChanged();
stack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 208f54c..a783ee9 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -876,6 +876,8 @@
if (canToastShowWhenLocked(callingPid)) {
attrs.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
}
+ // Toasts can't be clickable
+ attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
break;
}
@@ -3241,7 +3243,8 @@
statusBar.topAppWindowChanged(displayId, isFullscreen, isImmersive);
// TODO(b/118118435): Remove this after removing system UI visibilities.
- mDisplayContent.statusBarVisibilityChanged(visibility);
+ mDisplayContent.statusBarVisibilityChanged(
+ visibility & ~(View.STATUS_BAR_UNHIDE | View.NAVIGATION_BAR_UNHIDE));
}
});
return diff;
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 12579e6..b7d25c3 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -247,7 +247,7 @@
"startRecentsActivity");
mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
this, mDefaultDisplay.mDisplayId,
- mStackSupervisor.mRecentTasks.getRecentTaskIds());
+ mStackSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity);
// If we updated the launch-behind state, update the visibility of the activities after
// we fetch the visible tasks to be controlled by the animation
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 9a58a68..7a3e43b 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -358,7 +358,8 @@
* because it may call cancelAnimation() which needs to properly clean up the controller
* in the window manager.
*/
- public void initialize(int targetActivityType, SparseBooleanArray recentTaskIds) {
+ public void initialize(int targetActivityType, SparseBooleanArray recentTaskIds,
+ ActivityRecord targetActivity) {
mTargetActivityType = targetActivityType;
mDisplayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
@@ -400,16 +401,12 @@
}
// Adjust the wallpaper visibility for the showing target activity
- final ActivityRecord recentsComponentActivity =
- targetStack.getTopChild().getTopFullscreenActivity();
- if (recentsComponentActivity != null) {
- ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
- "setHomeApp(%s)", recentsComponentActivity.getName());
- mTargetActivityRecord = recentsComponentActivity;
- if (recentsComponentActivity.windowsCanBeWallpaperTarget()) {
- mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- mDisplayContent.setLayoutNeeded();
- }
+ ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
+ "setHomeApp(%s)", targetActivity.getName());
+ mTargetActivityRecord = targetActivity;
+ if (targetActivity.windowsCanBeWallpaperTarget()) {
+ mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ mDisplayContent.setLayoutNeeded();
}
// Save the minimized home height
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e90f3da..63ce1b1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2652,11 +2652,11 @@
void initializeRecentsAnimation(int targetActivityType,
IRecentsAnimationRunner recentsAnimationRunner,
RecentsAnimationController.RecentsAnimationCallbacks callbacks, int displayId,
- SparseBooleanArray recentTaskIds) {
+ SparseBooleanArray recentTaskIds, ActivityRecord targetActivity) {
mRecentsAnimationController = new RecentsAnimationController(this, recentsAnimationRunner,
callbacks, displayId);
mRoot.getDisplayContent(displayId).mAppTransition.updateBooster();
- mRecentsAnimationController.initialize(targetActivityType, recentTaskIds);
+ mRecentsAnimationController.initialize(targetActivityType, recentTaskIds, targetActivity);
}
@VisibleForTesting
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c4adc035..5e49c7a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -168,6 +168,7 @@
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Color;
+import android.location.LocationManager;
import android.media.AudioManager;
import android.media.IAudioService;
import android.net.ConnectivityManager;
@@ -1959,6 +1960,10 @@
return mContext.getSystemService(ConnectivityManager.class);
}
+ LocationManager getLocationManager() {
+ return mContext.getSystemService(LocationManager.class);
+ }
+
IWindowManager getIWindowManager() {
return IWindowManager.Stub
.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
@@ -4178,7 +4183,7 @@
private boolean passwordQualityInvocationOrderCheckEnabled(String packageName, int userId) {
try {
return mIPlatformCompat.isChangeEnabledByPackageName(ADMIN_APP_PASSWORD_COMPLEXITY,
- packageName);
+ packageName, userId);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e);
}
@@ -10899,6 +10904,36 @@
}
@Override
+ public void setLocationEnabled(ComponentName who, boolean locationEnabled) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ int userId = mInjector.userHandleGetCallingUserId();
+
+ synchronized (getLockObject()) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+ if (!isDeviceOwner(who, userId) && !isCurrentUserDemo()) {
+ throw new SecurityException(
+ "Permission denial: Profile owners cannot update location settings");
+ }
+ }
+
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ mInjector.getLocationManager().setLocationEnabledForUser(
+ locationEnabled, UserHandle.of(userId));
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_SECURE_SETTING)
+ .setAdmin(who)
+ .setStrings(Settings.Secure.LOCATION_MODE, Integer.toString(
+ locationEnabled ? Settings.Secure.LOCATION_MODE_ON
+ : Settings.Secure.LOCATION_MODE_OFF))
+ .write();
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public boolean setTime(ComponentName who, long millis) {
Preconditions.checkNotNull(who, "ComponentName is null in setTime");
enforceDeviceOwner(who);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 4aeeb0a..ec47a95 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -33,7 +33,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -43,17 +42,15 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
-import android.hardware.face.FaceManager;
-import android.hardware.face.IFaceService;
import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintService;
import android.os.Binder;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteException;
import android.security.KeyStore;
import androidx.test.InstrumentationRegistry;
@@ -68,8 +65,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.List;
-
@SmallTest
public class BiometricServiceTest {
@@ -98,71 +93,33 @@
@Mock
private PackageManager mPackageManager;
@Mock
- private AppOpsManager mAppOpsManager;
- @Mock
IBiometricServiceReceiver mReceiver1;
@Mock
IBiometricServiceReceiver mReceiver2;
@Mock
- FingerprintManager mFingerprintManager;
+ BiometricService.Injector mInjector;
@Mock
- FaceManager mFaceManager;
-
- private static class MockInjector extends BiometricService.Injector {
- @Override
- IActivityManager getActivityManagerService() {
- return mock(IActivityManager.class);
- }
-
- @Override
- IStatusBarService getStatusBarService() {
- return mock(IStatusBarService.class);
- }
-
- @Override
- IFingerprintService getFingerprintService() {
- return mock(IFingerprintService.class);
- }
-
- @Override
- IFaceService getFaceService() {
- return mock(IFaceService.class);
- }
-
- @Override
- BiometricService.SettingObserver getSettingObserver(Context context, Handler handler,
- List<BiometricService.EnabledOnKeyguardCallback> callbacks) {
- return mock(BiometricService.SettingObserver.class);
- }
-
- @Override
- KeyStore getKeyStore() {
- return mock(KeyStore.class);
- }
-
- @Override
- boolean isDebugEnabled(Context context, int userId) {
- return false;
- }
-
- @Override
- void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
- // no-op for test
- }
- }
+ IBiometricAuthenticator mFingerprintAuthenticator;
+ @Mock
+ IBiometricAuthenticator mFaceAuthenticator;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
- when(mContext.getSystemService(Context.FINGERPRINT_SERVICE))
- .thenReturn(mFingerprintManager);
- when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getResources()).thenReturn(mResources);
+ when(mInjector.getActivityManagerService()).thenReturn(mock(IActivityManager.class));
+ when(mInjector.getStatusBarService()).thenReturn(mock(IStatusBarService.class));
+ when(mInjector.getFingerprintAuthenticator()).thenReturn(mFingerprintAuthenticator);
+ when(mInjector.getFaceAuthenticator()).thenReturn(mFaceAuthenticator);
+ when(mInjector.getSettingObserver(any(), any(), any())).thenReturn(
+ mock(BiometricService.SettingObserver.class));
+ when(mInjector.getKeyStore()).thenReturn(mock(KeyStore.class));
+ when(mInjector.isDebugEnabled(any(), anyInt())).thenReturn(false);
+
when(mResources.getString(R.string.biometric_error_hw_unavailable))
.thenReturn(ERROR_HW_UNAVAILABLE);
when(mResources.getString(R.string.biometric_not_recognized))
@@ -172,61 +129,69 @@
}
@Test
- public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws Exception {
+ public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws
+ Exception {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(false);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)).thenReturn(false);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
- mBiometricService = new BiometricService(mContext, new MockInjector());
+ mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
false /* allowDeviceCredential */);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT), eq(ERROR_HW_UNAVAILABLE));
+ eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT),
+ eq(0 /* vendorCode */));
}
@Test
public void testAuthenticate_withoutEnrolled_returnsErrorNoBiometrics() throws Exception {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
- when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+ when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService = new BiometricService(mContext, new MockInjector());
+ mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
false /* allowDeviceCredential */);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS), any());
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS),
+ eq(0 /* vendorCode */));
}
@Test
- public void testAuthenticate_whenHalIsDead_returnsErrorHardwareUnavailable() throws Exception {
+ public void testAuthenticate_whenHalIsDead_returnsErrorHardwareUnavailable() throws
+ Exception {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
- when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
- when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+ when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+ when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(false);
- mBiometricService = new BiometricService(mContext, new MockInjector());
+ mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
false /* allowDeviceCredential */);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(ERROR_HW_UNAVAILABLE));
+ eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
+ eq(0 /* vendorCode */));
}
@Test
public void testAuthenticateFace_respectsUserSetting()
throws Exception {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
- when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
- when(mFaceManager.isHardwareDetected()).thenReturn(true);
+ when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+ when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService = new BiometricService(mContext, new MockInjector());
+ mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
// Disabled in user settings receives onError
@@ -235,7 +200,9 @@
false /* allowDeviceCredential */);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(ERROR_HW_UNAVAILABLE));
+ eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
+ eq(0 /* vendorCode */));
// Enrolled, not disabled in settings, user requires confirmation in settings
resetReceiver();
@@ -245,8 +212,8 @@
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
false /* allowDeviceCredential */);
waitForIdle();
- verify(mReceiver1, never()).onError(anyInt(), any(String.class));
- verify(mBiometricService.mFaceService).prepareForAuthentication(
+ verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
+ verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
eq(true) /* requireConfirmation */,
any(IBinder.class),
anyLong() /* sessionId */,
@@ -265,7 +232,7 @@
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
false /* allowDeviceCredential */);
waitForIdle();
- verify(mBiometricService.mFaceService).prepareForAuthentication(
+ verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
eq(false) /* requireConfirmation */,
any(IBinder.class),
anyLong() /* sessionId */,
@@ -281,7 +248,7 @@
@Test
public void testAuthenticate_happyPathWithoutConfirmation() throws Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
- mBiometricService = new BiometricService(mContext, new MockInjector());
+ mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
// Start testing the happy path
@@ -295,8 +262,9 @@
// Invokes <Modality>Service#prepareForAuthentication
ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
- verify(mReceiver1, never()).onError(anyInt(), any(String.class));
- verify(mBiometricService.mFingerprintService).prepareForAuthentication(
+ verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
+ verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
+ anyBoolean() /* requireConfirmation */,
any(IBinder.class),
anyLong() /* sessionId */,
anyInt() /* userId */,
@@ -316,7 +284,7 @@
BiometricService.STATE_AUTH_STARTED);
// startPreparedClient invoked
- verify(mBiometricService.mFingerprintService)
+ verify(mBiometricService.mAuthenticators.get(0).impl)
.startPreparedClient(cookieCaptor.getValue());
// StatusBar showBiometricDialog invoked
@@ -337,8 +305,7 @@
assertEquals(mBiometricService.mCurrentAuthSession.mState,
BiometricService.STATE_AUTHENTICATED_PENDING_SYSUI);
// Notify SystemUI hardware authenticated
- verify(mBiometricService.mStatusBarService).onBiometricAuthenticated(
- eq(true) /* authenticated */, eq(null) /* failureReason */);
+ verify(mBiometricService.mStatusBarService).onBiometricAuthenticated();
// SystemUI sends callback with dismissed reason
mBiometricService.mInternalReceiver.onDialogDismissed(
@@ -355,7 +322,7 @@
@Test
public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
- when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+ when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, true /* allowDeviceCredential */);
waitForIdle();
@@ -409,8 +376,10 @@
mBiometricService.mInternalReceiver.onAuthenticationFailed();
waitForIdle();
- verify(mBiometricService.mStatusBarService)
- .onBiometricAuthenticated(eq(false), eq(ERROR_NOT_RECOGNIZED));
+ verify(mBiometricService.mStatusBarService).onBiometricError(
+ eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
+ eq(0 /* vendorCode */));
verify(mReceiver1).onAuthenticationFailed();
assertEquals(mBiometricService.mCurrentAuthSession.mState,
BiometricService.STATE_AUTH_PAUSED);
@@ -426,15 +395,18 @@
mBiometricService.mInternalReceiver.onAuthenticationFailed();
waitForIdle();
- verify(mBiometricService.mStatusBarService)
- .onBiometricAuthenticated(eq(false), eq(ERROR_NOT_RECOGNIZED));
+ verify(mBiometricService.mStatusBarService).onBiometricError(
+ eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
+ eq(0 /* vendorCode */));
verify(mReceiver1).onAuthenticationFailed();
assertEquals(mBiometricService.mCurrentAuthSession.mState,
BiometricService.STATE_AUTH_STARTED);
}
@Test
- public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws Exception {
+ public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws
+ Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, false /* allowDeviceCredential */);
@@ -451,15 +423,15 @@
BiometricService.STATE_AUTH_STARTED);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
- BiometricConstants.BIOMETRIC_ERROR_CANCELED, ERROR_CANCELED);
+ BiometricAuthenticator.TYPE_FINGERPRINT,
+ BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
waitForIdle();
// Auth session doesn't become null until SystemUI responds that the animation is completed
assertNotNull(mBiometricService.mCurrentAuthSession);
// ERROR_CANCELED is not sent until SystemUI responded that animation is completed
- verify(mReceiver1, never()).onError(
- anyInt(), anyString());
- verify(mReceiver2, never()).onError(anyInt(), any(String.class));
+ verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
+ verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt());
// SystemUI dialog closed
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
@@ -469,8 +441,9 @@
.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
waitForIdle();
verify(mReceiver1).onError(
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
- eq(ERROR_CANCELED));
+ eq(0 /* vendorCode */));
assertNull(mBiometricService.mCurrentAuthSession);
}
@@ -482,14 +455,17 @@
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricAuthenticator.TYPE_FACE,
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
- ERROR_TIMEOUT);
+ 0 /* vendorCode */);
waitForIdle();
assertEquals(mBiometricService.mCurrentAuthSession.mState,
BiometricService.STATE_AUTH_PAUSED);
- verify(mBiometricService.mStatusBarService)
- .onBiometricAuthenticated(eq(false), eq(ERROR_TIMEOUT));
+ verify(mBiometricService.mStatusBarService).onBiometricError(
+ eq(BiometricAuthenticator.TYPE_FACE),
+ eq(BiometricConstants.BIOMETRIC_ERROR_TIMEOUT),
+ eq(0 /* vendorCode */));
// Timeout does not count as fail as per BiometricPrompt documentation.
verify(mReceiver1, never()).onAuthenticationFailed();
@@ -524,22 +500,25 @@
public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireCOnfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, false /* allowDeviceCredential */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricAuthenticator.TYPE_FACE,
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
- ERROR_TIMEOUT);
+ 0 /* vendorCode */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricAuthenticator.TYPE_FACE,
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
- ERROR_CANCELED);
+ 0 /* vendorCode */);
waitForIdle();
// Client receives error immediately
verify(mReceiver1).onError(
+ eq(BiometricAuthenticator.TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
- eq(ERROR_CANCELED));
+ eq(0 /* vendorCode */));
// Dialog is hidden immediately
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
// Auth session is over
@@ -558,26 +537,29 @@
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricAuthenticator.TYPE_FINGERPRINT,
BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
- ERROR_UNABLE_TO_PROCESS);
+ 0 /* vendorCode */);
waitForIdle();
// Sends error to SystemUI and does not notify client yet
assertEquals(mBiometricService.mCurrentAuthSession.mState,
BiometricService.STATE_ERROR_PENDING_SYSUI);
verify(mBiometricService.mStatusBarService).onBiometricError(
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
- eq(ERROR_UNABLE_TO_PROCESS));
+ eq(0 /* vendorCode */));
verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
- verify(mReceiver1, never()).onError(anyInt(), anyString());
+ verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
// SystemUI animation completed, client is notified, auth session is over
mBiometricService.mInternalReceiver
.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
waitForIdle();
verify(mReceiver1).onError(
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
- eq(ERROR_UNABLE_TO_PROCESS));
+ eq(0 /* vendorCode */));
assertNull(mBiometricService.mCurrentAuthSession);
}
@@ -590,8 +572,9 @@
mBiometricService.mInternalReceiver.onError(
getCookieForPendingSession(mBiometricService.mPendingAuthSession),
+ BiometricAuthenticator.TYPE_FACE,
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
- ERROR_LOCKOUT);
+ 0 /* vendorCode */);
waitForIdle();
// Pending auth session becomes current auth session, since device credential should
@@ -622,8 +605,9 @@
mBiometricService.mInternalReceiver.onError(
getCookieForPendingSession(mBiometricService.mPendingAuthSession),
+ BiometricAuthenticator.TYPE_FINGERPRINT,
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
- ERROR_LOCKOUT);
+ 0 /* vendorCode */);
waitForIdle();
// Error is sent to client
@@ -690,17 +674,18 @@
assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
mBiometricService.mCurrentAuthSession.mState);
- verify(mReceiver1, never()).onError(anyInt(), anyString());
+ verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricAuthenticator.TYPE_FINGERPRINT,
BiometricConstants.BIOMETRIC_ERROR_CANCELED,
- ERROR_CANCELED);
+ 0 /* vendorCode */);
waitForIdle();
assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
mBiometricService.mCurrentAuthSession.mState);
- verify(mReceiver1, never()).onError(anyInt(), anyString());
+ verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
}
@Test
@@ -714,15 +699,17 @@
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricAuthenticator.TYPE_FINGERPRINT,
BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
- ERROR_LOCKOUT);
+ 0 /* vendorCode */);
waitForIdle();
assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
mBiometricService.mCurrentAuthSession.mState);
verify(mBiometricService.mStatusBarService).onBiometricError(
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT),
- eq(ERROR_LOCKOUT));
+ eq(0 /* vendorCode */));
}
@Test
@@ -736,15 +723,17 @@
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricAuthenticator.TYPE_FINGERPRINT,
BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
- ERROR_UNABLE_TO_PROCESS);
+ 0 /* vendorCode */);
waitForIdle();
assertEquals(BiometricService.STATE_ERROR_PENDING_SYSUI,
mBiometricService.mCurrentAuthSession.mState);
verify(mBiometricService.mStatusBarService).onBiometricError(
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
- eq(ERROR_UNABLE_TO_PROCESS));
+ eq(0 /* vendorCode */));
}
@Test
@@ -758,9 +747,10 @@
.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
waitForIdle();
verify(mReceiver1).onError(
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
- eq(ERROR_USER_CANCELED));
- verify(mBiometricService.mFingerprintService).cancelAuthenticationFromService(
+ eq(0 /* vendorCode */));
+ verify(mBiometricService.mAuthenticators.get(0).impl).cancelAuthenticationFromService(
any(),
any(),
anyInt(),
@@ -778,13 +768,15 @@
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricAuthenticator.TYPE_FACE,
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
- ERROR_TIMEOUT);
+ 0 /* vendorCode */);
mBiometricService.mInternalReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_NEGATIVE);
waitForIdle();
- verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
+ verify(mBiometricService.mAuthenticators.get(0).impl,
+ never()).cancelAuthenticationFromService(
any(),
any(),
anyInt(),
@@ -794,20 +786,23 @@
}
@Test
- public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws Exception {
+ public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws
+ Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, false /* allowDeviceCredential */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricAuthenticator.TYPE_FACE,
BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
- ERROR_TIMEOUT);
+ 0 /* vendorCode */);
mBiometricService.mInternalReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
waitForIdle();
- verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
+ verify(mBiometricService.mAuthenticators.get(0).impl,
+ never()).cancelAuthenticationFromService(
any(),
any(),
anyInt(),
@@ -830,7 +825,8 @@
waitForIdle();
// doesn't send cancel to HAL
- verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
+ verify(mBiometricService.mAuthenticators.get(0).impl,
+ never()).cancelAuthenticationFromService(
any(),
any(),
anyInt(),
@@ -838,8 +834,9 @@
anyInt(),
anyBoolean());
verify(mReceiver1).onError(
+ eq(BiometricAuthenticator.TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
- eq(ERROR_USER_CANCELED));
+ eq(0 /* vendorCode */));
assertNull(mBiometricService.mCurrentAuthSession);
}
@@ -863,7 +860,7 @@
// Helper methods
- private void setupAuthForOnly(int modality) {
+ private void setupAuthForOnly(int modality) throws RemoteException {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(false);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
@@ -871,17 +868,17 @@
if (modality == BiometricAuthenticator.TYPE_FINGERPRINT) {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(true);
- when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
- when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+ when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+ when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
} else if (modality == BiometricAuthenticator.TYPE_FACE) {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
- when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
- when(mFaceManager.isHardwareDetected()).thenReturn(true);
+ when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+ when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
} else {
fail("Unknown modality: " + modality);
}
- mBiometricService = new BiometricService(mContext, new MockInjector());
+ mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
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 537287d..9863057 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -52,6 +52,7 @@
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockscreenCredential;
import com.android.server.LocalServices;
import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
import com.android.server.wm.WindowManagerInternal;
@@ -104,6 +105,7 @@
FaceManager mFaceManager;
PackageManager mPackageManager;
protected boolean mHasSecureLockScreen;
+ FakeSettings mSettings;
@Override
protected void setUp() throws Exception {
@@ -125,6 +127,7 @@
mFingerprintManager = mock(FingerprintManager.class);
mFaceManager = mock(FaceManager.class);
mPackageManager = mock(PackageManager.class);
+ mSettings = new FakeSettings();
LocalServices.removeServiceForTest(LockSettingsInternal.class);
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
@@ -162,7 +165,7 @@
mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils, mStorage,
mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager,
- mUserManagerInternal, mDeviceStateCache);
+ mUserManagerInternal, mDeviceStateCache, mSettings);
when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
installChildProfile(MANAGED_PROFILE_USER_ID);
@@ -195,6 +198,7 @@
mockBiometricsHardwareFingerprintsAndTemplates(PRIMARY_USER_ID);
mockBiometricsHardwareFingerprintsAndTemplates(MANAGED_PROFILE_USER_ID);
+ mSettings.setDeviceProvisioned(true);
mLocalService = LocalServices.getService(LockSettingsInternal.class);
}
@@ -307,4 +311,22 @@
protected static void assertArrayNotEquals(byte[] expected, byte[] actual) {
assertFalse(Arrays.equals(expected, actual));
}
+
+ protected LockscreenCredential newPassword(String password) {
+ return LockscreenCredential.createPasswordOrNone(password);
+ }
+
+ protected LockscreenCredential newPin(String pin) {
+ return LockscreenCredential.createPinOrNone(pin);
+ }
+
+ protected LockscreenCredential newPattern(String pattern) {
+ return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+ pattern.getBytes()));
+ }
+
+ protected LockscreenCredential nonePassword() {
+ return LockscreenCredential.createNone();
+ }
+
}
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..5c54883 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java
@@ -15,9 +15,6 @@
*/
package com.android.server.locksettings;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
import static com.android.server.testutils.TestUtils.assertExpectException;
import static org.mockito.Mockito.anyInt;
@@ -30,7 +27,7 @@
import androidx.test.filters.SmallTest;
-import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import org.mockito.ArgumentCaptor;
@@ -59,56 +56,53 @@
}
public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException {
- final byte[] password = "testSyntheticPasswordClearCredential-password".getBytes();
- final byte[] newPassword = "testSyntheticPasswordClearCredential-newpassword".getBytes();
+ final LockscreenCredential password = newPassword("password");
+ final LockscreenCredential newPassword = newPassword("newpassword");
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
// clear password
- mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null,
- PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, true);
+ mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID, true);
assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
// set a new password
- mService.setLockCredential(newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
- assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(newPassword,
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ mService.setLockCredential(newPassword, nonePassword(), PRIMARY_USER_ID,
+ false);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ newPassword, 0, PRIMARY_USER_ID)
.getResponseCode());
assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
}
public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException {
- final byte[] password = "testSyntheticPasswordClearCredential-password".getBytes();
- final byte[] newPassword = "testSyntheticPasswordClearCredential-newpassword".getBytes();
+ final LockscreenCredential password = newPassword("password");
+ final LockscreenCredential newPassword = newPassword("newpassword");
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
// Untrusted change password
- mService.setLockCredential(newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, true);
+ mService.setLockCredential(newPassword, nonePassword(), PRIMARY_USER_ID,
+ true);
assertNotEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
// Verify the password
- assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(newPassword,
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ newPassword, 0, PRIMARY_USER_ID).getResponseCode());
}
public void testUntrustedCredentialChangeMaintainsAuthSecret() throws RemoteException {
- final byte[] password =
- "testUntrustedCredentialChangeMaintainsAuthSecret-password".getBytes();
- final byte[] newPassword =
- "testUntrustedCredentialChangeMaintainsAuthSecret-newpassword".getBytes();
+ final LockscreenCredential password = newPassword("password");
+ final LockscreenCredential newPassword = newPassword("newpassword");
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
// Untrusted change password
- mService.setLockCredential(newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, true);
+ mService.setLockCredential(newPassword, nonePassword(), PRIMARY_USER_ID,
+ true);
// Verify the password
- assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(newPassword,
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ newPassword, 0, PRIMARY_USER_ID)
.getResponseCode());
// Ensure the same secret was passed each time
@@ -118,31 +112,30 @@
}
public void testUntrustedCredentialChangeBlockedIfSpNotCached() throws RemoteException {
- final byte[] password =
- "testUntrustedCredentialChangeBlockedIfSpNotCached-password".getBytes();
- final byte[] newPassword =
- "testUntrustedCredentialChangeBlockedIfSpNotCached-newpassword".getBytes();
+ final LockscreenCredential password = newPassword("password");
+ final LockscreenCredential newPassword = newPassword("newpassword");
// Disable caching for this test
enableSpCaching(false);
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ flushHandlerTasks();
+
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
// Untrusted change password
assertExpectException(
IllegalStateException.class,
/* messageRegex= */ "Untrusted credential reset not possible without cached SP",
- () -> mService.setLockCredential(newPassword,
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, true));
+ () -> mService.setLockCredential(newPassword, nonePassword(),
+ PRIMARY_USER_ID, true));
assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
// Verify the new password doesn't work but the old one still does
- assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(newPassword,
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(
+ newPassword, 0, PRIMARY_USER_ID)
.getResponseCode());
- assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(password,
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ password, 0, PRIMARY_USER_ID)
.getResponseCode());
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java b/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java
new file mode 100644
index 0000000..70a927c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java
@@ -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 com.android.server.locksettings;
+
+import android.provider.Settings;
+
+public class FakeSettings {
+
+ private int mDeviceProvisioned;
+
+ public void setDeviceProvisioned(boolean provisioned) {
+ mDeviceProvisioned = provisioned ? 1 : 0;
+ }
+
+ public int globalGetInt(String keyName) {
+ switch (keyName) {
+ case Settings.Global.DEVICE_PROVISIONED:
+ return mDeviceProvisioned;
+ default:
+ throw new IllegalArgumentException("Unhandled global settings: " + keyName);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index fcd98e0..7e7e170 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -20,9 +20,11 @@
import android.app.IActivityManager;
import android.app.admin.DeviceStateCache;
+import android.content.ContentResolver;
import android.content.Context;
import android.hardware.authsecret.V1_0.IAuthSecret;
import android.os.Handler;
+import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserManagerInternal;
@@ -31,6 +33,7 @@
import android.security.keystore.KeyPermanentlyInvalidatedException;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
import com.android.server.ServiceThread;
import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
@@ -50,12 +53,14 @@
private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
private UserManagerInternal mUserManagerInternal;
private DeviceStateCache mDeviceStateCache;
+ private FakeSettings mSettings;
public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
IActivityManager activityManager, LockPatternUtils lockPatternUtils,
IStorageManager storageManager, SyntheticPasswordManager spManager,
FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
- UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
+ UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache,
+ FakeSettings settings) {
super(context);
mLockSettingsStorage = storage;
mKeyStore = keyStore;
@@ -67,6 +72,7 @@
mRecoverableKeyStoreManager = recoverableKeyStoreManager;
mUserManagerInternal = userManagerInternal;
mDeviceStateCache = deviceStateCache;
+ mSettings = settings;
}
@Override
@@ -119,6 +125,12 @@
}
@Override
+ public int settingsGlobalGetInt(ContentResolver contentResolver, String keyName,
+ int defaultValue) {
+ return mSettings.globalGetInt(keyName);
+ }
+
+ @Override
public UserManagerInternal getUserManagerInternal() {
return mUserManagerInternal;
}
@@ -144,27 +156,33 @@
}
}
+ public MockInjector mInjector;
+
protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils,
LockSettingsStorage storage, FakeGateKeeperService gatekeeper, KeyStore keystore,
IStorageManager storageManager, IActivityManager mActivityManager,
SyntheticPasswordManager spManager, IAuthSecret authSecretService,
FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
- UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
+ UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache,
+ FakeSettings settings) {
super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils,
storageManager, spManager, gsiService,
- recoverableKeyStoreManager, userManagerInternal, deviceStateCache));
+ recoverableKeyStoreManager, userManagerInternal, deviceStateCache, settings));
mGateKeeperService = gatekeeper;
mAuthSecretService = authSecretService;
}
@Override
- protected void tieProfileLockToParent(int userId, byte[] password) {
- mStorage.writeChildProfileLock(userId, password);
+ protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
+ Parcel parcel = Parcel.obtain();
+ parcel.writeParcelable(password, 0);
+ mStorage.writeChildProfileLock(userId, parcel.marshall());
+ parcel.recycle();
}
@Override
- protected byte[] getDecryptedPasswordForTiedProfile(int userId) throws FileNotFoundException,
- KeyPermanentlyInvalidatedException {
+ protected LockscreenCredential getDecryptedPasswordForTiedProfile(int userId)
+ throws FileNotFoundException, KeyPermanentlyInvalidatedException {
byte[] storedData = mStorage.readChildProfileLock(userId);
if (storedData == null) {
throw new FileNotFoundException("Child profile lock file not found");
@@ -176,6 +194,13 @@
} catch (RemoteException e) {
// shouldn't happen.
}
- return storedData;
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.unmarshall(storedData, 0, storedData.length);
+ parcel.setDataPosition(0);
+ return (LockscreenCredential) parcel.readParcelable(null);
+ } finally {
+ parcel.recycle();
+ }
}
}
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 5818133..86ef31a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -16,14 +16,10 @@
package com.android.server.locksettings;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -39,6 +35,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.locksettings.FakeGateKeeperService.VerifyHandle;
import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
@@ -61,60 +58,51 @@
}
public void testCreatePasswordPrimaryUser() throws RemoteException {
- testCreateCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD,
- PASSWORD_QUALITY_ALPHABETIC);
+ testCreateCredential(PRIMARY_USER_ID, newPassword("password"));
}
public void testCreatePasswordFailsWithoutLockScreen() throws RemoteException {
- testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "password",
- CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
+ testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, newPassword("password"));
}
public void testCreatePatternPrimaryUser() throws RemoteException {
- testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN,
- PASSWORD_QUALITY_SOMETHING);
+ testCreateCredential(PRIMARY_USER_ID, newPattern("123456789"));
}
public void testCreatePatternFailsWithoutLockScreen() throws RemoteException {
- testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "123456789",
- CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING);
+ testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, newPattern("123456789"));
}
public void testChangePasswordPrimaryUser() throws RemoteException {
- testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
- "asdfghjk", CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
+ testChangeCredentials(PRIMARY_USER_ID, newPattern("78963214"), newPassword("asdfghjk"));
}
public void testChangePatternPrimaryUser() throws RemoteException {
- testChangeCredentials(PRIMARY_USER_ID, "!£$%^&*(())", CREDENTIAL_TYPE_PASSWORD,
- "1596321", CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING);
+ testChangeCredentials(PRIMARY_USER_ID, newPassword("!£$%^&*(())"), newPattern("1596321"));
}
public void testChangePasswordFailPrimaryUser() throws RemoteException {
final long sid = 1234;
- initializeStorageWithCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
+ initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"), sid);
- assertFalse(mService.setLockCredential("newpwd".getBytes(), CREDENTIAL_TYPE_PASSWORD,
- "badpwd".getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false));
- assertVerifyCredentials(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
+ assertFalse(mService.setLockCredential(newPassword("newpwd"), newPassword("badpwd"),
+ PRIMARY_USER_ID, false));
+ assertVerifyCredentials(PRIMARY_USER_ID, newPassword("password"), sid);
}
public void testClearPasswordPrimaryUser() throws RemoteException {
- final String PASSWORD = "password";
- initializeStorageWithCredential(PRIMARY_USER_ID, PASSWORD, CREDENTIAL_TYPE_PASSWORD, 1234);
- assertTrue(mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD.getBytes(),
- PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false));
- assertFalse(mService.havePassword(PRIMARY_USER_ID));
- assertFalse(mService.havePattern(PRIMARY_USER_ID));
+ initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"), 1234);
+ assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"),
+ PRIMARY_USER_ID, false));
+ assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(PRIMARY_USER_ID));
assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
}
public void testManagedProfileUnifiedChallenge() throws RemoteException {
- final String firstUnifiedPassword = "testManagedProfileUnifiedChallenge-pwd-1";
- final String secondUnifiedPassword = "testManagedProfileUnifiedChallenge-pwd-2";
- assertTrue(mService.setLockCredential(firstUnifiedPassword.getBytes(),
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- null, PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID, false));
+ final LockscreenCredential firstUnifiedPassword = newPassword("pwd-1");
+ final LockscreenCredential secondUnifiedPassword = newPassword("pwd-2");
+ assertTrue(mService.setLockCredential(firstUnifiedPassword,
+ nonePassword(), PRIMARY_USER_ID, false));
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
@@ -132,8 +120,8 @@
mGateKeeperService.clearAuthToken(TURNED_OFF_PROFILE_USER_ID);
// verify credential
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- firstUnifiedPassword.getBytes(), LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
- PRIMARY_USER_ID).getResponseCode());
+ firstUnifiedPassword, 0, PRIMARY_USER_ID)
+ .getResponseCode());
// Verify that we have a new auth token for the profile
assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
@@ -148,16 +136,15 @@
*/
mStorageManager.setIgnoreBadUnlock(true);
// Change primary password and verify that profile SID remains
- assertTrue(mService.setLockCredential(secondUnifiedPassword.getBytes(),
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, firstUnifiedPassword.getBytes(),
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false));
+ assertTrue(mService.setLockCredential(
+ secondUnifiedPassword, firstUnifiedPassword, PRIMARY_USER_ID, false));
mStorageManager.setIgnoreBadUnlock(false);
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
// Clear unified challenge
- assertTrue(mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
- secondUnifiedPassword.getBytes(), PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID,
+ assertTrue(mService.setLockCredential(nonePassword(),
+ secondUnifiedPassword, PRIMARY_USER_ID,
false));
assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
@@ -165,19 +152,19 @@
}
public void testManagedProfileSeparateChallenge() throws RemoteException {
- final String primaryPassword = "testManagedProfileSeparateChallenge-primary";
- final String profilePassword = "testManagedProfileSeparateChallenge-profile";
- assertTrue(mService.setLockCredential(primaryPassword.getBytes(),
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID, false));
+ final LockscreenCredential primaryPassword = newPassword("primary");
+ final LockscreenCredential profilePassword = newPassword("profile");
+ assertTrue(mService.setLockCredential(primaryPassword,
+ nonePassword(),
+ PRIMARY_USER_ID, false));
/* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
* credential as part of verifyCredential() before the new credential is committed in
* StorageManager. So we relax the check in our mock StorageManager to allow that.
*/
mStorageManager.setIgnoreBadUnlock(true);
- assertTrue(mService.setLockCredential(profilePassword.getBytes(),
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_COMPLEX, MANAGED_PROFILE_USER_ID, false));
+ assertTrue(mService.setLockCredential(profilePassword,
+ nonePassword(),
+ MANAGED_PROFILE_USER_ID, false));
mStorageManager.setIgnoreBadUnlock(false);
final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
@@ -190,81 +177,69 @@
mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
// verify primary credential
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- primaryPassword.getBytes(), LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
- PRIMARY_USER_ID).getResponseCode());
+ primaryPassword, 0, PRIMARY_USER_ID)
+ .getResponseCode());
assertNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
// verify profile credential
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- profilePassword.getBytes(), LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
- MANAGED_PROFILE_USER_ID).getResponseCode());
+ profilePassword, 0, MANAGED_PROFILE_USER_ID)
+ .getResponseCode());
assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
// Change primary credential and make sure we don't affect profile
mStorageManager.setIgnoreBadUnlock(true);
- assertTrue(mService.setLockCredential("pwd".getBytes(),
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- primaryPassword.getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false));
+ assertTrue(mService.setLockCredential(
+ newPassword("pwd"), primaryPassword, PRIMARY_USER_ID, false));
mStorageManager.setIgnoreBadUnlock(false);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- profilePassword.getBytes(), LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
- MANAGED_PROFILE_USER_ID).getResponseCode());
+ profilePassword, 0, MANAGED_PROFILE_USER_ID)
+ .getResponseCode());
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
}
public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception {
- final byte[] password = "password".getBytes();
-
assertTrue(mService.setLockCredential(
- password,
- CREDENTIAL_TYPE_PASSWORD,
- null,
- PASSWORD_QUALITY_ALPHABETIC,
+ newPassword("password"),
+ nonePassword(),
PRIMARY_USER_ID,
false));
verify(mRecoverableKeyStoreManager)
- .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, password, PRIMARY_USER_ID);
+ .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, "password".getBytes(),
+ PRIMARY_USER_ID);
}
public void testSetLockCredential_forProfileWithSeparateChallenge_sendsCredentials()
throws Exception {
- final byte[] pattern = "12345".getBytes();
-
assertTrue(mService.setLockCredential(
- pattern,
- CREDENTIAL_TYPE_PATTERN,
- null,
- PASSWORD_QUALITY_SOMETHING,
+ newPattern("12345"),
+ nonePassword(),
MANAGED_PROFILE_USER_ID,
false));
verify(mRecoverableKeyStoreManager)
- .lockScreenSecretChanged(CREDENTIAL_TYPE_PATTERN, pattern, MANAGED_PROFILE_USER_ID);
+ .lockScreenSecretChanged(CREDENTIAL_TYPE_PATTERN, "12345".getBytes(),
+ MANAGED_PROFILE_USER_ID);
}
public void testSetLockCredential_forProfileWithSeparateChallenge_updatesCredentials()
throws Exception {
- final String oldCredential = "12345";
- final byte[] newCredential = "newPassword".getBytes();
initializeStorageWithCredential(
MANAGED_PROFILE_USER_ID,
- oldCredential,
- CREDENTIAL_TYPE_PATTERN,
- PASSWORD_QUALITY_SOMETHING);
+ newPattern("12345"),
+ 1234);
assertTrue(mService.setLockCredential(
- newCredential,
- CREDENTIAL_TYPE_PASSWORD,
- oldCredential.getBytes(),
- PASSWORD_QUALITY_ALPHABETIC,
+ newPassword("newPassword"),
+ newPattern("12345"),
MANAGED_PROFILE_USER_ID,
false));
verify(mRecoverableKeyStoreManager)
- .lockScreenSecretChanged(
- CREDENTIAL_TYPE_PASSWORD, newCredential, MANAGED_PROFILE_USER_ID);
+ .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, "newPassword".getBytes(),
+ MANAGED_PROFILE_USER_ID);
}
public void testSetLockCredential_forProfileWithUnifiedChallenge_doesNotSendRandomCredential()
@@ -272,10 +247,8 @@
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
assertTrue(mService.setLockCredential(
- "12345".getBytes(),
- CREDENTIAL_TYPE_PATTERN,
- null,
- PASSWORD_QUALITY_SOMETHING,
+ newPattern("12345"),
+ nonePassword(),
PRIMARY_USER_ID,
false));
@@ -287,40 +260,35 @@
public void
testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_updatesBothCredentials()
throws Exception {
- final String oldCredential = "oldPassword";
- final byte[] newCredential = "newPassword".getBytes();
+ final LockscreenCredential oldCredential = newPassword("oldPassword");
+ final LockscreenCredential newCredential = newPassword("newPassword");
initializeStorageWithCredential(
- PRIMARY_USER_ID, oldCredential, CREDENTIAL_TYPE_PASSWORD, 1234);
+ PRIMARY_USER_ID, oldCredential, 1234);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
assertTrue(mService.setLockCredential(
newCredential,
- CREDENTIAL_TYPE_PASSWORD,
- oldCredential.getBytes(),
- PASSWORD_QUALITY_ALPHABETIC,
+ oldCredential,
PRIMARY_USER_ID,
false));
verify(mRecoverableKeyStoreManager)
- .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, newCredential, PRIMARY_USER_ID);
+ .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, newCredential.getCredential(),
+ PRIMARY_USER_ID);
verify(mRecoverableKeyStoreManager)
- .lockScreenSecretChanged(
- CREDENTIAL_TYPE_PASSWORD, newCredential, MANAGED_PROFILE_USER_ID);
+ .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, newCredential.getCredential(),
+ MANAGED_PROFILE_USER_ID);
}
public void
testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_removesBothCredentials()
throws Exception {
- final String oldCredential = "oldPassword";
- initializeStorageWithCredential(
- PRIMARY_USER_ID, oldCredential, CREDENTIAL_TYPE_PASSWORD, 1234);
+ initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("oldPassword"), 1234);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
assertTrue(mService.setLockCredential(
- null,
- CREDENTIAL_TYPE_NONE,
- oldCredential.getBytes(),
- PASSWORD_QUALITY_UNSPECIFIED,
+ nonePassword(),
+ newPassword("oldPassword"),
PRIMARY_USER_ID,
false));
@@ -331,17 +299,13 @@
}
public void testSetLockCredential_nullCredential_removeBiometrics() throws RemoteException {
- final String oldCredential = "oldPassword";
-
initializeStorageWithCredential(
PRIMARY_USER_ID,
- oldCredential,
- CREDENTIAL_TYPE_PATTERN,
- PASSWORD_QUALITY_SOMETHING);
+ newPattern("123654"),
+ 1234);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
- mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, oldCredential.getBytes(),
- PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
+ mService.setLockCredential(nonePassword(), newPattern("123654"), PRIMARY_USER_ID, false);
// Verify fingerprint is removed
verify(mFingerprintManager).remove(any(), eq(PRIMARY_USER_ID), any());
@@ -353,41 +317,33 @@
public void testSetLockCredential_forUnifiedToSeparateChallengeProfile_sendsNewCredentials()
throws Exception {
- final String parentPassword = "parentPassword";
- final byte[] profilePassword = "profilePassword".getBytes();
- initializeStorageWithCredential(
- PRIMARY_USER_ID, parentPassword, CREDENTIAL_TYPE_PASSWORD, 1234);
+ final LockscreenCredential parentPassword = newPassword("parentPassword");
+ final LockscreenCredential profilePassword = newPassword("profilePassword");
+ initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword, 1234);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
assertTrue(mService.setLockCredential(
profilePassword,
- CREDENTIAL_TYPE_PASSWORD,
- null,
- PASSWORD_QUALITY_ALPHABETIC,
+ nonePassword(),
MANAGED_PROFILE_USER_ID,
false));
verify(mRecoverableKeyStoreManager)
- .lockScreenSecretChanged(
- CREDENTIAL_TYPE_PASSWORD, profilePassword, MANAGED_PROFILE_USER_ID);
+ .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, profilePassword.getCredential(),
+ MANAGED_PROFILE_USER_ID);
}
public void
testSetLockCredential_forSeparateToUnifiedChallengeProfile_doesNotSendRandomCredential()
throws Exception {
- final String parentPassword = "parentPassword";
- final String profilePassword = "12345";
- initializeStorageWithCredential(
- PRIMARY_USER_ID, parentPassword, CREDENTIAL_TYPE_PASSWORD, 1234);
+ final LockscreenCredential parentPassword = newPassword("parentPassword");
+ final LockscreenCredential profilePassword = newPattern("12345");
+ initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword, 1234);
// Create and verify separate profile credentials.
- testCreateCredential(
- MANAGED_PROFILE_USER_ID,
- profilePassword,
- CREDENTIAL_TYPE_PATTERN,
- PASSWORD_QUALITY_SOMETHING);
+ testCreateCredential(MANAGED_PROFILE_USER_ID, profilePassword);
mService.setSeparateProfileChallengeEnabled(
- MANAGED_PROFILE_USER_ID, false, profilePassword.getBytes());
+ MANAGED_PROFILE_USER_ID, false, profilePassword);
// Called once for setting the initial separate profile credentials and not again during
// unification.
@@ -396,132 +352,121 @@
}
public void testVerifyCredential_forPrimaryUser_sendsCredentials() throws Exception {
- final String password = "password";
- initializeStorageWithCredential(PRIMARY_USER_ID, password, CREDENTIAL_TYPE_PASSWORD, 1234);
+ final LockscreenCredential password = newPassword("password");
+ initializeStorageWithCredential(PRIMARY_USER_ID, password, 1234);
reset(mRecoverableKeyStoreManager);
- mService.verifyCredential(
- password.getBytes(), CREDENTIAL_TYPE_PASSWORD, 1, PRIMARY_USER_ID);
+ mService.verifyCredential(password, 1, PRIMARY_USER_ID);
verify(mRecoverableKeyStoreManager)
.lockScreenSecretAvailable(
- CREDENTIAL_TYPE_PASSWORD, password.getBytes(), PRIMARY_USER_ID);
+ CREDENTIAL_TYPE_PASSWORD, password.getCredential(), PRIMARY_USER_ID);
}
public void testVerifyCredential_forProfileWithSeparateChallenge_sendsCredentials()
throws Exception {
- final byte[] pattern = "12345".getBytes();
+ final LockscreenCredential pattern = newPattern("12345");
assertTrue(mService.setLockCredential(
pattern,
- CREDENTIAL_TYPE_PATTERN,
- null,
- PASSWORD_QUALITY_SOMETHING,
+ nonePassword(),
MANAGED_PROFILE_USER_ID,
false));
reset(mRecoverableKeyStoreManager);
- mService.verifyCredential(pattern, CREDENTIAL_TYPE_PATTERN, 1, MANAGED_PROFILE_USER_ID);
+ mService.verifyCredential(pattern, 1, MANAGED_PROFILE_USER_ID);
verify(mRecoverableKeyStoreManager)
.lockScreenSecretAvailable(
- CREDENTIAL_TYPE_PATTERN, pattern, MANAGED_PROFILE_USER_ID);
+ CREDENTIAL_TYPE_PATTERN, pattern.getCredential(), MANAGED_PROFILE_USER_ID);
}
public void
testVerifyCredential_forPrimaryUserWithUnifiedChallengeProfile_sendsCredentialsForBoth()
throws Exception {
- final String pattern = "12345";
- initializeStorageWithCredential(PRIMARY_USER_ID, pattern, CREDENTIAL_TYPE_PATTERN, 1234);
+ final LockscreenCredential pattern = newPattern("12345");
+ initializeStorageWithCredential(PRIMARY_USER_ID, pattern, 1234);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
reset(mRecoverableKeyStoreManager);
- mService.verifyCredential(pattern.getBytes(), CREDENTIAL_TYPE_PATTERN, 1, PRIMARY_USER_ID);
+ mService.verifyCredential(pattern, 1, PRIMARY_USER_ID);
// Parent sends its credentials for both the parent and profile.
verify(mRecoverableKeyStoreManager)
.lockScreenSecretAvailable(
- CREDENTIAL_TYPE_PATTERN, pattern.getBytes(), PRIMARY_USER_ID);
+ CREDENTIAL_TYPE_PATTERN, pattern.getCredential(), PRIMARY_USER_ID);
verify(mRecoverableKeyStoreManager)
.lockScreenSecretAvailable(
- CREDENTIAL_TYPE_PATTERN, pattern.getBytes(), MANAGED_PROFILE_USER_ID);
+ CREDENTIAL_TYPE_PATTERN, pattern.getCredential(), MANAGED_PROFILE_USER_ID);
// Profile doesn't send its own random credentials.
verify(mRecoverableKeyStoreManager, never())
.lockScreenSecretAvailable(
eq(CREDENTIAL_TYPE_PASSWORD), any(), eq(MANAGED_PROFILE_USER_ID));
}
- private void testCreateCredential(int userId, String credential, int type, int quality)
+ private void testCreateCredential(int userId, LockscreenCredential credential)
throws RemoteException {
- assertTrue(mService.setLockCredential(credential.getBytes(), type, null, quality,
- userId, false));
- assertVerifyCredentials(userId, credential, type, -1);
+ assertTrue(mService.setLockCredential(credential, nonePassword(), userId, false));
+ assertVerifyCredentials(userId, credential, -1);
}
private void testCreateCredentialFailsWithoutLockScreen(
- int userId, String credential, int type, int quality) throws RemoteException {
+ int userId, LockscreenCredential credential) throws RemoteException {
mHasSecureLockScreen = false;
try {
- mService.setLockCredential(credential.getBytes(), type, null, quality,
- userId, false);
+ mService.setLockCredential(credential, null, userId, false);
fail("An exception should have been thrown.");
} catch (UnsupportedOperationException e) {
// Success - the exception was expected.
}
- assertFalse(mService.havePassword(userId));
- assertFalse(mService.havePattern(userId));
+ assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(userId));
}
- private void testChangeCredentials(int userId, String newCredential, int newType,
- String oldCredential, int oldType, int quality) throws RemoteException {
+ private void testChangeCredentials(int userId, LockscreenCredential newCredential,
+ LockscreenCredential oldCredential) throws RemoteException {
final long sid = 1234;
- initializeStorageWithCredential(userId, oldCredential, oldType, sid);
- assertTrue(mService.setLockCredential(newCredential.getBytes(), newType,
- oldCredential.getBytes(), quality, userId, false));
- assertVerifyCredentials(userId, newCredential, newType, sid);
+ initializeStorageWithCredential(userId, oldCredential, sid);
+ assertTrue(mService.setLockCredential(newCredential, oldCredential, userId, false));
+ assertVerifyCredentials(userId, newCredential, sid);
}
- private void assertVerifyCredentials(int userId, String credential, int type, long sid)
+ private void assertVerifyCredentials(int userId, LockscreenCredential credential, long sid)
throws RemoteException{
final long challenge = 54321;
- VerifyCredentialResponse response = mService.verifyCredential(credential.getBytes(),
- type, challenge, userId);
+ VerifyCredentialResponse response = mService.verifyCredential(credential,
+ challenge, userId);
assertEquals(GateKeeperResponse.RESPONSE_OK, response.getResponseCode());
if (sid != -1) assertEquals(sid, mGateKeeperService.getSecureUserId(userId));
- final int incorrectType;
- if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
- assertTrue(mService.havePassword(userId));
- assertFalse(mService.havePattern(userId));
- incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
- } else if (type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN){
- assertFalse(mService.havePassword(userId));
- assertTrue(mService.havePattern(userId));
- incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+ if (credential.isPassword()) {
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(userId));
+ } else if (credential.isPin()) {
+ assertEquals(CREDENTIAL_TYPE_PIN, mService.getCredentialType(userId));
+ } else if (credential.isPattern()) {
+ assertEquals(CREDENTIAL_TYPE_PATTERN, mService.getCredentialType(userId));
} else {
- assertFalse(mService.havePassword(userId));
- assertFalse(mService.havePassword(userId));
- incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+ assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(userId));
}
- // check for bad type
- assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential(
- credential.getBytes(), incorrectType, challenge, userId).getResponseCode());
// check for bad credential
+ final LockscreenCredential badCredential;
+ if (!credential.isNone()) {
+ badCredential = credential.duplicate();
+ badCredential.getCredential()[0] ^= 1;
+ } else {
+ badCredential = LockscreenCredential.createPin("0");
+ }
assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential(
- ("0" + credential).getBytes(), type, challenge, userId).getResponseCode());
+ badCredential, challenge, userId).getResponseCode());
}
- private void initializeStorageWithCredential(int userId, String credential, int type, long sid)
- throws RemoteException {
- byte[] credentialBytes = credential == null ? null : credential.getBytes();
- byte[] oldHash = new VerifyHandle(credential.getBytes(), sid).toBytes();
+ private void initializeStorageWithCredential(int userId, LockscreenCredential credential,
+ long sid) throws RemoteException {
+ byte[] oldHash = new VerifyHandle(credential.getCredential(), sid).toBytes();
if (mService.shouldMigrateToSyntheticPasswordLocked(userId)) {
- mService.initializeSyntheticPasswordLocked(oldHash, credentialBytes, type,
- type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ? PASSWORD_QUALITY_ALPHABETIC
- : PASSWORD_QUALITY_SOMETHING, userId);
+ mService.initializeSyntheticPasswordLocked(oldHash, credential, userId);
} else {
- if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+ if (credential.isPassword() || credential.isPin()) {
mStorage.writeCredentialHash(CredentialHash.create(oldHash,
LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
} else {
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 b60111e..8c2d172 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -17,11 +17,13 @@
package com.android.server.locksettings;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import static junit.framework.Assert.assertEquals;
-import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.never;
@@ -34,6 +36,8 @@
import static java.io.FileDescriptor.out;
import android.app.ActivityManager;
+import android.app.admin.PasswordMetrics;
+import android.app.admin.PasswordPolicy;
import android.content.Context;
import android.os.Binder;
import android.os.Handler;
@@ -88,6 +92,7 @@
@Test
public void testWrongPassword() throws Exception {
+ when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
when(mLockPatternUtils.checkCredential(
@@ -95,18 +100,20 @@
assertEquals(-1, mCommand.exec(mBinder, in, out, err,
new String[] { "set-pin", "--old", "1234" },
mShellCallback, mResultReceiver));
- verify(mLockPatternUtils, never()).setLockCredential(any(), any(),
- anyInt(), anyBoolean());
+ verify(mLockPatternUtils, never()).setLockCredential(any(), any(), anyInt());
}
@Test
public void testChangePin() throws Exception {
+ when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
PASSWORD_QUALITY_NUMERIC);
when(mLockPatternUtils.checkCredential(
LockscreenCredential.createPin("1234"), mUserId, null)).thenReturn(true);
+ when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+ .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_NUMERIC));
assertEquals(0, mCommand.exec(new Binder(), in, out, err,
new String[] { "set-pin", "--old", "1234", "4321" },
mShellCallback, mResultReceiver));
@@ -117,6 +124,23 @@
}
@Test
+ public void testChangePin_nonCompliant() throws Exception {
+ when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
+ PASSWORD_QUALITY_NUMERIC);
+ when(mLockPatternUtils.checkCredential(
+ LockscreenCredential.createPin("1234"), mUserId, null)).thenReturn(true);
+ when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+ .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_ALPHABETIC));
+ assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-pin", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils, never()).setLockCredential(any(), any(), anyInt());
+ }
+
+ @Test
public void testChangePin_noLockScreen() throws Exception {
when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
@@ -128,22 +152,42 @@
@Test
public void testChangePassword() throws Exception {
+ when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
PASSWORD_QUALITY_ALPHABETIC);
when(mLockPatternUtils.checkCredential(
LockscreenCredential.createPassword("1234"), mUserId, null)).thenReturn(true);
+ when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+ .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_ALPHABETIC));
assertEquals(0, mCommand.exec(new Binder(), in, out, err,
- new String[] { "set-password", "--old", "1234", "4321" },
+ new String[] { "set-password", "--old", "1234", "abcd" },
mShellCallback, mResultReceiver));
verify(mLockPatternUtils).setLockCredential(
- LockscreenCredential.createPassword("4321"),
+ LockscreenCredential.createPassword("abcd"),
LockscreenCredential.createPassword("1234"),
mUserId);
}
@Test
+ public void testChangePassword_nonCompliant() throws Exception {
+ when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
+ PASSWORD_QUALITY_ALPHABETIC);
+ when(mLockPatternUtils.checkCredential(
+ LockscreenCredential.createPassword("1234"), mUserId, null)).thenReturn(true);
+ when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+ .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_COMPLEX));
+ assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-password", "--old", "1234", "weakpassword" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils, never()).setLockCredential(any(), any(), anyInt());
+ }
+
+ @Test
public void testChangePassword_noLockScreen() throws Exception {
when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
@@ -155,11 +199,14 @@
@Test
public void testChangePattern() throws Exception {
+ when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
when(mLockPatternUtils.checkCredential(
LockscreenCredential.createPattern(stringToPattern("1234")),
mUserId, null)).thenReturn(true);
+ when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+ .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_SOMETHING));
assertEquals(0, mCommand.exec(new Binder(), in, out, err,
new String[] { "set-pattern", "--old", "1234", "4321" },
mShellCallback, mResultReceiver));
@@ -170,6 +217,22 @@
}
@Test
+ public void testChangePattern_nonCompliant() throws Exception {
+ when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.checkCredential(
+ LockscreenCredential.createPattern(stringToPattern("1234")),
+ mUserId, null)).thenReturn(true);
+ when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+ .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_NUMERIC));
+ assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-pattern", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils, never()).setLockCredential(any(), any(), anyInt());
+ }
+
+ @Test
public void testChangePattern_noLockScreen() throws Exception {
when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
@@ -181,11 +244,14 @@
@Test
public void testClear() throws Exception {
+ when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
when(mLockPatternUtils.checkCredential(
LockscreenCredential.createPattern(stringToPattern("1234")),
mUserId, null)).thenReturn(true);
+ when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+ .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_UNSPECIFIED));
assertEquals(0, mCommand.exec(new Binder(), in, out, err,
new String[] { "clear", "--old", "1234" },
mShellCallback, mResultReceiver));
@@ -195,7 +261,29 @@
mUserId);
}
+ @Test
+ public void testClear_nonCompliant() throws Exception {
+ when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.checkCredential(
+ LockscreenCredential.createPattern(stringToPattern("1234")),
+ mUserId, null)).thenReturn(true);
+ when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+ .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_SOMETHING));
+ assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "clear", "--old", "1234" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils, never()).setLockCredential(any(), any(), anyInt());
+ }
+
private List<LockPatternView.Cell> stringToPattern(String str) {
return LockPatternUtils.byteArrayToPattern(str.getBytes());
}
+
+ private PasswordMetrics metricsForAdminQuality(int quality) {
+ PasswordPolicy policy = new PasswordPolicy();
+ policy.quality = quality;
+ return policy.getMinMetrics();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index bc61c58..1581d9a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -16,20 +16,51 @@
package com.android.server.locksettings;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
import android.content.Context;
import com.android.server.PersistentDataBlockManagerInternal;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
import java.io.File;
+import java.util.Arrays;
public class LockSettingsStorageTestable extends LockSettingsStorage {
public File mStorageDir;
- public PersistentDataBlockManagerInternal mPersistentDataBlock;
+ public PersistentDataBlockManagerInternal mPersistentDataBlockManager;
+ private byte[] mPersistentData;
public LockSettingsStorageTestable(Context context, File storageDir) {
super(context);
mStorageDir = storageDir;
+ mPersistentDataBlockManager = mock(PersistentDataBlockManagerInternal.class);
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ byte[] handle = (byte[]) invocation.getArguments()[0];
+ if (handle != null) {
+ mPersistentData = Arrays.copyOf(handle, handle.length);
+ } else {
+ mPersistentData = null;
+ }
+ return null;
+ }
+ }).when(mPersistentDataBlockManager).setFrpCredentialHandle(any());
+ // For some reasons, simply mocking getFrpCredentialHandle() with
+ // when(mPersistentDataBlockManager.getFrpCredentialHandle()).thenReturn(mPersistentData)
+ // does not work, I had to use the long-winded way below.
+ doAnswer(new Answer<byte[]>() {
+ @Override
+ public byte[] answer(InvocationOnMock invocation) throws Throwable {
+ return mPersistentData;
+ }
+ }).when(mPersistentDataBlockManager).getFrpCredentialHandle();
}
@Override
@@ -57,8 +88,8 @@
}
@Override
- public PersistentDataBlockManagerInternal getPersistentDataBlock() {
- return mPersistentDataBlock;
+ PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
+ return mPersistentDataBlockManager;
}
private File makeDirs(File baseDir, String filePath) {
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 cb51897..7a18431 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -240,12 +240,12 @@
writePasswordBytes(PASSWORD_0, 10);
writePatternBytes(PATTERN_0, 20);
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN,
mStorage.readCredentialHash(10).type);
assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
mStorage.readCredentialHash(20).type);
mStorage.clearCache();
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN,
mStorage.readCredentialHash(10).type);
assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
mStorage.readCredentialHash(20).type);
@@ -352,20 +352,20 @@
}
public void testPersistentDataBlock_unavailable() {
- mStorage.mPersistentDataBlock = null;
+ mStorage.mPersistentDataBlockManager = null;
assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
}
public void testPersistentDataBlock_empty() {
- mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
+ mStorage.mPersistentDataBlockManager = mock(PersistentDataBlockManagerInternal.class);
assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
}
public void testPersistentDataBlock_withData() {
- mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
- when(mStorage.mPersistentDataBlock.getFrpCredentialHandle())
+ mStorage.mPersistentDataBlockManager = mock(PersistentDataBlockManagerInternal.class);
+ when(mStorage.mPersistentDataBlockManager.getFrpCredentialHandle())
.thenReturn(PersistentData.toBytes(PersistentData.TYPE_SP_WEAVER, SOME_USER_ID,
DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, PAYLOAD));
@@ -378,8 +378,8 @@
}
public void testPersistentDataBlock_exception() {
- mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
- when(mStorage.mPersistentDataBlock.getFrpCredentialHandle())
+ mStorage.mPersistentDataBlockManager = mock(PersistentDataBlockManagerInternal.class);
+ when(mStorage.mPersistentDataBlockManager.getFrpCredentialHandle())
.thenThrow(new IllegalStateException("oops"));
assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
}
@@ -453,7 +453,7 @@
private void assertPasswordBytes(byte[] password, int userId) {
CredentialHash cred = mStorage.readCredentialHash(userId);
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, cred.type);
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN, cred.type);
assertArrayEquals(password, cred.hash);
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
new file mode 100644
index 0000000..df719b6d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.locksettings;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
+import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+
+import android.app.admin.DevicePolicyManager;
+
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+
+
+/** Test setting a lockscreen credential and then verify it under USER_FRP */
+public class LockscreenFrpTest extends BaseLockSettingsServiceTests {
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ // FRP credential can only be verified prior to provisioning
+ mSettings.setDeviceProvisioned(false);
+ }
+
+ public void testFrpCredential_setPin() {
+ mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID, false);
+
+ assertEquals(CREDENTIAL_TYPE_PIN, mService.getCredentialType(USER_FRP));
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+ }
+
+ public void testFrpCredential_setPattern() {
+ mService.setLockCredential(newPattern("4321"), nonePassword(), PRIMARY_USER_ID, false);
+
+ assertEquals(CREDENTIAL_TYPE_PATTERN, mService.getCredentialType(USER_FRP));
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(newPattern("4321"), 0, USER_FRP).getResponseCode());
+ }
+
+ public void testFrpCredential_setPassword() {
+ mService.setLockCredential(newPassword("4321"), nonePassword(), PRIMARY_USER_ID, false);
+
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(USER_FRP));
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(newPassword("4321"), 0, USER_FRP).getResponseCode());
+ }
+
+ public void testFrpCredential_changeCredential() {
+ mService.setLockCredential(newPassword("1234"), nonePassword(), PRIMARY_USER_ID, false);
+ mService.setLockCredential(newPattern("5678"), newPassword("1234"), PRIMARY_USER_ID, false);
+
+ assertEquals(CREDENTIAL_TYPE_PATTERN, mService.getCredentialType(USER_FRP));
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(newPattern("5678"), 0, USER_FRP).getResponseCode());
+ }
+
+ public void testFrpCredential_removeCredential() {
+ mService.setLockCredential(newPassword("1234"), nonePassword(), PRIMARY_USER_ID, false);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(USER_FRP));
+
+ mService.setLockCredential(nonePassword(), newPassword("1234"), PRIMARY_USER_ID, false);
+ assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(USER_FRP));
+ }
+
+ public void testFrpCredential_cannotVerifyAfterProvsioning() {
+ mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID, false);
+
+ mSettings.setDeviceProvisioned(true);
+ assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+ mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+ }
+
+ public void testFrpCredential_legacyPinTypePersistentData() {
+ mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID, false);
+ PersistentData data = mStorage.readPersistentDataBlock();
+ // Tweak the existing persistent data to make it look like one with legacy credential type
+ assertEquals(CREDENTIAL_TYPE_PIN, data.payload[3]);
+ data.payload[3] = CREDENTIAL_TYPE_PASSWORD_OR_PIN;
+ mStorage.writePersistentDataBlock(data.type, data.userId,
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, data.payload);
+
+ assertEquals(CREDENTIAL_TYPE_PIN, mService.getCredentialType(USER_FRP));
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+
+ }
+
+ public void testFrpCredential_legacyPasswordTypePersistentData() {
+ mService.setLockCredential(newPassword("1234"), nonePassword(), PRIMARY_USER_ID, false);
+ PersistentData data = mStorage.readPersistentDataBlock();
+ // Tweak the existing persistent data to make it look like one with legacy credential type
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, data.payload[3]);
+ data.payload[3] = CREDENTIAL_TYPE_PASSWORD_OR_PIN;
+ mStorage.writePersistentDataBlock(data.type, data.userId,
+ DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, data.payload);
+
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(USER_FRP));
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+ }
+}
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 42ca42a..89a279c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -16,11 +16,9 @@
package com.android.server.locksettings;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
@@ -38,7 +36,6 @@
import androidx.test.filters.SmallTest;
-import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
@@ -72,15 +69,14 @@
public void testPasswordBasedSyntheticPassword() throws RemoteException {
final int USER_ID = 10;
- final byte[] password = "user-password".getBytes();
- final byte[] badPassword = "bad-password".getBytes();
+ final LockscreenCredential password = newPassword("user-password");
+ final LockscreenCredential badPassword = newPassword("bad-password");
MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage,
mGateKeeperService, mUserManager, mPasswordSlotManager);
AuthenticationToken authToken = manager.newSyntheticPasswordAndSid(mGateKeeperService, null,
null, USER_ID);
long handle = manager.createPasswordBasedSyntheticPassword(mGateKeeperService,
- password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, authToken,
- PASSWORD_QUALITY_ALPHABETIC, USER_ID);
+ password, authToken, USER_ID);
AuthenticationResult result = manager.unwrapPasswordBasedSyntheticPassword(
mGateKeeperService, handle, password, USER_ID, null);
@@ -105,97 +101,90 @@
}
public void testPasswordMigration() throws RemoteException {
- final byte[] password = "testPasswordMigration-password".getBytes();
+ final LockscreenCredential password = newPassword("testPasswordMigration-password");
disableSyntheticPassword();
- mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+ assertTrue(mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID, false));
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
enableSyntheticPassword();
// Performs migration
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ password, 0, PRIMARY_USER_ID)
.getResponseCode());
assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
// SP-based verification
- assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(password,
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ password, 0, PRIMARY_USER_ID)
.getResponseCode());
assertArrayNotEquals(primaryStorageKey,
mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
}
- protected void initializeCredentialUnderSP(byte[] password, int userId) throws RemoteException {
+ protected void initializeCredentialUnderSP(LockscreenCredential password, int userId)
+ throws RemoteException {
enableSyntheticPassword();
- int quality = password != null ? PASSWORD_QUALITY_ALPHABETIC
- : PASSWORD_QUALITY_UNSPECIFIED;
- int type = password != null ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
- : LockPatternUtils.CREDENTIAL_TYPE_NONE;
- mService.setLockCredential(password, type, null, quality, userId, false);
+ mService.setLockCredential(password, nonePassword(), userId, false);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(userId));
+ assertTrue(mService.isSyntheticPasswordBasedCredential(userId));
}
public void testSyntheticPasswordChangeCredential() throws RemoteException {
- final byte[] password = "testSyntheticPasswordChangeCredential-password".getBytes();
- final byte[] newPassword = "testSyntheticPasswordChangeCredential-newpassword".getBytes();
+ final LockscreenCredential password = newPassword("password");
+ final LockscreenCredential newPassword = newPassword("newpassword");
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
- mService.setLockCredential(newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, password,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+ mService.setLockCredential(newPassword, password, PRIMARY_USER_ID, false);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ newPassword, 0, PRIMARY_USER_ID)
.getResponseCode());
assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
}
public void testSyntheticPasswordVerifyCredential() throws RemoteException {
- final byte[] password = "testSyntheticPasswordVerifyCredential-password".getBytes();
- final byte[] badPassword = "testSyntheticPasswordVerifyCredential-badpassword".getBytes();
+ LockscreenCredential password = newPassword("password");
+ LockscreenCredential badPassword = newPassword("badpassword");
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ password, 0, PRIMARY_USER_ID)
.getResponseCode());
assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(
- badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ badPassword, 0, PRIMARY_USER_ID)
.getResponseCode());
}
public void testSyntheticPasswordClearCredential() throws RemoteException {
- final byte[] password = "testSyntheticPasswordClearCredential-password".getBytes();
- final byte[] badPassword = "testSyntheticPasswordClearCredential-newpassword".getBytes();
+ LockscreenCredential password = newPassword("password");
+ LockscreenCredential badPassword = newPassword("newpassword");
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
// clear password
- mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, password,
- PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
+ mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID, false);
assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
// set a new password
- mService.setLockCredential(badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+ mService.setLockCredential(badPassword, nonePassword(),
+ PRIMARY_USER_ID, false);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ badPassword, 0, PRIMARY_USER_ID)
.getResponseCode());
assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
}
public void testSyntheticPasswordChangeCredentialKeepsAuthSecret() throws RemoteException {
- final byte[] password =
- "testSyntheticPasswordChangeCredentialKeepsAuthSecret-password".getBytes();
- final byte[] badPassword =
- "testSyntheticPasswordChangeCredentialKeepsAuthSecret-new".getBytes();
+ LockscreenCredential password = newPassword("password");
+ LockscreenCredential badPassword = newPassword("new");
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
- mService.setLockCredential(badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, password,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+ mService.setLockCredential(badPassword, password, PRIMARY_USER_ID, false);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ badPassword, 0, PRIMARY_USER_ID)
.getResponseCode());
// Check the same secret was passed each time
@@ -205,41 +194,35 @@
}
public void testSyntheticPasswordVerifyPassesPrimaryUserAuthSecret() throws RemoteException {
- final byte[] password =
- "testSyntheticPasswordVerifyPassesPrimaryUserAuthSecret-password".getBytes();
- final byte[] newPassword =
- "testSyntheticPasswordVerifyPassesPrimaryUserAuthSecret-new".getBytes();
+ LockscreenCredential password = newPassword("password");
+ LockscreenCredential newPassword = newPassword("new");
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
reset(mAuthSecretService);
- assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(password,
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ password, 0, PRIMARY_USER_ID)
.getResponseCode());
verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
}
public void testSecondaryUserDoesNotPassAuthSecret() throws RemoteException {
- final byte[] password = "testSecondaryUserDoesNotPassAuthSecret-password".getBytes();
+ LockscreenCredential password = newPassword("password");
initializeCredentialUnderSP(password, SECONDARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, SECONDARY_USER_ID)
+ password, 0, SECONDARY_USER_ID)
.getResponseCode());
verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
}
public void testNoSyntheticPasswordOrCredentialDoesNotPassAuthSecret() throws RemoteException {
- // Setting null doesn't create a synthetic password
- initializeCredentialUnderSP(null, PRIMARY_USER_ID);
-
- reset(mAuthSecretService);
mService.onUnlockUser(PRIMARY_USER_ID);
flushHandlerTasks();
verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
}
public void testSyntheticPasswordAndCredentialDoesNotPassAuthSecret() throws RemoteException {
- final byte[] password = "passwordForASyntheticPassword".getBytes();
+ LockscreenCredential password = newPassword("passwordForASyntheticPassword");
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
reset(mAuthSecretService);
@@ -249,10 +232,9 @@
}
public void testSyntheticPasswordButNoCredentialPassesAuthSecret() throws RemoteException {
- final byte[] password = "getASyntheticPassword".getBytes();
+ LockscreenCredential password = newPassword("getASyntheticPassword");
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
- mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, password,
- PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
+ mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID, false);
reset(mAuthSecretService);
mService.onUnlockUser(PRIMARY_USER_ID);
@@ -261,15 +243,14 @@
}
public void testManagedProfileUnifiedChallengeMigration() throws RemoteException {
- final byte[] UnifiedPassword = "testManagedProfileUnifiedChallengeMigration-pwd".getBytes();
+ LockscreenCredential UnifiedPassword = newPassword("unified-pwd");
disableSyntheticPassword();
- mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+ mService.setLockCredential(UnifiedPassword, nonePassword(), PRIMARY_USER_ID, false);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
- final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
- final byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
+ byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+ byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
assertTrue(primarySid != 0);
assertTrue(profileSid != 0);
assertTrue(profileSid != primarySid);
@@ -277,12 +258,12 @@
// do migration
enableSyntheticPassword();
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ UnifiedPassword, 0, PRIMARY_USER_ID)
.getResponseCode());
// verify
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ UnifiedPassword, 0, PRIMARY_USER_ID)
.getResponseCode());
assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
@@ -295,19 +276,16 @@
}
public void testManagedProfileSeparateChallengeMigration() throws RemoteException {
- final byte[] primaryPassword =
- "testManagedProfileSeparateChallengeMigration-primary".getBytes();
- final byte[] profilePassword =
- "testManagedProfileSeparateChallengeMigration-profile".getBytes();
+ LockscreenCredential primaryPassword = newPassword("primary");
+ LockscreenCredential profilePassword = newPassword("profile");
disableSyntheticPassword();
- mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
- mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_ALPHABETIC, MANAGED_PROFILE_USER_ID, false);
+ mService.setLockCredential(primaryPassword, nonePassword(), PRIMARY_USER_ID, false);
+ mService.setLockCredential(profilePassword, nonePassword(),
+ MANAGED_PROFILE_USER_ID, false);
final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
- final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
- final byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
+ byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+ byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
assertTrue(primarySid != 0);
assertTrue(profileSid != 0);
assertTrue(profileSid != primarySid);
@@ -315,19 +293,19 @@
// do migration
enableSyntheticPassword();
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ primaryPassword, 0, PRIMARY_USER_ID)
.getResponseCode());
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- 0, MANAGED_PROFILE_USER_ID).getResponseCode());
+ profilePassword, 0, MANAGED_PROFILE_USER_ID)
+ .getResponseCode());
// verify
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
- .getResponseCode());
+ primaryPassword, 0, PRIMARY_USER_ID)
+ .getResponseCode());
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- 0, MANAGED_PROFILE_USER_ID).getResponseCode());
+ profilePassword, 0, MANAGED_PROFILE_USER_ID)
+ .getResponseCode());
assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
assertArrayNotEquals(primaryStorageKey,
@@ -339,101 +317,92 @@
}
public void testTokenBasedResetPassword() throws RemoteException {
- final byte[] password = "password".getBytes();
- final byte[] pattern = "123654".getBytes();
- final byte[] token = "some-high-entropy-secure-token".getBytes();
+ LockscreenCredential password = newPassword("password");
+ LockscreenCredential pattern = newPattern("123654");
+ byte[] token = "some-high-entropy-secure-token".getBytes();
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
// Disregard any reportPasswordChanged() invocations as part of credential setup.
flushHandlerTasks();
reset(mDevicePolicyManager);
- final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+ byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
assertFalse(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
assertTrue(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
- mService.verifyCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
- PRIMARY_USER_ID).getResponseCode();
+ mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
assertFalse(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
- mLocalService.setLockCredentialWithToken(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
- handle, token, PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
+ mLocalService.setLockCredentialWithToken(pattern, handle, token, PRIMARY_USER_ID);
// Verify DPM gets notified about new device lock
flushHandlerTasks();
- final PasswordMetrics metric = PasswordMetrics.computeForCredential(
- LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(pattern)));
+ final PasswordMetrics metric = PasswordMetrics.computeForCredential(pattern);
assertEquals(metric, mService.getUserPasswordMetrics(PRIMARY_USER_ID));
verify(mDevicePolicyManager).reportPasswordChanged(PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID)
+ pattern, 0, PRIMARY_USER_ID)
.getResponseCode());
assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
}
public void testTokenBasedClearPassword() throws RemoteException {
- final byte[] password = "password".getBytes();
- final byte[] pattern = "123654".getBytes();
- final byte[] token = "some-high-entropy-secure-token".getBytes();
+ LockscreenCredential password = newPassword("password");
+ LockscreenCredential pattern = newPattern("123654");
+ byte[] token = "some-high-entropy-secure-token".getBytes();
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
- final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+ byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- mService.verifyCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- 0, PRIMARY_USER_ID).getResponseCode();
+ mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- mLocalService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
- handle, token, PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
+ mLocalService.setLockCredentialWithToken(nonePassword(), handle, token, PRIMARY_USER_ID);
flushHandlerTasks(); // flush the unlockUser() call before changing password again
- mLocalService.setLockCredentialWithToken(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
- handle, token, PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
+ mLocalService.setLockCredentialWithToken(pattern, handle, token,
+ PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID)
+ pattern, 0, PRIMARY_USER_ID)
.getResponseCode());
assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
}
public void testTokenBasedResetPasswordAfterCredentialChanges() throws RemoteException {
- final byte[] password = "password".getBytes();
- final byte[] pattern = "123654".getBytes();
- final byte[] newPassword = "password".getBytes();
- final byte[] token = "some-high-entropy-secure-token".getBytes();
+ LockscreenCredential password = newPassword("password");
+ LockscreenCredential pattern = newPattern("123654");
+ LockscreenCredential newPassword = newPassword("password");
+ byte[] token = "some-high-entropy-secure-token".getBytes();
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
- final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+ byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- mService.verifyCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- 0, PRIMARY_USER_ID).getResponseCode();
+ mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- mService.setLockCredential(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, password,
- PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID, false);
+ mService.setLockCredential(pattern, password, PRIMARY_USER_ID, false);
- mLocalService.setLockCredentialWithToken(newPassword,
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, token,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ mLocalService.setLockCredentialWithToken(newPassword, handle, token, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ newPassword, 0, PRIMARY_USER_ID)
.getResponseCode());
assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
}
public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration()
throws RemoteException {
- final String token = "some-high-entropy-secure-token";
+ final byte[] token = "some-high-entropy-secure-token".getBytes();
enableSyntheticPassword();
- long handle = mLocalService.addEscrowToken(token.getBytes(), PRIMARY_USER_ID, null);
+ long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
@@ -441,9 +410,15 @@
public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration()
throws RemoteException {
- final String token = "some-high-entropy-secure-token";
- initializeCredentialUnderSP(null, PRIMARY_USER_ID);
- long handle = mLocalService.addEscrowToken(token.getBytes(), PRIMARY_USER_ID, null);
+ final byte[] token = "some-high-entropy-secure-token".getBytes();
+ // By first setting a password and then clearing it, we enter the state where SP is
+ // initialized but the user currently has no password
+ initializeCredentialUnderSP(newPassword("password"), PRIMARY_USER_ID);
+ assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"),
+ PRIMARY_USER_ID, false));
+ assertTrue(mService.isSyntheticPasswordBasedCredential(PRIMARY_USER_ID));
+
+ long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
@@ -451,12 +426,11 @@
public void testEscrowTokenActivatedLaterWithUserPasswordNeedsMigration()
throws RemoteException {
- final byte[] token = "some-high-entropy-secure-token".getBytes();
- final byte[] password = "password".getBytes();
+ byte[] token = "some-high-entropy-secure-token".getBytes();
+ LockscreenCredential password = newPassword("password");
// Set up pre-SP user password
disableSyntheticPassword();
- mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+ mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID, false);
enableSyntheticPassword();
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
@@ -464,14 +438,14 @@
assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
// Activate token (password gets migrated to SP at the same time)
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ password, 0, PRIMARY_USER_ID)
.getResponseCode());
// Verify token is activated
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
}
public void testEscrowTokenCannotBeActivatedOnUnmanagedUser() {
- final byte[] token = "some-high-entropy-secure-token".getBytes();
+ byte[] token = "some-high-entropy-secure-token".getBytes();
when(mUserManagerInternal.isDeviceManaged()).thenReturn(false);
when(mUserManagerInternal.isUserManaged(PRIMARY_USER_ID)).thenReturn(false);
when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
@@ -483,9 +457,9 @@
}
public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception {
- final byte[] password = "password".getBytes();
- final byte[] pattern = "123654".getBytes();
- final byte[] token = "some-high-entropy-secure-token".getBytes();
+ LockscreenCredential password = newPassword("password");
+ LockscreenCredential pattern = newPattern("123654");
+ byte[] token = "some-high-entropy-secure-token".getBytes();
mHasSecureLockScreen = false;
enableSyntheticPassword();
@@ -493,57 +467,50 @@
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
try {
- mLocalService.setLockCredentialWithToken(password,
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, token,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ mLocalService.setLockCredentialWithToken(password, handle, token, PRIMARY_USER_ID);
fail("An exception should have been thrown.");
} catch (UnsupportedOperationException e) {
// Success - the exception was expected.
}
- assertFalse(mService.havePassword(PRIMARY_USER_ID));
+ assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(PRIMARY_USER_ID));
try {
- mLocalService.setLockCredentialWithToken(pattern,
- LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, token,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ mLocalService.setLockCredentialWithToken(pattern, handle, token, PRIMARY_USER_ID);
fail("An exception should have been thrown.");
} catch (UnsupportedOperationException e) {
// Success - the exception was expected.
}
- assertFalse(mService.havePattern(PRIMARY_USER_ID));
+ assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(PRIMARY_USER_ID));
}
public void testGetHashFactorPrimaryUser() throws RemoteException {
- final byte[] password = "password".getBytes();
- mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
- final byte[] hashFactor = mService.getHashFactor(password, PRIMARY_USER_ID);
+ LockscreenCredential password = newPassword("password");
+ mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID, false);
+ byte[] hashFactor = mService.getHashFactor(password, PRIMARY_USER_ID);
assertNotNull(hashFactor);
- mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
- password, PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
- final byte[] newHashFactor = mService.getHashFactor(null, PRIMARY_USER_ID);
+ mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID, false);
+ byte[] newHashFactor = mService.getHashFactor(nonePassword(), PRIMARY_USER_ID);
assertNotNull(newHashFactor);
// Hash factor should never change after password change/removal
assertArrayEquals(hashFactor, newHashFactor);
}
public void testGetHashFactorManagedProfileUnifiedChallenge() throws RemoteException {
- final byte[] pattern = "1236".getBytes();
- mService.setLockCredential(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
- null, PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID, false);
+ LockscreenCredential pattern = newPattern("1236");
+ mService.setLockCredential(pattern, nonePassword(), PRIMARY_USER_ID, false);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
assertNotNull(mService.getHashFactor(null, MANAGED_PROFILE_USER_ID));
}
public void testGetHashFactorManagedProfileSeparateChallenge() throws RemoteException {
- final byte[] primaryPassword = "primary".getBytes();
- final byte[] profilePassword = "profile".getBytes();
- mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
- mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PASSWORD_QUALITY_ALPHABETIC, MANAGED_PROFILE_USER_ID, false);
- assertNotNull(mService.getHashFactor(profilePassword, MANAGED_PROFILE_USER_ID));
+ LockscreenCredential primaryPassword = newPassword("primary");
+ LockscreenCredential profilePassword = newPassword("profile");
+ mService.setLockCredential(primaryPassword, nonePassword(), PRIMARY_USER_ID, false);
+ mService.setLockCredential(profilePassword, nonePassword(),
+ MANAGED_PROFILE_USER_ID, false);
+ assertNotNull(
+ mService.getHashFactor(profilePassword, MANAGED_PROFILE_USER_ID));
}
public void testPasswordData_serializeDeserialize() {
@@ -551,7 +518,7 @@
data.scryptN = 11;
data.scryptR = 22;
data.scryptP = 33;
- data.passwordType = CREDENTIAL_TYPE_PASSWORD;
+ data.credentialType = CREDENTIAL_TYPE_PASSWORD;
data.salt = PAYLOAD;
data.passwordHandle = PAYLOAD2;
@@ -560,7 +527,7 @@
assertEquals(11, deserialized.scryptN);
assertEquals(22, deserialized.scryptR);
assertEquals(33, deserialized.scryptP);
- assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.passwordType);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.credentialType);
assertArrayEquals(PAYLOAD, deserialized.salt);
assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
}
@@ -569,7 +536,7 @@
// Test that we can deserialize existing PasswordData and don't inadvertently change the
// wire format.
byte[] serialized = new byte[] {
- 0, 0, 0, 2, /* CREDENTIAL_TYPE_PASSWORD */
+ 0, 0, 0, 2, /* CREDENTIAL_TYPE_PASSWORD_OR_PIN */
11, /* scryptN */
22, /* scryptR */
33, /* scryptP */
@@ -583,7 +550,7 @@
assertEquals(11, deserialized.scryptN);
assertEquals(22, deserialized.scryptR);
assertEquals(33, deserialized.scryptP);
- assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.passwordType);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD_OR_PIN, deserialized.credentialType);
assertArrayEquals(PAYLOAD, deserialized.salt);
assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
}
@@ -591,11 +558,11 @@
public void testGsiDisablesAuthSecret() throws RemoteException {
mGsiService.setIsGsiRunning(true);
- final byte[] password = "testGsiDisablesAuthSecret-password".getBytes();
+ LockscreenCredential password = newPassword("testGsiDisablesAuthSecret-password");
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ password, 0, PRIMARY_USER_ID)
.getResponseCode());
verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index a992dd1..7d3ec03 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -23,6 +23,7 @@
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static com.google.common.truth.Truth.assertThat;
@@ -31,7 +32,6 @@
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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -162,21 +162,6 @@
}
@Test
- public void isPin_isTrueForNumericString() {
- assertTrue(KeySyncTask.isPin("3298432574398654376547".getBytes()));
- }
-
- @Test
- public void isPin_isFalseForStringContainingLetters() {
- assertFalse(KeySyncTask.isPin("398i54369548654".getBytes()));
- }
-
- @Test
- public void isPin_isFalseForStringContainingSymbols() {
- assertFalse(KeySyncTask.isPin("-3987543643".getBytes()));
- }
-
- @Test
public void hashCredentialsBySaltedSha256_returnsSameHashForSameCredentialsAndSalt() {
String credentials = "password1234";
byte[] salt = randomBytes(16);
@@ -221,19 +206,19 @@
@Test
public void getUiFormat_returnsPinIfPin() {
assertEquals(UI_FORMAT_PIN,
- KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234".getBytes()));
+ KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PIN));
}
@Test
public void getUiFormat_returnsPasswordIfPassword() {
assertEquals(UI_FORMAT_PASSWORD,
- KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234a".getBytes()));
+ KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD));
}
@Test
public void getUiFormat_returnsPatternIfPattern() {
assertEquals(UI_FORMAT_PATTERN,
- KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PATTERN, "1234".getBytes()));
+ KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PATTERN));
}
@@ -683,7 +668,7 @@
mRecoverySnapshotStorage,
mSnapshotListenersStorage,
TEST_USER_ID,
- CREDENTIAL_TYPE_PASSWORD,
+ CREDENTIAL_TYPE_PIN,
/*credential=*/ pin.getBytes(),
/*credentialUpdated=*/ false,
mPlatformKeyManager,
@@ -799,7 +784,7 @@
mRecoverySnapshotStorage,
mSnapshotListenersStorage,
TEST_USER_ID,
- /*credentialType=*/ 3,
+ /*credentialType=*/ 5, // Some invalid credential type value
"12345".getBytes(),
/*credentialUpdated=*/ false,
mPlatformKeyManager,
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java
index 9e00077..d797955 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java
@@ -23,7 +23,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.app.timedetector.TimeSignal;
+import android.app.timedetector.PhoneTimeSuggestion;
import android.content.Intent;
import android.icu.util.Calendar;
import android.icu.util.GregorianCalendar;
@@ -45,6 +45,8 @@
.setActualTimeUtc(2018, 1, 1, 12, 0, 0)
.build();
+ private static final int ARBITRARY_PHONE_ID = 123456;
+
private Script mScript;
@Before
@@ -53,30 +55,32 @@
}
@Test
- public void testSuggestTime_nitz_timeDetectionEnabled() {
+ public void testSuggestPhoneTime_nitz_timeDetectionEnabled() {
Scenario scenario = SCENARIO_1;
mScript.pokeFakeClocks(scenario)
.pokeTimeDetectionEnabled(true);
- TimeSignal timeSignal = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
+ PhoneTimeSuggestion timeSuggestion =
+ scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
final int clockIncrement = 1000;
long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement;
mScript.simulateTimePassing(clockIncrement)
- .simulateTimeSignalReceived(timeSignal)
+ .simulatePhoneTimeSuggestion(timeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis);
}
@Test
- public void testSuggestTime_systemClockThreshold() {
+ public void testSuggestPhoneTime_systemClockThreshold() {
Scenario scenario = SCENARIO_1;
final int systemClockUpdateThresholdMillis = 1000;
mScript.pokeFakeClocks(scenario)
.pokeThresholds(systemClockUpdateThresholdMillis)
.pokeTimeDetectionEnabled(true);
- TimeSignal timeSignal1 = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
- TimestampedValue<Long> utcTime1 = timeSignal1.getUtcTime();
+ PhoneTimeSuggestion timeSuggestion1 =
+ scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
+ TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
final int clockIncrement = 100;
// Increment the the device clocks to simulate the passage of time.
@@ -86,7 +90,7 @@
TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
// Send the first time signal. It should be used.
- mScript.simulateTimeSignalReceived(timeSignal1)
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
.verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis1);
// Now send another time signal, but one that is too similar to the last one and should be
@@ -95,9 +99,9 @@
TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
mScript.peekElapsedRealtimeMillis(),
mScript.peekSystemClockMillis() + underThresholdMillis);
- TimeSignal timeSignal2 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime2);
+ PhoneTimeSuggestion timeSuggestion2 = new PhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
mScript.simulateTimePassing(clockIncrement)
- .simulateTimeSignalReceived(timeSignal2)
+ .simulatePhoneTimeSuggestion(timeSuggestion2)
.verifySystemClockWasNotSetAndResetCallTracking();
// Now send another time signal, but one that is on the threshold and so should be used.
@@ -105,42 +109,44 @@
mScript.peekElapsedRealtimeMillis(),
mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis);
- TimeSignal timeSignal3 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime3);
+ PhoneTimeSuggestion timeSuggestion3 = new PhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime3);
mScript.simulateTimePassing(clockIncrement);
long expectSystemClockMillis3 =
TimeDetectorStrategy.getTimeAt(utcTime3, mScript.peekElapsedRealtimeMillis());
- mScript.simulateTimeSignalReceived(timeSignal3)
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
.verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis3);
}
@Test
- public void testSuggestTime_nitz_timeDetectionDisabled() {
+ public void testSuggestPhoneTime_nitz_timeDetectionDisabled() {
Scenario scenario = SCENARIO_1;
mScript.pokeFakeClocks(scenario)
.pokeTimeDetectionEnabled(false);
- TimeSignal timeSignal = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
- mScript.simulateTimeSignalReceived(timeSignal)
+ PhoneTimeSuggestion timeSuggestion =
+ scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking();
}
@Test
- public void testSuggestTime_nitz_invalidNitzReferenceTimesIgnored() {
+ public void testSuggestPhoneTime_nitz_invalidNitzReferenceTimesIgnored() {
Scenario scenario = SCENARIO_1;
final int systemClockUpdateThreshold = 2000;
mScript.pokeFakeClocks(scenario)
.pokeThresholds(systemClockUpdateThreshold)
.pokeTimeDetectionEnabled(true);
- TimeSignal timeSignal1 = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
- TimestampedValue<Long> utcTime1 = timeSignal1.getUtcTime();
+ PhoneTimeSuggestion timeSuggestion1 =
+ scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
+ TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
// Initialize the strategy / device with a time set from NITZ.
mScript.simulateTimePassing(100);
long expectedSystemClockMillis1 =
TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
- mScript.simulateTimeSignalReceived(timeSignal1)
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1);
// The UTC time increment should be larger than the system clock update threshold so we
@@ -152,8 +158,8 @@
long referenceTimeBeforeLastSignalMillis = utcTime1.getReferenceTimeMillis() - 1;
TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
referenceTimeBeforeLastSignalMillis, validUtcTimeMillis);
- TimeSignal timeSignal2 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime2);
- mScript.simulateTimeSignalReceived(timeSignal2)
+ PhoneTimeSuggestion timeSuggestion2 = new PhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
.verifySystemClockWasNotSetAndResetCallTracking();
// Now supply a new signal that has an obviously bogus reference time : substantially in the
@@ -162,8 +168,8 @@
utcTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1;
TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
referenceTimeInFutureMillis, validUtcTimeMillis);
- TimeSignal timeSignal3 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime3);
- mScript.simulateTimeSignalReceived(timeSignal3)
+ PhoneTimeSuggestion timeSuggestion3 = new PhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime3);
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
.verifySystemClockWasNotSetAndResetCallTracking();
// Just to prove validUtcTimeMillis is valid.
@@ -172,13 +178,13 @@
validReferenceTimeMillis, validUtcTimeMillis);
long expectedSystemClockMillis4 =
TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis());
- TimeSignal timeSignal4 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime4);
- mScript.simulateTimeSignalReceived(timeSignal4)
+ PhoneTimeSuggestion timeSuggestion4 = new PhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime4);
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4);
}
@Test
- public void testSuggestTime_timeDetectionToggled() {
+ public void testSuggestPhoneTime_timeDetectionToggled() {
Scenario scenario = SCENARIO_1;
final int clockIncrementMillis = 100;
final int systemClockUpdateThreshold = 2000;
@@ -186,15 +192,16 @@
.pokeThresholds(systemClockUpdateThreshold)
.pokeTimeDetectionEnabled(false);
- TimeSignal timeSignal1 = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
- TimestampedValue<Long> utcTime1 = timeSignal1.getUtcTime();
+ PhoneTimeSuggestion timeSuggestion1 =
+ scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
+ TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
// Simulate time passing.
mScript.simulateTimePassing(clockIncrementMillis);
// Simulate the time signal being received. It should not be used because auto time
// detection is off but it should be recorded.
- mScript.simulateTimeSignalReceived(timeSignal1)
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
.verifySystemClockWasNotSetAndResetCallTracking();
// Simulate more time passing.
@@ -216,7 +223,7 @@
TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
mScript.peekElapsedRealtimeMillis(),
mScript.peekSystemClockMillis() + systemClockUpdateThreshold);
- TimeSignal timeSignal2 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime2);
+ PhoneTimeSuggestion timeSuggestion2 = new PhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
// Simulate more time passing.
mScript.simulateTimePassing(clockIncrementMillis);
@@ -226,7 +233,7 @@
// The new time, though valid, should not be set in the system clock because auto time is
// disabled.
- mScript.simulateTimeSignalReceived(timeSignal2)
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
.verifySystemClockWasNotSetAndResetCallTracking();
// Turn on auto time detection.
@@ -234,17 +241,6 @@
.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2);
}
- @Test
- public void testSuggestTime_unknownSource() {
- Scenario scenario = SCENARIO_1;
- mScript.pokeFakeClocks(scenario)
- .pokeTimeDetectionEnabled(true);
-
- TimeSignal timeSignal = scenario.createTimeSignalForActual("unknown");
- mScript.simulateTimeSignalReceived(timeSignal)
- .verifySystemClockWasNotSetAndResetCallTracking();
- }
-
/**
* A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving
* like the real thing should, it also asserts preconditions.
@@ -407,8 +403,8 @@
return mFakeCallback.peekSystemClockMillis();
}
- Script simulateTimeSignalReceived(TimeSignal timeSignal) {
- mSimpleTimeDetectorStrategy.suggestTime(timeSignal);
+ Script simulatePhoneTimeSuggestion(PhoneTimeSuggestion timeSuggestion) {
+ mSimpleTimeDetectorStrategy.suggestPhoneTime(timeSuggestion);
return this;
}
@@ -466,10 +462,10 @@
return mActualTimeMillis;
}
- TimeSignal createTimeSignalForActual(String sourceId) {
+ PhoneTimeSuggestion createPhoneTimeSuggestionForActual(int phoneId) {
TimestampedValue<Long> time = new TimestampedValue<>(
mInitialDeviceRealtimeMillis, mActualTimeMillis);
- return new TimeSignal(sourceId, time);
+ return new PhoneTimeSuggestion(phoneId, time);
}
static class Builder {
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 45fef76..37da018 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -28,7 +28,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.timedetector.TimeSignal;
+import android.app.timedetector.PhoneTimeSuggestion;
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.TimestampedValue;
@@ -67,10 +67,10 @@
public void testStubbedCall_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
- TimeSignal timeSignal = createNitzTimeSignal();
+ PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion();
try {
- mTimeDetectorService.suggestTime(timeSignal);
+ mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion);
} finally {
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME), anyString());
@@ -78,15 +78,15 @@
}
@Test
- public void testSuggestTime() {
+ public void testSuggestPhoneTime() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
- TimeSignal timeSignal = createNitzTimeSignal();
- mTimeDetectorService.suggestTime(timeSignal);
+ PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion();
+ mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion);
verify(mMockContext)
.enforceCallingPermission(eq(android.Manifest.permission.SET_TIME), anyString());
- mStubbedTimeDetectorStrategy.verifySuggestTimeCalled(timeSignal);
+ mStubbedTimeDetectorStrategy.verifySuggestPhoneTimeCalled(phoneTimeSuggestion);
}
@Test
@@ -115,15 +115,16 @@
mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(false);
}
- private static TimeSignal createNitzTimeSignal() {
+ private static PhoneTimeSuggestion createPhoneTimeSuggestion() {
+ int phoneId = 1234;
TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
- return new TimeSignal(TimeSignal.SOURCE_ID_NITZ, timeValue);
+ return new PhoneTimeSuggestion(phoneId, timeValue);
}
private static class StubbedTimeDetectorStrategy implements TimeDetectorStrategy {
// Call tracking.
- private TimeSignal mLastSuggestedTime;
+ private PhoneTimeSuggestion mLastPhoneSuggestion;
private Boolean mLastAutoTimeDetectionToggle;
private boolean mDumpCalled;
@@ -132,9 +133,9 @@
}
@Override
- public void suggestTime(TimeSignal timeSignal) {
+ public void suggestPhoneTime(PhoneTimeSuggestion timeSuggestion) {
resetCallTracking();
- mLastSuggestedTime = timeSignal;
+ mLastPhoneSuggestion = timeSuggestion;
}
@Override
@@ -150,13 +151,13 @@
}
void resetCallTracking() {
- mLastSuggestedTime = null;
+ mLastPhoneSuggestion = null;
mLastAutoTimeDetectionToggle = null;
mDumpCalled = false;
}
- void verifySuggestTimeCalled(TimeSignal expectedSignal) {
- assertEquals(expectedSignal, mLastSuggestedTime);
+ void verifySuggestPhoneTimeCalled(PhoneTimeSuggestion expectedSignal) {
+ assertEquals(expectedSignal, mLastPhoneSuggestion);
}
void verifyHandleAutoTimeDetectionToggleCalled(boolean expectedEnable) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 2836e69..03367db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -18,8 +18,6 @@
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -63,10 +61,8 @@
private ActivityMetricsLaunchObserver mLaunchObserver;
private ActivityMetricsLaunchObserverRegistry mLaunchObserverRegistry;
- private ActivityStack mStack;
- private TaskRecord mTask;
- private ActivityRecord mActivityRecord;
- private ActivityRecord mActivityRecordTrampoline;
+ private ActivityRecord mTrampolineActivity;
+ private ActivityRecord mTopActivity;
@Before
public void setUpAMLO() {
@@ -80,15 +76,10 @@
// Sometimes we need an ActivityRecord for ActivityMetricsLogger to do anything useful.
// This seems to be the easiest way to create an ActivityRecord.
- mStack = new StackBuilder(mRootActivityContainer)
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
- .setOnTop(true)
- .setCreateActivity(true)
+ mTrampolineActivity = new ActivityBuilder(mService).setCreateTask(true).build();
+ mTopActivity = new ActivityBuilder(mService)
+ .setTask(mTrampolineActivity.getTaskRecord())
.build();
- mTask = mStack.topTask();
- mActivityRecord = mTask.getTopActivity();
- mActivityRecordTrampoline = new ActivityBuilder(mService).setTask(mTask).build();
}
@After
@@ -123,8 +114,7 @@
return verify(mock, timeout(TimeUnit.SECONDS.toMillis(5)));
}
- @Test
- public void testOnIntentStarted() throws Exception {
+ private void onIntentStarted() {
Intent intent = new Intent("action 1");
mActivityMetricsLogger.notifyActivityLaunching(intent);
@@ -134,123 +124,120 @@
}
@Test
- public void testOnIntentFailed() throws Exception {
- testOnIntentStarted();
-
- ActivityRecord activityRecord = null;
+ public void testOnIntentFailed() {
+ onIntentStarted();
// Bringing an intent that's already running 'to front' is not considered
// as an ACTIVITY_LAUNCHED state transition.
mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT,
- activityRecord);
+ null /* launchedActivity */);
verifyAsync(mLaunchObserver).onIntentFailed();
verifyNoMoreInteractions(mLaunchObserver);
}
- @Test
- public void testOnActivityLaunched() throws Exception {
- testOnIntentStarted();
+ private void onActivityLaunched() {
+ onIntentStarted();
- mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS,
- mActivityRecord);
+ mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, mTopActivity);
- verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mActivityRecord), anyInt());
+ verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTopActivity), anyInt());
verifyNoMoreInteractions(mLaunchObserver);
}
@Test
- public void testOnActivityLaunchFinished() throws Exception {
- testOnActivityLaunched();
+ public void testOnActivityLaunchFinished() {
+ onActivityLaunched();
- mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
- SystemClock.elapsedRealtimeNanos());
+ mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
+ SystemClock.elapsedRealtimeNanos());
- mActivityMetricsLogger.notifyWindowsDrawn(mActivityRecord.getWindowingMode(),
- SystemClock.elapsedRealtimeNanos());
+ notifyWindowsDrawn(mTopActivity);
- verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecord), anyLong());
- verifyNoMoreInteractions(mLaunchObserver);
- }
-
- @Test
- public void testOnActivityLaunchCancelled() throws Exception {
- testOnActivityLaunched();
-
- mActivityRecord.mDrawn = true;
-
- // Cannot time already-visible activities.
- mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mActivityRecord);
-
- verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mActivityRecord));
- verifyNoMoreInteractions(mLaunchObserver);
- }
-
- @Test
- public void testOnReportFullyDrawn() throws Exception {
- testOnActivityLaunched();
-
- mActivityMetricsLogger.logAppTransitionReportedDrawn(mActivityRecord, false);
-
- verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mActivityRecord), anyLong());
+ verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mTopActivity), anyLong());
verifyNoMoreInteractions(mLaunchObserver);
}
@Test
- public void testOnActivityLaunchedTrampoline() throws Exception {
- testOnIntentStarted();
+ public void testOnActivityLaunchCancelled() {
+ onActivityLaunched();
- mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS,
- mActivityRecord);
+ mTopActivity.mDrawn = true;
- verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mActivityRecord), anyInt());
+ // Cannot time already-visible activities.
+ mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity);
+
+ verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTopActivity));
+ verifyNoMoreInteractions(mLaunchObserver);
+ }
+
+ @Test
+ public void testOnReportFullyDrawn() {
+ onActivityLaunched();
+
+ mActivityMetricsLogger.logAppTransitionReportedDrawn(mTopActivity, false);
+
+ verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mTopActivity), anyLong());
+ verifyNoMoreInteractions(mLaunchObserver);
+ }
+
+ private void onActivityLaunchedTrampoline() {
+ onIntentStarted();
+
+ mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, mTopActivity);
+
+ verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTopActivity), anyInt());
// A second, distinct, activity launch is coalesced into the the current app launch sequence
- mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS,
- mActivityRecordTrampoline);
+ mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, mTrampolineActivity);
verifyNoMoreInteractions(mLaunchObserver);
}
- @Test
- public void testOnActivityLaunchFinishedTrampoline() throws Exception {
- testOnActivityLaunchedTrampoline();
-
- mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
- SystemClock.elapsedRealtimeNanos());
-
- mActivityMetricsLogger.notifyWindowsDrawn(mActivityRecordTrampoline.getWindowingMode(),
- SystemClock.elapsedRealtimeNanos());
-
- verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecordTrampoline),
- anyLong());
- verifyNoMoreInteractions(mLaunchObserver);
+ private void notifyWindowsDrawn(ActivityRecord r) {
+ mActivityMetricsLogger.notifyWindowsDrawn(r.getWindowingMode(),
+ SystemClock.elapsedRealtimeNanos());
}
@Test
- public void testOnActivityLaunchCancelledTrampoline() throws Exception {
- testOnActivityLaunchedTrampoline();
+ public void testOnActivityLaunchFinishedTrampoline() {
+ onActivityLaunchedTrampoline();
- mActivityRecordTrampoline.mDrawn = true;
+ mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
+ SystemClock.elapsedRealtimeNanos());
- // Cannot time already-visible activities.
- mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT,
- mActivityRecordTrampoline);
+ notifyWindowsDrawn(mTrampolineActivity);
- verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mActivityRecordTrampoline));
- verifyNoMoreInteractions(mLaunchObserver);
+ notifyWindowsDrawn(mTopActivity);
+
+ verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mTrampolineActivity),
+ anyLong());
+ verifyNoMoreInteractions(mLaunchObserver);
}
@Test
- public void testActivityRecordProtoIsNotTooBig() throws Exception {
+ public void testOnActivityLaunchCancelledTrampoline() {
+ onActivityLaunchedTrampoline();
+
+ mTrampolineActivity.mDrawn = true;
+
+ // Cannot time already-visible activities.
+ mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mTrampolineActivity);
+
+ verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTrampolineActivity));
+ verifyNoMoreInteractions(mLaunchObserver);
+ }
+
+ @Test
+ public void testActivityRecordProtoIsNotTooBig() {
// The ActivityRecordProto must not be too big, otherwise converting it at runtime
// will become prohibitively expensive.
- assertWithMessage("mActivityRecord: %s", mActivityRecord).
- that(activityRecordToProto(mActivityRecord).length).
- isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE);
+ assertWithMessage("mTopActivity: %s", mTopActivity)
+ .that(activityRecordToProto(mTopActivity).length)
+ .isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE);
- assertWithMessage("mActivityRecordTrampoline: %s", mActivityRecordTrampoline).
- that(activityRecordToProto(mActivityRecordTrampoline).length).
- isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE);
+ assertWithMessage("mTrampolineActivity: %s", mTrampolineActivity)
+ .that(activityRecordToProto(mTrampolineActivity).length)
+ .isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 7449efd..1fb6a56 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -60,11 +60,9 @@
private ActivityRecord mActivity;
public void setUpOnDisplay(DisplayContent dc) {
- mStack = createTaskStackOnDisplay(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, dc);
- mTask = createTaskInStack(mStack, 0 /* userId */);
- mActivity = WindowTestUtils.createTestActivityRecord(dc);
-
- mTask.addChild(mActivity, 0);
+ mActivity = createTestActivityRecord(dc, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
+ mTask = mActivity.getTask();
+ mStack = mTask.mStack;
// Set a remote animator with snapshot disabled. Snapshots don't work in wmtests.
RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
@@ -167,7 +165,7 @@
// setup currently defaults to no snapshot.
setUpOnDisplay(mDisplayContent);
- mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ mTask.mTaskRecord.setWindowingMode(WINDOWING_MODE_FREEFORM);
assertEquals(1, mDisplayContent.mChangingApps.size());
assertTrue(mActivity.isInChangeTransition());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index fff77f5..7026004 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -152,7 +152,7 @@
hiddenActivity.setHidden(true);
mDisplayContent.getConfiguration().windowConfiguration.setRotation(
mDisplayContent.getRotation());
- mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+ mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray(), homeActivity);
// Ensure that we are animating the target activity as well
assertTrue(mController.isAnimatingTask(homeActivity.getTask()));
@@ -181,7 +181,7 @@
mDisplayContent.getConfiguration().windowConfiguration.setRotation(
mDisplayContent.getRotation());
- mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+ mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray(), homeAppWindow);
mController.startAnimation();
// Ensure that we are animating the app and wallpaper target
@@ -210,7 +210,7 @@
mDisplayContent.getConfiguration().windowConfiguration.setRotation(
mDisplayContent.getRotation());
- mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+ mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray(), homeActivity);
mController.startAnimation();
// Cancel the animation and ensure the controller is still running
@@ -242,7 +242,7 @@
doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
// Start and finish the animation
- mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+ mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray(), homeActivity);
mController.startAnimation();
// Reset at this point since we may remove adapters that couldn't be created
reset(mController);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index ebedde7..839ddb2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -78,7 +78,7 @@
mRecentsAnimationController = mock(RecentsAnimationController.class);
mService.mWindowManager.setRecentsAnimationController(mRecentsAnimationController);
doNothing().when(mService.mWindowManager).initializeRecentsAnimation(
- anyInt(), any(), any(), anyInt(), any());
+ anyInt(), any(), any(), anyInt(), any(), any());
doReturn(true).when(mService.mWindowManager).canStartRecentsAnimation();
final RecentTasks recentTasks = mService.getRecentTasks();
@@ -385,7 +385,8 @@
return null;
}).when(mService.mWindowManager).initializeRecentsAnimation(
anyInt() /* targetActivityType */, any() /* recentsAnimationRunner */,
- any() /* callbacks */, anyInt() /* displayId */, any() /* recentTaskIds */);
+ any() /* callbacks */, anyInt() /* displayId */, any() /* recentTaskIds */,
+ any() /* targetActivity */);
}
Intent recentsIntent = new Intent();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
index 5b9a785..2fc03c7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
@@ -21,12 +21,15 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
@@ -119,7 +122,7 @@
@Test
public void testRemoveContainer() {
final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
- final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stack);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
assertNotNull(stack);
assertNotNull(task);
@@ -135,10 +138,10 @@
@Test
public void testRemoveContainer_deferRemoval() {
final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
- final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stack);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
// Stack removal is deferred if one of its child is animating.
- task.setLocalIsAnimating(true);
+ doReturn(true).when(task).isSelfAnimating();
stack.removeIfPossible();
// For the case of deferred removal the task controller will still be connected to the its
@@ -157,8 +160,7 @@
public void testReparent() {
// Create first stack on primary display.
final TaskStack stack1 = createTaskStackOnDisplay(mDisplayContent);
- final WindowTestUtils.TestTask task1 = WindowTestUtils.createTestTask(stack1);
- task1.mOnDisplayChangedCalled = false;
+ final Task task1 = createTaskInStack(stack1, 0 /* userId */);
// Create second display and put second stack on it.
final DisplayContent dc = createNewDisplay();
@@ -170,7 +172,7 @@
final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1);
final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2);
assertEquals(stack1PositionInParent, stack2PositionInParent + 1);
- assertTrue(task1.mOnDisplayChangedCalled);
+ verify(task1, times(1)).onDisplayChanged(any());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 2ba1834..4dfa2664 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -16,11 +16,16 @@
package com.android.server.wm;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import android.graphics.Point;
import android.graphics.Rect;
@@ -45,7 +50,7 @@
@Test
public void testRemoveContainer() {
final TaskStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
- final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1);
+ final Task task = createTaskInStack(stackController1, 0 /* userId */);
final ActivityRecord activity =
WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
@@ -59,11 +64,11 @@
@Test
public void testRemoveContainer_deferRemoval() {
final TaskStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
- final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1);
+ final Task task = createTaskInStack(stackController1, 0 /* userId */);
final ActivityRecord activity =
WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
- task.mShouldDeferRemoval = true;
+ doReturn(true).when(task).shouldDeferRemoval();
task.removeIfPossible();
// For the case of deferred removal the task will still be connected to the its app token
@@ -81,9 +86,9 @@
@Test
public void testReparent() {
final TaskStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
- final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1);
+ final Task task = createTaskInStack(stackController1, 0 /* userId */);
final TaskStack stackController2 = createTaskStackOnDisplay(mDisplayContent);
- final WindowTestUtils.TestTask task2 = WindowTestUtils.createTestTask(stackController2);
+ final Task task2 = createTaskInStack(stackController2, 0 /* userId */);
boolean gotException = false;
try {
@@ -104,34 +109,33 @@
task.reparent(stackController2, 0, false/* moveParents */);
assertEquals(stackController2, task.getParent());
- assertEquals(0, task.positionInParent());
- assertEquals(1, task2.positionInParent());
+ assertEquals(0, task.getParent().mChildren.indexOf(task));
+ assertEquals(1, task2.getParent().mChildren.indexOf(task2));
}
@Test
public void testReparent_BetweenDisplays() {
// Create first stack on primary display.
final TaskStack stack1 = createTaskStackOnDisplay(mDisplayContent);
- final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stack1);
- task.mOnDisplayChangedCalled = false;
+ final Task task = createTaskInStack(stack1, 0 /* userId */);
assertEquals(mDisplayContent, stack1.getDisplayContent());
// Create second display and put second stack on it.
final DisplayContent dc = createNewDisplay();
final TaskStack stack2 = createTaskStackOnDisplay(dc);
- final WindowTestUtils.TestTask task2 = WindowTestUtils.createTestTask(stack2);
+ final Task task2 = createTaskInStack(stack2, 0 /* userId */);
// Reparent and check state
task.reparent(stack2, 0, false /* moveParents */);
assertEquals(stack2, task.getParent());
- assertEquals(0, task.positionInParent());
- assertEquals(1, task2.positionInParent());
- assertTrue(task.mOnDisplayChangedCalled);
+ assertEquals(0, task.getParent().mChildren.indexOf(task));
+ assertEquals(1, task2.getParent().mChildren.indexOf(task2));
+ verify(task, times(1)).onDisplayChanged(any());
}
@Test
public void testBounds() {
final TaskStack stack1 = createTaskStackOnDisplay(mDisplayContent);
- final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stack1);
+ final Task task = createTaskInStack(stack1, 0 /* userId */);
// Check that setting bounds also updates surface position
Rect bounds = new Rect(10, 10, 100, 200);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
index 5c547c2..8cd97cb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
@@ -16,24 +16,24 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.DisplayCutout.fromBoundingRect;
-import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
import android.view.Gravity;
-import android.view.IWindow;
import android.view.WindowManager;
import androidx.test.filters.FlakyTest;
@@ -57,29 +57,11 @@
@RunWith(WindowTestRunner.class)
public class WindowFrameTests extends WindowTestsBase {
- private final IWindow mIWindow = new TestIWindow();
private final Rect mEmptyRect = new Rect();
private DisplayContent mTestDisplayContent;
- static class FrameTestWindowState extends WindowState {
- boolean mDockedResizingForTest = false;
- FrameTestWindowState(WindowManagerService wm, IWindow iWindow, WindowToken windowToken,
- WindowManager.LayoutParams attrs) {
- super(wm, mock(Session.class), iWindow, windowToken, null, 0, 0, attrs, 0, 0,
- false /* ownerCanAddInternalSystemWindow */);
- }
-
- @Override
- boolean isDockedResizing() {
- return mDockedResizingForTest;
- }
- }
-
- TaskStack mStubStack;
-
@Before
public void setUp() throws Exception {
- mStubStack = mock(TaskStack.class);
DisplayInfo testDisplayInfo = new DisplayInfo(mDisplayInfo);
testDisplayInfo.displayCutout = null;
mTestDisplayContent = createNewDisplay(testDisplayInfo);
@@ -129,8 +111,7 @@
expectedRect.bottom);
}
- private void assertPolicyCrop(
- FrameTestWindowState w, int left, int top, int right, int bottom) {
+ private void assertPolicyCrop(WindowState w, int left, int top, int right, int bottom) {
Rect policyCrop = new Rect();
w.calculatePolicyCrop(policyCrop);
assertRect(policyCrop, left, top, right, bottom);
@@ -139,7 +120,7 @@
@Test
public void testLayoutInFullscreenTaskInsets() {
// fullscreen task doesn't use bounds for computeFrame
- WindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+ WindowState w = createWindow();
w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
final int bottomContentInset = 100;
@@ -196,7 +177,7 @@
@Test
public void testLayoutInFullscreenTaskNoInsets() {
// fullscreen task doesn't use bounds for computeFrame
- WindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+ WindowState w = createWindow();
w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
// With no insets or system decor all the frames incoming from PhoneWindowManager
@@ -278,16 +259,16 @@
final int logicalWidth = displayInfo.logicalWidth;
final int logicalHeight = displayInfo.logicalHeight;
- final int taskLeft = logicalWidth / 4;
- final int taskTop = logicalHeight / 4;
- final int taskRight = logicalWidth / 4 * 3;
- final int taskBottom = logicalHeight / 4 * 3;
- final Rect taskBounds = new Rect(taskLeft, taskTop, taskRight, taskBottom);
- WindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+ final Rect taskBounds = new Rect(
+ logicalWidth / 4, logicalHeight / 4, logicalWidth / 4 * 3, logicalHeight / 4 * 3);
+ WindowState w = createWindow();
final Task task = w.getTask();
// Use split-screen because it is non-fullscreen, but also not floating
- task.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- task.setBounds(taskBounds);
+ task.mTaskRecord.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ task.mTaskRecord.setBounds(taskBounds);
+ // The bounds we are requesting might be different from what the system resolved based on
+ // other factors.
+ final Rect resolvedTaskBounds = task.getBounds();
w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
@@ -296,8 +277,8 @@
w.computeFrameLw();
// For non fullscreen tasks the containing frame is based off the
// task bounds not the parent frame.
- assertFrame(w, taskLeft, taskTop, taskRight, taskBottom);
- assertContentFrame(w, taskBounds);
+ assertEquals(resolvedTaskBounds, w.getFrameLw());
+ assertContentFrame(w, resolvedTaskBounds);
assertContentInset(w, 0, 0, 0, 0);
pf.set(0, 0, logicalWidth, logicalHeight);
@@ -307,36 +288,38 @@
final Rect cf = new Rect(0, 0, cfRight, cfBottom);
windowFrames.setFrames(pf, pf, pf, cf, cf, pf, cf, mEmptyRect);
w.computeFrameLw();
- assertFrame(w, taskLeft, taskTop, taskRight, taskBottom);
- int contentInsetRight = taskRight - cfRight;
- int contentInsetBottom = taskBottom - cfBottom;
+ assertEquals(resolvedTaskBounds, w.getFrameLw());
+ int contentInsetRight = resolvedTaskBounds.right - cfRight;
+ int contentInsetBottom = resolvedTaskBounds.bottom - cfBottom;
assertContentInset(w, 0, 0, contentInsetRight, contentInsetBottom);
- assertContentFrame(w, new Rect(taskLeft, taskTop, taskRight - contentInsetRight,
- taskBottom - contentInsetBottom));
+ assertContentFrame(w, new Rect(resolvedTaskBounds.left, resolvedTaskBounds.top,
+ resolvedTaskBounds.right - contentInsetRight,
+ resolvedTaskBounds.bottom - contentInsetBottom));
pf.set(0, 0, logicalWidth, logicalHeight);
// If we set displayed bounds, the insets will be computed with the main task bounds
// but the frame will be positioned according to the displayed bounds.
final int insetLeft = logicalWidth / 5;
final int insetTop = logicalHeight / 5;
- final int insetRight = insetLeft + (taskRight - taskLeft);
- final int insetBottom = insetTop + (taskBottom - taskTop);
- task.setOverrideDisplayedBounds(taskBounds);
- task.setBounds(insetLeft, insetTop, insetRight, insetBottom);
+ final int insetRight = insetLeft + (resolvedTaskBounds.right - resolvedTaskBounds.left);
+ final int insetBottom = insetTop + (resolvedTaskBounds.bottom - resolvedTaskBounds.top);
+ task.mTaskRecord.setDisplayedBounds(resolvedTaskBounds);
+ task.mTaskRecord.setBounds(insetLeft, insetTop, insetRight, insetBottom);
windowFrames.setFrames(pf, pf, pf, cf, cf, pf, cf, mEmptyRect);
w.computeFrameLw();
- assertFrame(w, taskLeft, taskTop, taskRight, taskBottom);
+ assertEquals(resolvedTaskBounds, w.getFrameLw());
contentInsetRight = insetRight - cfRight;
contentInsetBottom = insetBottom - cfBottom;
assertContentInset(w, 0, 0, contentInsetRight, contentInsetBottom);
- assertContentFrame(w, new Rect(taskLeft, taskTop, taskRight - contentInsetRight,
- taskBottom - contentInsetBottom));
+ assertContentFrame(w, new Rect(resolvedTaskBounds.left, resolvedTaskBounds.top,
+ resolvedTaskBounds.right - contentInsetRight,
+ resolvedTaskBounds.bottom - contentInsetBottom));
}
@Test
@FlakyTest(bugId = 130388666)
public void testCalculatePolicyCrop() {
- final FrameTestWindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+ final WindowState w = createWindow();
w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
final DisplayInfo displayInfo = w.getDisplayContent().getDisplayInfo();
@@ -381,7 +364,7 @@
// to the computed window frame.
assertPolicyCrop(w, 0, 0, logicalWidth / 2, logicalHeight / 2);
- w.mDockedResizingForTest = true;
+ doReturn(true).when(w).isDockedResizing();
// But if we are docked resizing it won't be, however we will still be
// shrunk to the decor frame and the display.
assertPolicyCrop(w, 0, 0,
@@ -402,7 +385,7 @@
final int taskRight = logicalWidth / 4 * 3;
final int taskBottom = logicalHeight / 4 * 3;
final Rect taskBounds = new Rect(taskLeft, taskTop, taskRight, taskBottom);
- WindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+ WindowState w = createWindow();
final Task task = w.getTask();
// Use split-screen because it is non-fullscreen, but also not floating
task.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
@@ -440,7 +423,7 @@
@FlakyTest(bugId = 130388666)
public void testDisplayCutout() {
// Regular fullscreen task and window
- WindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+ WindowState w = createWindow();
w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
final Rect pf = new Rect(0, 0, 1000, 2000);
@@ -464,7 +447,7 @@
@FlakyTest(bugId = 130388666)
public void testDisplayCutout_tempDisplayedBounds() {
// Regular fullscreen task and window
- WindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+ WindowState w = createWindow();
final Task task = w.getTask();
task.setBounds(new Rect(0, 0, 1000, 2000));
task.setOverrideDisplayedBounds(new Rect(0, -500, 1000, 1500));
@@ -489,11 +472,12 @@
@Test
public void testFreeformContentInsets() {
+ removeGlobalMinSizeRestriction();
// fullscreen task doesn't use bounds for computeFrame
- WindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+ WindowState w = createWindow();
final Task task = w.getTask();
w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
- task.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ task.mTaskRecord.setWindowingMode(WINDOWING_MODE_FREEFORM);
DisplayContent dc = mTestDisplayContent;
dc.mInputMethodTarget = w;
@@ -515,7 +499,7 @@
// First check that it only gets moved up enough to show window.
final Rect winRect = new Rect(200, 200, 300, 500);
- task.setBounds(winRect);
+ task.mTaskRecord.setBounds(winRect);
w.getWindowFrames().setFrames(pf, df, of, cf, vf, dcf, sf, mEmptyRect);
w.computeFrameLw();
@@ -527,7 +511,7 @@
// Now check that it won't get moved beyond the top and then has appropriate insets
winRect.bottom = 600;
- task.setBounds(winRect);
+ task.mTaskRecord.setBounds(winRect);
w.setBounds(winRect);
w.getWindowFrames().setFrames(pf, df, of, cf, vf, dcf, sf, mEmptyRect);
w.computeFrameLw();
@@ -544,16 +528,9 @@
assertEquals(winRect, w.getFrameLw());
}
- private FrameTestWindowState createWindow(int width, int height) {
- final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
- attrs.width = width;
- attrs.height = height;
-
- ActivityRecord activity = createActivityRecord(mTestDisplayContent,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-
- FrameTestWindowState ws = new FrameTestWindowState(mWm, mIWindow, activity, attrs);
- activity.addWindow(ws);
+ private WindowState createWindow() {
+ final WindowState ws = createWindow(null, TYPE_APPLICATION, mTestDisplayContent, "WindowFrameTests");
+ spyOn(ws);
return ws;
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index 4fbf820..51daf65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -33,16 +33,16 @@
* A collection of static functions that provide access to WindowManager related test functionality.
*/
class WindowTestUtils {
- private static int sNextTaskId = 0;
/** Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
- static Task createTaskInStack(WindowManagerService service, TaskStack stack,
- int userId) {
+ static Task createTaskInStack(WindowManagerService service, TaskStack stack, int userId) {
synchronized (service.mGlobalLock) {
- final Task newTask = new Task(sNextTaskId++, stack, userId, service, 0, false,
- new ActivityManager.TaskDescription(), null);
- stack.addTask(newTask, POSITION_TOP);
- return newTask;
+ final TaskRecord task = new ActivityTestsBase.TaskBuilder(
+ stack.mActivityStack.mStackSupervisor)
+ .setUserId(userId)
+ .setStack(stack.mActivityStack)
+ .build();
+ return task.mTask;
}
}
@@ -53,17 +53,33 @@
return activity;
}
+ static ActivityRecord createTestActivityRecord(ActivityStack stack) {
+ synchronized (stack.mService.mGlobalLock) {
+ final ActivityRecord activity = new ActivityTestsBase.ActivityBuilder(
+ stack.mService)
+ .setStack(stack)
+ .setCreateTask(true)
+ .build();
+ postCreateActivitySetup(activity, stack.mTaskStack.getDisplayContent());
+ return activity;
+ }
+ }
+
static ActivityRecord createTestActivityRecord(DisplayContent dc) {
synchronized (dc.mWmService.mGlobalLock) {
final ActivityRecord activity = new ActivityBuilder(dc.mWmService.mAtmService).build();
- activity.onDisplayChanged(dc);
- activity.setOccludesParent(true);
- activity.setHidden(false);
- activity.hiddenRequested = false;
+ postCreateActivitySetup(activity, dc);
return activity;
}
}
+ private static void postCreateActivitySetup(ActivityRecord activity, DisplayContent dc) {
+ activity.onDisplayChanged(dc);
+ activity.setOccludesParent(true);
+ activity.setHidden(false);
+ activity.hiddenRequested = false;
+ }
+
static TestWindowToken createTestWindowToken(int type, DisplayContent dc) {
return createTestWindowToken(type, dc, false /* persistOnEmpty */);
}
@@ -92,49 +108,6 @@
}
}
- /* Used so we can gain access to some protected members of the {@link Task} class */
- static class TestTask extends Task {
- boolean mShouldDeferRemoval = false;
- boolean mOnDisplayChangedCalled = false;
- private boolean mIsAnimating = false;
-
- TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service,
- int resizeMode, boolean supportsPictureInPicture,
- TaskRecord taskRecord) {
- super(taskId, stack, userId, service, resizeMode, supportsPictureInPicture,
- new ActivityManager.TaskDescription(), taskRecord);
- stack.addTask(this, POSITION_TOP);
- }
-
- boolean shouldDeferRemoval() {
- return mShouldDeferRemoval;
- }
-
- int positionInParent() {
- return getParent().mChildren.indexOf(this);
- }
-
- @Override
- void onDisplayChanged(DisplayContent dc) {
- super.onDisplayChanged(dc);
- mOnDisplayChangedCalled = true;
- }
-
- @Override
- boolean isSelfAnimating() {
- return mIsAnimating;
- }
-
- void setLocalIsAnimating(boolean isAnimating) {
- mIsAnimating = isAnimating;
- }
- }
-
- static TestTask createTestTask(TaskStack stack) {
- return new TestTask(sNextTaskId++, stack, 0, stack.mWmService, RESIZE_MODE_UNRESIZEABLE,
- false, mock(TaskRecord.class));
- }
-
/** Used to track resize reports. */
static class TestWindowState extends WindowState {
boolean mResizeReported;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index af9c510..780fed9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -211,11 +211,7 @@
ActivityRecord createTestActivityRecord(DisplayContent dc, int
windowingMode, int activityType) {
final TaskStack stack = createTaskStackOnDisplay(windowingMode, activityType, dc);
- final Task task = createTaskInStack(stack, 0 /* userId */);
- final ActivityRecord activity =
- WindowTestUtils.createTestActivityRecord(dc);
- task.addChild(activity, 0);
- return activity;
+ return WindowTestUtils.createTestActivityRecord(stack.mActivityStack);
}
WindowState createWindow(WindowState parent, int type, String name) {
@@ -327,14 +323,14 @@
TaskStack createTaskStackOnDisplay(int windowingMode, int activityType, DisplayContent dc) {
synchronized (mWm.mGlobalLock) {
- final Configuration overrideConfig = new Configuration();
- overrideConfig.windowConfiguration.setWindowingMode(windowingMode);
- overrideConfig.windowConfiguration.setActivityType(activityType);
- final int stackId = ++sNextStackId;
- final TaskStack stack = new TaskStack(mWm, stackId, mock(ActivityStack.class));
- dc.setStackOnDisplay(stackId, true, stack);
- stack.onRequestedOverrideConfigurationChanged(overrideConfig);
- return stack;
+ final ActivityStack stack = new ActivityTestsBase.StackBuilder(
+ dc.mWmService.mAtmService.mRootActivityContainer)
+ .setDisplay(dc.mActivityDisplay)
+ .setWindowingMode(windowingMode)
+ .setActivityType(activityType)
+ .setCreateActivity(false)
+ .build();
+ return stack.mTaskStack;
}
}
@@ -386,4 +382,9 @@
displayInfo.ownerUid = SYSTEM_UID;
return createNewDisplay(displayInfo);
}
+
+ /** Sets the default minimum task size to 1 so that tests can use small task sizes */
+ public void removeGlobalMinSizeRestriction() {
+ mWm.mAtmService.mRootActivityContainer.mDefaultMinSizeOfResizeableTaskDp = 1;
+ }
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java
index 5d1f730..87e077e 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProto.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java
@@ -334,8 +334,6 @@
proto.write(IntervalStatsProto.UsageStats.PACKAGE_INDEX, packageIndex + 1);
} else {
// Package not in Stringpool for some reason, write full string instead
- Slog.w(TAG, "UsageStats package name (" + usageStats.mPackageName
- + ") not found in IntervalStats string cache");
proto.write(IntervalStatsProto.UsageStats.PACKAGE, usageStats.mPackageName);
}
// Time attributes stored as an offset of the beginTime.
@@ -430,8 +428,6 @@
proto.write(IntervalStatsProto.Event.PACKAGE_INDEX, packageIndex + 1);
} else {
// Package not in Stringpool for some reason, write full string instead
- Slog.w(TAG, "Usage event package name (" + event.mPackage
- + ") not found in IntervalStats string cache");
proto.write(IntervalStatsProto.Event.PACKAGE, event.mPackage);
}
if (event.mClass != null) {
@@ -440,8 +436,6 @@
proto.write(IntervalStatsProto.Event.CLASS_INDEX, classIndex + 1);
} else {
// Class not in Stringpool for some reason, write full string instead
- Slog.w(TAG, "Usage event class name (" + event.mClass
- + ") not found in IntervalStats string cache");
proto.write(IntervalStatsProto.Event.CLASS, event.mClass);
}
}
@@ -454,10 +448,6 @@
if (taskRootPackageIndex >= 0) {
proto.write(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX,
taskRootPackageIndex + 1);
- } else {
- // Task root package not in Stringpool for some reason.
- Slog.w(TAG, "Usage event task root package name (" + event.mTaskRootPackage
- + ") not found in IntervalStats string cache");
}
}
if (event.mTaskRootClass != null) {
@@ -465,10 +455,6 @@
if (taskRootClassIndex >= 0) {
proto.write(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX,
taskRootClassIndex + 1);
- } else {
- // Task root class not in Stringpool for some reason.
- Slog.w(TAG, "Usage event task root class name (" + event.mTaskRootClass
- + ") not found in IntervalStats string cache");
}
}
switch (event.mEventType) {
@@ -496,9 +482,6 @@
channelIndex + 1);
} else {
// Channel not in Stringpool for some reason, write full string instead
- Slog.w(TAG, "Usage event notification channel name ("
- + event.mNotificationChannelId
- + ") not found in IntervalStats string cache");
proto.write(IntervalStatsProto.Event.NOTIFICATION_CHANNEL,
event.mNotificationChannelId);
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 23df1c5..5783932 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -146,17 +146,16 @@
}
// During system reboot, add a DEVICE_SHUTDOWN event to the end of event list, the timestamp
- // is last time UsageStatsDatabase is persisted to disk.
+ // is last time UsageStatsDatabase is persisted to disk or the last event's time whichever
+ // is higher (because the file system timestamp is round down to integral seconds).
// Also add a DEVICE_STARTUP event with current system timestamp.
final IntervalStats currentDailyStats = mCurrentStats[INTERVAL_DAILY];
if (currentDailyStats != null) {
- // File system timestamp only has precision of 1 second, add 1000ms to make up
- // for the loss of round up.
- final Event shutdownEvent =
- new Event(DEVICE_SHUTDOWN, currentDailyStats.lastTimeSaved + 1000);
+ final Event shutdownEvent = new Event(DEVICE_SHUTDOWN,
+ Math.max(currentDailyStats.lastTimeSaved, currentDailyStats.endTime));
shutdownEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
currentDailyStats.addEvent(shutdownEvent);
- final Event startupEvent = new Event(DEVICE_STARTUP, currentTimeMillis);
+ final Event startupEvent = new Event(DEVICE_STARTUP, System.currentTimeMillis());
startupEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
currentDailyStats.addEvent(startupEvent);
}
diff --git a/startop/apps/test/Android.bp b/startop/apps/test/Android.bp
index 2ff26b8..5de7fd2 100644
--- a/startop/apps/test/Android.bp
+++ b/startop/apps/test/Android.bp
@@ -18,7 +18,8 @@
name: "startop_test_app",
srcs: [
"src/ComplexLayoutInflationActivity.java",
- "src/CPUIntensive.java",
+ "src/CPUIntensiveBenchmarkActivity.java",
+ "src/CPUIntensiveBenchmarks.java",
"src/EmptyActivity.java",
"src/FrameLayoutInflationActivity.java",
"src/LayoutInflationActivity.java",
diff --git a/startop/apps/test/AndroidManifest.xml b/startop/apps/test/AndroidManifest.xml
index ebe2584..b08072e 100644
--- a/startop/apps/test/AndroidManifest.xml
+++ b/startop/apps/test/AndroidManifest.xml
@@ -37,6 +37,18 @@
</activity>
<activity
+ android:label="CPU Intensive Benchmark Test"
+ android:name=".CPUIntensiveBenchmarkActivity"
+ android:exported="true" >
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity
android:label="Empty Activity Layout Test"
android:name=".EmptyActivity"
android:exported="true" >
diff --git a/startop/apps/test/src/CPUIntensive.java b/startop/apps/test/src/CPUIntensive.java
deleted file mode 100644
index a411e8c..0000000
--- a/startop/apps/test/src/CPUIntensive.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * A threaded CPU intensive class for use in benchmarks.
- */
-
-package com.android.startop.test;
-
-final class CPUIntensive {
- public static final int THREAD_COUNT = 8;
- public static final int ARRAY_SIZE = 30000;
- public static int[][] array = new int[THREAD_COUNT][ARRAY_SIZE];
-
- static class WorkerThread extends Thread {
- int mThreadNumber;
- WorkerThread(int number) {
- mThreadNumber = number;
- }
- public void run() {
- final int arrayLength = array[mThreadNumber].length;
- for (int i = 0; i < arrayLength; ++i) {
- array[mThreadNumber][i] = i * i;
- }
- for (int i = 0; i < arrayLength; ++i) {
- for (int j = 0; j < arrayLength; ++j) {
- int swap = array[mThreadNumber][j];
- array[mThreadNumber][j] = array[mThreadNumber][(j + i) % arrayLength];
- array[mThreadNumber][(j + i) % arrayLength] = swap;
- }
- }
- }
- };
-
- public static void doSomeWork(int threadCount) {
- WorkerThread[] threads = new WorkerThread[threadCount];
- // Create the threads.
- for (int i = 0; i < threadCount; ++i) {
- threads[i] = new WorkerThread(i);
- }
- // Start the threads.
- for (int i = 0; i < threadCount; ++i) {
- threads[i].start();
- }
- // Join the threads.
- for (int i = 0; i < threadCount; ++i) {
- try {
- threads[i].join();
- } catch (Exception ex) {
- }
- }
- }
-}
-
diff --git a/startop/apps/test/src/CPUIntensiveBenchmarkActivity.java b/startop/apps/test/src/CPUIntensiveBenchmarkActivity.java
new file mode 100644
index 0000000..2ec5308
--- /dev/null
+++ b/startop/apps/test/src/CPUIntensiveBenchmarkActivity.java
@@ -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.
+ */
+
+package com.android.startop.test;
+
+import android.os.Bundle;
+
+public class CPUIntensiveBenchmarkActivity extends SystemServerBenchmarkActivity {
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.system_server_benchmark_page);
+
+ mBenchmarkList = findViewById(R.id.benchmark_list);
+
+ CPUIntensiveBenchmarks.initializeBenchmarks(this, this);
+ }
+}
diff --git a/startop/apps/test/src/CPUIntensiveBenchmarks.java b/startop/apps/test/src/CPUIntensiveBenchmarks.java
new file mode 100644
index 0000000..19d0b63
--- /dev/null
+++ b/startop/apps/test/src/CPUIntensiveBenchmarks.java
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+/**
+ * A threaded CPU intensive class for use in benchmarks.
+ */
+
+package com.android.startop.test;
+
+import android.app.Activity;
+
+public class CPUIntensiveBenchmarks {
+ public static final int ARRAY_SIZE = 30000;
+ public static int[][] mArray;
+
+ static class WorkerThread extends Thread {
+ int mThreadNumber;
+ WorkerThread(int number) {
+ mThreadNumber = number;
+ }
+ public void run() {
+ final int arrayLength = mArray[mThreadNumber].length;
+ for (int i = 0; i < arrayLength; ++i) {
+ mArray[mThreadNumber][i] = i * i;
+ }
+ for (int i = 0; i < arrayLength; ++i) {
+ for (int j = 0; j < arrayLength; ++j) {
+ int swap = mArray[mThreadNumber][j];
+ mArray[mThreadNumber][j] = mArray[mThreadNumber][(j + i) % arrayLength];
+ mArray[mThreadNumber][(j + i) % arrayLength] = swap;
+ }
+ }
+ }
+ };
+
+ static void doSomeWork(int threadCount) {
+ mArray = new int[threadCount][ARRAY_SIZE];
+ WorkerThread[] threads = new WorkerThread[threadCount];
+ // Create the threads.
+ for (int i = 0; i < threadCount; ++i) {
+ threads[i] = new WorkerThread(i);
+ }
+ // Start the threads.
+ for (int i = 0; i < threadCount; ++i) {
+ threads[i].start();
+ }
+ // Join the threads.
+ for (int i = 0; i < threadCount; ++i) {
+ try {
+ threads[i].join();
+ } catch (Exception ex) {
+ }
+ }
+ }
+
+ // Time limit to run benchmarks in seconds
+ public static final int TIME_LIMIT = 5;
+
+ static void initializeBenchmarks(Activity parent, BenchmarkRunner benchmarks) {
+ benchmarks.addBenchmark("Use 1 thread", () -> {
+ doSomeWork(1);
+ });
+ benchmarks.addBenchmark("Use 2 threads", () -> {
+ doSomeWork(2);
+ });
+ benchmarks.addBenchmark("Use 4 threads", () -> {
+ doSomeWork(4);
+ });
+ benchmarks.addBenchmark("Use 8 threads", () -> {
+ doSomeWork(8);
+ });
+ benchmarks.addBenchmark("Use 16 threads", () -> {
+ doSomeWork(16);
+ });
+ }
+}
diff --git a/startop/apps/test/src/SystemServerBenchmarkActivity.java b/startop/apps/test/src/SystemServerBenchmarkActivity.java
index 75ea69b..6be8df3 100644
--- a/startop/apps/test/src/SystemServerBenchmarkActivity.java
+++ b/startop/apps/test/src/SystemServerBenchmarkActivity.java
@@ -17,28 +17,20 @@
package com.android.startop.test;
import android.app.Activity;
-import android.app.ActivityManager;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.AsyncTask;
import android.os.Bundle;
-import android.view.ViewGroup;
import android.widget.Button;
import android.widget.GridLayout;
import android.widget.TextView;
public class SystemServerBenchmarkActivity extends Activity implements BenchmarkRunner {
- private GridLayout benchmarkList;
+ protected GridLayout mBenchmarkList;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.system_server_benchmark_page);
- benchmarkList = findViewById(R.id.benchmark_list);
+ mBenchmarkList = findViewById(R.id.benchmark_list);
SystemServerBenchmarks.initializeBenchmarks(this, this);
}
@@ -49,7 +41,7 @@
* @param name A short name that shows up in the UI or benchmark results
*/
public void addBenchmark(CharSequence name, Runnable thunk) {
- Context context = benchmarkList.getContext();
+ Context context = mBenchmarkList.getContext();
Button button = new Button(context);
TextView mean = new TextView(context);
TextView stdev = new TextView(context);
@@ -68,8 +60,8 @@
});
});
- benchmarkList.addView(button);
- benchmarkList.addView(mean);
- benchmarkList.addView(stdev);
+ mBenchmarkList.addView(button);
+ mBenchmarkList.addView(mean);
+ mBenchmarkList.addView(stdev);
}
}
diff --git a/startop/apps/test/src/SystemServerBenchmarks.java b/startop/apps/test/src/SystemServerBenchmarks.java
index 5918503..25b43f4 100644
--- a/startop/apps/test/src/SystemServerBenchmarks.java
+++ b/startop/apps/test/src/SystemServerBenchmarks.java
@@ -60,22 +60,6 @@
benchmarks.addBenchmark("Empty", () -> {
});
- benchmarks.addBenchmark("CPU Intensive (1 thread)", () -> {
- CPUIntensive.doSomeWork(1);
- });
-
- benchmarks.addBenchmark("CPU Intensive (2 thread)", () -> {
- CPUIntensive.doSomeWork(2);
- });
-
- benchmarks.addBenchmark("CPU Intensive (4 thread)", () -> {
- CPUIntensive.doSomeWork(4);
- });
-
- benchmarks.addBenchmark("CPU Intensive (8 thread)", () -> {
- CPUIntensive.doSomeWork(8);
- });
-
PackageManager pm = parent.getPackageManager();
benchmarks.addBenchmark("getInstalledApplications", () -> {
pm.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 3f348a4..60290e3 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -2049,6 +2049,10 @@
return "DISCONNECTING";
case STATE_SELECT_PHONE_ACCOUNT:
return "SELECT_PHONE_ACCOUNT";
+ case STATE_SIMULATED_RINGING:
+ return "SIMULATED_RINGING";
+ case STATE_AUDIO_PROCESSING:
+ return "AUDIO_PROCESSING";
default:
Log.w(Call.class, "Unknown state %d", state);
return "UNKNOWN";
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 06a7370..2eb4809 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -424,18 +424,8 @@
* {@link #getActiveModemCount} returns 1 while this API returns 2.
*/
public @ModemCount int getSupportedModemCount() {
- // TODO: b/139642279 when turning on this feature, remove dependency of
- // PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE and always return result based on
- // PROPERTY_MAX_ACTIVE_MODEMS.
- String rebootRequired = SystemProperties.get(
- TelephonyProperties.PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE);
- if (rebootRequired.equals("false")) {
- // If no reboot is required, return max possible active modems.
- return SystemProperties.getInt(
- TelephonyProperties.PROPERTY_MAX_ACTIVE_MODEMS, getPhoneCount());
- } else {
- return getPhoneCount();
- }
+ return SystemProperties.getInt(TelephonyProperties.PROPERTY_MAX_ACTIVE_MODEMS,
+ getActiveModemCount());
}
/** {@hide} */
@@ -10237,11 +10227,13 @@
/**
* Action set from carrier signalling broadcast receivers to enable/disable radio
- * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+ * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
* @param subId the subscription ID that this action applies to.
* @param enabled control enable or disable radio.
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void carrierActionSetRadioEnabled(int subId, boolean enabled) {
try {
ITelephony service = getITelephony();
@@ -10256,11 +10248,13 @@
/**
* Action set from carrier signalling broadcast receivers to start/stop reporting default
* network available events
- * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+ * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
* @param subId the subscription ID that this action applies to.
* @param report control start/stop reporting network status.
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void carrierActionReportDefaultNetworkStatus(int subId, boolean report) {
try {
ITelephony service = getITelephony();
@@ -10274,10 +10268,12 @@
/**
* Action set from carrier signalling broadcast receivers to reset all carrier actions
- * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+ * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
* @param subId the subscription ID that this action applies to.
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void carrierActionResetAll(int subId) {
try {
ITelephony service = getITelephony();
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 39e00cc..f79a5c6 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1778,21 +1778,6 @@
boolean isInEmergencySmsMode();
/**
- * Get a list of SMS apps on a user.
- */
- String[] getSmsApps(int userId);
-
- /**
- * Get the default SMS app on a given user.
- */
- String getDefaultSmsApp(int userId);
-
- /**
- * Set the default SMS app to a given package on a given user.
- */
- void setDefaultSmsApp(int userId, String packageName);
-
- /**
* Return the modem radio power state for slot index.
*
*/
diff --git a/tests/BootImageProfileTest/AndroidTest.xml b/tests/BootImageProfileTest/AndroidTest.xml
index c132007..b4f2663 100644
--- a/tests/BootImageProfileTest/AndroidTest.xml
+++ b/tests/BootImageProfileTest/AndroidTest.xml
@@ -22,8 +22,8 @@
<!-- we need this magic flag, otherwise it always reboots and breaks the selinux -->
<option name="force-skip-system-props" value="true" />
- <option name="run-command" value="setprop dalvik.vm.profilesystemserver true" />
- <option name="run-command" value="setprop dalvik.vm.profilebootclasspath true" />
+ <option name="run-command" value="device_config put runtime_native_boot profilesystemserver true" />
+ <option name="run-command" value="device_config put runtime_native_boot profilebootclasspath true" />
<!-- Profiling does not pick up the above changes we restart the shell -->
<option name="run-command" value="stop" />
diff --git a/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
index fe1d9d2..ccdd452 100644
--- a/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
+++ b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
@@ -46,9 +46,11 @@
*/
@Test
public void testProperties() throws Exception {
- String res = mTestDevice.getProperty("dalvik.vm.profilebootclasspath");
+ String res = mTestDevice.getProperty(
+ "persist.device_config.runtime_native_boot.profilebootclasspath");
assertTrue("profile boot class path not enabled", res != null && res.equals("true"));
- res = mTestDevice.getProperty("dalvik.vm.profilesystemserver");
+ res = mTestDevice.getProperty(
+ "persist.device_config.runtime_native_boot.profilesystemserver");
assertTrue("profile system server not enabled", res != null && res.equals("true"));
}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index db5e7f8..79b8b46 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -32,6 +32,7 @@
import android.content.pm.PackageInstaller;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
+import android.provider.DeviceConfig;
import android.text.TextUtils;
import androidx.test.InstrumentationRegistry;
@@ -51,6 +52,8 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.concurrent.TimeUnit;
+
/**
* Tests for rollback of staged installs.
* <p>
@@ -64,6 +67,8 @@
private static final String NETWORK_STACK_CONNECTOR_CLASS =
"android.net.INetworkStackConnector";
+ private static final String PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT =
+ "watchdog_trigger_failure_count";
private static final String MODULE_META_DATA_PACKAGE = getModuleMetadataPackageName();
@@ -76,7 +81,8 @@
Manifest.permission.INSTALL_PACKAGES,
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS,
- Manifest.permission.FORCE_STOP_PACKAGES);
+ Manifest.permission.FORCE_STOP_PACKAGES,
+ Manifest.permission.WRITE_DEVICE_CONFIG);
}
/**
@@ -119,6 +125,13 @@
assertThat(rollback).packagesContainsExactly(
Rollback.from(TestApp.A2).to(TestApp.A1));
assertThat(rollback.isStaged()).isTrue();
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+ Integer.toString(5), false);
+ RollbackUtils.sendCrashBroadcast(TestApp.A, 4);
+ // Sleep for a while to make sure we don't trigger rollback
+ Thread.sleep(TimeUnit.SECONDS.toMillis(30));
}
/**
@@ -128,8 +141,8 @@
*/
@Test
public void testBadApkOnly_Phase3() throws Exception {
- // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
- RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
+ // One more crash to trigger rollback
+ RollbackUtils.sendCrashBroadcast(TestApp.A, 1);
// We expect the device to be rebooted automatically. Wait for that to happen.
Thread.sleep(30 * 1000);
@@ -158,18 +171,6 @@
assertThat(rollback.getCommittedSessionId()).isNotEqualTo(-1);
}
- @Test
- public void resetNetworkStack() throws Exception {
- RollbackManager rm = RollbackUtils.getRollbackManager();
- String networkStack = getNetworkStackPackageName();
-
- rm.expireRollbackForPackage(networkStack);
- Uninstall.packages(networkStack);
-
- assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
- networkStack)).isNull();
- }
-
/**
* Stage install ModuleMetadata package to simulate a Mainline module update.
*/
@@ -210,45 +211,37 @@
@Test
public void testNetworkFailedRollback_Phase1() throws Exception {
- resetNetworkStack();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ String networkStack = getNetworkStackPackageName();
+
+ rm.expireRollbackForPackage(networkStack);
+ Uninstall.packages(networkStack);
+
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ networkStack)).isNull();
}
@Test
public void testNetworkFailedRollback_Phase2() throws Exception {
- assertNetworkStackRollbackAvailable();
- }
-
- @Test
- public void testNetworkFailedRollback_Phase3() throws Exception {
- assertNoNetworkStackRollbackCommitted();
- }
-
- @Test
- public void testNetworkFailedRollback_Phase4() throws Exception {
- assertNetworkStackRollbackCommitted();
- }
-
- @Test
- public void assertNetworkStackRollbackAvailable() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
getNetworkStackPackageName())).isNotNull();
}
@Test
- public void assertNetworkStackRollbackCommitted() throws Exception {
- RollbackManager rm = RollbackUtils.getRollbackManager();
- assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
- getNetworkStackPackageName())).isNotNull();
- }
-
- @Test
- public void assertNoNetworkStackRollbackCommitted() throws Exception {
+ public void testNetworkFailedRollback_Phase3() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
getNetworkStackPackageName())).isNull();
}
+ @Test
+ public void testNetworkFailedRollback_Phase4() throws Exception {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ getNetworkStackPackageName())).isNotNull();
+ }
+
private String getNetworkStackPackageName() {
Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS);
ComponentName comp = intent.resolveSystemService(
@@ -294,17 +287,28 @@
@Test
public void testNetworkPassedDoesNotRollback_Phase1() throws Exception {
- resetNetworkStack();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ String networkStack = getNetworkStackPackageName();
+
+ rm.expireRollbackForPackage(networkStack);
+ Uninstall.packages(networkStack);
+
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ networkStack)).isNull();
}
@Test
public void testNetworkPassedDoesNotRollback_Phase2() throws Exception {
- assertNetworkStackRollbackAvailable();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ getNetworkStackPackageName())).isNotNull();
}
@Test
public void testNetworkPassedDoesNotRollback_Phase3() throws Exception {
- assertNoNetworkStackRollbackCommitted();
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ getNetworkStackPackageName())).isNull();
}
@Nullable
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 57f211d..f7fe6c7 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -94,7 +94,6 @@
// Reboot device to activate staged package
getDevice().reboot();
- getDevice().waitForDeviceAvailable();
runPhase("testNativeWatchdogTriggersRollback_Phase2");
@@ -134,7 +133,6 @@
Thread.sleep(5000);
// Reboot device to activate staged package
getDevice().reboot();
- getDevice().waitForDeviceAvailable();
// Verify rollback was enabled
runPhase("testNetworkFailedRollback_Phase2");
@@ -180,7 +178,6 @@
Thread.sleep(5000);
// Reboot device to activate staged package
getDevice().reboot();
- getDevice().waitForDeviceAvailable();
// Verify rollback was enabled
runPhase("testNetworkPassedDoesNotRollback_Phase2");
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 2ca0d1a8..1569112 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -271,7 +271,7 @@
.addCapability(NET_CAPABILITY_NOT_METERED);
assertParcelingIsLossless(netCap);
netCap.setSSID(TEST_SSID);
- assertParcelSane(netCap, 11);
+ assertParcelSane(netCap, 12);
}
@Test
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index cf3fba8..61f37fd 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -33,6 +33,7 @@
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
@@ -492,6 +493,8 @@
private INetworkMonitor mNetworkMonitor;
private INetworkMonitorCallbacks mNmCallbacks;
private int mNmValidationResult = VALIDATION_RESULT_BASE;
+ private int mProbesCompleted;
+ private int mProbesSucceeded;
private String mNmValidationRedirectUrl = null;
private boolean mNmProvNotificationRequested = false;
@@ -559,6 +562,7 @@
mNmProvNotificationRequested = false;
}
+ mNmCallbacks.notifyProbeStatusChanged(mProbesCompleted, mProbesSucceeded);
mNmCallbacks.notifyNetworkTested(
mNmValidationResult, mNmValidationRedirectUrl);
@@ -581,7 +585,7 @@
* @param validated Indicate if network should pretend to be validated.
*/
public void connect(boolean validated) {
- connect(validated, true);
+ connect(validated, true, false /* isStrictMode */);
}
/**
@@ -589,13 +593,13 @@
* @param validated Indicate if network should pretend to be validated.
* @param hasInternet Indicate if network should pretend to have NET_CAPABILITY_INTERNET.
*/
- public void connect(boolean validated, boolean hasInternet) {
+ public void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_INTERNET));
ConnectivityManager.NetworkCallback callback = null;
final ConditionVariable validatedCv = new ConditionVariable();
if (validated) {
- setNetworkValid();
+ setNetworkValid(isStrictMode);
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(getNetworkCapabilities().getTransportTypes()[0])
.clearCapabilities()
@@ -620,15 +624,15 @@
if (validated) {
// Wait for network to validate.
waitFor(validatedCv);
- setNetworkInvalid();
+ setNetworkInvalid(isStrictMode);
}
if (callback != null) mCm.unregisterNetworkCallback(callback);
}
- public void connectWithCaptivePortal(String redirectUrl) {
- setNetworkPortal(redirectUrl);
- connect(false);
+ public void connectWithCaptivePortal(String redirectUrl, boolean isStrictMode) {
+ setNetworkPortal(redirectUrl, isStrictMode);
+ connect(false, true /* hasInternet */, isStrictMode);
}
public void connectWithPartialConnectivity() {
@@ -636,34 +640,75 @@
connect(false);
}
- public void connectWithPartialValidConnectivity() {
- setNetworkPartialValid();
- connect(false);
+ public void connectWithPartialValidConnectivity(boolean isStrictMode) {
+ setNetworkPartialValid(isStrictMode);
+ connect(false, true /* hasInternet */, isStrictMode);
}
- void setNetworkValid() {
+ void setNetworkValid(boolean isStrictMode) {
mNmValidationResult = VALIDATION_RESULT_VALID;
mNmValidationRedirectUrl = null;
+ int probesSucceeded = VALIDATION_RESULT_BASE & ~NETWORK_VALIDATION_PROBE_HTTP;
+ if (isStrictMode) {
+ probesSucceeded |= NETWORK_VALIDATION_PROBE_PRIVDNS;
+ }
+ // The probesCompleted equals to probesSucceeded for the case of valid network, so put
+ // the same value into two different parameter of the method.
+ setProbesStatus(probesSucceeded, probesSucceeded);
}
- void setNetworkInvalid() {
+ void setNetworkInvalid(boolean isStrictMode) {
mNmValidationResult = VALIDATION_RESULT_INVALID;
mNmValidationRedirectUrl = null;
+ int probesCompleted = VALIDATION_RESULT_BASE;
+ int probesSucceeded = VALIDATION_RESULT_INVALID;
+ // If the isStrictMode is true, it means the network is invalid when NetworkMonitor
+ // tried to validate the private DNS but failed.
+ if (isStrictMode) {
+ probesCompleted &= ~NETWORK_VALIDATION_PROBE_HTTP;
+ probesSucceeded = probesCompleted;
+ probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
+ }
+ setProbesStatus(probesCompleted, probesSucceeded);
}
- void setNetworkPortal(String redirectUrl) {
- setNetworkInvalid();
+ void setNetworkPortal(String redirectUrl, boolean isStrictMode) {
+ setNetworkInvalid(isStrictMode);
mNmValidationRedirectUrl = redirectUrl;
+ // Suppose the portal is found when NetworkMonitor probes NETWORK_VALIDATION_PROBE_HTTP
+ // in the beginning, so the NETWORK_VALIDATION_PROBE_HTTPS hasn't probed yet.
+ int probesCompleted = VALIDATION_RESULT_BASE & ~NETWORK_VALIDATION_PROBE_HTTPS;
+ int probesSucceeded = VALIDATION_RESULT_INVALID;
+ if (isStrictMode) {
+ probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
+ }
+ setProbesStatus(probesCompleted, probesSucceeded);
}
void setNetworkPartial() {
mNmValidationResult = VALIDATION_RESULT_PARTIAL;
mNmValidationRedirectUrl = null;
+ int probesCompleted = VALIDATION_RESULT_BASE;
+ int probesSucceeded = VALIDATION_RESULT_BASE & ~NETWORK_VALIDATION_PROBE_HTTPS;
+ setProbesStatus(probesCompleted, probesSucceeded);
}
- void setNetworkPartialValid() {
- mNmValidationResult = VALIDATION_RESULT_PARTIAL | VALIDATION_RESULT_VALID;
- mNmValidationRedirectUrl = null;
+ void setNetworkPartialValid(boolean isStrictMode) {
+ setNetworkPartial();
+ mNmValidationResult |= VALIDATION_RESULT_VALID;
+ int probesCompleted = VALIDATION_RESULT_BASE;
+ int probesSucceeded = VALIDATION_RESULT_BASE & ~NETWORK_VALIDATION_PROBE_HTTPS;
+ // Suppose the partial network cannot pass the private DNS validation as well, so only
+ // add NETWORK_VALIDATION_PROBE_DNS in probesCompleted but not probesSucceeded.
+ if (isStrictMode) {
+ probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
+ }
+ setProbesStatus(probesCompleted, probesSucceeded);
+ }
+
+ void setProbesStatus(int probesCompleted, int probesSucceeded) {
+ mProbesCompleted = probesCompleted;
+ mProbesSucceeded = probesSucceeded;
}
public String waitForRedirectUrl() {
@@ -2226,7 +2271,7 @@
// With HTTPS probe disabled, NetworkMonitor should pass the network validation with http
// probe.
- mWiFiNetworkAgent.setNetworkPartialValid();
+ mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */);
// If the user chooses yes to use this partial connectivity wifi, switch the default
// network to wifi and check if wifi becomes valid or not.
mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
@@ -2299,7 +2344,7 @@
callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
- mWiFiNetworkAgent.setNetworkValid();
+ mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
// Need a trigger point to let NetworkMonitor tell ConnectivityService that network is
// validated.
@@ -2317,7 +2362,7 @@
// NetworkMonitor will immediately (once the HTTPS probe fails...) report the network as
// valid, because ConnectivityService calls setAcceptPartialConnectivity before it calls
// notifyNetworkConnected.
- mWiFiNetworkAgent.connectWithPartialValidConnectivity();
+ mWiFiNetworkAgent.connectWithPartialValidConnectivity(false /* isStrictMode */);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
@@ -2343,7 +2388,7 @@
// Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
String redirectUrl = "http://android.com/path";
- mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl);
+ mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), redirectUrl);
@@ -2361,7 +2406,7 @@
mWiFiNetworkAgent);
// Report partial connectivity is accepted.
- mWiFiNetworkAgent.setNetworkPartialValid();
+ mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */);
mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
false /* always */);
waitForIdle();
@@ -2392,7 +2437,7 @@
// Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
String firstRedirectUrl = "http://example.com/firstPath";
- mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
+ mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl);
@@ -2405,13 +2450,13 @@
// Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
String secondRedirectUrl = "http://example.com/secondPath";
- mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
+ mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl, false /* isStrictMode */);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl);
// Make captive portal disappear then revalidate.
// Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
- mWiFiNetworkAgent.setNetworkValid();
+ mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -2423,7 +2468,7 @@
// Break network connectivity.
// Expect NET_CAPABILITY_VALIDATED onLost callback.
- mWiFiNetworkAgent.setNetworkInvalid();
+ mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
validatedCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
}
@@ -2454,7 +2499,7 @@
mServiceContext.expectNoStartActivityIntent(fastTimeoutMs);
// Turn into a captive portal.
- mWiFiNetworkAgent.setNetworkPortal("http://example.com");
+ mWiFiNetworkAgent.setNetworkPortal("http://example.com", false /* isStrictMode */);
mCm.reportNetworkConnectivity(wifiNetwork, false);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
validatedCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -2475,7 +2520,7 @@
assertEquals(testValue, signInIntent.getStringExtra(testKey));
// Report that the captive portal is dismissed, and check that callbacks are fired
- mWiFiNetworkAgent.setNetworkValid();
+ mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -2504,7 +2549,7 @@
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
String firstRedirectUrl = "http://example.com/firstPath";
- mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
+ mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */);
mWiFiNetworkAgent.expectDisconnected();
mWiFiNetworkAgent.expectPreventReconnectReceived();
@@ -3219,7 +3264,7 @@
Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi.
- mWiFiNetworkAgent.setNetworkInvalid();
+ mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
mCm.reportNetworkConnectivity(wifiNetwork, false);
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -3263,7 +3308,7 @@
wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi and expect the dialog to appear.
- mWiFiNetworkAgent.setNetworkInvalid();
+ mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
mCm.reportNetworkConnectivity(wifiNetwork, false);
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -4299,7 +4344,7 @@
mCm.registerNetworkCallback(request, callback);
// Bring up wifi aware network.
- wifiAware.connect(false, false);
+ wifiAware.connect(false, false, false /* isStrictMode */);
callback.expectAvailableCallbacksUnvalidated(wifiAware);
assertNull(mCm.getActiveNetworkInfo());
@@ -4537,6 +4582,44 @@
}
@Test
+ public void testPrivateDnsNotification() throws Exception {
+ NetworkRequest request = new NetworkRequest.Builder()
+ .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
+ .build();
+ TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(request, callback);
+ // Bring up wifi.
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(false);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ // Private DNS resolution failed, checking if the notification will be shown or not.
+ mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
+ mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+ waitForIdle();
+ // If network validation failed, NetworkMonitor will re-evaluate the network.
+ // ConnectivityService should filter the redundant notification. This part is trying to
+ // simulate that situation and check if ConnectivityService could filter that case.
+ mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+ waitForIdle();
+ verify(mNotificationManager, timeout(TIMEOUT_MS).times(1)).notifyAsUser(anyString(),
+ eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any(), eq(UserHandle.ALL));
+ // If private DNS resolution successful, the PRIVATE_DNS_BROKEN notification shouldn't be
+ // shown.
+ mWiFiNetworkAgent.setNetworkValid(true /* isStrictMode */);
+ mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+ waitForIdle();
+ verify(mNotificationManager, timeout(TIMEOUT_MS).times(1)).cancelAsUser(anyString(),
+ eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), eq(UserHandle.ALL));
+ // If private DNS resolution failed again, the PRIVATE_DNS_BROKEN notification should be
+ // shown again.
+ mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
+ mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+ waitForIdle();
+ verify(mNotificationManager, timeout(TIMEOUT_MS).times(2)).notifyAsUser(anyString(),
+ eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any(), eq(UserHandle.ALL));
+ }
+
+ @Test
public void testPrivateDnsSettingsChange() throws Exception {
// Clear any interactions that occur as a result of CS starting up.
reset(mMockDnsResolver);
@@ -4793,7 +4876,7 @@
// by NetworkMonitor
assertFalse(NetworkMonitorUtils.isValidationRequired(
vpnNetworkAgent.getNetworkCapabilities()));
- vpnNetworkAgent.setNetworkValid();
+ vpnNetworkAgent.setNetworkValid(false /* isStrictMode */);
vpnNetworkAgent.connect(false);
mMockVpn.connect();
@@ -4882,7 +4965,8 @@
ranges.add(new UidRange(uid, uid));
mMockVpn.setNetworkAgent(vpnNetworkAgent);
mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */);
+ vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+ false /* isStrictMode */);
mMockVpn.connect();
defaultCallback.assertNoCallback();
@@ -4913,7 +4997,8 @@
ranges.add(new UidRange(uid, uid));
mMockVpn.setNetworkAgent(vpnNetworkAgent);
mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true /* validated */, true /* hasInternet */);
+ vpnNetworkAgent.connect(true /* validated */, true /* hasInternet */,
+ false /* isStrictMode */);
mMockVpn.connect();
defaultCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
@@ -4945,7 +5030,8 @@
ranges.add(new UidRange(uid, uid));
mMockVpn.setNetworkAgent(vpnNetworkAgent);
mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */);
+ vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */,
+ false /* isStrictMode */);
mMockVpn.connect();
// Even though the VPN is unvalidated, it becomes the default network for our app.
@@ -4968,7 +5054,7 @@
vpnNetworkAgent.getNetworkCapabilities()));
// Pretend that the VPN network validates.
- vpnNetworkAgent.setNetworkValid();
+ vpnNetworkAgent.setNetworkValid(false /* isStrictMode */);
vpnNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
// Expect to see the validated capability, but no other changes, because the VPN is already
// the default network for the app.
@@ -5000,7 +5086,8 @@
mMockVpn.setNetworkAgent(vpnNetworkAgent);
mMockVpn.connect();
mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */);
+ vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+ false /* isStrictMode */);
vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
@@ -5099,7 +5186,8 @@
mMockVpn.setNetworkAgent(vpnNetworkAgent);
mMockVpn.connect();
mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */);
+ vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+ false /* isStrictMode */);
vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
diff --git a/tools/aapt2/format/Container.cpp b/tools/aapt2/format/Container.cpp
index f189048..9cef7b3 100644
--- a/tools/aapt2/format/Container.cpp
+++ b/tools/aapt2/format/Container.cpp
@@ -30,6 +30,7 @@
constexpr const static uint32_t kContainerFormatMagic = 0x54504141u;
constexpr const static uint32_t kContainerFormatVersion = 1u;
+constexpr const static size_t kPaddingAlignment = 4u;
ContainerWriter::ContainerWriter(ZeroCopyOutputStream* out, size_t entry_count)
: out_(out), total_entry_count_(entry_count), current_entry_count_(0u) {
@@ -49,11 +50,17 @@
}
}
-inline static void WritePadding(int padding, CodedOutputStream* out) {
- if (padding < 4) {
- const uint32_t zero = 0u;
- out->WriteRaw(&zero, padding);
- }
+inline static size_t CalculatePaddingForAlignment(size_t size) {
+ size_t overage = size % kPaddingAlignment;
+ return overage == 0 ? 0 : kPaddingAlignment - overage;
+}
+
+inline static void WritePadding(size_t padding, CodedOutputStream* out) {
+ CHECK(padding < kPaddingAlignment);
+ const uint32_t zero = 0u;
+ static_assert(sizeof(zero) >= kPaddingAlignment, "Not enough source bytes for padding");
+
+ out->WriteRaw(&zero, padding);
}
bool ContainerWriter::AddResTableEntry(const pb::ResourceTable& table) {
@@ -70,7 +77,7 @@
// Write the aligned size.
const ::google::protobuf::uint64 size = table.ByteSize();
- const int padding = 4 - (size % 4);
+ const int padding = CalculatePaddingForAlignment(size);
coded_out.WriteLittleEndian64(size);
// Write the table.
@@ -103,9 +110,9 @@
// Write the aligned size.
const ::google::protobuf::uint32 header_size = file.ByteSize();
- const int header_padding = 4 - (header_size % 4);
+ const int header_padding = CalculatePaddingForAlignment(header_size);
const ::google::protobuf::uint64 data_size = in->TotalSize();
- const int data_padding = 4 - (data_size % 4);
+ const int data_padding = CalculatePaddingForAlignment(data_size);
coded_out.WriteLittleEndian64(kResFileEntryHeaderSize + header_size + header_padding + data_size +
data_padding);
diff --git a/tools/aapt2/formats.md b/tools/aapt2/formats.md
index bb31a00..25a0e79 100644
--- a/tools/aapt2/formats.md
+++ b/tools/aapt2/formats.md
@@ -23,7 +23,7 @@
| Size (in bytes) | Field | Description |
|:----------------|:---------------|:----------------------------------------------------------------------------------------------------------|
| `4` | `entry_type` | The type of the entry. This can be one of two types: `RES_TABLE (0x00000000)` or `RES_FILE (0x00000001)`. |
-| `8` | `entry_length` | The length of the data that follows. |
+| `8` | `entry_length` | The length of the data that follows. Do not use if `entry_type` is `RES_FILE`; this value may be wrong. |
| `entry_length` | `data` | The payload. The contents of this varies based on the `entry_type`. |
If the `entry_type` is equal to `RES_TABLE (0x00000000)`, the `data` field contains a serialized
@@ -32,13 +32,14 @@
If the `entry_type` is equal to `RES_FILE (0x00000001)`, the `data` field contains the following:
-| Size (in bytes) | Field | Description |
-|:----------------|:---------------|:----------------------------------------------------------------------------------------------------------|
-| `4` | `header_size` | The size of the `header` field. |
-| `8` | `data_size` | The size of the `data` field. |
-| `header_size` | `header` | The serialized Protobuf message [aapt.pb.internal.CompiledFile](ResourcesInternal.proto). |
-| `x` | `padding` | Up to 4 bytes of zeros, if padding is necessary to align the `data` field on a 32-bit boundary. |
-| `data_size` | `data` | The payload, which is determined by the `type` field in the `aapt.pb.internal.CompiledFile`. This can be a PNG file, binary XML, or [aapt.pb.XmlNode](Resources.proto). |
+| Size (in bytes) | Field | Description |
+|:----------------|:-----------------|:----------------------------------------------------------------------------------------------------------|
+| `4` | `header_size` | The size of the `header` field. |
+| `8` | `data_size` | The size of the `data` field. |
+| `header_size` | `header` | The serialized Protobuf message [aapt.pb.internal.CompiledFile](ResourcesInternal.proto). |
+| `x` | `header_padding` | Up to 3 bytes of zeros, if padding is necessary to align the `data` field on a 32-bit boundary. |
+| `data_size` | `data` | The payload, which is determined by the `type` field in the `aapt.pb.internal.CompiledFile`. This can be a PNG file, binary XML, or [aapt.pb.XmlNode](Resources.proto). |
+| `y` | `data_padding` | Up to 3 bytes of zeros, if `data_size` is not a multiple of 4. |
## AAPT2 Static Library Format (extension `.sapk`)
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 0b55794..62ba95d 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -16,6 +16,7 @@
package android.net.wifi;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -27,6 +28,8 @@
import android.os.Parcelable;
import android.text.TextUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -34,7 +37,7 @@
import java.util.Locale;
/**
- * Describes the state of any Wifi connection that is active or
+ * Describes the state of any Wi-Fi connection that is active or
* is in the process of being set up.
*/
public class WifiInfo implements Parcelable {
@@ -96,6 +99,47 @@
private int mRssi;
/**
+ * Wi-Fi unknown technology
+ */
+ public static final int WIFI_TECHNOLOGY_UNKNOWN = 0;
+
+ /**
+ * Wi-Fi 802.11a/b/g
+ */
+ public static final int WIFI_TECHNOLOGY_LEGACY = 1;
+
+ /**
+ * Wi-Fi 802.11n
+ */
+ public static final int WIFI_TECHNOLOGY_11N = 4;
+
+ /**
+ * Wi-Fi 802.11ac
+ */
+ public static final int WIFI_TECHNOLOGY_11AC = 5;
+
+ /**
+ * Wi-Fi 802.11ax
+ */
+ public static final int WIFI_TECHNOLOGY_11AX = 6;
+
+ /** @hide */
+ @IntDef(prefix = { "WIFI_TECHNOLOGY_" }, value = {
+ WIFI_TECHNOLOGY_UNKNOWN,
+ WIFI_TECHNOLOGY_LEGACY,
+ WIFI_TECHNOLOGY_11N,
+ WIFI_TECHNOLOGY_11AC,
+ WIFI_TECHNOLOGY_11AX
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WifiTechnology{}
+
+ /**
+ * Wi-Fi technology for the connection
+ */
+ private @WifiTechnology int mWifiTechnology;
+
+ /**
* The unit in which links speeds are expressed.
*/
public static final String LINK_SPEED_UNITS = "Mbps";
@@ -286,6 +330,7 @@
txSuccessRate = source.txSuccessRate;
rxSuccessRate = source.rxSuccessRate;
score = source.score;
+ mWifiTechnology = source.mWifiTechnology;
}
}
@@ -374,6 +419,22 @@
}
/**
+ * Sets the Wi-Fi technology
+ * @hide
+ */
+ public void setWifiTechnology(@WifiTechnology int wifiTechnology) {
+ mWifiTechnology = wifiTechnology;
+ }
+
+ /**
+ * Get connection Wi-Fi technology
+ * @return the connection Wi-Fi technology
+ */
+ public @WifiTechnology int getWifiTechnology() {
+ return mWifiTechnology;
+ }
+
+ /**
* Returns the current link speed in {@link #LINK_SPEED_UNITS}.
* @return the link speed or {@link #LINK_SPEED_UNKNOWN} if link speed is unknown.
* @see #LINK_SPEED_UNITS
@@ -679,6 +740,7 @@
.append(", MAC: ").append(mMacAddress == null ? none : mMacAddress)
.append(", Supplicant state: ")
.append(mSupplicantState == null ? none : mSupplicantState)
+ .append(", Wi-Fi technology: ").append(mWifiTechnology)
.append(", RSSI: ").append(mRssi)
.append(", Link speed: ").append(mLinkSpeed).append(LINK_SPEED_UNITS)
.append(", Tx Link speed: ").append(mTxLinkSpeed).append(LINK_SPEED_UNITS)
@@ -734,6 +796,7 @@
dest.writeString(mNetworkSuggestionOrSpecifierPackageName);
dest.writeString(mFqdn);
dest.writeString(mProviderFriendlyName);
+ dest.writeInt(mWifiTechnology);
}
/** Implement the Parcelable interface {@hide} */
@@ -775,6 +838,7 @@
info.mNetworkSuggestionOrSpecifierPackageName = in.readString();
info.mFqdn = in.readString();
info.mProviderFriendlyName = in.readString();
+ info.mWifiTechnology = in.readInt();
return info;
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index dd9ee3b..04b073b 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -38,6 +38,7 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.net.NetworkStack;
import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
@@ -533,7 +534,9 @@
*
* @hide
*/
- public static final String EXTRA_WIFI_AP_INTERFACE_NAME = "wifi_ap_interface_name";
+ @SystemApi
+ public static final String EXTRA_WIFI_AP_INTERFACE_NAME =
+ "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME";
/**
* The intended ip mode for this softap.
* @see #IFACE_IP_MODE_TETHERED
@@ -541,7 +544,8 @@
*
* @hide
*/
- public static final String EXTRA_WIFI_AP_MODE = "wifi_ap_mode";
+ @SystemApi
+ public static final String EXTRA_WIFI_AP_MODE = "android.net.wifi.extra.WIFI_AP_MODE";
/** @hide */
@IntDef(flag = false, prefix = { "WIFI_AP_STATE_" }, value = {
@@ -647,6 +651,7 @@
*
* @hide
*/
+ @SystemApi
public static final int IFACE_IP_MODE_UNSPECIFIED = -1;
/**
@@ -656,6 +661,7 @@
*
* @hide
*/
+ @SystemApi
public static final int IFACE_IP_MODE_CONFIGURATION_ERROR = 0;
/**
@@ -665,6 +671,7 @@
*
* @hide
*/
+ @SystemApi
public static final int IFACE_IP_MODE_TETHERED = 1;
/**
@@ -674,6 +681,7 @@
*
* @hide
*/
+ @SystemApi
public static final int IFACE_IP_MODE_LOCAL_ONLY = 2;
/**
@@ -2661,16 +2669,21 @@
/**
* Call allowing ConnectivityService to update WifiService with interface mode changes.
*
- * The possible modes include: {@link #IFACE_IP_MODE_TETHERED},
- * {@link #IFACE_IP_MODE_LOCAL_ONLY},
- * {@link #IFACE_IP_MODE_CONFIGURATION_ERROR}
- *
- * @param ifaceName String name of the updated interface
- * @param mode int representing the new mode
+ * @param ifaceName String name of the updated interface, or null to represent all interfaces
+ * @param mode int representing the new mode, one of:
+ * {@link #IFACE_IP_MODE_TETHERED},
+ * {@link #IFACE_IP_MODE_LOCAL_ONLY},
+ * {@link #IFACE_IP_MODE_CONFIGURATION_ERROR},
+ * {@link #IFACE_IP_MODE_UNSPECIFIED}
*
* @hide
*/
- public void updateInterfaceIpState(String ifaceName, int mode) {
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+ })
+ public void updateInterfaceIpState(@Nullable String ifaceName, @IfaceIpMode int mode) {
try {
IWifiManager iWifiManager = getIWifiManager();
if (iWifiManager == null) {
@@ -2693,6 +2706,11 @@
*
* @hide
*/
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+ })
public boolean startSoftAp(@Nullable WifiConfiguration wifiConfig) {
try {
IWifiManager iWifiManager = getIWifiManager();
@@ -2710,6 +2728,11 @@
*
* @hide
*/
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+ })
public boolean stopSoftAp() {
try {
IWifiManager iWifiManager = getIWifiManager();
diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
index 6c2d7ff..ba9dd37 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
@@ -157,7 +157,8 @@
*/
public @NonNull Builder setBssidPattern(
@NonNull MacAddress baseAddress, @NonNull MacAddress mask) {
- checkNotNull(baseAddress, mask);
+ checkNotNull(baseAddress);
+ checkNotNull(mask);
mBssidPatternMatcher = Pair.create(baseAddress, mask);
return this;
}
diff --git a/wifi/java/android/net/wifi/hotspot2/ConfigParser.java b/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
index e8e8731..bb01365 100644
--- a/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
@@ -182,6 +182,12 @@
throw new IOException("Passpoint profile missing credential");
}
+ // Don't allow the installer to make changes to the update identifier. This is an
+ // indicator of an R2 (or newer) network.
+ if (config.getUpdateIdentifier() != Integer.MIN_VALUE) {
+ config.setUpdateIdentifier(Integer.MIN_VALUE);
+ }
+
// Parse CA (Certificate Authority) certificate.
byte[] caCertData = mimeParts.get(TYPE_CA_CERT);
if (caCertData != null) {
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 557b7c9..e9aa076 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -65,6 +65,7 @@
* Configurations under HomeSp subtree.
*/
private HomeSp mHomeSp = null;
+
/**
* Set the Home SP (Service Provider) information.
*
@@ -248,7 +249,10 @@
mSubscriptionExpirationTimeInMillis = subscriptionExpirationTimeInMillis;
}
/**
- * @hide
+ * Utility method to get the time this subscription will expire. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * @return The time this subscription will expire, or Long.MIN_VALUE to indicate unset value
*/
public long getSubscriptionExpirationTimeInMillis() {
return mSubscriptionExpirationTimeInMillis;
@@ -521,6 +525,8 @@
.append("\n");
builder.append("UsageLimitDataLimit: ").append(mUsageLimitDataLimit).append("\n");
builder.append("UsageLimitTimeLimit: ").append(mUsageLimitTimeLimitInMinutes).append("\n");
+ builder.append("Provisioned by a subscription server: ")
+ .append(isOsuProvisioned() ? "Yes" : "No").append("\n");
if (mHomeSp != null) {
builder.append("HomeSP Begin ---\n");
builder.append(mHomeSp);
@@ -728,4 +734,14 @@
}
return true;
}
+
+ /**
+ * Indicates if the Passpoint Configuration was provisioned by a subscription (OSU) server,
+ * which means that it's an R2 (or R3) profile.
+ *
+ * @return true if the Passpoint Configuration was provisioned by a subscription server.
+ */
+ public boolean isOsuProvisioned() {
+ return getUpdateIdentifier() != Integer.MIN_VALUE;
+ }
}
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64
new file mode 100644
index 0000000..f4d2abb
--- /dev/null
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64
@@ -0,0 +1,88 @@
+TUlNRS1WZXJzaW9uOiAxLjAKQ29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7IGJvdW5kYXJ5
+PXtib3VuZGFyeX07IGNoYXJzZXQ9VVRGLTgKQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmFz
+ZTY0CgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXBhc3Nwb2ludC1w
+cm9maWxlOyBjaGFyc2V0PVVURi04CkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IGJhc2U2NAoK
+UEUxbmJYUlVjbVZsSUhodGJHNXpQU0p6ZVc1amJXdzZaRzFrWkdZeExqSWlQZ29nSUR4V1pYSkVW
+RVErTVM0eVBDOVdaWEpFVkVRKwpDaUFnUEU1dlpHVStDaUFnSUNBOFRtOWtaVTVoYldVK1VHVnlV
+SEp2ZG1sa1pYSlRkV0p6WTNKcGNIUnBiMjQ4TDA1dlpHVk9ZVzFsClBnb2dJQ0FnUEZKVVVISnZj
+R1Z5ZEdsbGN6NEtJQ0FnSUNBZ1BGUjVjR1UrQ2lBZ0lDQWdJQ0FnUEVSRVJrNWhiV1UrZFhKdU9u
+ZG0KWVRwdGJ6cG9iM1J6Y0c5ME1tUnZkREF0Y0dWeWNISnZkbWxrWlhKemRXSnpZM0pwY0hScGIy
+NDZNUzR3UEM5RVJFWk9ZVzFsUGdvZwpJQ0FnSUNBOEwxUjVjR1UrQ2lBZ0lDQThMMUpVVUhKdmNH
+VnlkR2xsY3o0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoCmJXVStWWEJrWVhSbFNX
+UmxiblJwWm1sbGNqd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lEeFdZV3gxWlQ0eE1qTTBQQzlXWVd4
+MVpUNEsKSUNBZ0lEd3ZUbTlrWlQ0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoYldV
+K2FUQXdNVHd2VG05a1pVNWhiV1UrQ2lBZwpJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJS
+bFRtRnRaVDVJYjIxbFUxQThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJRHhPCmIyUmxQZ29nSUNB
+Z0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGtaeWFXVnVaR3g1VG1GdFpUd3ZUbTlrWlU1aGJXVStDaUFn
+SUNBZ0lDQWcKSUNBOFZtRnNkV1UrUTJWdWRIVnllU0JJYjNWelpUd3ZWbUZzZFdVK0NpQWdJQ0Fn
+SUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnSUR4TwpiMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZP
+WVcxbFBrWlJSRTQ4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ1BGWmhiSFZsClBtMXBOaTVq
+Ynk1MWF6d3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnSUR4T2IyUmxQ
+Z29nSUNBZ0lDQWcKSUNBZ1BFNXZaR1ZPWVcxbFBsSnZZVzFwYm1kRGIyNXpiM0owYVhWdFQwazhM
+MDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnUEZaaApiSFZsUGpFeE1qSXpNeXcwTkRVMU5qWThM
+MVpoYkhWbFBnb2dJQ0FnSUNBZ0lEd3ZUbTlrWlQ0S0lDQWdJQ0FnUEM5T2IyUmxQZ29nCklDQWdJ
+Q0E4VG05a1pUNEtJQ0FnSUNBZ0lDQThUbTlrWlU1aGJXVStRM0psWkdWdWRHbGhiRHd2VG05a1pV
+NWhiV1UrQ2lBZ0lDQWcKSUNBZ1BFNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVTVoYldVK1Vt
+VmhiRzA4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZwpQRlpoYkhWbFBuTm9ZV3RsYmk1emRH
+bHljbVZrTG1OdmJUd3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnCklE
+eE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsVnpaWEp1WVcxbFVHRnpjM2R2Y21R
+OEwwNXZaR1ZPWVcxbFBnb2cKSUNBZ0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4
+T2IyUmxUbUZ0WlQ1VmMyVnlibUZ0WlR3dlRtOWtaVTVoYldVKwpDaUFnSUNBZ0lDQWdJQ0FnSUR4
+V1lXeDFaVDVxWVcxbGN6d3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0Fn
+CklDQWdJQ0E4VG05a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxCaGMzTjNiM0pr
+UEM5T2IyUmxUbUZ0WlQ0S0lDQWcKSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQbGx0T1hWYVJFRjNUbmM5
+UFR3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNBOEwwNXZaR1UrQ2lBZwpJQ0FnSUNBZ0lDQThUbTlr
+WlQ0S0lDQWdJQ0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWxQa1ZCVUUxbGRHaHZaRHd2VG05a1pVNWhi
+V1UrCkNpQWdJQ0FnSUNBZ0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBZ0lDQWdJQ0FnSUR4T2IyUmxU
+bUZ0WlQ1RlFWQlVlWEJsUEM5T2IyUmwKVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnSUNBOFZtRnNk
+V1UrTWpFOEwxWmhiSFZsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThMMDV2WkdVKwpDaUFnSUNBZ0lDQWdJ
+Q0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnSUNBZ0lEeE9iMlJsVG1GdFpUNUpibTVsY2sxbGRH
+aHZaRHd2ClRtOWtaVTVoYldVK0NpQWdJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQazFUTFVOSVFW
+QXRWakk4TDFaaGJIVmxQZ29nSUNBZ0lDQWcKSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lD
+QThMMDV2WkdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnSUR4TwpiMlJsUGdvZ0lD
+QWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBrUnBaMmwwWVd4RFpYSjBhV1pwWTJGMFpUd3ZUbTlrWlU1
+aGJXVStDaUFnCklDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcx
+bFBrTmxjblJwWm1sallYUmxWSGx3WlR3dlRtOWsKWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4
+V1lXeDFaVDU0TlRBNWRqTThMMVpoYkhWbFBnb2dJQ0FnSUNBZ0lDQWdQQzlPYjJSbApQZ29nSUNB
+Z0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmxUbUZ0WlQ1RFpYSjBVMGhC
+TWpVMlJtbHVaMlZ5CmNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZz
+ZFdVK01XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXgKWmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4
+WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaand2Vm1Gc2RXVStDaUFnSUNBZwpJQ0Fn
+SUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJSbFBnb2dJ
+Q0FnSUNBZ0lDQWdQRTV2ClpHVk9ZVzFsUGxOSlRUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJ
+Q0E4VG05a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk8KWVcxbFBrbE5VMGs4TDA1dlpHVk9Z
+VzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdVK2FXMXphVHd2Vm1Gc2RXVStDaUFnSUNBZwpJ
+Q0FnSUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BF
+NXZaR1ZPWVcxbFBrVkJVRlI1CmNHVThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnSUNBOFZt
+RnNkV1UrTWpROEwxWmhiSFZsUGdvZ0lDQWdJQ0FnSUNBZ1BDOU8KYjJSbFBnb2dJQ0FnSUNBZ0lE
+d3ZUbTlrWlQ0S0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ1BDOU9iMlJsUGdvZ0lEd3ZUbTlrWlQ0
+SwpQQzlOWjIxMFZISmxaVDRLCgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlv
+bi94LXg1MDktY2EtY2VydApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQKCkxTMHRM
+UzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJMUkVORFFXaERaMEYzU1VKQlow
+bEtRVWxNYkVaa2QzcE0KVm5WeVRVRXdSME5UY1VkVFNXSXpSRkZGUWtOM1ZVRk5Ra2w0UlVSQlQw
+Sm5UbFlLUWtGTlZFSXdWa0pWUTBKRVVWUkZkMGhvWTA1TgpWRmwzVFZSRmVVMVVSVEZOUkVVeFYy
+aGpUazFxV1hkTlZFRTFUVlJGTVUxRVJURlhha0ZUVFZKQmR3cEVaMWxFVmxGUlJFVjNaRVpSClZr
+Rm5VVEJGZUUxSlNVSkpha0ZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVDBOQlVUaEJUVWxKUWtO
+blMwTkJVVVZCQ25wdVFWQlYKZWpJMlRYTmhaVFIzY3pRelkzcFNOREV2U2pKUmRISlRTVnBWUzIx
+V1ZYTldkVzFFWWxsSWNsQk9kbFJZUzFOTldFRmpaWGRQVWtSUgpXVmdLVW5GMlNIWndiamhEYzJO
+Q01TdHZSMWhhZGtoM2VHbzBlbFl3VjB0dlN6SjZaVmhyWVhVemRtTjViRE5JU1V0MWNFcG1jVEpV
+ClJVRkRaV1pXYW1vd2RBcEtWeXRZTXpWUVIxZHdPUzlJTlhwSlZVNVdUbFpxVXpkVmJYTTRORWwy
+UzJoU1FqZzFNVEpRUWpsVmVVaGgKWjFoWlZsZzFSMWR3UVdOV2NIbG1jbXhTQ2taSk9WRmthR2dy
+VUdKck1IVjVhM1JrWW1ZdlEyUm1aMGhQYjJWaWNsUjBkMUpzYWswdwpiMFIwV0NzeVEzWTJhakIz
+UWtzM2FFUTRjRkIyWmpFcmRYa0tSM3BqZW1sblFWVXZORXQzTjJWYWNYbGtaamxDS3pWU2RYQlNL
+MGxhCmFYQllOREY0UldsSmNrdFNkM0ZwTlRFM1YxZDZXR05xWVVjeVkwNWlaalExTVFwNGNFZzFV
+RzVXTTJreGRIRXdOR3BOUjFGVmVrWjMKU1VSQlVVRkNielJIUVUxSU5IZElVVmxFVmxJd1QwSkNX
+VVZHU1hkWU5IWnpPRUpwUW1OVFkyOWtDalZ1YjFwSVVrMDRSVFFyYVUxRgpTVWRCTVZWa1NYZFJO
+MDFFYlVGR1NYZFlOSFp6T0VKcFFtTlRZMjlrTlc1dldraFNUVGhGTkN0cGIxSmhhMFpFUVZNS1RW
+SkJkMFJuCldVUldVVkZFUlhka1JsRldRV2RSTUVWNFoyZHJRV2QxVlZZelJFMTBWelp6ZDBSQldV
+UldVakJVUWtGVmQwRjNSVUl2ZWtGTVFtZE8KVmdwSVVUaEZRa0ZOUTBGUldYZEVVVmxLUzI5YVNX
+aDJZMDVCVVVWTVFsRkJSR2RuUlVKQlJtWlJjVTlVUVRkU2RqZExLMngxVVRkdwpibUZ6TkVKWmQw
+aEZDamxIUlZBdmRXOW9kalpMVDNrd1ZFZFJSbUp5VWxScVJtOU1WazVDT1VKYU1YbHRUVVJhTUM5
+VVNYZEpWV00zCmQyazNZVGgwTlcxRmNWbElNVFV6ZDFjS1lWZHZiMmxUYW5sTVRHaDFTVFJ6VG5K
+T1EwOTBhWE5rUW5FeWNqSk5SbGgwTm1nd2JVRlIKV1U5UWRqaFNPRXMzTDJablUzaEhSbkY2YUhs
+T2JXMVdUQW94Y1VKS2JHUjRNelJUY0hkelZFRk1VVlpRWWpSb1IzZEtlbHBtY2pGUQpZM0JGVVhn
+MmVFMXVWR3c0ZUVWWFdrVXpUWE01T1hWaFZYaGlVWEZKZDFKMUNreG5RVTlyVGtOdFdUSnRPRGxX
+YUhwaFNFb3hkVlk0Ck5VRmtUUzkwUkN0WmMyMXNibTVxZERsTVVrTmxhbUpDYVhCcVNVZHFUMWh5
+WnpGS1VDdHNlRllLYlhWTk5IWklLMUF2Yld4dGVITlEKVUhvd1pEWTFZaXRGUjIxS1duQnZUR3RQ
+TDNSa1RrNTJRMWw2YWtwd1ZFVlhjRVZ6VHpaT1RXaExXVzg5Q2kwdExTMHRSVTVFSUVORgpVbFJK
+UmtsRFFWUkZMUzB0TFMwSwotLXtib3VuZGFyeX0tLQo=
\ No newline at end of file
diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
index 0ce5d66c..ea08ea8 100644
--- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
@@ -38,6 +38,7 @@
private static final String TEST_PACKAGE_NAME = "com.test.example";
private static final String TEST_FQDN = "test.com";
private static final String TEST_PROVIDER_NAME = "test";
+ private static final int TEST_WIFI_TECHNOLOGY = WifiInfo.WIFI_TECHNOLOGY_11AC;
/**
* Verify parcel write/read with WifiInfo.
@@ -54,6 +55,7 @@
writeWifiInfo.setFQDN(TEST_FQDN);
writeWifiInfo.setProviderFriendlyName(TEST_PROVIDER_NAME);
writeWifiInfo.setNetworkSuggestionOrSpecifierPackageName(TEST_PACKAGE_NAME);
+ writeWifiInfo.setWifiTechnology(TEST_WIFI_TECHNOLOGY);
Parcel parcel = Parcel.obtain();
writeWifiInfo.writeToParcel(parcel, 0);
@@ -72,5 +74,6 @@
assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getNetworkSuggestionOrSpecifierPackageName());
assertEquals(TEST_FQDN, readWifiInfo.getPasspointFqdn());
assertEquals(TEST_PROVIDER_NAME, readWifiInfo.getPasspointProviderFriendlyName());
+ assertEquals(TEST_WIFI_TECHNOLOGY, readWifiInfo.getWifiTechnology());
}
}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
index d9a1d9af..c815d75 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
@@ -54,6 +54,8 @@
"assets/hsr1/HSR1ProfileWithInvalidContentType.base64";
private static final String PASSPOINT_INSTALLATION_FILE_WITHOUT_PROFILE =
"assets/hsr1/HSR1ProfileWithoutProfile.base64";
+ private static final String PASSPOINT_INSTALLATION_FILE_WITH_UPDATE_ID =
+ "assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64";
/**
* Read the content of the given resource file into a String.
@@ -201,4 +203,22 @@
assertNull(ConfigParser.parsePasspointConfig(
"application/x-wifi-config", configStr.getBytes()));
}
+
+ /**
+ * Verify a valid installation file is parsed successfully with the matching contents, and that
+ * Update identifier is cleared.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void parseConfigFileWithUpdateIdentifier() throws Exception {
+ String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_UPDATE_ID);
+ PasspointConfiguration expectedConfig = generateConfigurationFromProfile();
+ PasspointConfiguration actualConfig =
+ ConfigParser.parsePasspointConfig(
+ "application/x-wifi-config", configStr.getBytes());
+ // Expected configuration does not contain an update identifier
+ assertTrue(actualConfig.equals(expectedConfig));
+ }
+
}
\ No newline at end of file
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index c3b074e..f501b16 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -183,7 +183,7 @@
PasspointConfiguration config = PasspointTestUtils.createConfig();
assertTrue(config.validate());
- assertTrue(config.validateForR2());
+ assertFalse(config.isOsuProvisioned());
}
/**
@@ -241,7 +241,6 @@
config.setPolicy(null);
assertTrue(config.validate());
- assertTrue(config.validateForR2());
}
/**
@@ -271,7 +270,6 @@
config.setAaaServerTrustedNames(null);
assertTrue(config.validate());
- assertTrue(config.validateForR2());
}
/**
@@ -348,4 +346,17 @@
PasspointConfiguration copyConfig = new PasspointConfiguration(sourceConfig);
assertTrue(copyConfig.equals(sourceConfig));
}
+
+ /**
+ * Verify that a configuration containing all fields is valid for R2.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateFullR2Config() throws Exception {
+ PasspointConfiguration config = PasspointTestUtils.createR2Config();
+ assertTrue(config.validate());
+ assertTrue(config.validateForR2());
+ assertTrue(config.isOsuProvisioned());
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
index adf74eb..8d55acb 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
@@ -132,7 +132,6 @@
*/
public static PasspointConfiguration createConfig() {
PasspointConfiguration config = new PasspointConfiguration();
- config.setUpdateIdentifier(1234);
config.setHomeSp(createHomeSp());
config.setAaaServerTrustedNames(
new String[] {"trusted.fqdn.com", "another-trusted.fqdn.com"});
@@ -145,7 +144,6 @@
trustRootCertList.put("trustRoot.cert2.com",
new byte[CERTIFICATE_FINGERPRINT_BYTES]);
config.setTrustRootCertList(trustRootCertList);
- config.setUpdateIdentifier(1);
config.setCredentialPriority(120);
config.setSubscriptionCreationTimeInMillis(231200);
config.setSubscriptionExpirationTimeInMillis(2134232);
@@ -160,4 +158,15 @@
config.setServiceFriendlyNames(friendlyNames);
return config;
}
+
+ /**
+ * Helper function for creating an R2 {@link PasspointConfiguration} for testing.
+ *
+ * @return {@link PasspointConfiguration}
+ */
+ public static PasspointConfiguration createR2Config() {
+ PasspointConfiguration config = createConfig();
+ config.setUpdateIdentifier(1234);
+ return config;
+ }
}