diff options
144 files changed, 6075 insertions, 1433 deletions
diff --git a/apct-tests/perftests/core/Android.mk b/apct-tests/perftests/core/Android.mk index 3f87a1c8b598..968478c3f338 100644 --- a/apct-tests/perftests/core/Android.mk +++ b/apct-tests/perftests/core/Android.mk @@ -9,8 +9,11 @@ LOCAL_SRC_FILES := \ src/android/os/ISomeService.aidl LOCAL_STATIC_JAVA_LIBRARIES := \ + androidx.appcompat_appcompat \ androidx.test.rules \ androidx.annotation_annotation \ + apct-perftests-overlay-apps \ + apct-perftests-resources-manager-apps \ apct-perftests-utils \ guava @@ -25,5 +28,6 @@ LOCAL_JNI_SHARED_LIBRARIES := libperftestscore_jni LOCAL_ASSET_DIR := $(TOP)/external/google-fonts/dancing-script LOCAL_COMPATIBILITY_SUITE += device-tests +LOCAL_CERTIFICATE := platform -include $(BUILD_PACKAGE) +include $(BUILD_PACKAGE)
\ No newline at end of file diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml index a564a4d27fb3..525975d36772 100644 --- a/apct-tests/perftests/core/AndroidManifest.xml +++ b/apct-tests/perftests/core/AndroidManifest.xml @@ -5,8 +5,11 @@ <permission android:name="com.android.perftests.core.TestPermission" /> <uses-permission android:name="com.android.perftests.core.TestPermission" /> - <uses-permission - android:name="android.permission.GET_ACCOUNTS" /> + <uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES" /> + <uses-permission android:name="android.permission.DELETE_PACKAGES" /> + <uses-permission android:name="android.permission.GET_ACCOUNTS" /> + <uses-permission android:name="android.permission.INSTALL_PACKAGES"/> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <application> <uses-library android:name="android.test.runner" /> diff --git a/apct-tests/perftests/core/apps/overlay/Android.bp b/apct-tests/perftests/core/apps/overlay/Android.bp new file mode 100644 index 000000000000..7bee30ee9cb4 --- /dev/null +++ b/apct-tests/perftests/core/apps/overlay/Android.bp @@ -0,0 +1,188 @@ +// 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. + +android_test_helper_app { + name: "Overlay0", + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay0", + ] +} + +android_test_helper_app { + name: "Overlay1", + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay1", + ] +} + +android_test_helper_app { + name: "Overlay2", + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay2", + ] +} + +android_test_helper_app { + name: "Overlay3", + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay3", + ] +} + +android_test_helper_app { + name: "Overlay4", + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay4", + ] +} + +android_test_helper_app { + name: "Overlay5", + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay5", + ] +} + +android_test_helper_app { + name: "Overlay6", + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay6", + ] +} + +android_test_helper_app { + name: "Overlay7", + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay7", + ] +} +android_test_helper_app { + name: "Overlay8", + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay8", + ] +} + +android_test_helper_app { + name: "Overlay9", + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay9", + ] +} + +android_test_helper_app { + name: "LargeOverlay0", + resource_dirs : [ "res_large" ], + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay.large0", + ] +} + +android_test_helper_app { + name: "LargeOverlay1", + resource_dirs : [ "res_large" ], + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay.large1", + ] +} + +android_test_helper_app { + name: "LargeOverlay2", + resource_dirs : [ "res_large" ], + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay.large2", + ] +} + +android_test_helper_app { + name: "LargeOverlay3", + resource_dirs : [ "res_large" ], + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay.large3", + ] +} + +android_test_helper_app { + name: "LargeOverlay4", + resource_dirs : [ "res_large" ], + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay.large4", + ] +} + +android_test_helper_app { + name: "LargeOverlay5", + resource_dirs : [ "res_large" ], + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay.large5", + ] +} + +android_test_helper_app { + name: "LargeOverlay6", + resource_dirs : [ "res_large" ], + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay.large6", + ] +} + +android_test_helper_app { + name: "LargeOverlay7", + resource_dirs : [ "res_large" ], + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay.large7", + ] +} + +android_test_helper_app { + name: "LargeOverlay8", + resource_dirs : [ "res_large" ], + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay.large8", + ] +} + +android_test_helper_app { + name: "LargeOverlay9", + resource_dirs : [ "res_large" ], + aaptflags: [ + "--rename-manifest-package com.android.perftests.overlay.large9", + ] +} + +java_library { + name: "apct-perftests-overlay-apps", + java_resources: [ + ":Overlay0", + ":Overlay1", + ":Overlay2", + ":Overlay3", + ":Overlay4", + ":Overlay5", + ":Overlay6", + ":Overlay7", + ":Overlay8", + ":Overlay9", + ":LargeOverlay0", + ":LargeOverlay1", + ":LargeOverlay2", + ":LargeOverlay3", + ":LargeOverlay4", + ":LargeOverlay5", + ":LargeOverlay6", + ":LargeOverlay7", + ":LargeOverlay8", + ":LargeOverlay9", + ], +}
\ No newline at end of file diff --git a/apct-tests/perftests/core/apps/overlay/AndroidManifest.xml b/apct-tests/perftests/core/apps/overlay/AndroidManifest.xml new file mode 100644 index 000000000000..52f5a89bc9e4 --- /dev/null +++ b/apct-tests/perftests/core/apps/overlay/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.perftests.overlay"> + <application android:hasCode="false" /> + <uses-sdk android:targetSdkVersion="29" /> + <overlay android:targetPackage="com.android.perftests.core" android:targetName="TestResources"/> +</manifest>
\ No newline at end of file diff --git a/apct-tests/perftests/core/apps/overlay/res/values/values.xml b/apct-tests/perftests/core/apps/overlay/res/values/values.xml new file mode 100644 index 000000000000..a1a8d83eb193 --- /dev/null +++ b/apct-tests/perftests/core/apps/overlay/res/values/values.xml @@ -0,0 +1,19 @@ +<?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. +--> + +<resources> + <string name="short_text">B</string> +</resources> diff --git a/apct-tests/perftests/core/apps/overlay/res_large/values/values.xml b/apct-tests/perftests/core/apps/overlay/res_large/values/values.xml new file mode 100644 index 000000000000..e74144648e32 --- /dev/null +++ b/apct-tests/perftests/core/apps/overlay/res_large/values/values.xml @@ -0,0 +1,274 @@ +<?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. +--> + +<resources> + <string name="short_text000">B</string> + <string name="short_text001">B</string> + <string name="short_text002">B</string> + <string name="short_text003">B</string> + <string name="short_text004">B</string> + <string name="short_text005">B</string> + <string name="short_text006">B</string> + <string name="short_text007">B</string> + <string name="short_text008">B</string> + <string name="short_text009">B</string> + <string name="short_text010">B</string> + <string name="short_text011">B</string> + <string name="short_text012">B</string> + <string name="short_text013">B</string> + <string name="short_text014">B</string> + <string name="short_text015">B</string> + <string name="short_text016">B</string> + <string name="short_text017">B</string> + <string name="short_text018">B</string> + <string name="short_text019">B</string> + <string name="short_text020">B</string> + <string name="short_text021">B</string> + <string name="short_text022">B</string> + <string name="short_text023">B</string> + <string name="short_text024">B</string> + <string name="short_text025">B</string> + <string name="short_text026">B</string> + <string name="short_text027">B</string> + <string name="short_text028">B</string> + <string name="short_text029">B</string> + <string name="short_text030">B</string> + <string name="short_text031">B</string> + <string name="short_text032">B</string> + <string name="short_text033">B</string> + <string name="short_text034">B</string> + <string name="short_text035">B</string> + <string name="short_text036">B</string> + <string name="short_text037">B</string> + <string name="short_text038">B</string> + <string name="short_text039">B</string> + <string name="short_text040">B</string> + <string name="short_text041">B</string> + <string name="short_text042">B</string> + <string name="short_text043">B</string> + <string name="short_text044">B</string> + <string name="short_text045">B</string> + <string name="short_text046">B</string> + <string name="short_text047">B</string> + <string name="short_text048">B</string> + <string name="short_text049">B</string> + <string name="short_text050">B</string> + <string name="short_text051">B</string> + <string name="short_text052">B</string> + <string name="short_text053">B</string> + <string name="short_text054">B</string> + <string name="short_text055">B</string> + <string name="short_text056">B</string> + <string name="short_text057">B</string> + <string name="short_text058">B</string> + <string name="short_text059">B</string> + <string name="short_text060">B</string> + <string name="short_text061">B</string> + <string name="short_text062">B</string> + <string name="short_text063">B</string> + <string name="short_text064">B</string> + <string name="short_text065">B</string> + <string name="short_text066">B</string> + <string name="short_text067">B</string> + <string name="short_text068">B</string> + <string name="short_text069">B</string> + <string name="short_text070">B</string> + <string name="short_text071">B</string> + <string name="short_text072">B</string> + <string name="short_text073">B</string> + <string name="short_text074">B</string> + <string name="short_text075">B</string> + <string name="short_text076">B</string> + <string name="short_text077">B</string> + <string name="short_text078">B</string> + <string name="short_text079">B</string> + <string name="short_text080">B</string> + <string name="short_text081">B</string> + <string name="short_text082">B</string> + <string name="short_text083">B</string> + <string name="short_text084">B</string> + <string name="short_text085">B</string> + <string name="short_text086">B</string> + <string name="short_text087">B</string> + <string name="short_text088">B</string> + <string name="short_text089">B</string> + <string name="short_text090">B</string> + <string name="short_text091">B</string> + <string name="short_text092">B</string> + <string name="short_text093">B</string> + <string name="short_text094">B</string> + <string name="short_text095">B</string> + <string name="short_text096">B</string> + <string name="short_text097">B</string> + <string name="short_text098">B</string> + <string name="short_text099">B</string> + <string name="short_text100">B</string> + <string name="short_text101">B</string> + <string name="short_text102">B</string> + <string name="short_text103">B</string> + <string name="short_text104">B</string> + <string name="short_text105">B</string> + <string name="short_text106">B</string> + <string name="short_text107">B</string> + <string name="short_text108">B</string> + <string name="short_text109">B</string> + <string name="short_text110">B</string> + <string name="short_text111">B</string> + <string name="short_text112">B</string> + <string name="short_text113">B</string> + <string name="short_text114">B</string> + <string name="short_text115">B</string> + <string name="short_text116">B</string> + <string name="short_text117">B</string> + <string name="short_text118">B</string> + <string name="short_text119">B</string> + <string name="short_text120">B</string> + <string name="short_text121">B</string> + <string name="short_text122">B</string> + <string name="short_text123">B</string> + <string name="short_text124">B</string> + <string name="short_text125">B</string> + <string name="short_text126">B</string> + <string name="short_text127">B</string> + <string name="short_text128">B</string> + <string name="short_text129">B</string> + <string name="short_text130">B</string> + <string name="short_text131">B</string> + <string name="short_text132">B</string> + <string name="short_text133">B</string> + <string name="short_text134">B</string> + <string name="short_text135">B</string> + <string name="short_text136">B</string> + <string name="short_text137">B</string> + <string name="short_text138">B</string> + <string name="short_text139">B</string> + <string name="short_text140">B</string> + <string name="short_text141">B</string> + <string name="short_text142">B</string> + <string name="short_text143">B</string> + <string name="short_text144">B</string> + <string name="short_text145">B</string> + <string name="short_text146">B</string> + <string name="short_text147">B</string> + <string name="short_text148">B</string> + <string name="short_text149">B</string> + <string name="short_text150">B</string> + <string name="short_text151">B</string> + <string name="short_text152">B</string> + <string name="short_text153">B</string> + <string name="short_text154">B</string> + <string name="short_text155">B</string> + <string name="short_text156">B</string> + <string name="short_text157">B</string> + <string name="short_text158">B</string> + <string name="short_text159">B</string> + <string name="short_text160">B</string> + <string name="short_text161">B</string> + <string name="short_text162">B</string> + <string name="short_text163">B</string> + <string name="short_text164">B</string> + <string name="short_text165">B</string> + <string name="short_text166">B</string> + <string name="short_text167">B</string> + <string name="short_text168">B</string> + <string name="short_text169">B</string> + <string name="short_text170">B</string> + <string name="short_text171">B</string> + <string name="short_text172">B</string> + <string name="short_text173">B</string> + <string name="short_text174">B</string> + <string name="short_text175">B</string> + <string name="short_text176">B</string> + <string name="short_text177">B</string> + <string name="short_text178">B</string> + <string name="short_text179">B</string> + <string name="short_text180">B</string> + <string name="short_text181">B</string> + <string name="short_text182">B</string> + <string name="short_text183">B</string> + <string name="short_text184">B</string> + <string name="short_text185">B</string> + <string name="short_text186">B</string> + <string name="short_text187">B</string> + <string name="short_text188">B</string> + <string name="short_text189">B</string> + <string name="short_text190">B</string> + <string name="short_text191">B</string> + <string name="short_text192">B</string> + <string name="short_text193">B</string> + <string name="short_text194">B</string> + <string name="short_text195">B</string> + <string name="short_text196">B</string> + <string name="short_text197">B</string> + <string name="short_text198">B</string> + <string name="short_text199">B</string> + <string name="short_text200">B</string> + <string name="short_text201">B</string> + <string name="short_text202">B</string> + <string name="short_text203">B</string> + <string name="short_text204">B</string> + <string name="short_text205">B</string> + <string name="short_text206">B</string> + <string name="short_text207">B</string> + <string name="short_text208">B</string> + <string name="short_text209">B</string> + <string name="short_text210">B</string> + <string name="short_text211">B</string> + <string name="short_text212">B</string> + <string name="short_text213">B</string> + <string name="short_text214">B</string> + <string name="short_text215">B</string> + <string name="short_text216">B</string> + <string name="short_text217">B</string> + <string name="short_text218">B</string> + <string name="short_text219">B</string> + <string name="short_text220">B</string> + <string name="short_text221">B</string> + <string name="short_text222">B</string> + <string name="short_text223">B</string> + <string name="short_text224">B</string> + <string name="short_text225">B</string> + <string name="short_text226">B</string> + <string name="short_text227">B</string> + <string name="short_text228">B</string> + <string name="short_text229">B</string> + <string name="short_text230">B</string> + <string name="short_text231">B</string> + <string name="short_text232">B</string> + <string name="short_text233">B</string> + <string name="short_text234">B</string> + <string name="short_text235">B</string> + <string name="short_text236">B</string> + <string name="short_text237">B</string> + <string name="short_text238">B</string> + <string name="short_text239">B</string> + <string name="short_text240">B</string> + <string name="short_text241">B</string> + <string name="short_text242">B</string> + <string name="short_text243">B</string> + <string name="short_text244">B</string> + <string name="short_text245">B</string> + <string name="short_text246">B</string> + <string name="short_text247">B</string> + <string name="short_text248">B</string> + <string name="short_text249">B</string> + <string name="short_text250">B</string> + <string name="short_text251">B</string> + <string name="short_text252">B</string> + <string name="short_text253">B</string> + <string name="short_text254">B</string> + <string name="short_text255">B</string> +</resources> diff --git a/apct-tests/perftests/core/apps/reources_manager/Android.bp b/apct-tests/perftests/core/apps/reources_manager/Android.bp new file mode 100644 index 000000000000..451613236140 --- /dev/null +++ b/apct-tests/perftests/core/apps/reources_manager/Android.bp @@ -0,0 +1,34 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test_helper_app { + name: "LargeResourcesCompressed", + static_libs: [ "androidx.appcompat_appcompat" ], +} + +genrule { + name: "LargeResourcesUncompressed", + srcs: [ ":LargeResourcesCompressed" ], + out: ["LargeResourcesUncompressed.apk"], + cmd: "cp $(in) $(out) && unzip -o $(out) resources.arsc" + + " && zip $(out) resources.arsc" +} + +java_library { + name: "apct-perftests-resources-manager-apps", + java_resources: [ + ":LargeResourcesCompressed", + ":LargeResourcesUncompressed", + ], +}
\ No newline at end of file diff --git a/apct-tests/perftests/core/apps/reources_manager/AndroidManifest.xml b/apct-tests/perftests/core/apps/reources_manager/AndroidManifest.xml new file mode 100644 index 000000000000..adb4e406c608 --- /dev/null +++ b/apct-tests/perftests/core/apps/reources_manager/AndroidManifest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="fake.android"> + <application android:hasCode="false" /> + <uses-sdk android:targetSdkVersion="29" /> +</manifest>
\ No newline at end of file diff --git a/apct-tests/perftests/core/res/color/color_state_list.xml b/apct-tests/perftests/core/res/color/color_state_list.xml new file mode 100644 index 000000000000..142e47ae2738 --- /dev/null +++ b/apct-tests/perftests/core/res/color/color_state_list.xml @@ -0,0 +1,25 @@ +<?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. +--> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_focused="true" + android:color="#000000" /> + <item android:state_pressed="true" + android:state_enabled="false" + android:color="#212121" /> + <item android:state_enabled="false" + android:color="#414141" /> + <item android:color="#616161" /> +</selector>
\ No newline at end of file diff --git a/apct-tests/perftests/core/res/values/overlayable.xml b/apct-tests/perftests/core/res/values/overlayable.xml new file mode 100644 index 000000000000..70cedd7b0b75 --- /dev/null +++ b/apct-tests/perftests/core/res/values/overlayable.xml @@ -0,0 +1,280 @@ +<?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 + --> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <overlayable name="TestResources"> + <policy type="public"> + <item type="string" name="short_text" /> + <item type="string" name="short_text000" /> + <item type="string" name="short_text001" /> + <item type="string" name="short_text002" /> + <item type="string" name="short_text003" /> + <item type="string" name="short_text004" /> + <item type="string" name="short_text005" /> + <item type="string" name="short_text006" /> + <item type="string" name="short_text007" /> + <item type="string" name="short_text008" /> + <item type="string" name="short_text009" /> + <item type="string" name="short_text010" /> + <item type="string" name="short_text011" /> + <item type="string" name="short_text012" /> + <item type="string" name="short_text013" /> + <item type="string" name="short_text014" /> + <item type="string" name="short_text015" /> + <item type="string" name="short_text016" /> + <item type="string" name="short_text017" /> + <item type="string" name="short_text018" /> + <item type="string" name="short_text019" /> + <item type="string" name="short_text020" /> + <item type="string" name="short_text021" /> + <item type="string" name="short_text022" /> + <item type="string" name="short_text023" /> + <item type="string" name="short_text024" /> + <item type="string" name="short_text025" /> + <item type="string" name="short_text026" /> + <item type="string" name="short_text027" /> + <item type="string" name="short_text028" /> + <item type="string" name="short_text029" /> + <item type="string" name="short_text030" /> + <item type="string" name="short_text031" /> + <item type="string" name="short_text032" /> + <item type="string" name="short_text033" /> + <item type="string" name="short_text034" /> + <item type="string" name="short_text035" /> + <item type="string" name="short_text036" /> + <item type="string" name="short_text037" /> + <item type="string" name="short_text038" /> + <item type="string" name="short_text039" /> + <item type="string" name="short_text040" /> + <item type="string" name="short_text041" /> + <item type="string" name="short_text042" /> + <item type="string" name="short_text043" /> + <item type="string" name="short_text044" /> + <item type="string" name="short_text045" /> + <item type="string" name="short_text046" /> + <item type="string" name="short_text047" /> + <item type="string" name="short_text048" /> + <item type="string" name="short_text049" /> + <item type="string" name="short_text050" /> + <item type="string" name="short_text051" /> + <item type="string" name="short_text052" /> + <item type="string" name="short_text053" /> + <item type="string" name="short_text054" /> + <item type="string" name="short_text055" /> + <item type="string" name="short_text056" /> + <item type="string" name="short_text057" /> + <item type="string" name="short_text058" /> + <item type="string" name="short_text059" /> + <item type="string" name="short_text060" /> + <item type="string" name="short_text061" /> + <item type="string" name="short_text062" /> + <item type="string" name="short_text063" /> + <item type="string" name="short_text064" /> + <item type="string" name="short_text065" /> + <item type="string" name="short_text066" /> + <item type="string" name="short_text067" /> + <item type="string" name="short_text068" /> + <item type="string" name="short_text069" /> + <item type="string" name="short_text070" /> + <item type="string" name="short_text071" /> + <item type="string" name="short_text072" /> + <item type="string" name="short_text073" /> + <item type="string" name="short_text074" /> + <item type="string" name="short_text075" /> + <item type="string" name="short_text076" /> + <item type="string" name="short_text077" /> + <item type="string" name="short_text078" /> + <item type="string" name="short_text079" /> + <item type="string" name="short_text080" /> + <item type="string" name="short_text081" /> + <item type="string" name="short_text082" /> + <item type="string" name="short_text083" /> + <item type="string" name="short_text084" /> + <item type="string" name="short_text085" /> + <item type="string" name="short_text086" /> + <item type="string" name="short_text087" /> + <item type="string" name="short_text088" /> + <item type="string" name="short_text089" /> + <item type="string" name="short_text090" /> + <item type="string" name="short_text091" /> + <item type="string" name="short_text092" /> + <item type="string" name="short_text093" /> + <item type="string" name="short_text094" /> + <item type="string" name="short_text095" /> + <item type="string" name="short_text096" /> + <item type="string" name="short_text097" /> + <item type="string" name="short_text098" /> + <item type="string" name="short_text099" /> + <item type="string" name="short_text100" /> + <item type="string" name="short_text101" /> + <item type="string" name="short_text102" /> + <item type="string" name="short_text103" /> + <item type="string" name="short_text104" /> + <item type="string" name="short_text105" /> + <item type="string" name="short_text106" /> + <item type="string" name="short_text107" /> + <item type="string" name="short_text108" /> + <item type="string" name="short_text109" /> + <item type="string" name="short_text110" /> + <item type="string" name="short_text111" /> + <item type="string" name="short_text112" /> + <item type="string" name="short_text113" /> + <item type="string" name="short_text114" /> + <item type="string" name="short_text115" /> + <item type="string" name="short_text116" /> + <item type="string" name="short_text117" /> + <item type="string" name="short_text118" /> + <item type="string" name="short_text119" /> + <item type="string" name="short_text120" /> + <item type="string" name="short_text121" /> + <item type="string" name="short_text122" /> + <item type="string" name="short_text123" /> + <item type="string" name="short_text124" /> + <item type="string" name="short_text125" /> + <item type="string" name="short_text126" /> + <item type="string" name="short_text127" /> + <item type="string" name="short_text128" /> + <item type="string" name="short_text129" /> + <item type="string" name="short_text130" /> + <item type="string" name="short_text131" /> + <item type="string" name="short_text132" /> + <item type="string" name="short_text133" /> + <item type="string" name="short_text134" /> + <item type="string" name="short_text135" /> + <item type="string" name="short_text136" /> + <item type="string" name="short_text137" /> + <item type="string" name="short_text138" /> + <item type="string" name="short_text139" /> + <item type="string" name="short_text140" /> + <item type="string" name="short_text141" /> + <item type="string" name="short_text142" /> + <item type="string" name="short_text143" /> + <item type="string" name="short_text144" /> + <item type="string" name="short_text145" /> + <item type="string" name="short_text146" /> + <item type="string" name="short_text147" /> + <item type="string" name="short_text148" /> + <item type="string" name="short_text149" /> + <item type="string" name="short_text150" /> + <item type="string" name="short_text151" /> + <item type="string" name="short_text152" /> + <item type="string" name="short_text153" /> + <item type="string" name="short_text154" /> + <item type="string" name="short_text155" /> + <item type="string" name="short_text156" /> + <item type="string" name="short_text157" /> + <item type="string" name="short_text158" /> + <item type="string" name="short_text159" /> + <item type="string" name="short_text160" /> + <item type="string" name="short_text161" /> + <item type="string" name="short_text162" /> + <item type="string" name="short_text163" /> + <item type="string" name="short_text164" /> + <item type="string" name="short_text165" /> + <item type="string" name="short_text166" /> + <item type="string" name="short_text167" /> + <item type="string" name="short_text168" /> + <item type="string" name="short_text169" /> + <item type="string" name="short_text170" /> + <item type="string" name="short_text171" /> + <item type="string" name="short_text172" /> + <item type="string" name="short_text173" /> + <item type="string" name="short_text174" /> + <item type="string" name="short_text175" /> + <item type="string" name="short_text176" /> + <item type="string" name="short_text177" /> + <item type="string" name="short_text178" /> + <item type="string" name="short_text179" /> + <item type="string" name="short_text180" /> + <item type="string" name="short_text181" /> + <item type="string" name="short_text182" /> + <item type="string" name="short_text183" /> + <item type="string" name="short_text184" /> + <item type="string" name="short_text185" /> + <item type="string" name="short_text186" /> + <item type="string" name="short_text187" /> + <item type="string" name="short_text188" /> + <item type="string" name="short_text189" /> + <item type="string" name="short_text190" /> + <item type="string" name="short_text191" /> + <item type="string" name="short_text192" /> + <item type="string" name="short_text193" /> + <item type="string" name="short_text194" /> + <item type="string" name="short_text195" /> + <item type="string" name="short_text196" /> + <item type="string" name="short_text197" /> + <item type="string" name="short_text198" /> + <item type="string" name="short_text199" /> + <item type="string" name="short_text200" /> + <item type="string" name="short_text201" /> + <item type="string" name="short_text202" /> + <item type="string" name="short_text203" /> + <item type="string" name="short_text204" /> + <item type="string" name="short_text205" /> + <item type="string" name="short_text206" /> + <item type="string" name="short_text207" /> + <item type="string" name="short_text208" /> + <item type="string" name="short_text209" /> + <item type="string" name="short_text210" /> + <item type="string" name="short_text211" /> + <item type="string" name="short_text212" /> + <item type="string" name="short_text213" /> + <item type="string" name="short_text214" /> + <item type="string" name="short_text215" /> + <item type="string" name="short_text216" /> + <item type="string" name="short_text217" /> + <item type="string" name="short_text218" /> + <item type="string" name="short_text219" /> + <item type="string" name="short_text220" /> + <item type="string" name="short_text221" /> + <item type="string" name="short_text222" /> + <item type="string" name="short_text223" /> + <item type="string" name="short_text224" /> + <item type="string" name="short_text225" /> + <item type="string" name="short_text226" /> + <item type="string" name="short_text227" /> + <item type="string" name="short_text228" /> + <item type="string" name="short_text229" /> + <item type="string" name="short_text230" /> + <item type="string" name="short_text231" /> + <item type="string" name="short_text232" /> + <item type="string" name="short_text233" /> + <item type="string" name="short_text234" /> + <item type="string" name="short_text235" /> + <item type="string" name="short_text236" /> + <item type="string" name="short_text237" /> + <item type="string" name="short_text238" /> + <item type="string" name="short_text239" /> + <item type="string" name="short_text240" /> + <item type="string" name="short_text241" /> + <item type="string" name="short_text242" /> + <item type="string" name="short_text243" /> + <item type="string" name="short_text244" /> + <item type="string" name="short_text245" /> + <item type="string" name="short_text246" /> + <item type="string" name="short_text247" /> + <item type="string" name="short_text248" /> + <item type="string" name="short_text249" /> + <item type="string" name="short_text250" /> + <item type="string" name="short_text251" /> + <item type="string" name="short_text252" /> + <item type="string" name="short_text253" /> + <item type="string" name="short_text254" /> + <item type="string" name="short_text255" /> + </policy> + </overlayable> +</resources> diff --git a/apct-tests/perftests/core/res/values/strings.xml b/apct-tests/perftests/core/res/values/strings.xml deleted file mode 100644 index 7ab325f79dc7..000000000000 --- a/apct-tests/perftests/core/res/values/strings.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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 - --> - -<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="long_text">text text text text text text text text text text text text - text text text text text text text text text text text text text text text text - text text text text text text text text text text text text text text text text - text text text text text text text text text text text text text text text text - text text text text text text text text text text text text text text text text - text text text text text text text text text text text text text text text text - text text text text text text text typo text text text text text text text text - text text text text text text text text text text text text text text text text - text text text text text text text text text text text text text text text text - text text text text text text text text text text text text text text text text - text text text text text text text text text text text text text text text text - text text text text text text text text text text text text </string> - <string name="short_text">text text</string> -</resources> diff --git a/apct-tests/perftests/core/res/values/values.xml b/apct-tests/perftests/core/res/values/values.xml new file mode 100644 index 000000000000..aad42ba04e11 --- /dev/null +++ b/apct-tests/perftests/core/res/values/values.xml @@ -0,0 +1,368 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="long_text">text text text text text text text text text text text text + text text text text text text text text text text text text text text text text + text text text text text text text text text text text text text text text text + text text text text text text text text text text text text text text text text + text text text text text text text text text text text text text text text text + text text text text text text text text text text text text text text text text + text text text text text text text typo text text text text text text text text + text text text text text text text text text text text text text text text text + text text text text text text text text text text text text text text text text + text text text text text text text text text text text text text text text text + text text text text text text text text text text text text text text text text + text text text text text text text text text text text text </string> + + <plurals name="plurals_text"> + <item quantity="one">1 text</item> + <item quantity="other"><xliff:g id="count" example="3">%d</xliff:g> texts</item> + </plurals> + + <string-array name="strings"> + <item>Run</item> + <item>the</item> + <item>performance</item> + <item>tests!</item> + <item>Run</item> + <item>the</item> + <item>performance</item> + <item>tests!</item> + <item>Run</item> + <item>the</item> + <item>performance</item> + <item>tests!</item> + <item>Run</item> + <item>the</item> + <item>performance</item> + <item>tests!</item> + <item>Run</item> + <item>the</item> + <item>performance</item> + <item>tests!</item> + <item>Run</item> + <item>the</item> + <item>performance</item> + <item>tests!</item> + <item>Run</item> + <item>the</item> + <item>performance</item> + <item>tests!</item> + <item>Run</item> + <item>the</item> + <item>performance</item> + <item>tests!</item> + </string-array> + + <integer-array name="ints"> + <item>0</item> + <item>1</item> + <item>2</item> + <item>3</item> + <item>0</item> + <item>1</item> + <item>2</item> + <item>3</item> + <item>0</item> + <item>1</item> + <item>2</item> + <item>3</item> + <item>0</item> + <item>1</item> + <item>2</item> + <item>3</item> + <item>0</item> + <item>1</item> + <item>2</item> + <item>3</item> + <item>0</item> + <item>1</item> + <item>2</item> + <item>3</item> + <item>0</item> + <item>1</item> + <item>2</item> + <item>3</item> + <item>0</item> + <item>1</item> + <item>2</item> + <item>3</item> + </integer-array> + + <color name="white">#ffffff</color> + + <integer name="forty_two">42</integer> + + <string name="short_text">text text</string> + <string name="short_text000">B</string> + <string name="short_text001">B</string> + <string name="short_text002">B</string> + <string name="short_text003">B</string> + <string name="short_text004">B</string> + <string name="short_text005">B</string> + <string name="short_text006">B</string> + <string name="short_text007">B</string> + <string name="short_text008">B</string> + <string name="short_text009">B</string> + <string name="short_text010">B</string> + <string name="short_text011">B</string> + <string name="short_text012">B</string> + <string name="short_text013">B</string> + <string name="short_text014">B</string> + <string name="short_text015">B</string> + <string name="short_text016">B</string> + <string name="short_text017">B</string> + <string name="short_text018">B</string> + <string name="short_text019">B</string> + <string name="short_text020">B</string> + <string name="short_text021">B</string> + <string name="short_text022">B</string> + <string name="short_text023">B</string> + <string name="short_text024">B</string> + <string name="short_text025">B</string> + <string name="short_text026">B</string> + <string name="short_text027">B</string> + <string name="short_text028">B</string> + <string name="short_text029">B</string> + <string name="short_text030">B</string> + <string name="short_text031">B</string> + <string name="short_text032">B</string> + <string name="short_text033">B</string> + <string name="short_text034">B</string> + <string name="short_text035">B</string> + <string name="short_text036">B</string> + <string name="short_text037">B</string> + <string name="short_text038">B</string> + <string name="short_text039">B</string> + <string name="short_text040">B</string> + <string name="short_text041">B</string> + <string name="short_text042">B</string> + <string name="short_text043">B</string> + <string name="short_text044">B</string> + <string name="short_text045">B</string> + <string name="short_text046">B</string> + <string name="short_text047">B</string> + <string name="short_text048">B</string> + <string name="short_text049">B</string> + <string name="short_text050">B</string> + <string name="short_text051">B</string> + <string name="short_text052">B</string> + <string name="short_text053">B</string> + <string name="short_text054">B</string> + <string name="short_text055">B</string> + <string name="short_text056">B</string> + <string name="short_text057">B</string> + <string name="short_text058">B</string> + <string name="short_text059">B</string> + <string name="short_text060">B</string> + <string name="short_text061">B</string> + <string name="short_text062">B</string> + <string name="short_text063">B</string> + <string name="short_text064">B</string> + <string name="short_text065">B</string> + <string name="short_text066">B</string> + <string name="short_text067">B</string> + <string name="short_text068">B</string> + <string name="short_text069">B</string> + <string name="short_text070">B</string> + <string name="short_text071">B</string> + <string name="short_text072">B</string> + <string name="short_text073">B</string> + <string name="short_text074">B</string> + <string name="short_text075">B</string> + <string name="short_text076">B</string> + <string name="short_text077">B</string> + <string name="short_text078">B</string> + <string name="short_text079">B</string> + <string name="short_text080">B</string> + <string name="short_text081">B</string> + <string name="short_text082">B</string> + <string name="short_text083">B</string> + <string name="short_text084">B</string> + <string name="short_text085">B</string> + <string name="short_text086">B</string> + <string name="short_text087">B</string> + <string name="short_text088">B</string> + <string name="short_text089">B</string> + <string name="short_text090">B</string> + <string name="short_text091">B</string> + <string name="short_text092">B</string> + <string name="short_text093">B</string> + <string name="short_text094">B</string> + <string name="short_text095">B</string> + <string name="short_text096">B</string> + <string name="short_text097">B</string> + <string name="short_text098">B</string> + <string name="short_text099">B</string> + <string name="short_text100">B</string> + <string name="short_text101">B</string> + <string name="short_text102">B</string> + <string name="short_text103">B</string> + <string name="short_text104">B</string> + <string name="short_text105">B</string> + <string name="short_text106">B</string> + <string name="short_text107">B</string> + <string name="short_text108">B</string> + <string name="short_text109">B</string> + <string name="short_text110">B</string> + <string name="short_text111">B</string> + <string name="short_text112">B</string> + <string name="short_text113">B</string> + <string name="short_text114">B</string> + <string name="short_text115">B</string> + <string name="short_text116">B</string> + <string name="short_text117">B</string> + <string name="short_text118">B</string> + <string name="short_text119">B</string> + <string name="short_text120">B</string> + <string name="short_text121">B</string> + <string name="short_text122">B</string> + <string name="short_text123">B</string> + <string name="short_text124">B</string> + <string name="short_text125">B</string> + <string name="short_text126">B</string> + <string name="short_text127">B</string> + <string name="short_text128">B</string> + <string name="short_text129">B</string> + <string name="short_text130">B</string> + <string name="short_text131">B</string> + <string name="short_text132">B</string> + <string name="short_text133">B</string> + <string name="short_text134">B</string> + <string name="short_text135">B</string> + <string name="short_text136">B</string> + <string name="short_text137">B</string> + <string name="short_text138">B</string> + <string name="short_text139">B</string> + <string name="short_text140">B</string> + <string name="short_text141">B</string> + <string name="short_text142">B</string> + <string name="short_text143">B</string> + <string name="short_text144">B</string> + <string name="short_text145">B</string> + <string name="short_text146">B</string> + <string name="short_text147">B</string> + <string name="short_text148">B</string> + <string name="short_text149">B</string> + <string name="short_text150">B</string> + <string name="short_text151">B</string> + <string name="short_text152">B</string> + <string name="short_text153">B</string> + <string name="short_text154">B</string> + <string name="short_text155">B</string> + <string name="short_text156">B</string> + <string name="short_text157">B</string> + <string name="short_text158">B</string> + <string name="short_text159">B</string> + <string name="short_text160">B</string> + <string name="short_text161">B</string> + <string name="short_text162">B</string> + <string name="short_text163">B</string> + <string name="short_text164">B</string> + <string name="short_text165">B</string> + <string name="short_text166">B</string> + <string name="short_text167">B</string> + <string name="short_text168">B</string> + <string name="short_text169">B</string> + <string name="short_text170">B</string> + <string name="short_text171">B</string> + <string name="short_text172">B</string> + <string name="short_text173">B</string> + <string name="short_text174">B</string> + <string name="short_text175">B</string> + <string name="short_text176">B</string> + <string name="short_text177">B</string> + <string name="short_text178">B</string> + <string name="short_text179">B</string> + <string name="short_text180">B</string> + <string name="short_text181">B</string> + <string name="short_text182">B</string> + <string name="short_text183">B</string> + <string name="short_text184">B</string> + <string name="short_text185">B</string> + <string name="short_text186">B</string> + <string name="short_text187">B</string> + <string name="short_text188">B</string> + <string name="short_text189">B</string> + <string name="short_text190">B</string> + <string name="short_text191">B</string> + <string name="short_text192">B</string> + <string name="short_text193">B</string> + <string name="short_text194">B</string> + <string name="short_text195">B</string> + <string name="short_text196">B</string> + <string name="short_text197">B</string> + <string name="short_text198">B</string> + <string name="short_text199">B</string> + <string name="short_text200">B</string> + <string name="short_text201">B</string> + <string name="short_text202">B</string> + <string name="short_text203">B</string> + <string name="short_text204">B</string> + <string name="short_text205">B</string> + <string name="short_text206">B</string> + <string name="short_text207">B</string> + <string name="short_text208">B</string> + <string name="short_text209">B</string> + <string name="short_text210">B</string> + <string name="short_text211">B</string> + <string name="short_text212">B</string> + <string name="short_text213">B</string> + <string name="short_text214">B</string> + <string name="short_text215">B</string> + <string name="short_text216">B</string> + <string name="short_text217">B</string> + <string name="short_text218">B</string> + <string name="short_text219">B</string> + <string name="short_text220">B</string> + <string name="short_text221">B</string> + <string name="short_text222">B</string> + <string name="short_text223">B</string> + <string name="short_text224">B</string> + <string name="short_text225">B</string> + <string name="short_text226">B</string> + <string name="short_text227">B</string> + <string name="short_text228">B</string> + <string name="short_text229">B</string> + <string name="short_text230">B</string> + <string name="short_text231">B</string> + <string name="short_text232">B</string> + <string name="short_text233">B</string> + <string name="short_text234">B</string> + <string name="short_text235">B</string> + <string name="short_text236">B</string> + <string name="short_text237">B</string> + <string name="short_text238">B</string> + <string name="short_text239">B</string> + <string name="short_text240">B</string> + <string name="short_text241">B</string> + <string name="short_text242">B</string> + <string name="short_text243">B</string> + <string name="short_text244">B</string> + <string name="short_text245">B</string> + <string name="short_text246">B</string> + <string name="short_text247">B</string> + <string name="short_text248">B</string> + <string name="short_text249">B</string> + <string name="short_text250">B</string> + <string name="short_text251">B</string> + <string name="short_text252">B</string> + <string name="short_text253">B</string> + <string name="short_text254">B</string> + <string name="short_text255">B</string> +</resources> diff --git a/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java b/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java new file mode 100644 index 000000000000..fcb13a8d51f1 --- /dev/null +++ b/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java @@ -0,0 +1,234 @@ +/* + * 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; + +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.content.om.OverlayManager; +import android.os.UserHandle; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.perftests.utils.TestPackageInstaller; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; + +import com.android.perftests.core.R; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; + +/** + * Benchmarks for {@link android.content.om.OverlayManager}. + */ +@LargeTest +public class OverlayManagerPerfTest { + private static final int OVERLAY_PKG_COUNT = 10; + private static Context sContext; + private static OverlayManager sOverlayManager; + private static Executor sExecutor; + private static ArrayList<TestPackageInstaller.InstalledPackage> sSmallOverlays = + new ArrayList<>(); + private static ArrayList<TestPackageInstaller.InstalledPackage> sLargeOverlays = + new ArrayList<>(); + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @BeforeClass + public static void classSetUp() throws Exception { + sContext = InstrumentationRegistry.getTargetContext(); + sOverlayManager = new OverlayManager(sContext); + sExecutor = (command) -> new Thread(command).start(); + + // Install all of the test overlays. + TestPackageInstaller installer = new TestPackageInstaller(sContext); + for (int i = 0; i < OVERLAY_PKG_COUNT; i++) { + sSmallOverlays.add(installer.installPackage("Overlay" + i +".apk")); + sLargeOverlays.add(installer.installPackage("LargeOverlay" + i +".apk")); + } + } + + @AfterClass + public static void classTearDown() throws Exception { + for (TestPackageInstaller.InstalledPackage overlay : sSmallOverlays) { + overlay.uninstall(); + } + + for (TestPackageInstaller.InstalledPackage overlay : sLargeOverlays) { + overlay.uninstall(); + } + } + + @After + public void tearDown() throws Exception { + // Disable all test overlays after each test. + for (TestPackageInstaller.InstalledPackage overlay : sSmallOverlays) { + assertSetEnabled(sContext, overlay.getPackageName(), false); + } + + for (TestPackageInstaller.InstalledPackage overlay : sLargeOverlays) { + assertSetEnabled(sContext, overlay.getPackageName(), false); + } + } + + /** + * Enables the overlay and waits for the APK path change sto be propagated to the context + * AssetManager. + */ + private void assertSetEnabled(Context context, String overlayPackage, boolean eanabled) + throws Exception { + sOverlayManager.setEnabled(overlayPackage, true, UserHandle.SYSTEM); + + // Wait for the overlay changes to propagate + FutureTask<Boolean> task = new FutureTask<>(() -> { + while (true) { + for (String path : context.getAssets().getApkPaths()) { + if (eanabled == path.contains(overlayPackage)) { + return true; + } + } + } + }); + + sExecutor.execute(task); + assertTrue("Failed to load overlay " + overlayPackage, + task.get(20, TimeUnit.SECONDS)); + } + + @Test + public void setEnabledWarmCache() throws Exception { + String packageName = sSmallOverlays.get(0).getPackageName(); + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + assertSetEnabled(sContext, packageName, true); + + // Disable the overlay for the next iteration of the test + state.pauseTiming(); + assertSetEnabled(sContext, packageName, false); + state.resumeTiming(); + } + } + + @Test + public void setEnabledColdCacheSmallOverlay() throws Exception { + String packageName = sSmallOverlays.get(0).getPackageName(); + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + assertSetEnabled(sContext, packageName, true); + + // Disable the overlay and remove the idmap for the next iteration of the test + state.pauseTiming(); + assertSetEnabled(sContext, packageName, false); + sOverlayManager.invalidateCachesForOverlay(packageName, UserHandle.SYSTEM); + state.resumeTiming(); + } + } + + @Test + public void setEnabledColdCacheLargeOverlay() throws Exception { + String packageName = sLargeOverlays.get(0).getPackageName(); + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + assertSetEnabled(sContext, packageName, true); + + // Disable the overlay and remove the idmap for the next iteration of the test + state.pauseTiming(); + assertSetEnabled(sContext, packageName, false); + sOverlayManager.invalidateCachesForOverlay(packageName, UserHandle.SYSTEM); + state.resumeTiming(); + } + } + + @Test + public void setEnabledDisable() throws Exception { + String packageName = sSmallOverlays.get(0).getPackageName(); + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + assertSetEnabled(sContext, packageName, true); + state.resumeTiming(); + + assertSetEnabled(sContext, packageName, false); + } + } + + @Test + public void getStringOneSmallOverlay() throws Exception { + String packageName = sSmallOverlays.get(0).getPackageName(); + assertSetEnabled(sContext, packageName, true); + + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + sContext.getString(R.string.short_text); + } + + assertSetEnabled(sContext, packageName, false); + } + + @Test + public void getStringOneLargeOverlay() throws Exception { + String packageName = sLargeOverlays.get(0).getPackageName(); + assertSetEnabled(sContext, packageName, true); + + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + for (int resId = R.string.short_text000; resId < R.string.short_text255; resId++) { + sContext.getString(resId); + } + } + + assertSetEnabled(sContext, packageName, false); + } + + @Test + public void getStringTenOverlays() throws Exception { + // Enable all test overlays + for (TestPackageInstaller.InstalledPackage overlay : sSmallOverlays) { + assertSetEnabled(sContext, overlay.getPackageName(), true); + } + + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + sContext.getString(R.string.short_text); + } + } + + @Test + public void getStringLargeTenOverlays() throws Exception { + // Enable all test overlays + for (TestPackageInstaller.InstalledPackage overlay : sLargeOverlays) { + assertSetEnabled(sContext, overlay.getPackageName(), true); + } + + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + for (int resId = R.string.short_text000; resId < R.string.short_text255; resId++) { + sContext.getString(resId); + } + } + } +} diff --git a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java new file mode 100644 index 000000000000..2955d2ca7d0e --- /dev/null +++ b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java @@ -0,0 +1,139 @@ +/* + * 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; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.view.Display; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Benchmarks for {@link android.app.ResourcesManager}. + */ +@LargeTest +public class ResourcesManagerPerfTest { + private static Context sContext; + private static File sResourcesCompressed; + private static File sResourcesUncompressed; + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @BeforeClass + public static void setUp() throws Exception { + sContext = InstrumentationRegistry.getTargetContext(); + sResourcesCompressed = copyApkToTemp("LargeResourcesCompressed.apk", + "LargeResourcesCompressed.apk"); + sResourcesUncompressed = copyApkToTemp("LargeResourcesUncompressed.apk", + "LargeResourcesUncompressed.apk"); + } + + @AfterClass + public static void tearDown() { + Assert.assertTrue(sResourcesCompressed.delete()); + Assert.assertTrue(sResourcesUncompressed.delete()); + } + + private static File copyApkToTemp(String inputFileName, String fileName) throws Exception { + File file = File.createTempFile(fileName, null, sContext.getCacheDir()); + try (OutputStream tempOutputStream = new FileOutputStream(file); + InputStream is = sContext.getResources().getAssets().openNonAsset(inputFileName)) { + byte[] buffer = new byte[4096]; + int n; + while ((n = is.read(buffer)) >= 0) { + tempOutputStream.write(buffer, 0, n); + } + tempOutputStream.flush(); + } + return file; + } + + private void getResourcesForPath(String path) { + ResourcesManager.getInstance().getResources(null, path, null, null, null, + Display.DEFAULT_DISPLAY, null, sContext.getResources().getCompatibilityInfo(), + null); + } + + @Test + public void getResourcesCached() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + getResourcesForPath(sResourcesCompressed.getPath()); + while (state.keepRunning()) { + getResourcesForPath(sResourcesCompressed.getPath()); + } + } + + @Test + public void getResourcesCompressedUncached() { + ResourcesManager resourcesManager = ResourcesManager.getInstance(); + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + resourcesManager.invalidatePath(sResourcesCompressed.getPath()); + state.resumeTiming(); + + getResourcesForPath(sResourcesCompressed.getPath()); + } + } + + @Test + public void getResourcesUncompressedUncached() { + ResourcesManager resourcesManager = ResourcesManager.getInstance(); + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + resourcesManager.invalidatePath(sResourcesUncompressed.getPath()); + state.resumeTiming(); + + getResourcesForPath(sResourcesUncompressed.getPath()); + } + } + + @Test + public void applyConfigurationToResourcesLocked() { + ResourcesManager resourcesManager = ResourcesManager.getInstance(); + Configuration c = new Configuration(resourcesManager.getConfiguration()); + c.uiMode = Configuration.UI_MODE_TYPE_WATCH; + + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + resourcesManager.applyConfigurationToResources(c, null); + + // Alternate configurations to ensure the set configuration is different each iteration + if (c.uiMode == Configuration.UI_MODE_TYPE_WATCH) { + c.uiMode = Configuration.UI_MODE_TYPE_TELEVISION; + } else { + c.uiMode = Configuration.UI_MODE_TYPE_WATCH; + } + } + } +} diff --git a/apct-tests/perftests/core/src/android/app/ResourcesPerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesPerfTest.java index c3e43ee07453..72162448a2e0 100644 --- a/apct-tests/perftests/core/src/android/app/ResourcesPerfTest.java +++ b/apct-tests/perftests/core/src/android/app/ResourcesPerfTest.java @@ -18,15 +18,18 @@ package android.app; import static org.junit.Assert.fail; -import android.content.res.AssetManager; +import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; +import android.util.TypedValue; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; -import org.junit.After; +import com.android.perftests.core.R; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -43,36 +46,123 @@ public class ResourcesPerfTest { @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - private AssetManager mAsset; private Resources mRes; - private int mTextId; - private int mColorId; - private int mIntegerId; - private int mLayoutId; - @Before public void setUp() { - mAsset = new AssetManager(); - mAsset.addAssetPath("/system/framework/framework-res.apk"); - mRes = new Resources(mAsset, null, null); + Context context = InstrumentationRegistry.getTargetContext(); + mRes = context.getResources(); + } + + @Test + public void getValue() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + TypedValue value = new TypedValue(); + while (state.keepRunning()) { + mRes.getValue(R.integer.forty_two, value, false /* resolve_refs */); + } + } - mTextId = mRes.getIdentifier("cancel", "string", "android"); - mColorId = mRes.getIdentifier("transparent", "color", "android"); - mIntegerId = mRes.getIdentifier("config_shortAnimTime", "integer", "android"); - mLayoutId = mRes.getIdentifier("two_line_list_item", "layout", "android"); + @Test + public void getFrameworkValue() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + TypedValue value = new TypedValue(); + while (state.keepRunning()) { + mRes.getValue(com.android.internal.R.integer.autofill_max_visible_datasets, value, + false /* resolve_refs */); + } + } + + @Test + public void getValueString() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + TypedValue value = new TypedValue(); + while (state.keepRunning()) { + mRes.getValue(R.string.long_text, value, false /* resolve_refs */); + } + } + + @Test + public void getFrameworkStringValue() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + TypedValue value = new TypedValue(); + while (state.keepRunning()) { + mRes.getValue(com.android.internal.R.string.cancel, value, false /* resolve_refs */); + } } - @After - public void tearDown() { - mAsset.close(); + @Test + public void getValueManyConfigurations() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + TypedValue value = new TypedValue(); + while (state.keepRunning()) { + mRes.getValue(com.android.internal.R.string.mmcc_illegal_me, value, + false /* resolve_refs */); + } } @Test public void getText() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { - mRes.getText(mTextId); + mRes.getText(R.string.long_text); + } + } + + + @Test + public void getFont() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + mRes.getFont(R.font.samplefont); + } + } + + @Test + public void getString() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + mRes.getString(R.string.long_text); + } + } + + @Test + public void getQuantityString() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + mRes.getQuantityString(R.plurals.plurals_text, 5); + } + } + + @Test + public void getQuantityText() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + mRes.getQuantityText(R.plurals.plurals_text, 5); + } + } + + @Test + public void getTextArray() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + mRes.getTextArray(R.array.strings); + } + } + + @Test + public void getStringArray() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + mRes.getStringArray(R.array.strings); + } + } + + @Test + public void getIntegerArray() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + mRes.getIntArray(R.array.ints); } } @@ -80,15 +170,23 @@ public class ResourcesPerfTest { public void getColor() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { - mRes.getColor(mColorId, null); + mRes.getColor(R.color.white, null); } } @Test - public void getInteger() { + public void getColorStateList() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { - mRes.getInteger(mIntegerId); + mRes.getColorStateList(R.color.color_state_list, null); + } + } + + @Test + public void getVectorDrawable() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + mRes.getDrawable(R.drawable.vector_drawable01, null); } } @@ -96,13 +194,32 @@ public class ResourcesPerfTest { public void getLayoutAndTravese() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { - try (XmlResourceParser parser = mRes.getLayout(mLayoutId)) { + try (XmlResourceParser parser = mRes.getLayout(R.layout.test_relative_layout)) { + while (parser.next() != XmlPullParser.END_DOCUMENT) { + // Walk the entire tree + } + } catch (IOException | XmlPullParserException exception) { + fail("Parsing of the layout failed. Something is really broken"); + } + } + } + + @Test + public void getLayoutAndTraverseInvalidateCaches() { + mRes.flushLayoutCache(); + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + try (XmlResourceParser parser = mRes.getLayout(R.layout.test_relative_layout)) { while (parser.next() != XmlPullParser.END_DOCUMENT) { // Walk the entire tree } } catch (IOException | XmlPullParserException exception) { fail("Parsing of the layout failed. Something is really broken"); } + + state.pauseTiming(); + mRes.flushLayoutCache(); + state.resumeTiming(); } } -} +}
\ No newline at end of file diff --git a/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java index 1b07572fd3f8..6123e69b584e 100644 --- a/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java +++ b/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java @@ -16,13 +16,19 @@ package android.app; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.content.res.Resources; +import android.os.UserHandle; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; +import android.view.Display; import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -35,13 +41,69 @@ public class ResourcesThemePerfTest { @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + private Context mContext; + private int mThemeResId; private Resources.Theme mTheme; @Before - public void setUp() { - Context context = InstrumentationRegistry.getTargetContext(); - mTheme = context.getTheme(); + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getTargetContext(); + mThemeResId = com.android.perftests.core.R.style.Base_V7_Theme_AppCompat; + mTheme = mContext.getResources().newTheme(); + mTheme.applyStyle(mThemeResId, true /* force */); + } + + @Test + public void applyStyle() { + Resources.Theme destTheme = mContext.getResources().newTheme(); + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + destTheme.applyStyle(mThemeResId, true /* force */); + } + } + @Test + public void rebase() { + Resources.Theme destTheme = mContext.getResources().newTheme(); + destTheme.applyStyle(mThemeResId, true /* force */); + destTheme.applyStyle(android.R.style.Theme_Material, true /* force */); + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + destTheme.rebase(); + } + } + + @Test + public void setToSameAssetManager() { + Resources.Theme destTheme = mContext.getResources().newTheme(); + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + destTheme.setTo(mTheme); + } + } + + @Test + public void setToDifferentAssetManager() throws Exception { + // Create a new Resources object with the same asset paths but a different AssetManager + PackageManager packageManager = mContext.getApplicationContext().getPackageManager(); + ApplicationInfo ai = packageManager.getApplicationInfo(mContext.getPackageName(), + UserHandle.myUserId()); + + ResourcesManager resourcesManager = ResourcesManager.getInstance(); + Configuration c = resourcesManager.getConfiguration(); + c.orientation = (c.orientation == Configuration.ORIENTATION_PORTRAIT) + ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT; + + Resources destResources = resourcesManager.getResources(null, ai.sourceDir, + ai.splitSourceDirs, ai.resourceDirs, ai.sharedLibraryFiles, Display.DEFAULT_DISPLAY, + c, mContext.getResources().getCompatibilityInfo(), null); + Assert.assertNotEquals(destResources.getAssets(), mContext.getAssets()); + + Resources.Theme destTheme = destResources.newTheme(); + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + destTheme.setTo(mTheme); + } } @Test @@ -51,5 +113,4 @@ public class ResourcesThemePerfTest { mTheme.obtainStyledAttributes(android.R.style.Theme_Material, android.R.styleable.View); } } - -} +}
\ No newline at end of file diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java b/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java new file mode 100644 index 000000000000..a433d801acaf --- /dev/null +++ b/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java @@ -0,0 +1,145 @@ +/* + * 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.perftests.utils; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.pm.PackageInstaller; +import android.content.res.Resources; +import android.util.Log; + +import org.junit.Assert; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Installs packages included within the assets directory. + */ +public class TestPackageInstaller { + private static final String LOG_TAG = "TestPackageInstaller"; + private static final String BROADCAST_ACTION = + "com.android.perftests.core.ACTION_INSTALL_COMMIT"; + + private final Context mContext; + public TestPackageInstaller(Context context) { + mContext = context; + } + + + + /** + * Installs an APK located at the specified path in the assets directory. + **/ + public InstalledPackage installPackage(String resourceName) throws IOException, + InterruptedException { + Log.d(LOG_TAG, "Installing resource APK '" + resourceName + "'"); + LocalBroadcastReceiver intentSender = new LocalBroadcastReceiver(mContext); + + // Initialize the package install session. + PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller(); + PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( + PackageInstaller.SessionParams.MODE_FULL_INSTALL); + params.setInstallAsInstantApp(false); + int sessionId = packageInstaller.createSession(params); + PackageInstaller.Session session = packageInstaller.openSession(sessionId); + + // Copy the apk to the install session. + try (OutputStream os = session.openWrite("TestPackageInstaller", 0, -1); + InputStream is = mContext.getResources().getAssets().openNonAsset(resourceName)) { + if (is == null) { + throw new IOException("Resource " + resourceName + " not found"); + } + byte[] buffer = new byte[4096]; + int n; + while ((n = is.read(buffer)) >= 0) { + os.write(buffer, 0, n); + } + } + + session.commit(intentSender.getIntentSender(sessionId)); + session.close(); + + // Retrieve the results of the installation. + Intent intent = intentSender.getIntentSenderResult(); + int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE); + Assert.assertEquals(PackageInstaller.STATUS_SUCCESS, status); + String packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME); + return new InstalledPackage(sessionId, packageName); + } + + public class InstalledPackage { + private int mSessionId; + private String mPackageName; + + private InstalledPackage(int sessionId, String packageName) { + mSessionId = sessionId; + mPackageName = packageName; + } + + public String getPackageName() { + return mPackageName; + } + + public void uninstall() throws Exception { + Log.d(LOG_TAG, "Uninstalling package '" + mPackageName + "'"); + LocalBroadcastReceiver intentSender = new LocalBroadcastReceiver(mContext); + PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller(); + packageInstaller.uninstall(mPackageName, intentSender.getIntentSender(mSessionId)); + int status = intentSender.getIntentSenderResult() + .getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); + Assert.assertEquals(PackageInstaller.STATUS_SUCCESS, status); + } + } + + private class LocalBroadcastReceiver extends BroadcastReceiver { + private final BlockingQueue<Intent> mIntentSenderResults = new LinkedBlockingQueue<>(); + private final Context mContext; + + private LocalBroadcastReceiver(Context context) { + mContext = context; + } + + @Override + public void onReceive(Context context, Intent intent) { + mIntentSenderResults.add(intent); + } + + IntentSender getIntentSender(int sessionId) { + String action = BROADCAST_ACTION + "." + sessionId; + IntentFilter filter = new IntentFilter(action); + mContext.registerReceiver(this, filter); + + Intent intent = new Intent(action); + PendingIntent pending = PendingIntent.getBroadcast(mContext, sessionId, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + return pending.getIntentSender(); + } + + Intent getIntentSenderResult() throws InterruptedException { + return mIntentSenderResults.take(); + } + } +} diff --git a/apex/jobscheduler/framework/java/android/app/DeviceIdleFrameworkInitializer.java b/apex/jobscheduler/framework/java/android/app/DeviceIdleFrameworkInitializer.java index a807eb1d3311..5b1405628b49 100644 --- a/apex/jobscheduler/framework/java/android/app/DeviceIdleFrameworkInitializer.java +++ b/apex/jobscheduler/framework/java/android/app/DeviceIdleFrameworkInitializer.java @@ -36,7 +36,7 @@ public class DeviceIdleFrameworkInitializer { SystemServiceRegistry.registerCachedService( Context.DEVICE_IDLE_CONTROLLER, DeviceIdleManager.class, (context, b) -> new DeviceIdleManager( - context.getOuterContext(), IDeviceIdleController.Stub.asInterface(b))); + context, IDeviceIdleController.Stub.asInterface(b))); PowerManager.setIsIgnoringBatteryOptimizationsCallback((packageName) -> { // No need for synchronization on sIDeviceIdleController; worst case // we just initialize it twice. diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java new file mode 100644 index 000000000000..3cfb08082ff7 --- /dev/null +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -0,0 +1,112 @@ +package com.android.server.usage; + +import android.app.usage.AppStandbyInfo; +import android.app.usage.UsageEvents; +import android.app.usage.UsageStatsManager.StandbyBuckets; +import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener; +import android.content.Context; +import android.os.Looper; + +import com.android.internal.util.IndentingPrintWriter; + +import java.io.PrintWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.Set; + +public interface AppStandbyInternal { + /** + * TODO AppStandbyController should probably be a binder service, and then we shouldn't need + * this method. + */ + static AppStandbyInternal newAppStandbyController(ClassLoader loader, Context context, + Looper looper) { + try { + final Class<?> clazz = Class.forName("com.android.server.usage.AppStandbyController", + true, loader); + final Constructor<?> ctor = clazz.getConstructor(Context.class, Looper.class); + return (AppStandbyInternal) ctor.newInstance(context, looper); + } catch (NoSuchMethodException | InstantiationException + | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) { + throw new RuntimeException("Unable to instantiate AppStandbyController!", e); + } + } + + void onBootPhase(int phase); + + boolean isParoledOrCharging(); + + void postCheckIdleStates(int userId); + + /** + * We send a different message to check idle states once, otherwise we would end up + * scheduling a series of repeating checkIdleStates each time we fired off one. + */ + void postOneTimeCheckIdleStates(); + + void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId); + + void setLastJobRunTime(String packageName, int userId, long elapsedRealtime); + + long getTimeSinceLastJobRun(String packageName, int userId); + + void onUserRemoved(int userId); + + void addListener(AppIdleStateChangeListener listener); + + void removeListener(AppIdleStateChangeListener listener); + + int getAppId(String packageName); + + boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime, + boolean shouldObfuscateInstantApps); + + /** + * Checks if an app has been idle for a while and filters out apps that are excluded. + * It returns false if the current system state allows all apps to be considered active. + * This happens if the device is plugged in or temporarily allowed to make exceptions. + * Called by interface impls. + */ + boolean isAppIdleFiltered(String packageName, int appId, int userId, + long elapsedRealtime); + + int[] getIdleUidsForUser(int userId); + + void setAppIdleAsync(String packageName, boolean idle, int userId); + + @StandbyBuckets + int getAppStandbyBucket(String packageName, int userId, + long elapsedRealtime, boolean shouldObfuscateInstantApps); + + List<AppStandbyInfo> getAppStandbyBuckets(int userId); + + void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, + int reason, long elapsedRealtime, boolean resetTimeout); + + void addActiveDeviceAdmin(String adminPkg, int userId); + + void setActiveAdminApps(Set<String> adminPkgs, int userId); + + void onAdminDataAvailable(); + + void clearCarrierPrivilegedApps(); + + void flushToDisk(int userId); + + void flushDurationsToDisk(); + + void initializeDefaultsForSystemApps(int userId); + + void postReportContentProviderUsage(String name, String packageName, int userId); + + void postReportSyncScheduled(String packageName, int userId, boolean exempted); + + void postReportExemptedSyncStart(String packageName, int userId); + + void dumpUser(IndentingPrintWriter idpw, int userId, String pkg); + + void dumpState(String[] args, PrintWriter pw); + + boolean isAppIdleEnabled(); +} diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java index 1e4861a89694..1e4861a89694 100644 --- a/services/usage/java/com/android/server/usage/AppIdleHistory.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 75e8fb5a34fe..df5d6aeb8ee5 100644 --- a/services/usage/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -120,9 +120,9 @@ import java.util.concurrent.CountDownLatch; * Manages the standby state of an app, listening to various events. * * Unit test: - atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java + atest com.android.server.usage.AppStandbyControllerTests */ -public class AppStandbyController { +public class AppStandbyController implements AppStandbyInternal { private static final String TAG = "AppStandbyController"; static final boolean DEBUG = false; @@ -247,7 +247,7 @@ public class AppStandbyController { /** The length of time phone must be charging before considered stable enough to run jobs */ long mStableChargingThresholdMillis; - volatile boolean mAppIdleEnabled; + private volatile boolean mAppIdleEnabled; boolean mAppIdleTempParoled; boolean mCharging; boolean mChargingStable; @@ -320,7 +320,7 @@ public class AppStandbyController { } } - AppStandbyController(Context context, Looper looper) { + public AppStandbyController(Context context, Looper looper) { this(new Injector(context, looper)); } @@ -351,6 +351,7 @@ public class AppStandbyController { null, mHandler); } + @VisibleForTesting void setAppIdleEnabled(boolean enabled) { synchronized (mAppIdleLock) { if (mAppIdleEnabled != enabled) { @@ -363,6 +364,12 @@ public class AppStandbyController { } } + @Override + public boolean isAppIdleEnabled() { + return mAppIdleEnabled; + } + + @Override public void onBootPhase(int phase) { mInjector.onBootPhase(phase); if (phase == PHASE_SYSTEM_SERVICES_READY) { @@ -400,7 +407,7 @@ public class AppStandbyController { } } - void reportContentProviderUsage(String authority, String providerPkgName, int userId) { + private void reportContentProviderUsage(String authority, String providerPkgName, int userId) { if (!mAppIdleEnabled) return; // Get sync adapters for the authority @@ -432,7 +439,7 @@ public class AppStandbyController { } } - void reportExemptedSyncScheduled(String packageName, int userId) { + private void reportExemptedSyncScheduled(String packageName, int userId) { if (!mAppIdleEnabled) return; final int bucketToPromote; @@ -463,7 +470,7 @@ public class AppStandbyController { } } - void reportUnexemptedSyncScheduled(String packageName, int userId) { + private void reportUnexemptedSyncScheduled(String packageName, int userId) { if (!mAppIdleEnabled) return; final long elapsedRealtime = mInjector.elapsedRealtime(); @@ -482,7 +489,7 @@ public class AppStandbyController { } } - void reportExemptedSyncStart(String packageName, int userId) { + private void reportExemptedSyncStart(String packageName, int userId) { if (!mAppIdleEnabled) return; final long elapsedRealtime = mInjector.elapsedRealtime(); @@ -497,6 +504,7 @@ public class AppStandbyController { } } + @VisibleForTesting void setChargingState(boolean charging) { synchronized (mAppIdleLock) { if (mCharging != charging) { @@ -517,7 +525,7 @@ public class AppStandbyController { } } - void updateChargingStableState() { + private void updateChargingStableState() { synchronized (mAppIdleLock) { if (mChargingStable != mCharging) { if (DEBUG) Slog.d(TAG, "Setting mChargingStable to " + mCharging); @@ -527,8 +535,7 @@ public class AppStandbyController { } } - /** Paroled here means temporary pardon from being inactive */ - void setAppIdleParoled(boolean paroled) { + private void setAppIdleParoled(boolean paroled) { synchronized (mAppIdleLock) { final long now = mInjector.currentTimeMillis(); if (mAppIdleTempParoled != paroled) { @@ -545,7 +552,8 @@ public class AppStandbyController { } } - boolean isParoledOrCharging() { + @Override + public boolean isParoledOrCharging() { if (!mAppIdleEnabled) return true; synchronized (mAppIdleLock) { // Only consider stable charging when determining charge state. @@ -583,15 +591,13 @@ public class AppStandbyController { mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED); } - void postCheckIdleStates(int userId) { + @Override + public void postCheckIdleStates(int userId) { mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0)); } - /** - * We send a different message to check idle states once, otherwise we would end up - * scheduling a series of repeating checkIdleStates each time we fired off one. - */ - void postOneTimeCheckIdleStates() { + @Override + public void postOneTimeCheckIdleStates() { if (mInjector.getBootPhase() < PHASE_SYSTEM_SERVICES_READY) { // Not booted yet; wait for it! mPendingOneTimeCheckIdleStates = true; @@ -601,10 +607,7 @@ public class AppStandbyController { } } - /** - * Check all running users' or specified user's apps to see if they enter an idle state. - * @return Returns whether checking should continue periodically. - */ + @VisibleForTesting boolean checkIdleStates(int checkUserId) { if (!mAppIdleEnabled) { return false; @@ -776,19 +779,15 @@ public class AppStandbyController { * @return the bucket for the app, based on time since last used */ @GuardedBy("mAppIdleLock") - @StandbyBuckets int getBucketForLocked(String packageName, int userId, + @StandbyBuckets + private int getBucketForLocked(String packageName, int userId, long elapsedRealtime) { int bucketIndex = mAppIdleHistory.getThresholdIndex(packageName, userId, elapsedRealtime, mAppStandbyScreenThresholds, mAppStandbyElapsedThresholds); return THRESHOLD_BUCKETS[bucketIndex]; } - /** - * Check if it's been a while since last parole and let idle apps do some work. - * If network is not available, delay parole until it is available up until the end of the - * parole window. Force the parole to be set if end of the parole window is reached. - */ - void checkParoleTimeout() { + private void checkParoleTimeout() { boolean setParoled = false; boolean waitForNetwork = false; NetworkInfo activeNetwork = mConnectivityManager.getActiveNetworkInfo(); @@ -845,7 +844,7 @@ public class AppStandbyController { } } - void onDeviceIdleModeChanged() { + private void onDeviceIdleModeChanged() { final boolean deviceIdle = mPowerManager.isDeviceIdleMode(); if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle); boolean paroled = false; @@ -869,7 +868,8 @@ public class AppStandbyController { setAppIdleParoled(paroled); } - void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) { + @Override + public void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) { if (!mAppIdleEnabled) return; synchronized (mAppIdleLock) { // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back @@ -950,14 +950,7 @@ public class AppStandbyController { } } - /** - * Forces the app's beginIdleTime and lastUsedTime to reflect idle or active. If idle, - * then it rolls back the beginIdleTime and lastUsedTime to a point in time that's behind - * the threshold for idle. - * - * This method is always called from the handler thread, so not much synchronization is - * required. - */ + @VisibleForTesting void forceIdleState(String packageName, int userId, boolean idle) { if (!mAppIdleEnabled) return; @@ -983,12 +976,14 @@ public class AppStandbyController { } } + @Override public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) { synchronized (mAppIdleLock) { mAppIdleHistory.setLastJobRunTime(packageName, userId, elapsedRealtime); } } + @Override public long getTimeSinceLastJobRun(String packageName, int userId) { final long elapsedRealtime = mInjector.elapsedRealtime(); synchronized (mAppIdleLock) { @@ -996,6 +991,7 @@ public class AppStandbyController { } } + @Override public void onUserRemoved(int userId) { synchronized (mAppIdleLock) { mAppIdleHistory.onUserRemoved(userId); @@ -1011,7 +1007,8 @@ public class AppStandbyController { } } - void addListener(AppIdleStateChangeListener listener) { + @Override + public void addListener(AppIdleStateChangeListener listener) { synchronized (mPackageAccessListeners) { if (!mPackageAccessListeners.contains(listener)) { mPackageAccessListeners.add(listener); @@ -1019,13 +1016,15 @@ public class AppStandbyController { } } - void removeListener(AppIdleStateChangeListener listener) { + @Override + public void removeListener(AppIdleStateChangeListener listener) { synchronized (mPackageAccessListeners) { mPackageAccessListeners.remove(listener); } } - int getAppId(String packageName) { + @Override + public int getAppId(String packageName) { try { ApplicationInfo ai = mPackageManager.getApplicationInfo(packageName, PackageManager.MATCH_ANY_USER @@ -1036,7 +1035,8 @@ public class AppStandbyController { } } - boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime, + @Override + public boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime, boolean shouldObfuscateInstantApps) { if (isParoledOrCharging()) { return false; @@ -1048,8 +1048,7 @@ public class AppStandbyController { return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime); } - /** Returns true if this app should be whitelisted for some reason, to never go into standby */ - boolean isAppSpecial(String packageName, int appId, int userId) { + private boolean isAppSpecial(String packageName, int appId, int userId) { if (packageName == null) return false; // If not enabled at all, of course nobody is ever idle. if (!mAppIdleEnabled) { @@ -1102,13 +1101,8 @@ public class AppStandbyController { return false; } - /** - * Checks if an app has been idle for a while and filters out apps that are excluded. - * It returns false if the current system state allows all apps to be considered active. - * This happens if the device is plugged in or temporarily allowed to make exceptions. - * Called by interface impls. - */ - boolean isAppIdleFiltered(String packageName, int appId, int userId, + @Override + public boolean isAppIdleFiltered(String packageName, int appId, int userId, long elapsedRealtime) { if (isAppSpecial(packageName, appId, userId)) { return false; @@ -1117,7 +1111,8 @@ public class AppStandbyController { } } - int[] getIdleUidsForUser(int userId) { + @Override + public int[] getIdleUidsForUser(int userId) { if (!mAppIdleEnabled) { return new int[0]; } @@ -1181,13 +1176,15 @@ public class AppStandbyController { return res; } - void setAppIdleAsync(String packageName, boolean idle, int userId) { + @Override + public void setAppIdleAsync(String packageName, boolean idle, int userId) { if (packageName == null || !mAppIdleEnabled) return; mHandler.obtainMessage(MSG_FORCE_IDLE_STATE, userId, idle ? 1 : 0, packageName) .sendToTarget(); } + @Override @StandbyBuckets public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime, boolean shouldObfuscateInstantApps) { if (!mAppIdleEnabled || (shouldObfuscateInstantApps @@ -1200,18 +1197,21 @@ public class AppStandbyController { } } + @Override public List<AppStandbyInfo> getAppStandbyBuckets(int userId) { synchronized (mAppIdleLock) { return mAppIdleHistory.getAppStandbyBuckets(userId, mAppIdleEnabled); } } + @VisibleForTesting void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, int reason, long elapsedRealtime) { setAppStandbyBucket(packageName, userId, newBucket, reason, elapsedRealtime, false); } - void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, + @Override + public void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, int reason, long elapsedRealtime, boolean resetTimeout) { synchronized (mAppIdleLock) { // If the package is not installed, don't allow the bucket to be set. @@ -1279,6 +1279,7 @@ public class AppStandbyController { } } + @Override public void addActiveDeviceAdmin(String adminPkg, int userId) { synchronized (mActiveAdminApps) { Set<String> adminPkgs = mActiveAdminApps.get(userId); @@ -1290,6 +1291,7 @@ public class AppStandbyController { } } + @Override public void setActiveAdminApps(Set<String> adminPkgs, int userId) { synchronized (mActiveAdminApps) { if (adminPkgs == null) { @@ -1300,6 +1302,7 @@ public class AppStandbyController { } } + @Override public void onAdminDataAvailable() { mAdminDataAvailableLatch.countDown(); } @@ -1314,6 +1317,7 @@ public class AppStandbyController { } } + @VisibleForTesting Set<String> getActiveAdminAppsForTest(int userId) { synchronized (mActiveAdminApps) { return mActiveAdminApps.get(userId); @@ -1342,7 +1346,8 @@ public class AppStandbyController { } } - void clearCarrierPrivilegedApps() { + @Override + public void clearCarrierPrivilegedApps() { if (DEBUG) { Slog.i(TAG, "Clearing carrier privileged apps list"); } @@ -1368,7 +1373,7 @@ public class AppStandbyController { return packageName != null && packageName.equals(activeScorer); } - void informListeners(String packageName, int userId, int bucket, int reason, + private void informListeners(String packageName, int userId, int bucket, int reason, boolean userInteraction) { final boolean idle = bucket >= STANDBY_BUCKET_RARE; synchronized (mPackageAccessListeners) { @@ -1381,7 +1386,7 @@ public class AppStandbyController { } } - void informParoleStateChanged() { + private void informParoleStateChanged() { final boolean paroled = isParoledOrCharging(); synchronized (mPackageAccessListeners) { for (AppIdleStateChangeListener listener : mPackageAccessListeners) { @@ -1390,13 +1395,15 @@ public class AppStandbyController { } } - void flushToDisk(int userId) { + @Override + public void flushToDisk(int userId) { synchronized (mAppIdleLock) { mAppIdleHistory.writeAppIdleTimes(userId); } } - void flushDurationsToDisk() { + @Override + public void flushDurationsToDisk() { // Persist elapsed and screen on time. If this fails for whatever reason, the apps will be // considered not-idle, which is the safest outcome in such an event. synchronized (mAppIdleLock) { @@ -1404,10 +1411,11 @@ public class AppStandbyController { } } - boolean isDisplayOn() { + private boolean isDisplayOn() { return mInjector.isDefaultDisplayOn(); } + @VisibleForTesting void clearAppIdleForPackage(String packageName, int userId) { synchronized (mAppIdleLock) { mAppIdleHistory.clearUsage(packageName, userId); @@ -1431,7 +1439,8 @@ public class AppStandbyController { } } - void initializeDefaultsForSystemApps(int userId) { + @Override + public void initializeDefaultsForSystemApps(int userId) { if (!mSystemServicesReady) { // Do it later, since SettingsProvider wasn't queried yet for app_standby_enabled mPendingInitializeDefaults = true; @@ -1461,7 +1470,8 @@ public class AppStandbyController { } } - void postReportContentProviderUsage(String name, String packageName, int userId) { + @Override + public void postReportContentProviderUsage(String name, String packageName, int userId) { SomeArgs args = SomeArgs.obtain(); args.arg1 = name; args.arg2 = packageName; @@ -1470,23 +1480,27 @@ public class AppStandbyController { .sendToTarget(); } - void postReportSyncScheduled(String packageName, int userId, boolean exempted) { + @Override + public void postReportSyncScheduled(String packageName, int userId, boolean exempted) { mHandler.obtainMessage(MSG_REPORT_SYNC_SCHEDULED, userId, exempted ? 1 : 0, packageName) .sendToTarget(); } - void postReportExemptedSyncStart(String packageName, int userId) { + @Override + public void postReportExemptedSyncStart(String packageName, int userId) { mHandler.obtainMessage(MSG_REPORT_EXEMPTED_SYNC_START, userId, 0, packageName) .sendToTarget(); } - void dumpUser(IndentingPrintWriter idpw, int userId, String pkg) { + @Override + public void dumpUser(IndentingPrintWriter idpw, int userId, String pkg) { synchronized (mAppIdleLock) { mAppIdleHistory.dump(idpw, userId, pkg); } } - void dumpState(String[] args, PrintWriter pw) { + @Override + public void dumpState(String[] args, PrintWriter pw) { synchronized (mAppIdleLock) { pw.println("Carrier privileged apps (have=" + mHaveCarrierPrivilegedApps + "): " + mCarrierPrivilegedApps); diff --git a/api/current.txt b/api/current.txt index 3ce043c89b65..a123f2cc8ba2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2834,6 +2834,7 @@ package android.accessibilityservice { method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo(); method @NonNull public final android.accessibilityservice.AccessibilityService.SoftKeyboardController getSoftKeyboardController(); method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows(); + method @NonNull public final android.util.SparseArray<java.util.List<android.view.accessibility.AccessibilityWindowInfo>> getWindowsOnAllDisplays(); method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent); method public final android.os.IBinder onBind(android.content.Intent); method @Deprecated protected boolean onGesture(int); @@ -6363,6 +6364,7 @@ package android.app { method public android.view.WindowAnimationFrameStats getWindowAnimationFrameStats(); method public android.view.WindowContentFrameStats getWindowContentFrameStats(int); method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows(); + method @NonNull public android.util.SparseArray<java.util.List<android.view.accessibility.AccessibilityWindowInfo>> getWindowsOnAllDisplays(); method public void grantRuntimePermission(String, String); method public void grantRuntimePermissionAsUser(String, String, android.os.UserHandle); method public boolean injectInputEvent(android.view.InputEvent, boolean); @@ -57410,7 +57412,7 @@ package android.widget { method public boolean onPrivateIMECommand(String, android.os.Bundle); method public void onRestoreInstanceState(android.os.Parcelable); method public android.os.Parcelable onSaveInstanceState(); - method protected void onSelectionChanged(int, int); + method @CallSuper protected void onSelectionChanged(int, int); method protected void onTextChanged(CharSequence, int, int, int); method public boolean onTextContextMenuItem(int); method public void removeTextChangedListener(android.text.TextWatcher); diff --git a/api/system-current.txt b/api/system-current.txt index 6f4cb0f1a30d..696801a28882 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -24,6 +24,7 @@ package android { field public static final String BACKUP = "android.permission.BACKUP"; field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE"; field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE"; + field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE"; field @Deprecated public static final String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE"; field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE"; field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"; @@ -7304,6 +7305,14 @@ package android.telephony { method @NonNull public android.telephony.CarrierRestrictionRules.Builder setMultiSimPolicy(int); } + public abstract class CellBroadcastService extends android.app.Service { + ctor public CellBroadcastService(); + method @CallSuper public android.os.IBinder onBind(android.content.Intent); + method public abstract void onCdmaCellBroadcastSms(int, byte[]); + method public abstract void onGsmCellBroadcastSms(int, byte[]); + field public static final String CELL_BROADCAST_SERVICE_INTERFACE = "android.telephony.CellBroadcastService"; + } + public final class DataFailCause { field public static final int ACCESS_ATTEMPT_ALREADY_IN_PROGRESS = 2219; // 0x8ab field public static final int ACCESS_BLOCK = 2087; // 0x827 @@ -7876,8 +7885,8 @@ package android.telephony { field public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864; // 0x4000000 field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000 field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000 - field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER = 268435456; // 0x10000000 - field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER = 536870912; // 0x20000000 + field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000 + field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000 field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800 field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000 field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000 diff --git a/api/test-current.txt b/api/test-current.txt index 3dfd62e11622..d709886eff66 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5,6 +5,7 @@ package android { field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS"; field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; + field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE"; field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; @@ -2404,7 +2405,6 @@ package android.provider { field public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS = "location_access_check_delay_millis"; field public static final String LOCATION_ACCESS_CHECK_INTERVAL_MILLIS = "location_access_check_interval_millis"; field public static final String NOTIFICATION_BADGING = "notification_badging"; - field @Deprecated public static final String NOTIFICATION_BUBBLES = "notification_bubbles"; field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds"; field public static final String USER_SETUP_COMPLETE = "user_setup_complete"; field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service"; @@ -2901,8 +2901,10 @@ package android.telephony { } public class PhoneStateListener { - field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER = 268435456; // 0x10000000 - field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER = 536870912; // 0x20000000 + method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber); + method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber); + field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000 + field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000 } public class ServiceState implements android.os.Parcelable { diff --git a/cmds/locksettings/TEST_MAPPING b/cmds/locksettings/TEST_MAPPING index c1cba5f7f22d..56f5cc034f05 100644 --- a/cmds/locksettings/TEST_MAPPING +++ b/cmds/locksettings/TEST_MAPPING @@ -1,5 +1,5 @@ { - "presubmit": [ + "presubmit-devicepolicy": [ { "name": "CtsDevicePolicyManagerTestCases", "options": [ diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp index 42132ee0daae..7d446a9a1ed6 100644 --- a/cmds/statsd/src/main.cpp +++ b/cmds/statsd/src/main.cpp @@ -53,6 +53,8 @@ void sigHandler(int sig) { if (gStatsService != nullptr) { gStatsService->Terminate(); } + ALOGW("statsd terminated on receiving signal %d.", sig); + exit(1); } void registerSigHandler() diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp index 76ee9a6e5996..460b9e0995c8 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -1199,87 +1199,66 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActi ConfigKey cfgKey1(uid, 12341); long timeBase1 = 1; - sp<StatsLogProcessor> processor = + sp<StatsLogProcessor> processor1 = CreateStatsLogProcessor(timeBase1, timeBase1, config1, cfgKey1); // Metric 1 is not active. // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_EQ(1, processor->mMetricsManagers.size()); - auto it = processor->mMetricsManagers.find(cfgKey1); - EXPECT_TRUE(it != processor->mMetricsManagers.end()); + EXPECT_EQ(1, processor1->mMetricsManagers.size()); + auto it = processor1->mMetricsManagers.find(cfgKey1); + EXPECT_TRUE(it != processor1->mMetricsManagers.end()); auto& metricsManager1 = it->second; EXPECT_TRUE(metricsManager1->isActive()); - auto metricIt = metricsManager1->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId1) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); - auto& metricProducer1 = *metricIt; - EXPECT_FALSE(metricProducer1->isActive()); - - metricIt = metricsManager1->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId2) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1->mAllMetricProducers.end()); - auto& metricProducer2 = *metricIt; - EXPECT_TRUE(metricProducer2->isActive()); - - int i = 0; - for (; i < metricsManager1->mAllAtomMatchers.size(); i++) { - if (metricsManager1->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger1->atom_matcher_id()) { - break; - } - } - const auto& activation1 = metricProducer1->mEventActivationMap.at(i); - EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns); - EXPECT_EQ(0, activation1->start_ns); - EXPECT_EQ(kNotActive, activation1->state); - EXPECT_EQ(ACTIVATE_ON_BOOT, activation1->activationType); - - i = 0; - for (; i < metricsManager1->mAllAtomMatchers.size(); i++) { - if (metricsManager1->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger2->atom_matcher_id()) { - break; - } - } - const auto& activation2 = metricProducer1->mEventActivationMap.at(i); - EXPECT_EQ(200 * NS_PER_SEC, activation2->ttl_ns); - EXPECT_EQ(0, activation2->start_ns); - EXPECT_EQ(kNotActive, activation2->state); - EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation2->activationType); + EXPECT_EQ(metricsManager1->mAllMetricProducers.size(), 2); + // We assume that the index of a MetricProducer within the mAllMetricProducers + // array follows the order in which metrics are added to the config. + auto& metricProducer1_1 = metricsManager1->mAllMetricProducers[0]; + EXPECT_EQ(metricProducer1_1->getMetricId(), metricId1); + EXPECT_FALSE(metricProducer1_1->isActive()); // inactive due to associated MetricActivation + + auto& metricProducer1_2 = metricsManager1->mAllMetricProducers[1]; + EXPECT_EQ(metricProducer1_2->getMetricId(), metricId2); + EXPECT_TRUE(metricProducer1_2->isActive()); + + EXPECT_EQ(metricProducer1_1->mEventActivationMap.size(), 2); + // The key in mEventActivationMap is the index of the associated atom matcher. We assume + // that matchers are indexed in the order that they are added to the config. + const auto& activation1_1_1 = metricProducer1_1->mEventActivationMap.at(0); + EXPECT_EQ(100 * NS_PER_SEC, activation1_1_1->ttl_ns); + EXPECT_EQ(0, activation1_1_1->start_ns); + EXPECT_EQ(kNotActive, activation1_1_1->state); + EXPECT_EQ(ACTIVATE_ON_BOOT, activation1_1_1->activationType); + + const auto& activation1_1_2 = metricProducer1_1->mEventActivationMap.at(1); + EXPECT_EQ(200 * NS_PER_SEC, activation1_1_2->ttl_ns); + EXPECT_EQ(0, activation1_1_2->start_ns); + EXPECT_EQ(kNotActive, activation1_1_2->state); + EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1_1_2->activationType); // }}}------------------------------------------------------------------------------ // Trigger Activation 1 for Metric 1 std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")}; auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 100 + timeBase1); - processor->OnLogEvent(event.get()); + processor1->OnLogEvent(event.get()); // Metric 1 is not active; Activation 1 set to kActiveOnBoot // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_FALSE(metricProducer1->isActive()); - EXPECT_EQ(0, activation1->start_ns); - EXPECT_EQ(kActiveOnBoot, activation1->state); - EXPECT_EQ(0, activation2->start_ns); - EXPECT_EQ(kNotActive, activation2->state); + EXPECT_FALSE(metricProducer1_1->isActive()); + EXPECT_EQ(0, activation1_1_1->start_ns); + EXPECT_EQ(kActiveOnBoot, activation1_1_1->state); + EXPECT_EQ(0, activation1_1_2->start_ns); + EXPECT_EQ(kNotActive, activation1_1_2->state); - EXPECT_TRUE(metricProducer2->isActive()); + EXPECT_TRUE(metricProducer1_2->isActive()); // }}}----------------------------------------------------------------------------- // Simulate shutdown by saving state to disk int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC; - processor->SaveActiveConfigsToDisk(shutDownTime); - EXPECT_FALSE(metricProducer1->isActive()); - int64_t ttl1 = metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC; + processor1->SaveActiveConfigsToDisk(shutDownTime); + EXPECT_FALSE(metricProducer1_1->isActive()); // Simulate device restarted state by creating new instance of StatsLogProcessor with the // same config. @@ -1293,55 +1272,34 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActi EXPECT_EQ(1, processor2->mMetricsManagers.size()); it = processor2->mMetricsManagers.find(cfgKey1); EXPECT_TRUE(it != processor2->mMetricsManagers.end()); - auto& metricsManager1001 = it->second; - EXPECT_TRUE(metricsManager1001->isActive()); - - metricIt = metricsManager1001->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId1) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); - auto& metricProducer1001 = *metricIt; - EXPECT_FALSE(metricProducer1001->isActive()); - - metricIt = metricsManager1001->mAllMetricProducers.begin(); - for (; metricIt != metricsManager1001->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId2) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManager1001->mAllMetricProducers.end()); - auto& metricProducer1002 = *metricIt; - EXPECT_TRUE(metricProducer1002->isActive()); - - i = 0; - for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) { - if (metricsManager1001->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger1->atom_matcher_id()) { - break; - } - } - const auto& activation1001_1 = metricProducer1001->mEventActivationMap.at(i); - EXPECT_EQ(100 * NS_PER_SEC, activation1001_1->ttl_ns); - EXPECT_EQ(0, activation1001_1->start_ns); - EXPECT_EQ(kNotActive, activation1001_1->state); - EXPECT_EQ(ACTIVATE_ON_BOOT, activation1001_1->activationType); - - i = 0; - for (; i < metricsManager1001->mAllAtomMatchers.size(); i++) { - if (metricsManager1001->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger2->atom_matcher_id()) { - break; - } - } + auto& metricsManager2 = it->second; + EXPECT_TRUE(metricsManager2->isActive()); - const auto& activation1001_2 = metricProducer1001->mEventActivationMap.at(i); - EXPECT_EQ(200 * NS_PER_SEC, activation1001_2->ttl_ns); - EXPECT_EQ(0, activation1001_2->start_ns); - EXPECT_EQ(kNotActive, activation1001_2->state); - EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1001_2->activationType); + EXPECT_EQ(metricsManager2->mAllMetricProducers.size(), 2); + // We assume that the index of a MetricProducer within the mAllMetricProducers + // array follows the order in which metrics are added to the config. + auto& metricProducer2_1 = metricsManager2->mAllMetricProducers[0]; + EXPECT_EQ(metricProducer2_1->getMetricId(), metricId1); + EXPECT_FALSE(metricProducer2_1->isActive()); + + auto& metricProducer2_2 = metricsManager2->mAllMetricProducers[1]; + EXPECT_EQ(metricProducer2_2->getMetricId(), metricId2); + EXPECT_TRUE(metricProducer2_2->isActive()); + + EXPECT_EQ(metricProducer2_1->mEventActivationMap.size(), 2); + // The key in mEventActivationMap is the index of the associated atom matcher. We assume + // that matchers are indexed in the order that they are added to the config. + const auto& activation2_1_1 = metricProducer2_1->mEventActivationMap.at(0); + EXPECT_EQ(100 * NS_PER_SEC, activation2_1_1->ttl_ns); + EXPECT_EQ(0, activation2_1_1->start_ns); + EXPECT_EQ(kNotActive, activation2_1_1->state); + EXPECT_EQ(ACTIVATE_ON_BOOT, activation2_1_1->activationType); + + const auto& activation2_1_2 = metricProducer2_1->mEventActivationMap.at(1); + EXPECT_EQ(200 * NS_PER_SEC, activation2_1_2->ttl_ns); + EXPECT_EQ(0, activation2_1_2->start_ns); + EXPECT_EQ(kNotActive, activation2_1_2->state); + EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation2_1_2->activationType); // }}}----------------------------------------------------------------------------------- // Load saved state from disk. @@ -1350,13 +1308,14 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActi // Metric 1 active; Activation 1 is active, Activation 2 is not active // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducer1001->isActive()); - EXPECT_EQ(timeBase2 + ttl1 - activation1001_1->ttl_ns, activation1001_1->start_ns); - EXPECT_EQ(kActive, activation1001_1->state); - EXPECT_EQ(0, activation1001_2->start_ns); - EXPECT_EQ(kNotActive, activation1001_2->state); + EXPECT_TRUE(metricProducer2_1->isActive()); + int64_t ttl1 = metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC; + EXPECT_EQ(timeBase2 + ttl1 - activation2_1_1->ttl_ns, activation2_1_1->start_ns); + EXPECT_EQ(kActive, activation2_1_1->state); + EXPECT_EQ(0, activation2_1_2->start_ns); + EXPECT_EQ(kNotActive, activation2_1_2->state); - EXPECT_TRUE(metricProducer1002->isActive()); + EXPECT_TRUE(metricProducer2_2->isActive()); // }}}-------------------------------------------------------------------------------- // Trigger Activation 2 for Metric 1. @@ -1369,23 +1328,23 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActi // Metric 1 active; Activation 1 is active, Activation 2 is active // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducer1001->isActive()); - EXPECT_EQ(timeBase2 + ttl1 - activation1001_1->ttl_ns, activation1001_1->start_ns); - EXPECT_EQ(kActive, activation1001_1->state); - EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activation1001_2->start_ns); - EXPECT_EQ(kActive, activation1001_2->state); + EXPECT_TRUE(metricProducer2_1->isActive()); + EXPECT_EQ(timeBase2 + ttl1 - activation2_1_1->ttl_ns, activation2_1_1->start_ns); + EXPECT_EQ(kActive, activation2_1_1->state); + EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activation2_1_2->start_ns); + EXPECT_EQ(kActive, activation2_1_2->state); - EXPECT_TRUE(metricProducer1002->isActive()); + EXPECT_TRUE(metricProducer2_2->isActive()); // }}}--------------------------------------------------------------------------- // Simulate shutdown by saving state to disk shutDownTime = timeBase2 + 50 * NS_PER_SEC; processor2->SaveActiveConfigsToDisk(shutDownTime); - EXPECT_TRUE(metricProducer1001->isActive()); - EXPECT_TRUE(metricProducer1002->isActive()); - ttl1 = timeBase2 + metric1ActivationTrigger1->ttl_seconds() * NS_PER_SEC - shutDownTime; - int64_t ttl2 = screenOnEvent->GetElapsedTimestampNs() + - metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC - shutDownTime; + EXPECT_TRUE(metricProducer2_1->isActive()); + EXPECT_TRUE(metricProducer2_2->isActive()); + ttl1 -= shutDownTime - timeBase2; + int64_t ttl2 = metric1ActivationTrigger2->ttl_seconds() * NS_PER_SEC + - (shutDownTime - screenOnEvent->GetElapsedTimestampNs()); // Simulate device restarted state by creating new instance of StatsLogProcessor with the // same config. @@ -1399,55 +1358,34 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActi EXPECT_EQ(1, processor3->mMetricsManagers.size()); it = processor3->mMetricsManagers.find(cfgKey1); EXPECT_TRUE(it != processor3->mMetricsManagers.end()); - auto& metricsManagerTimeBase3 = it->second; - EXPECT_TRUE(metricsManagerTimeBase3->isActive()); - - metricIt = metricsManagerTimeBase3->mAllMetricProducers.begin(); - for (; metricIt != metricsManagerTimeBase3->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId1) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManagerTimeBase3->mAllMetricProducers.end()); - auto& metricProducerTimeBase3_1 = *metricIt; - EXPECT_FALSE(metricProducerTimeBase3_1->isActive()); - - metricIt = metricsManagerTimeBase3->mAllMetricProducers.begin(); - for (; metricIt != metricsManagerTimeBase3->mAllMetricProducers.end(); metricIt++) { - if ((*metricIt)->getMetricId() == metricId2) { - break; - } - } - EXPECT_TRUE(metricIt != metricsManagerTimeBase3->mAllMetricProducers.end()); - auto& metricProducerTimeBase3_2 = *metricIt; - EXPECT_TRUE(metricProducerTimeBase3_2->isActive()); - - i = 0; - for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) { - if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger1->atom_matcher_id()) { - break; - } - } - const auto& activationTimeBase3_1 = metricProducerTimeBase3_1->mEventActivationMap.at(i); - EXPECT_EQ(100 * NS_PER_SEC, activationTimeBase3_1->ttl_ns); - EXPECT_EQ(0, activationTimeBase3_1->start_ns); - EXPECT_EQ(kNotActive, activationTimeBase3_1->state); - EXPECT_EQ(ACTIVATE_ON_BOOT, activationTimeBase3_1->activationType); - - i = 0; - for (; i < metricsManagerTimeBase3->mAllAtomMatchers.size(); i++) { - if (metricsManagerTimeBase3->mAllAtomMatchers[i]->getId() == - metric1ActivationTrigger2->atom_matcher_id()) { - break; - } - } + auto& metricsManager3 = it->second; + EXPECT_TRUE(metricsManager3->isActive()); - const auto& activationTimeBase3_2 = metricProducerTimeBase3_1->mEventActivationMap.at(i); - EXPECT_EQ(200 * NS_PER_SEC, activationTimeBase3_2->ttl_ns); - EXPECT_EQ(0, activationTimeBase3_2->start_ns); - EXPECT_EQ(kNotActive, activationTimeBase3_2->state); - EXPECT_EQ(ACTIVATE_IMMEDIATELY, activationTimeBase3_2->activationType); + EXPECT_EQ(metricsManager3->mAllMetricProducers.size(), 2); + // We assume that the index of a MetricProducer within the mAllMetricProducers + // array follows the order in which metrics are added to the config. + auto& metricProducer3_1 = metricsManager3->mAllMetricProducers[0]; + EXPECT_EQ(metricProducer3_1->getMetricId(), metricId1); + EXPECT_FALSE(metricProducer3_1->isActive()); + + auto& metricProducer3_2 = metricsManager3->mAllMetricProducers[1]; + EXPECT_EQ(metricProducer3_2->getMetricId(), metricId2); + EXPECT_TRUE(metricProducer3_2->isActive()); + + EXPECT_EQ(metricProducer3_1->mEventActivationMap.size(), 2); + // The key in mEventActivationMap is the index of the associated atom matcher. We assume + // that matchers are indexed in the order that they are added to the config. + const auto& activation3_1_1 = metricProducer3_1->mEventActivationMap.at(0); + EXPECT_EQ(100 * NS_PER_SEC, activation3_1_1->ttl_ns); + EXPECT_EQ(0, activation3_1_1->start_ns); + EXPECT_EQ(kNotActive, activation3_1_1->state); + EXPECT_EQ(ACTIVATE_ON_BOOT, activation3_1_1->activationType); + + const auto& activation3_1_2 = metricProducer3_1->mEventActivationMap.at(1); + EXPECT_EQ(200 * NS_PER_SEC, activation3_1_2->ttl_ns); + EXPECT_EQ(0, activation3_1_2->start_ns); + EXPECT_EQ(kNotActive, activation3_1_2->state); + EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation3_1_2->activationType); // }}}---------------------------------------------------------------------------------- // Load saved state from disk. @@ -1456,13 +1394,13 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActi // Metric 1 active: Activation 1 is active, Activation 2 is active // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducerTimeBase3_1->isActive()); - EXPECT_EQ(timeBase3 + ttl1 - activationTimeBase3_1->ttl_ns, activationTimeBase3_1->start_ns); - EXPECT_EQ(kActive, activationTimeBase3_1->state); - EXPECT_EQ(timeBase3 + ttl2 - activationTimeBase3_2->ttl_ns, activationTimeBase3_2->start_ns); - EXPECT_EQ(kActive, activationTimeBase3_2->state); + EXPECT_TRUE(metricProducer3_1->isActive()); + EXPECT_EQ(timeBase3 + ttl1 - activation3_1_1->ttl_ns, activation3_1_1->start_ns); + EXPECT_EQ(kActive, activation3_1_1->state); + EXPECT_EQ(timeBase3 + ttl2 - activation3_1_2->ttl_ns, activation3_1_2->start_ns); + EXPECT_EQ(kActive, activation3_1_2->state); - EXPECT_TRUE(metricProducerTimeBase3_2->isActive()); + EXPECT_TRUE(metricProducer3_2->isActive()); // }}}------------------------------------------------------------------------------- @@ -1473,15 +1411,16 @@ TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivationsDifferentActi ); processor3->OnLogEvent(screenOnEvent.get()); - // Metric 1 active; Activation 1 is not active, Activation 2 is set to active + // Metric 1 active; Activation 1 is inactive (above screenOnEvent causes ttl1 to expire), + // Activation 2 is set to active // Metric 2 is active. // {{{--------------------------------------------------------------------------- - EXPECT_TRUE(metricProducerTimeBase3_1->isActive()); - EXPECT_EQ(kNotActive, activationTimeBase3_1->state); - EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activationTimeBase3_2->start_ns); - EXPECT_EQ(kActive, activationTimeBase3_2->state); + EXPECT_TRUE(metricProducer3_1->isActive()); + EXPECT_EQ(kNotActive, activation3_1_1->state); + EXPECT_EQ(screenOnEvent->GetElapsedTimestampNs(), activation3_1_2->start_ns); + EXPECT_EQ(kActive, activation3_1_2->state); - EXPECT_TRUE(metricProducerTimeBase3_2->isActive()); + EXPECT_TRUE(metricProducer3_2->isActive()); // }}}--------------------------------------------------------------------------- } diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index d3c274f4bdfe..4603f08c765f 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -605,7 +605,7 @@ public abstract class AccessibilityService extends Service { } /** - * Gets the windows on the screen. This method returns only the windows + * Gets the windows on the screen of the default display. This method returns only the windows * that a sighted user can interact with, as opposed to all windows. * For example, if there is a modal dialog shown and the user cannot touch * anything behind it, then only the modal window will be reported @@ -632,6 +632,34 @@ public abstract class AccessibilityService extends Service { } /** + * Gets the windows on the screen of all displays. This method returns only the windows + * that a sighted user can interact with, as opposed to all windows. + * For example, if there is a modal dialog shown and the user cannot touch + * anything behind it, then only the modal window will be reported + * (assuming it is the top one). For convenience the returned windows + * are ordered in a descending layer order, which is the windows that + * are on top are reported first. Since the user can always + * interact with the window that has input focus by typing, the focused + * window is always returned (even if covered by a modal window). + * <p> + * <strong>Note:</strong> In order to access the windows your service has + * to declare the capability to retrieve window content by setting the + * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent} + * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}. + * Also the service has to opt-in to retrieve the interactive windows by + * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} + * flag. + * </p> + * + * @return The windows of all displays if there are windows and the service is can retrieve + * them, otherwise an empty list. The key of SparseArray is display ID. + */ + @NonNull + public final SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays() { + return AccessibilityInteractionClient.getInstance().getWindowsOnAllDisplays(mConnectionId); + } + + /** * Gets the root node in the currently active window if this service * can retrieve window content. The active window is the one that the user * is currently touching or the window with input focus, if the user is not diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 1ca07dd6ed23..4841781170e1 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -60,7 +60,7 @@ interface IAccessibilityServiceConnection { AccessibilityWindowInfo getWindow(int windowId); - List<AccessibilityWindowInfo> getWindows(); + AccessibilityWindowInfo.WindowListSparseArray getWindows(); AccessibilityServiceInfo getServiceInfo(); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 9b667a118ebc..b1565ab8a501 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -87,12 +87,6 @@ public class KeyguardManager { "android.app.action.CONFIRM_FRP_CREDENTIAL"; /** - * @hide - */ - public static final String EXTRA_BIOMETRIC_PROMPT_BUNDLE = - "android.app.extra.BIOMETRIC_PROMPT_BUNDLE"; - - /** * A CharSequence dialog title to show to the user when used with a * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}. * @hide diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 40cb29fc80ab..cb9ebac728ec 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -20,6 +20,7 @@ import static android.app.ActivityThread.DEBUG_CONFIGURATION; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -191,6 +192,17 @@ public class ResourcesManager { } } Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path); + + for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) { + final ApkKey key = mCachedApkAssets.keyAt(i); + if (key.path.equals(path)) { + WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.remove(key); + if (apkAssetsRef != null && apkAssetsRef.get() != null) { + apkAssetsRef.get().close(); + } + mCachedApkAssets.remove(key); + } + } } } @@ -1000,6 +1012,14 @@ public class ResourcesManager { } } + @TestApi + public final boolean applyConfigurationToResources(@NonNull Configuration config, + @Nullable CompatibilityInfo compat) { + synchronized(this) { + return applyConfigurationToResourcesLocked(config, compat); + } + } + public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config, @Nullable CompatibilityInfo compat) { try { diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 049933743450..e81dc1c59040 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -1343,13 +1343,13 @@ public final class SystemServiceRegistry { * @hide */ public static <T> void registerCachedService(String serviceName, Class<T> serviceWrapperClass, - BiFunction<ContextImpl, IBinder, T> serviceFetcher) { + BiFunction<Context, IBinder, T> serviceFetcher) { registerService(serviceName, serviceWrapperClass, new CachedServiceFetcher<T>() { @Override public T createService(ContextImpl ctx) throws ServiceNotFoundException { IBinder b = ServiceManager.getServiceOrThrow(serviceName); - return serviceFetcher.apply(ctx, b); + return serviceFetcher.apply(ctx.getOuterContext(), b); }}); } diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index fd93450c29f0..13d566c0e04b 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -42,6 +42,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.Log; +import android.util.SparseArray; import android.view.Display; import android.view.InputEvent; import android.view.KeyEvent; @@ -535,7 +536,7 @@ public final class UiAutomation { } /** - * Gets the windows on the screen. This method returns only the windows + * Gets the windows on the screen of the default display. This method returns only the windows * that a sighted user can interact with, as opposed to all windows. * For example, if there is a modal dialog shown and the user cannot touch * anything behind it, then only the modal window will be reported @@ -562,6 +563,35 @@ public final class UiAutomation { } /** + * Gets the windows on the screen of all displays. This method returns only the windows + * that a sighted user can interact with, as opposed to all windows. + * For example, if there is a modal dialog shown and the user cannot touch + * anything behind it, then only the modal window will be reported + * (assuming it is the top one). For convenience the returned windows + * are ordered in a descending layer order, which is the windows that + * are higher in the Z-order are reported first. + * <p> + * <strong>Note:</strong> In order to access the windows you have to opt-in + * to retrieve the interactive windows by setting the + * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag. + * </p> + * + * @return The windows of all displays if there are windows and the service is can retrieve + * them, otherwise an empty list. The key of SparseArray is display ID. + */ + @NonNull + public SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays() { + final int connectionId; + synchronized (mLock) { + throwIfNotConnectedLocked(); + connectionId = mConnectionId; + } + // Calling out without a lock held. + return AccessibilityInteractionClient.getInstance() + .getWindowsOnAllDisplays(connectionId); + } + + /** * Gets the root {@link AccessibilityNodeInfo} in the active window. * * @return The root info. diff --git a/core/java/android/app/usage/AppStandbyInfo.java b/core/java/android/app/usage/AppStandbyInfo.java index ebdbf833b81a..c283702a6bbf 100644 --- a/core/java/android/app/usage/AppStandbyInfo.java +++ b/core/java/android/app/usage/AppStandbyInfo.java @@ -22,6 +22,10 @@ import android.os.Parcelable; /** * A pair of {package, bucket} to denote the app standby bucket for a given package. * Used as a vehicle of data across the binder IPC. + * + * Note we're not moving this class to the jobscheduler apex, because it's consumed by + * UsageStatsManager, which is not updatable anyway, so making this updatable isn't really + * beneficial. * @hide */ public final class AppStandbyInfo implements Parcelable { diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java index e24512ac525d..c3daad188792 100644 --- a/core/java/android/content/PermissionChecker.java +++ b/core/java/android/content/PermissionChecker.java @@ -16,8 +16,6 @@ package android.content; -import static android.app.AppOpsManager.strOpToOp; - import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -52,6 +50,19 @@ import java.lang.annotation.RetentionPolicy; * permission model for which the user had disabled the "permission" * which is achieved by disallowing the corresponding app op. * </p> + * <p> + * This class has two types of methods and you should be careful which + * type to call based on whether permission protected data is being + * passed to the app or you are just checking whether the app holds a + * permission. The reason is that a permission check requires checking + * the runtime permission and if it is granted checking the corresponding + * app op as for apps not supporting the runtime mode we never revoke + * permissions but disable app ops. Since there are two types of app op + * checks, one that does not leave a record an action was performed and + * another the does, one needs to call the preflight flavor of the checks + * named xxxForPreflight only if no private data is being delivered but + * a permission check is what is needed and the xxxForDataDelivery where + * the permission check is right before private data delivery. * * @hide */ @@ -65,6 +76,9 @@ public final class PermissionChecker { /** Permission result: The permission is denied because the app op is not allowed. */ public static final int PERMISSION_DENIED_APP_OP = PackageManager.PERMISSION_DENIED - 1; + /** Constant when the PID for which we check permissions is unknown. */ + public static final int PID_UNKNOWN = -1; + /** @hide */ @IntDef({PERMISSION_GRANTED, PERMISSION_DENIED, @@ -77,63 +91,97 @@ public final class PermissionChecker { } /** - * @deprecated Use {@link #checkPermission(Context, String, int, int, String, String)} instead + * Checks whether a given package in a UID and PID has a given permission + * and whether the app op that corresponds to this permission is allowed. + * + * <strong>NOTE:</strong> Use this method only for permission checks at the + * point where you will deliver the permission protected data to clients. + * + * <p>For example, if an app registers a location listener it should have the location + * permission but no data is actually sent to the app at the moment of registration + * and you should use {@link #checkPermissionForPreflight(Context, String, int, int, String)} + * to determine if the app has or may have location permission (if app has only foreground + * location the grant state depends on the app's fg/gb state) and this check will not + * leave a trace that permission protected data was delivered. When you are about to + * deliver the location data to a registered listener you should use this method which + * will evaluate the permission access based on the current fg/bg state of the app and + * leave a record that the data was accessed. + * + * @param context Context for accessing resources. + * @param permission The permission to check. + * @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID + * is not known. + * @param uid The uid for which to check. + * @param packageName The package name for which to check. If null the + * the first package for the calling UID will be used. + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * @param message A message describing the reason the permission was checked + * + * @see #checkPermissionForPreflight(Context, String, int, int, String) */ - @Deprecated @PermissionResult - public static int checkPermission(@NonNull Context context, @NonNull String permission, - int pid, int uid, @Nullable String packageName) { - return checkPermission(context, permission, pid, uid, packageName, null); + public static int checkPermissionForDataDelivery(@NonNull Context context, + @NonNull String permission, int pid, int uid, @Nullable String packageName, + @Nullable String message) { + return checkPermissionCommon(context, permission, pid, uid, packageName, message, + true /*forDataDelivery*/); } /** * Checks whether a given package in a UID and PID has a given permission * and whether the app op that corresponds to this permission is allowed. * + * <strong>NOTE:</strong> Use this method only for permission checks at the + * preflight point where you will not deliver the permission protected data + * to clients but schedule permission data delivery, apps register listeners, + * etc. + * + * <p>For example, if an app registers a location listener it should have the location + * permission but no data is actually sent to the app at the moment of registration + * and you should use this method to determine if the app has or may have location + * permission (if app has only foreground location the grant state depends on the app's + * fg/gb state) and this check will not leave a trace that permission protected data + * was delivered. When you are about to deliver the location data to a registered + * listener you should use {@link #checkPermissionForDataDelivery(Context, String, + * int, int, String, String)} which will evaluate the permission access based on the current + * fg/bg state of the app and leave a record that the data was accessed. + * * @param context Context for accessing resources. * @param permission The permission to check. * @param pid The process id for which to check. * @param uid The uid for which to check. * @param packageName The package name for which to check. If null the * the first package for the calling UID will be used. - * @param message A message describing the reason the permission was checked - * * @return The permission check result which is either {@link #PERMISSION_GRANTED} * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * + * @see #checkPermissionForDataDelivery(Context, String, int, int, String, String) */ @PermissionResult - public static int checkPermission(@NonNull Context context, @NonNull String permission, - int pid, int uid, @Nullable String packageName, @Nullable String message) { - if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) { - return PERMISSION_DENIED; - } - - AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); - String op = appOpsManager.permissionToOp(permission); - if (op == null) { - return PERMISSION_GRANTED; - } - - if (packageName == null) { - String[] packageNames = context.getPackageManager().getPackagesForUid(uid); - if (packageNames == null || packageNames.length <= 0) { - return PERMISSION_DENIED; - } - packageName = packageNames[0]; - } - - if (appOpsManager.noteProxyOpNoThrow(strOpToOp(op), packageName, uid, message) - != AppOpsManager.MODE_ALLOWED) { - return PERMISSION_DENIED_APP_OP; - } - - return PERMISSION_GRANTED; + public static int checkPermissionForPreflight(@NonNull Context context, + @NonNull String permission, int pid, int uid, @Nullable String packageName) { + return checkPermissionCommon(context, permission, pid, uid, packageName, null /*message*/, + false /*forDataDelivery*/); } /** * Checks whether your app has a given permission and whether the app op * that corresponds to this permission is allowed. * + * <strong>NOTE:</strong> Use this method only for permission checks at the + * point where you will deliver the permission protected data to clients. + * + * <p>For example, if an app registers a location listener it should have the location + * permission but no data is actually sent to the app at the moment of registration + * and you should use {@link #checkSelfPermissionForPreflight(Context, String)} + * to determine if the app has or may have location permission (if app has only foreground + * location the grant state depends on the app's fg/gb state) and this check will not + * leave a trace that permission protected data was delivered. When you are about to + * deliver the location data to a registered listener you should use this method + * which will evaluate the permission access based on the current fg/bg state of the + * app and leave a record that the data was accessed. + * * <p>This API assumes the the {@link Binder#getCallingUid()} is the same as * {@link Process#myUid()}. * @@ -141,74 +189,229 @@ public final class PermissionChecker { * @param permission The permission to check. * @return The permission check result which is either {@link #PERMISSION_GRANTED} * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * @param message A message describing the reason the permission was checked + * + * @see #checkSelfPermissionForPreflight(Context, String) */ @PermissionResult - public static int checkSelfPermission(@NonNull Context context, - @NonNull String permission) { - return checkPermission(context, permission, Process.myPid(), - Process.myUid(), context.getPackageName(), null /* self access */); + public static int checkSelfPermissionForDataDelivery(@NonNull Context context, + @NonNull String permission, @Nullable String message) { + return checkPermissionForDataDelivery(context, permission, Process.myPid(), + Process.myUid(), context.getPackageName(), message); } /** - * @deprecated Use {@link #checkCallingPermission(Context, String, String, String)} instead + * Checks whether your app has a given permission and whether the app op + * that corresponds to this permission is allowed. + * + * <strong>NOTE:</strong> Use this method only for permission checks at the + * preflight point where you will not deliver the permission protected data + * to clients but schedule permission data delivery, apps register listeners, + * etc. + * + * <p>For example, if an app registers a location listener it should have the location + * permission but no data is actually sent to the app at the moment of registration + * and you should use this method to determine if the app has or may have location + * permission (if app has only foreground location the grant state depends on the + * app's fg/gb state) and this check will not leave a trace that permission protected + * data was delivered. When you are about to deliver the location data to a registered + * listener you should use this method which will evaluate the permission access based + * on the current fg/bg state of the app and leave a record that the data was accessed. + * + * <p>This API assumes the the {@link Binder#getCallingUid()} is the same as + * {@link Process#myUid()}. + * + * @param context Context for accessing resources. + * @param permission The permission to check. + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * + * @see #checkSelfPermissionForDataDelivery(Context, String, String) */ - @Deprecated @PermissionResult - public static int checkCallingPermission(@NonNull Context context, - @NonNull String permission, @Nullable String packageName) { - return checkCallingPermission(context, permission, packageName, null); + public static int checkSelfPermissionForPreflight(@NonNull Context context, + @NonNull String permission) { + return checkPermissionForPreflight(context, permission, Process.myPid(), + Process.myUid(), context.getPackageName()); } /** * Checks whether the IPC you are handling has a given permission and whether * the app op that corresponds to this permission is allowed. * + * <strong>NOTE:</strong> Use this method only for permission checks at the + * point where you will deliver the permission protected data to clients. + * + * <p>For example, if an app registers a location listener it should have the location + * permission but no data is actually sent to the app at the moment of registration + * and you should use {@link #checkCallingPermissionForPreflight(Context, String, String)} + * to determine if the app has or may have location permission (if app has only foreground + * location the grant state depends on the app's fg/gb state) and this check will not + * leave a trace that permission protected data was delivered. When you are about to + * deliver the location data to a registered listener you should use this method which + * will evaluate the permission access based on the current fg/bg state of the app and + * leave a record that the data was accessed. + * * @param context Context for accessing resources. * @param permission The permission to check. * @param packageName The package name making the IPC. If null the * the first package for the calling UID will be used. - * @param message A message describing the reason the permission was checked - * * @return The permission check result which is either {@link #PERMISSION_GRANTED} * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * @param message A message describing the reason the permission was checked + * + * @see #checkCallingPermissionForPreflight(Context, String, String) */ @PermissionResult - public static int checkCallingPermission(@NonNull Context context, + public static int checkCallingPermissionForDataDelivery(@NonNull Context context, @NonNull String permission, @Nullable String packageName, @Nullable String message) { if (Binder.getCallingPid() == Process.myPid()) { return PERMISSION_DENIED; } - return checkPermission(context, permission, Binder.getCallingPid(), + return checkPermissionForDataDelivery(context, permission, Binder.getCallingPid(), Binder.getCallingUid(), packageName, message); } /** - * @deprecated Use {@link #checkCallingOrSelfPermission(Context, String, String)} instead + * Checks whether the IPC you are handling has a given permission and whether + * the app op that corresponds to this permission is allowed. + * + * <strong>NOTE:</strong> Use this method only for permission checks at the + * preflight point where you will not deliver the permission protected data + * to clients but schedule permission data delivery, apps register listeners, + * etc. + * + * <p>For example, if an app registers a location listener it should have the location + * permission but no data is actually sent to the app at the moment of registration + * and you should use this method to determine if the app has or may have location + * permission (if app has only foreground location the grant state depends on the app's + * fg/gb state) and this check will not leave a trace that permission protected data + * was delivered. When you are about to deliver the location data to a registered + * listener you should use {@link #checkCallingOrSelfPermissionForDataDelivery(Context, + * String, String)} which will evaluate the permission access based on the current fg/bg state + * of the app and leave a record that the data was accessed. + * + * @param context Context for accessing resources. + * @param permission The permission to check. + * @param packageName The package name making the IPC. If null the + * the first package for the calling UID will be used. + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * + * @see #checkCallingPermissionForDataDelivery(Context, String, String, String) */ - @Deprecated @PermissionResult - public static int checkCallingOrSelfPermission(@NonNull Context context, - @NonNull String permission) { - return checkCallingOrSelfPermission(context, permission, null); + public static int checkCallingPermissionForPreflight(@NonNull Context context, + @NonNull String permission, @Nullable String packageName) { + if (Binder.getCallingPid() == Process.myPid()) { + return PERMISSION_DENIED; + } + return checkPermissionForPreflight(context, permission, Binder.getCallingPid(), + Binder.getCallingUid(), packageName); } /** * Checks whether the IPC you are handling or your app has a given permission * and whether the app op that corresponds to this permission is allowed. * + * <strong>NOTE:</strong> Use this method only for permission checks at the + * point where you will deliver the permission protected data to clients. + * + * <p>For example, if an app registers a location listener it should have the location + * permission but no data is actually sent to the app at the moment of registration + * and you should use {@link #checkCallingOrSelfPermissionForPreflight(Context, String)} + * to determine if the app has or may have location permission (if app has only foreground + * location the grant state depends on the app's fg/gb state) and this check will not + * leave a trace that permission protected data was delivered. When you are about to + * deliver the location data to a registered listener you should use this method which + * will evaluate the permission access based on the current fg/bg state of the app and + * leave a record that the data was accessed. + * * @param context Context for accessing resources. * @param permission The permission to check. - * @param message A message describing the reason the permission was checked - * * @return The permission check result which is either {@link #PERMISSION_GRANTED} * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * @param message A message describing the reason the permission was checked + * + * @see #checkCallingOrSelfPermissionForPreflight(Context, String) */ @PermissionResult - public static int checkCallingOrSelfPermission(@NonNull Context context, + public static int checkCallingOrSelfPermissionForDataDelivery(@NonNull Context context, @NonNull String permission, @Nullable String message) { String packageName = (Binder.getCallingPid() == Process.myPid()) ? context.getPackageName() : null; - return checkPermission(context, permission, Binder.getCallingPid(), + return checkPermissionForDataDelivery(context, permission, Binder.getCallingPid(), Binder.getCallingUid(), packageName, message); } -} + + /** + * Checks whether the IPC you are handling or your app has a given permission + * and whether the app op that corresponds to this permission is allowed. + * + * <strong>NOTE:</strong> Use this method only for permission checks at the + * preflight point where you will not deliver the permission protected data + * to clients but schedule permission data delivery, apps register listeners, + * etc. + * + * <p>For example, if an app registers a location listener it should have the location + * permission but no data is actually sent to the app at the moment of registration + * and you should use this method to determine if the app has or may have location + * permission (if app has only foreground location the grant state depends on the + * app's fg/gb state) and this check will not leave a trace that permission protected + * data was delivered. When you are about to deliver the location data to a registered + * listener you should use {@link #checkCallingOrSelfPermissionForDataDelivery(Context, + * String, String)} which will evaluate the permission access based on the current fg/bg state + * of the app and leave a record that the data was accessed. + * + * @param context Context for accessing resources. + * @param permission The permission to check. + * @return The permission check result which is either {@link #PERMISSION_GRANTED} + * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * + * @see #checkCallingOrSelfPermissionForDataDelivery(Context, String, String) + */ + @PermissionResult + public static int checkCallingOrSelfPermissionForPreflight(@NonNull Context context, + @NonNull String permission) { + String packageName = (Binder.getCallingPid() == Process.myPid()) + ? context.getPackageName() : null; + return checkPermissionForPreflight(context, permission, Binder.getCallingPid(), + Binder.getCallingUid(), packageName); + } + + private static int checkPermissionCommon(@NonNull Context context, @NonNull String permission, + int pid, int uid, @Nullable String packageName, @Nullable String message, + boolean forDataDelivery) { + if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) { + return PERMISSION_DENIED; + } + + AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); + String op = appOpsManager.permissionToOp(permission); + if (op == null) { + return PERMISSION_GRANTED; + } + + if (packageName == null) { + String[] packageNames = context.getPackageManager().getPackagesForUid(uid); + if (packageNames == null || packageNames.length <= 0) { + return PERMISSION_DENIED; + } + packageName = packageNames[0]; + } + + if (forDataDelivery) { + if (appOpsManager.noteProxyOpNoThrow(op, packageName, uid, message) + != AppOpsManager.MODE_ALLOWED) { + return PERMISSION_DENIED_APP_OP; + } + } else { + final int mode = appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName); + if (mode != AppOpsManager.MODE_ALLOWED && mode != AppOpsManager.MODE_FOREGROUND) { + return PERMISSION_DENIED_APP_OP; + } + } + + return PERMISSION_GRANTED; + } +}
\ No newline at end of file diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl index 43a4fe5bc414..3d7e3befd9f1 100644 --- a/core/java/android/content/om/IOverlayManager.aidl +++ b/core/java/android/content/om/IOverlayManager.aidl @@ -157,4 +157,10 @@ interface IOverlayManager { * Returns the list of default overlay packages, or an empty array if there are none. */ String[] getDefaultOverlayPackages(); + + /** + * Invalidates and removes the idmap for an overlay, + * @param packageName The name of the overlay package whose idmap should be deleted. + */ + void invalidateCachesForOverlay(in String packageName, in int userIs); } diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java index f2716fedc186..853e8189ea8a 100644 --- a/core/java/android/content/om/OverlayManager.java +++ b/core/java/android/content/om/OverlayManager.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; @@ -163,4 +164,28 @@ public class OverlayManager { throw e.rethrowFromSystemServer(); } } + + /** + * Returns information about all overlays for the given target package for + * the specified user. The returned list is ordered according to the + * overlay priority with the highest priority at the end of the list. + * + * @param targetPackageName The name of the target package. + * @param user The user to get the OverlayInfos for. + * + * @hide + */ + @TestApi + @RequiresPermission(anyOf = { + "android.permission.INTERACT_ACROSS_USERS", + }) + @NonNull + public void invalidateCachesForOverlay(@NonNull final String targetPackageName, + @NonNull UserHandle user) { + try { + mService.invalidateCachesForOverlay(targetPackageName, user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 69462ab99483..a35ad567ed81 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -188,7 +188,7 @@ public final class ApkAssets { /** * Closes this class and the contained {@link #mStringBlock}. */ - public void close() throws Throwable { + public void close() { synchronized (this) { if (mOpen) { mOpen = false; diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java index b7bc8229fa45..2ae1932c3437 100644 --- a/core/java/android/content/res/StringBlock.java +++ b/core/java/android/content/res/StringBlock.java @@ -175,7 +175,7 @@ final class StringBlock { } } - public void close() throws Throwable { + public void close() { synchronized (this) { if (mOpen) { mOpen = false; diff --git a/core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl b/core/java/android/hardware/biometrics/Authenticator.java index 8b35852efd31..6d7e7488f2d0 100644 --- a/core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl +++ b/core/java/android/hardware/biometrics/Authenticator.java @@ -17,10 +17,19 @@ package android.hardware.biometrics; /** - * Communication channel between ConfirmDeviceCredential / ConfirmLock* and BiometricService. + * Type of authenticators defined on a granularity that the BiometricManager / BiometricPrompt + * supports. * @hide */ -interface IBiometricConfirmDeviceCredentialCallback { - // Invoked when authentication should be canceled. - oneway void cancel(); -}
\ No newline at end of file +public class Authenticator { + + /** + * Device credential, e.g. Pin/Pattern/Password. + */ + public static final int TYPE_CREDENTIAL = 1 << 0; + /** + * Encompasses all biometrics on the device, e.g. Fingerprint/Iris/Face. + */ + public static final int TYPE_BIOMETRIC = 1 << 1; + +} diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index d8110f33d723..cbe8a052db2f 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -201,55 +201,5 @@ public class BiometricManager { } } - /** - * TODO(b/123378871): Remove when moved. - * @hide - */ - @RequiresPermission(USE_BIOMETRIC_INTERNAL) - public void onConfirmDeviceCredentialSuccess() { - if (mService != null) { - try { - mService.onConfirmDeviceCredentialSuccess(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } else { - Slog.w(TAG, "onConfirmDeviceCredentialSuccess(): Service not connected"); - } - } - - /** - * TODO(b/123378871): Remove when moved. - * @hide - */ - @RequiresPermission(USE_BIOMETRIC_INTERNAL) - public void onConfirmDeviceCredentialError(int error, String message) { - if (mService != null) { - try { - mService.onConfirmDeviceCredentialError(error, message); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } else { - Slog.w(TAG, "onConfirmDeviceCredentialError(): Service not connected"); - } - } - - /** - * TODO(b/123378871): Remove when moved. - * @hide - */ - @RequiresPermission(USE_BIOMETRIC_INTERNAL) - public void registerCancellationCallback(IBiometricConfirmDeviceCredentialCallback callback) { - if (mService != null) { - try { - mService.registerCancellationCallback(callback); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } else { - Slog.w(TAG, "registerCancellationCallback(): Service not connected"); - } - } } diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index fb6b231632f1..cf86e25112d2 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -69,24 +69,21 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan /** * @hide */ - public static final String KEY_POSITIVE_TEXT = "positive_text"; - /** - * @hide - */ public static final String KEY_NEGATIVE_TEXT = "negative_text"; /** * @hide */ public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation"; /** + * This is deprecated. Internally we should use {@link #KEY_AUTHENTICATORS_ALLOWED} * @hide */ public static final String KEY_ALLOW_DEVICE_CREDENTIAL = "allow_device_credential"; /** + * If this key is set, we will ignore {@link #KEY_ALLOW_DEVICE_CREDENTIAL} * @hide */ - public static final String KEY_FROM_CONFIRM_DEVICE_CREDENTIAL - = "from_confirm_device_credential"; + public static final String KEY_AUTHENTICATORS_ALLOWED = "authenticators_allowed"; /** * Error/help message will show for this amount of time. @@ -100,7 +97,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan /** * @hide */ - public static final int DISMISSED_REASON_CONFIRMED = 1; + public static final int DISMISSED_REASON_BIOMETRIC_CONFIRMED = 1; /** * Dialog is done animating away after user clicked on the button set via @@ -119,7 +116,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * Authenticated, confirmation not required. Dialog animated away. * @hide */ - public static final int DISMISSED_REASON_CONFIRM_NOT_REQUIRED = 4; + public static final int DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED = 4; /** * Error message shown on SystemUI. When BiometricService receives this, the UI is already @@ -134,6 +131,11 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan */ public static final int DISMISSED_REASON_SERVER_REQUESTED = 6; + /** + * @hide + */ + public static final int DISMISSED_REASON_CREDENTIAL_CONFIRMED = 7; + private static class ButtonInfo { Executor executor; DialogInterface.OnClickListener listener; @@ -203,30 +205,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** - * Optional: Set the text for the positive button. If not set, the positive button - * will not show. - * @param text - * @return - * @hide - */ - @NonNull public Builder setPositiveButton(@NonNull CharSequence text, - @NonNull @CallbackExecutor Executor executor, - @NonNull DialogInterface.OnClickListener listener) { - if (TextUtils.isEmpty(text)) { - throw new IllegalArgumentException("Text must be set and non-empty"); - } - if (executor == null) { - throw new IllegalArgumentException("Executor must not be null"); - } - if (listener == null) { - throw new IllegalArgumentException("Listener must not be null"); - } - mBundle.putCharSequence(KEY_POSITIVE_TEXT, text); - mPositiveButtonInfo = new ButtonInfo(executor, listener); - return this; - } - - /** * Required: Set the text for the negative button. This would typically be used as a * "Cancel" button, but may be also used to show an alternative method for authentication, * such as screen that asks for a backup password. @@ -298,17 +276,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** - * TODO(123378871): Remove when moved. - * @return - * @hide - */ - @RequiresPermission(USE_BIOMETRIC_INTERNAL) - @NonNull public Builder setFromConfirmDeviceCredential() { - mBundle.putBoolean(KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, true); - return this; - } - - /** * Creates a {@link BiometricPrompt}. * @return a {@link BiometricPrompt} * @throws IllegalArgumentException if any of the required fields are not set. @@ -317,15 +284,19 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan final CharSequence title = mBundle.getCharSequence(KEY_TITLE); final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT); final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE); - final boolean enableFallback = mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL); + final boolean allowCredential = mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL); + final Object authenticatorsAllowed = mBundle.get(KEY_AUTHENTICATORS_ALLOWED); if (TextUtils.isEmpty(title) && !useDefaultTitle) { throw new IllegalArgumentException("Title must be set and non-empty"); - } else if (TextUtils.isEmpty(negative) && !enableFallback) { + } else if (TextUtils.isEmpty(negative) && !allowCredential) { throw new IllegalArgumentException("Negative text must be set and non-empty"); - } else if (!TextUtils.isEmpty(negative) && enableFallback) { + } else if (!TextUtils.isEmpty(negative) && allowCredential) { throw new IllegalArgumentException("Can't have both negative button behavior" + " and device credential enabled"); + } else if (authenticatorsAllowed != null && allowCredential) { + throw new IllegalArgumentException("setAuthenticatorsAllowed and" + + " setDeviceCredentialAllowed should not be used simultaneously"); } return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo); } @@ -384,7 +355,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan @Override public void onDialogDismissed(int reason) throws RemoteException { // Check the reason and invoke OnClickListener(s) if necessary - if (reason == DISMISSED_REASON_CONFIRMED) { + if (reason == DISMISSED_REASON_BIOMETRIC_CONFIRMED) { mPositiveButtonInfo.executor.execute(() -> { mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE); }); @@ -532,8 +503,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan public void authenticateUser(@NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, - int userId, - IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback) { + int userId) { if (cancel == null) { throw new IllegalArgumentException("Must supply a cancellation signal"); } @@ -543,8 +513,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan if (callback == null) { throw new IllegalArgumentException("Must supply a callback"); } - authenticateInternal(null /* crypto */, cancel, executor, callback, userId, - confirmDeviceCredentialCallback); + authenticateInternal(null /* crypto */, cancel, executor, callback, userId); } /** @@ -595,8 +564,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan if (mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL)) { throw new IllegalArgumentException("Device credential not supported with crypto"); } - authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId(), - null /* confirmDeviceCredentialCallback */); + authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId()); } /** @@ -638,8 +606,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan if (callback == null) { throw new IllegalArgumentException("Must supply a callback"); } - authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId(), - null /* confirmDeviceCredentialCallback */); + authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId()); } private void cancelAuthentication() { @@ -656,8 +623,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, - int userId, - IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback) { + int userId) { try { if (cancel.isCanceled()) { Log.w(TAG, "Authentication already canceled"); @@ -672,7 +638,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan final long sessionId = crypto != null ? crypto.getOpId() : 0; if (BiometricManager.hasBiometrics(mContext)) { mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver, - mContext.getOpPackageName(), mBundle, confirmDeviceCredentialCallback); + mContext.getOpPackageName(), mBundle); } else { mExecutor.execute(() -> { callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT, diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index f0a0b2f0235f..6a3bf38a97e1 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -17,7 +17,6 @@ package android.hardware.biometrics; import android.os.Bundle; -import android.hardware.biometrics.IBiometricConfirmDeviceCredentialCallback; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -31,10 +30,8 @@ import android.hardware.biometrics.IBiometricServiceReceiver; interface IBiometricService { // Requests authentication. The service choose the appropriate biometric to use, and show // the corresponding BiometricDialog. - // TODO(b/123378871): Remove callback when moved. void authenticate(IBinder token, long sessionId, int userId, - IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle, - IBiometricConfirmDeviceCredentialCallback callback); + IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle); // Cancel authentication for the given sessionId void cancelAuthentication(IBinder token, String opPackageName); @@ -57,16 +54,4 @@ interface IBiometricService { // Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password) void resetLockout(in byte [] token); - - // TODO(b/123378871): Remove when moved. - // CDCA needs to send results to BiometricService if it was invoked using BiometricPrompt's - // setAllowDeviceCredential method, since there's no way for us to intercept onActivityResult. - // CDCA is launched from BiometricService (startActivityAsUser) instead of *ForResult. - void onConfirmDeviceCredentialSuccess(); - // TODO(b/123378871): Remove when moved. - void onConfirmDeviceCredentialError(int error, String message); - // TODO(b/123378871): Remove when moved. - // When ConfirmLock* is invoked from BiometricPrompt, it needs to register a callback so that - // it can receive the cancellation signal. - void registerCancellationCallback(IBiometricConfirmDeviceCredentialCallback callback); } diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl index ca6114e4d842..66b6e896fc13 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl @@ -38,4 +38,6 @@ oneway interface IBiometricServiceReceiverInternal { void onDialogDismissed(int reason); // Notifies that the user has pressed the "try again" button on SystemUI void onTryAgainPressed(); + // Notifies that the user has pressed the "use password" button on SystemUI + void onDeviceCredentialPressed(); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 3c26df3c560b..e4e8bf791d47 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7908,16 +7908,6 @@ public final class Settings { public static final String NOTIFICATION_BADGING = "notification_badging"; /** - * Whether the notification bubbles are globally enabled - * The value is boolean (1 or 0). - * @hide - * @deprecated use {@link Global#NOTIFICATION_BUBBLES} instead. - */ - @TestApi - @Deprecated - public static final String NOTIFICATION_BUBBLES = "notification_bubbles"; - - /** * Whether notifications are dismissed by a right-to-left swipe (instead of a left-to-right * swipe). * diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java index 70dfef574ca5..dfc5c8217fda 100644 --- a/core/java/android/speech/RecognitionService.java +++ b/core/java/android/speech/RecognitionService.java @@ -170,13 +170,23 @@ public abstract class RecognitionService extends Service { * Checks whether the caller has sufficient permissions * * @param listener to send the error message to in case of error + * @param forDataDelivery If the permission check is for delivering the sensitive data. * @return {@code true} if the caller has enough permissions, {@code false} otherwise */ - private boolean checkPermissions(IRecognitionListener listener) { + private boolean checkPermissions(IRecognitionListener listener, boolean forDataDelivery) { if (DBG) Log.d(TAG, "checkPermissions"); - if (PermissionChecker.checkCallingOrSelfPermission(this, - android.Manifest.permission.RECORD_AUDIO) == PermissionChecker.PERMISSION_GRANTED) { - return true; + if (forDataDelivery) { + if (PermissionChecker.checkCallingOrSelfPermissionForDataDelivery(this, + android.Manifest.permission.RECORD_AUDIO, null /*message*/) + == PermissionChecker.PERMISSION_GRANTED) { + return true; + } + } else { + if (PermissionChecker.checkCallingOrSelfPermissionForPreflight(this, + android.Manifest.permission.RECORD_AUDIO) + == PermissionChecker.PERMISSION_GRANTED) { + return true; + } } try { Log.e(TAG, "call for recognition service without RECORD_AUDIO permissions"); @@ -342,7 +352,7 @@ public abstract class RecognitionService extends Service { public void startListening(Intent recognizerIntent, IRecognitionListener listener) { if (DBG) Log.d(TAG, "startListening called by:" + listener.asBinder()); final RecognitionService service = mServiceRef.get(); - if (service != null && service.checkPermissions(listener)) { + if (service != null && service.checkPermissions(listener, true /*forDataDelivery*/)) { service.mHandler.sendMessage(Message.obtain(service.mHandler, MSG_START_LISTENING, service.new StartListeningArgs( recognizerIntent, listener, Binder.getCallingUid()))); @@ -353,7 +363,7 @@ public abstract class RecognitionService extends Service { public void stopListening(IRecognitionListener listener) { if (DBG) Log.d(TAG, "stopListening called by:" + listener.asBinder()); final RecognitionService service = mServiceRef.get(); - if (service != null && service.checkPermissions(listener)) { + if (service != null && service.checkPermissions(listener, false /*forDataDelivery*/)) { service.mHandler.sendMessage(Message.obtain(service.mHandler, MSG_STOP_LISTENING, listener)); } @@ -363,7 +373,7 @@ public abstract class RecognitionService extends Service { public void cancel(IRecognitionListener listener) { if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder()); final RecognitionService service = mServiceRef.get(); - if (service != null && service.checkPermissions(listener)) { + if (service != null && service.checkPermissions(listener, false /*forDataDelivery*/)) { service.mHandler.sendMessage(Message.obtain(service.mHandler, MSG_CANCEL, listener)); } diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index d9fa9f24f1ae..bb10ef10d79e 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -259,23 +259,38 @@ public final class AccessibilityInteractionClient } /** - * Gets the info for all windows. + * Gets the info for all windows of the default display. * * @param connectionId The id of a connection for interacting with the system. * @return The {@link AccessibilityWindowInfo} list. */ public List<AccessibilityWindowInfo> getWindows(int connectionId) { + final SparseArray<List<AccessibilityWindowInfo>> windows = + getWindowsOnAllDisplays(connectionId); + if (windows.size() > 0) { + return windows.valueAt(Display.DEFAULT_DISPLAY); + } + return Collections.emptyList(); + } + + /** + * Gets the info for all windows of all displays. + * + * @param connectionId The id of a connection for interacting with the system. + * @return The SparseArray of {@link AccessibilityWindowInfo} list. + * The key of SparseArray is display ID. + */ + public SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays(int connectionId) { try { IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { - SparseArray<List<AccessibilityWindowInfo>> allWindows = + SparseArray<List<AccessibilityWindowInfo>> windows = sAccessibilityCache.getWindowsOnAllDisplays(); - List<AccessibilityWindowInfo> windows; - if (allWindows != null) { + if (windows != null) { if (DEBUG) { Log.i(LOG_TAG, "Windows cache hit"); } - return allWindows.valueAt(Display.DEFAULT_DISPLAY); + return windows; } if (DEBUG) { Log.i(LOG_TAG, "Windows cache miss"); @@ -287,9 +302,7 @@ public final class AccessibilityInteractionClient Binder.restoreCallingIdentity(identityToken); } if (windows != null) { - allWindows = new SparseArray<>(); - allWindows.put(Display.DEFAULT_DISPLAY, windows); - sAccessibilityCache.setWindowsOnAllDisplays(allWindows); + sAccessibilityCache.setWindowsOnAllDisplays(windows); return windows; } } else { @@ -298,9 +311,11 @@ public final class AccessibilityInteractionClient } } } catch (RemoteException re) { - Log.e(LOG_TAG, "Error while calling remote getWindows", re); + Log.e(LOG_TAG, "Error while calling remote getWindowsOnAllDisplays", re); } - return Collections.emptyList(); + + final SparseArray<List<AccessibilityWindowInfo>> emptyWindows = new SparseArray<>(); + return emptyWindows; } /** diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.aidl b/core/java/android/view/accessibility/AccessibilityWindowInfo.aidl index fdb25fb1ee61..c36c4aa5b00a 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.aidl +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.aidl @@ -17,3 +17,4 @@ package android.view.accessibility; parcelable AccessibilityWindowInfo; +parcelable AccessibilityWindowInfo.WindowListSparseArray; diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index 6a3af3478449..5fa8a6e0e06b 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -26,9 +26,12 @@ import android.os.Parcelable; import android.text.TextUtils; import android.util.LongArray; import android.util.Pools.SynchronizedPool; +import android.util.SparseArray; import android.view.Display; import android.view.accessibility.AccessibilityEvent.WindowsChangeTypes; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; @@ -797,4 +800,49 @@ public final class AccessibilityWindowInfo implements Parcelable { return new AccessibilityWindowInfo[size]; } }; + + /** + * Transfers a sparsearray with lists having {@link AccessibilityWindowInfo}s across an IPC. + * The key of this sparsearray is display Id. + * + * @hide + */ + public static final class WindowListSparseArray + extends SparseArray<List<AccessibilityWindowInfo>> implements Parcelable { + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + final int count = size(); + dest.writeInt(count); + for (int i = 0; i < count; i++) { + dest.writeParcelableList(valueAt(i), 0); + dest.writeInt(keyAt(i)); + } + } + + public static final Parcelable.Creator<WindowListSparseArray> CREATOR = + new Parcelable.Creator<WindowListSparseArray>() { + public WindowListSparseArray createFromParcel( + Parcel source) { + final WindowListSparseArray array = new WindowListSparseArray(); + final ClassLoader loader = array.getClass().getClassLoader(); + final int count = source.readInt(); + for (int i = 0; i < count; i++) { + List<AccessibilityWindowInfo> windows = new ArrayList<>(); + source.readParcelableList(windows, loader); + array.put(source.readInt(), windows); + } + return array; + } + + public WindowListSparseArray[] newArray(int size) { + return new WindowListSparseArray[size]; + } + }; + } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index f997d6878a9c..31f50555af1d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -23,6 +23,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_C import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; import android.R; +import android.annotation.CallSuper; import android.annotation.CheckResult; import android.annotation.ColorInt; import android.annotation.DrawableRes; @@ -10446,10 +10447,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * This method is called when the selection has changed, in case any * subclasses would like to know. + * </p> + * <p class="note"><strong>Note:</strong> Always call the super implementation, which informs + * the accessibility subsystem about the selection change. + * </p> * * @param selStart The new selection start location. * @param selEnd The new selection end location. */ + @CallSuper protected void onSelectionChanged(int selStart, int selEnd) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); } diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 5142d3cd2b99..033e9b2c6eba 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -239,6 +239,13 @@ public final class SystemUiDeviceConfigFlags { public static final String ASSIST_TRANSCRIPTION_MIN_DURATION = "assist_transcription_min_duration"; + /** + * (boolean) Whether or not to enable an extra section in the notification shade which + * filters for "people" related messages. + */ + public static final String NOTIFICATIONS_USE_PEOPLE_FILTERING = + "notifications_use_people_filtering"; + // Flags related to brightline falsing /** diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 103c79d22530..fd3cd42b07a1 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -203,13 +203,15 @@ public class RuntimeInit { public static void preForkInit() { if (DEBUG) Slog.d(TAG, "Entered preForkInit."); RuntimeInit.enableDdms(); + // TODO(b/142019040#comment13): Decide whether to load the default instance eagerly, i.e. + // MimeMap.setDefault(DefaultMimeMapFactory.create()); /* * Replace libcore's minimal default mapping between MIME types and file * extensions with a mapping that's suitable for Android. Android's mapping * contains many more entries that are derived from IANA registrations but * with several customizations (extensions, overrides). */ - MimeMap.setDefault(DefaultMimeMapFactory.create()); + MimeMap.setDefaultSupplier(DefaultMimeMapFactory::create); } @UnsupportedAppUsage diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 9441825a1ed6..c8ba52a63151 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -151,17 +151,17 @@ oneway interface IStatusBar void showShutdownUi(boolean isReboot, String reason); - // Used to show the dialog when BiometricService starts authentication - void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, - boolean requireConfirmation, int userId, String opPackageName); - // Used to hide the dialog when a biometric is authenticated + // 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 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(String error); - // Used to hide the biometric dialog when the AuthenticationClient is stopped - void hideBiometricDialog(); + void onBiometricError(int errorCode, String error); + // Used to hide the authentication dialog, e.g. when the application cancels authentication + void hideAuthenticationDialog(); /** * Notifies System UI that the display is ready to show system decorations. diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 4c3a177a013b..a845b587c49f 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -99,15 +99,15 @@ interface IStatusBarService void showPinningEnterExitToast(boolean entering); void showPinningEscapeToast(); - // Used to show the dialog when BiometricService starts authentication - void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, - boolean requireConfirmation, int userId, String opPackageName); - // Used to hide the dialog when a biometric is authenticated + // 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 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(String error); - // Used to hide the biometric dialog when the AuthenticationClient is stopped - void hideBiometricDialog(); + void onBiometricError(int errorCode, String error); + // Used to hide the authentication dialog, e.g. when the application cancels authentication + void hideAuthenticationDialog(); } diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 8aa6f860aa61..3cefeea68406 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -191,9 +191,21 @@ jint android_os_Process_getGidForName(JNIEnv* env, jobject clazz, jstring name) return -1; } +static bool verifyGroup(JNIEnv* env, int grp) +{ + if (grp < SP_DEFAULT || grp >= SP_CNT) { + signalExceptionForError(env, EINVAL, grp); + return false; + } + return true; +} + void android_os_Process_setThreadGroup(JNIEnv* env, jobject clazz, int tid, jint grp) { ALOGV("%s tid=%d grp=%" PRId32, __func__, tid, grp); + if (!verifyGroup(env, grp)) { + return; + } SchedPolicy sp = (SchedPolicy) grp; int res = set_sched_policy(tid, sp); if (res != NO_ERROR) { @@ -204,6 +216,9 @@ void android_os_Process_setThreadGroup(JNIEnv* env, jobject clazz, int tid, jint void android_os_Process_setThreadGroupAndCpuset(JNIEnv* env, jobject clazz, int tid, jint grp) { ALOGV("%s tid=%d grp=%" PRId32, __func__, tid, grp); + if (!verifyGroup(env, grp)) { + return; + } SchedPolicy sp = (SchedPolicy) grp; int res = set_sched_policy(tid, sp); @@ -234,6 +249,9 @@ void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jin grp = SP_FOREGROUND; isDefault = true; } + if (!verifyGroup(env, grp)) { + return; + } SchedPolicy sp = (SchedPolicy) grp; if (kDebugPolicy) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7a0d0cbe6095..7ce370ff52be 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -794,6 +794,18 @@ android:permissionFlags="hardRestricted" android:protectionLevel="dangerous" /> + <!-- @SystemApi @TestApi Allows an application to forward cell broadcast messages to the cell + broadcast module. This is required in order to bind to the cell broadcast service, and + ensures that only the system can forward messages to it. + + <p>Protection level: signature|privileged + + @hide --> + <permission android:name="android.permission.BIND_CELL_BROADCAST_SERVICE" + android:label="@string/permlab_bindCellBroadcastService" + android:description="@string/permdesc_bindCellBroadcastService" + android:protectionLevel="signature" /> + <!-- @SystemApi @TestApi Allows an application to read previously received cell broadcast messages and to register a content observer to get notifications when a cell broadcast has been received and added to the database. For diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index e3337b7f9ae5..7eca699d9eca 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -433,6 +433,10 @@ --> </string-array> + <!-- Package name for the default CellBroadcastService module [DO NOT TRANSLATE] --> + <string name="cellbroadcast_default_package" translatable="false">com.android.cellbroadcastreceiver + </string> + <!-- If the mobile hotspot feature requires provisioning, a package name and class name can be provided to launch a supported application that provisions the devices. diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index b53a399f0c8a..4e327082047b 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -890,6 +890,16 @@ messages. This means the app could monitor or delete messages sent to your device without showing them to you.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR LIMIT=NONE] --> + <string name="permlab_bindCellBroadcastService">Forward cell broadcast messages</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] --> + <string name="permdesc_bindCellBroadcastService">Allows the app to bind to the + cell broadcast module in order to forward cell broadcast messages + as they are received. Cell broadcast alerts are delivered in some + locations to warn you of emergency situations. Malicious apps may + interfere with the performance or operation of your device when an + emergency cell broadcast is received.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_readCellBroadcasts">read cell broadcast messages</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e2f2b2c64b77..a8b534061aeb 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -747,6 +747,7 @@ <java-symbol type="string" name="config_default_dns_server" /> <java-symbol type="string" name="config_ethernet_iface_regex" /> <java-symbol type="array" name="config_ethernet_interfaces" /> + <java-symbol type="string" name="cellbroadcast_default_package" /> <java-symbol type="string" name="config_forceVoiceInteractionServicePackage" /> <java-symbol type="string" name="config_mms_user_agent" /> <java-symbol type="string" name="config_mms_user_agent_profile_url" /> diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 682416c58c72..3586216ad421 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -23,8 +23,6 @@ import android.graphics.Region; import android.os.Bundle; import android.os.IBinder; -import java.util.List; - /** * Stub implementation of IAccessibilityServiceConnection so each test doesn't need to implement * all of the methods @@ -73,7 +71,7 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon return null; } - public List<AccessibilityWindowInfo> getWindows() { + public AccessibilityWindowInfo.WindowListSparseArray getWindows() { return null; } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 89523d6bbefb..51136b993e57 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -127,6 +127,7 @@ applications that come with the platform <permission name="android.permission.ACCESS_IMS_CALL_SERVICE"/> <permission name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"/> <permission name="android.permission.BIND_CARRIER_SERVICES"/> + <permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/> <permission name="android.permission.BIND_IMS_SERVICE"/> <permission name="android.permission.BIND_TELEPHONY_DATA_SERVICE"/> <permission name="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"/> diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 68480ced0692..f3d68755a513 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -940,9 +940,8 @@ public class LocationManager { @Nullable LocationRequest locationRequest, @NonNull LocationListener listener, @Nullable Looper looper) { - requestLocationUpdates(locationRequest, - new LocationListenerTransport(looper == null ? new Handler() : new Handler(looper), - listener)); + Handler handler = looper == null ? new Handler() : new Handler(looper); + requestLocationUpdates(locationRequest, new HandlerExecutor(handler), listener); } /** @@ -969,7 +968,31 @@ public class LocationManager { @Nullable LocationRequest locationRequest, @NonNull @CallbackExecutor Executor executor, @NonNull LocationListener listener) { - requestLocationUpdates(locationRequest, new LocationListenerTransport(executor, listener)); + synchronized (mListeners) { + LocationListenerTransport transport = mListeners.get(listener); + if (transport != null) { + transport.unregister(); + } else { + transport = new LocationListenerTransport(listener); + mListeners.put(listener, transport); + } + transport.register(executor); + + boolean registered = false; + try { + mService.requestLocationUpdates(locationRequest, transport, null, + mContext.getPackageName()); + registered = true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } finally { + if (!registered) { + // allow gc after exception + transport.unregister(); + mListeners.remove(listener); + } + } + } } /** @@ -1009,23 +1032,6 @@ public class LocationManager { } } - private void requestLocationUpdates(@Nullable LocationRequest request, - @NonNull LocationListenerTransport transport) { - synchronized (mListeners) { - LocationListenerTransport oldTransport = mListeners.put(transport.getKey(), transport); - if (oldTransport != null) { - oldTransport.unregisterListener(); - } - - try { - mService.requestLocationUpdates(request, transport, null, - mContext.getPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - } - /** * Set the last known location with a new location. * @@ -1079,7 +1085,7 @@ public class LocationManager { if (transport == null) { return; } - transport.unregisterListener(); + transport.unregister(); try { mService.removeUpdates(transport, null, mContext.getPackageName()); @@ -2237,49 +2243,45 @@ public class LocationManager { private class LocationListenerTransport extends ILocationListener.Stub { - private final Executor mExecutor; - @Nullable private volatile LocationListener mListener; + private final LocationListener mListener; + @Nullable private volatile Executor mExecutor = null; - private LocationListenerTransport(@NonNull Handler handler, - @NonNull LocationListener listener) { - Preconditions.checkArgument(handler != null, "invalid null handler"); + private LocationListenerTransport(@NonNull LocationListener listener) { Preconditions.checkArgument(listener != null, "invalid null listener"); - - mExecutor = new HandlerExecutor(handler); mListener = listener; } - private LocationListenerTransport(@NonNull Executor executor, - @NonNull LocationListener listener) { - Preconditions.checkArgument(executor != null, "invalid null executor"); - Preconditions.checkArgument(listener != null, "invalid null listener"); - - mExecutor = executor; - mListener = listener; + public LocationListener getKey() { + return mListener; } - private LocationListener getKey() { - return mListener; + public void register(@NonNull Executor executor) { + Preconditions.checkArgument(executor != null, "invalid null executor"); + mExecutor = executor; } - private void unregisterListener() { - mListener = null; + public void unregister() { + mExecutor = null; } @Override public void onLocationChanged(Location location) { + Executor currentExecutor = mExecutor; + if (currentExecutor == null) { + return; + } + try { - mExecutor.execute(() -> { + currentExecutor.execute(() -> { try { - LocationListener listener = mListener; - if (listener == null) { + if (currentExecutor != mExecutor) { return; } // we may be under the binder identity if a direct executor is used long identity = Binder.clearCallingIdentity(); try { - listener.onLocationChanged(location); + mListener.onLocationChanged(location); } finally { Binder.restoreCallingIdentity(identity); } @@ -2295,18 +2297,22 @@ public class LocationManager { @Override public void onStatusChanged(String provider, int status, Bundle extras) { + Executor currentExecutor = mExecutor; + if (currentExecutor == null) { + return; + } + try { - mExecutor.execute(() -> { + currentExecutor.execute(() -> { try { - LocationListener listener = mListener; - if (listener == null) { + if (currentExecutor != mExecutor) { return; } // we may be under the binder identity if a direct executor is used long identity = Binder.clearCallingIdentity(); try { - listener.onStatusChanged(provider, status, extras); + mListener.onStatusChanged(provider, status, extras); } finally { Binder.restoreCallingIdentity(identity); } @@ -2322,18 +2328,22 @@ public class LocationManager { @Override public void onProviderEnabled(String provider) { + Executor currentExecutor = mExecutor; + if (currentExecutor == null) { + return; + } + try { - mExecutor.execute(() -> { + currentExecutor.execute(() -> { try { - LocationListener listener = mListener; - if (listener == null) { + if (currentExecutor != mExecutor) { return; } // we may be under the binder identity if a direct executor is used long identity = Binder.clearCallingIdentity(); try { - listener.onProviderEnabled(provider); + mListener.onProviderEnabled(provider); } finally { Binder.restoreCallingIdentity(identity); } @@ -2349,18 +2359,22 @@ public class LocationManager { @Override public void onProviderDisabled(String provider) { + Executor currentExecutor = mExecutor; + if (currentExecutor == null) { + return; + } + try { - mExecutor.execute(() -> { + currentExecutor.execute(() -> { try { - LocationListener listener = mListener; - if (listener == null) { + if (currentExecutor != mExecutor) { return; } // we may be under the binder identity if a direct executor is used long identity = Binder.clearCallingIdentity(); try { - listener.onProviderDisabled(provider); + mListener.onProviderDisabled(provider); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java index a107dd793551..c205bb49228c 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java @@ -15,11 +15,11 @@ */ package com.android.systemui.car; -import android.content.Context; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationData; import javax.inject.Inject; import javax.inject.Singleton; @@ -34,8 +34,8 @@ import javax.inject.Singleton; public class CarNotificationEntryManager extends NotificationEntryManager { @Inject - public CarNotificationEntryManager(Context context) { - super(context); + public CarNotificationEntryManager(NotificationData notificationData) { + super(notificationData); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java index ea39317fb045..81ca9eaf8e36 100644 --- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java +++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java @@ -111,8 +111,9 @@ public class RecentLocationAccesses { for (int op : LOCATION_OPS) { final String permission = AppOpsManager.opToPermission(op); final int permissionFlags = pm.getPermissionFlags(permission, packageName, user); - if (PermissionChecker.checkPermission(mContext, permission, -1, uid, packageName) - == PermissionChecker.PERMISSION_GRANTED) { + if (PermissionChecker.checkPermissionForPreflight(mContext, permission, + PermissionChecker.PID_UNKNOWN, uid, packageName) + == PermissionChecker.PERMISSION_GRANTED) { if ((permissionFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) == 0) { showApp = false; diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java index 60c9984e5ed4..104cc8f9841c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java +++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java @@ -110,9 +110,9 @@ public class RecentLocationApps { final String permission = AppOpsManager.opToPermission(op); final int permissionFlags = pm.getPermissionFlags(permission, packageName, user); - if (PermissionChecker.checkPermission(mContext, permission, -1, uid, - packageName) - == PermissionChecker.PERMISSION_GRANTED) { + if (PermissionChecker.checkPermissionForPreflight(mContext, permission, + PermissionChecker.PID_UNKNOWN, uid, packageName) + == PermissionChecker.PERMISSION_GRANTED) { if ((permissionFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) == 0) { diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 8c2e43170cc2..b8372adfa074 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -114,7 +114,6 @@ public class SecureSettings { Settings.Secure.ASSIST_GESTURE_WAKE_ENABLED, Settings.Secure.VR_DISPLAY_MODE, Settings.Secure.NOTIFICATION_BADGING, - Settings.Secure.NOTIFICATION_BUBBLES, Settings.Secure.NOTIFICATION_DISMISS_RTL, Settings.Secure.QS_AUTO_ADDED_TILES, Settings.Secure.SCREENSAVER_ENABLED, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index f160edc6e446..976f3365ab98 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -157,7 +157,6 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.VR_DISPLAY_MODE, new DiscreteValueValidator(new String[] {"0", "1"})); VALIDATORS.put(Secure.NOTIFICATION_BADGING, BOOLEAN_VALIDATOR); - VALIDATORS.put(Secure.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.NOTIFICATION_DISMISS_RTL, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.QS_AUTO_ADDED_TILES, TILE_LIST_VALIDATOR); VALIDATORS.put(Secure.SCREENSAVER_ENABLED, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 720266a72423..16c96e67c85a 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -4385,8 +4385,7 @@ public class SettingsProvider extends ContentProvider { if (currentVersion == 182) { // Remove secure bubble settings. - getSecureSettingsLocked(userId).deleteSettingLocked( - Secure.NOTIFICATION_BUBBLES); + getSecureSettingsLocked(userId).deleteSettingLocked("notification_bubbles"); // Add global bubble settings. getGlobalSettingsLocked().insertSettingLocked(Global.NOTIFICATION_BUBBLES, diff --git a/packages/SystemUI/res/drawable/auth_dialog_lock.xml b/packages/SystemUI/res/drawable/auth_dialog_lock.xml new file mode 100644 index 000000000000..8146c16e4aaf --- /dev/null +++ b/packages/SystemUI/res/drawable/auth_dialog_lock.xml @@ -0,0 +1,27 @@ +<!-- + ~ 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="32dp" + android:height="32dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="?android:attr/colorAccent" + android:pathData="M12,15m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/> + <path + android:fillColor="?android:attr/colorAccent" + android:pathData="M18,8h-1.5V5.5C16.5,3.01 14.49,1 12,1S7.5,3.01 7.5,5.5V8H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10C20,8.9 19.1,8 18,8zM9.5,5.5C9.5,4.12 10.62,3 12,3c1.38,0 2.5,1.12 2.5,2.5V8h-5V5.5zM18,20H6V10h1.5h9H18V20z"/> +</vector> diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml new file mode 100644 index 000000000000..c3fa39e5a87f --- /dev/null +++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml @@ -0,0 +1,113 @@ +<!-- + ~ 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. + --> + +<com.android.systemui.biometrics.AuthCredentialPatternView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:elevation="@dimen/biometric_dialog_elevation"> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:gravity="center" + android:orientation="vertical"> + + <Space + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1"/> + + <ImageView + android:layout_width="32dp" + android:layout_height="32dp" + android:background="@drawable/auth_dialog_lock"/> + + <TextView + android:id="@+id/title" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" + android:layout_marginTop="12dp" + android:textSize="20sp" + android:gravity="center" + android:textColor="?android:attr/textColorPrimary"/> + + <TextView + android:id="@+id/subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" + android:layout_marginTop="8dp" + android:textSize="16sp" + android:gravity="center" + android:textColor="?android:attr/textColorPrimary"/> + + <TextView + android:id="@+id/description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" + android:layout_marginTop="8dp" + android:gravity="center" + android:textSize="16sp" + android:textColor="?android:attr/textColorPrimary"/> + + <Space + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1"/> + + <TextView + android:id="@+id/error" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" + android:textSize="16sp" + android:gravity="center" + android:textColor="?android:attr/colorError"/> + + <Space + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1"/> + + </LinearLayout> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:orientation="vertical" + android:gravity="center"> + + <com.android.internal.widget.LockPatternView + android:id="@+id/lockPattern" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="40dp" + android:layout_marginRight="40dp" + android:layout_gravity="center" + android:clipChildren="false" + android:clipToPadding="false" + style="@style/LockPatternStyleBiometricPrompt"/> + + </LinearLayout> + +</com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/auth_container_view.xml b/packages/SystemUI/res/layout/auth_container_view.xml index 23199aacc093..3db01a4e7f3a 100644 --- a/packages/SystemUI/res/layout/auth_container_view.xml +++ b/packages/SystemUI/res/layout/auth_container_view.xml @@ -34,7 +34,7 @@ android:elevation="@dimen/biometric_dialog_elevation"/> <ScrollView - android:id="@+id/scrollview" + android:id="@+id/biometric_scrollview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|bottom" diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml new file mode 100644 index 000000000000..4aed0333e9ca --- /dev/null +++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml @@ -0,0 +1,101 @@ +<!-- + ~ 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. + --> + +<com.android.systemui.biometrics.AuthCredentialPasswordView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center_horizontal" + android:elevation="@dimen/biometric_dialog_elevation"> + + <Space + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1"/> + + <ImageView + android:layout_width="32dp" + android:layout_height="32dp" + android:background="@drawable/auth_dialog_lock"/> + + <TextView + android:id="@+id/title" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" + android:layout_marginTop="12dp" + android:textSize="20sp" + android:gravity="center" + android:textColor="?android:attr/textColorPrimary"/> + + <TextView + android:id="@+id/subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" + android:layout_marginTop="8dp" + android:textSize="16sp" + android:gravity="center" + android:textColor="?android:attr/textColorPrimary"/> + + <TextView + android:id="@+id/description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" + android:layout_marginTop="8dp" + android:gravity="center" + android:textSize="16sp" + android:textColor="?android:attr/textColorPrimary"/> + + <Space + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1"/> + + <TextView + android:id="@+id/error" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" + android:textSize="16sp" + android:gravity="center" + android:textColor="?android:attr/colorError"/> + + <EditText + android:id="@+id/lockPassword" + android:layout_marginBottom="20dp" + android:layout_marginLeft="100dp" + android:layout_marginRight="100dp" + android:layout_width="208dp" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:gravity="center" + android:inputType="textPassword" + android:maxLength="500" + android:textSize="16sp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:imeOptions="flagForceAscii" + style="@style/LockPatternStyleBiometricPrompt"/> + + <Space + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="5"/> + +</com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml new file mode 100644 index 000000000000..c9edcd606277 --- /dev/null +++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml @@ -0,0 +1,97 @@ +<!-- + ~ 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. + --> + +<com.android.systemui.biometrics.AuthCredentialPatternView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center_horizontal" + android:elevation="@dimen/biometric_dialog_elevation"> + + <Space + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1"/> + + <ImageView + android:layout_width="32dp" + android:layout_height="32dp" + android:background="@drawable/auth_dialog_lock"/> + + <TextView + android:id="@+id/title" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" + android:layout_marginTop="12dp" + android:textSize="20sp" + android:gravity="center" + android:textColor="?android:attr/textColorPrimary"/> + + <TextView + android:id="@+id/subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" + android:layout_marginTop="8dp" + android:textSize="16sp" + android:gravity="center" + android:textColor="?android:attr/textColorPrimary"/> + + <TextView + android:id="@+id/description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" + android:layout_marginTop="8dp" + android:gravity="center" + android:textSize="16sp" + android:textColor="?android:attr/textColorPrimary"/> + + <Space + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="3"/> + + <TextView + android:id="@+id/error" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" + android:textSize="16sp" + android:gravity="center" + android:textColor="?android:attr/colorError"/> + + <com.android.internal.widget.LockPatternView + android:id="@+id/lockPattern" + android:layout_marginBottom="20dp" + android:layout_marginLeft="40dp" + android:layout_marginRight="40dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:clipChildren="false" + android:clipToPadding="false" + style="@style/LockPatternStyleBiometricPrompt"/> + + <Space + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1"/> + +</com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 1079206c81ce..d722d618e416 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1009,10 +1009,15 @@ <!-- Biometric Dialog values --> <dimen name="biometric_dialog_biometric_icon_size">64dp</dimen> <dimen name="biometric_dialog_corner_size">4dp</dimen> + <!-- Y translation when showing/dismissing the dialog--> <dimen name="biometric_dialog_animation_translation_offset">350dp</dimen> <dimen name="biometric_dialog_border_padding">4dp</dimen> <dimen name="biometric_dialog_elevation">1dp</dimen> <dimen name="biometric_dialog_icon_padding">16dp</dimen> + <!-- Y translation for biometric contents when transitioning to device credential UI --> + <dimen name="biometric_dialog_medium_to_large_translation_offset">100dp</dimen> + <!-- Y translation for credential contents when animating in --> + <dimen name="biometric_dialog_credential_translation_offset">60dp</dimen> <!-- Wireless Charging Animation values --> <dimen name="wireless_charging_dots_radius_start">0dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c5547800360d..8335c116c95f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -311,6 +311,21 @@ <!-- Talkback string when a biometric is authenticated [CHAR LIMIT=NONE] --> <string name="biometric_dialog_authenticated">Authenticated</string> + <!-- Button text shown on BiometricPrompt giving the user the option to use an alternate form of authentication (Pin) [CHAR LIMIT=30] --> + <string name="biometric_dialog_use_pin">Use PIN</string> + <!-- Button text shown on BiometricPrompt giving the user the option to use an alternate form of authentication (Pattern) [CHAR LIMIT=30] --> + <string name="biometric_dialog_use_pattern">Use pattern</string> + <!-- Button text shown on BiometricPrompt giving the user the option to use an alternate form of authentication (Pass) [CHAR LIMIT=30] --> + <string name="biometric_dialog_use_password">Use password</string> + <!-- Error string shown when the user enters an incorrect PIN [CHAR LIMIT=40]--> + <string name="biometric_dialog_wrong_pin">Wrong PIN</string> + <!-- Error string shown when the user enters an incorrect pattern [CHAR LIMIT=40]--> + <string name="biometric_dialog_wrong_pattern">Wrong pattern</string> + <!-- Error string shown when the user enters an incorrect password [CHAR LIMIT=40]--> + <string name="biometric_dialog_wrong_password">Wrong password</string> + <!-- Error string shown when the user enters too many incorrect attempts [CHAR LIMIT=120]--> + <string name="biometric_dialog_credential_too_many_attempts">Too many incorrect attempts.\nTry again in <xliff:g id="number">%d</xliff:g> seconds.</string> + <!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication --> <string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string> <!-- Content description of the fingerprint icon when the system-provided fingerprint dialog is showing, for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 6374191c4d7b..96fbcbba6e8b 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -314,6 +314,12 @@ <item name="*android:errorColor">?android:attr/colorError</item> </style> + <style name="LockPatternStyleBiometricPrompt"> + <item name="*android:regularColor">?android:attr/colorForeground</item> + <item name="*android:successColor">?android:attr/colorForeground</item> + <item name="*android:errorColor">?android:attr/colorError</item> + </style> + <style name="qs_theme" parent="@*android:style/Theme.DeviceDefault.QuickSettings"> <item name="lightIconTheme">@style/QSIconTheme</item> <item name="darkIconTheme">@style/QSIconTheme</item> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index a00a8e7aa4e9..b9fe9334d14c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -76,7 +76,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe // How much you need to drag the bouncer to trigger an auth retry (in dps.) private static final float MIN_DRAG_SIZE = 10; // How much to scale the default slop by, to avoid accidental drags. - private static final float SLOP_SCALE = 2f; + private static final float SLOP_SCALE = 4f; private KeyguardSecurityModel mSecurityModel; private LockPatternUtils mLockPatternUtils; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index 73bbce9c5b35..d20cd72f0712 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -16,8 +16,6 @@ package com.android.systemui.biometrics; -import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -25,6 +23,7 @@ import android.animation.ValueAnimator; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.hardware.biometrics.BiometricPrompt; import android.os.Bundle; @@ -34,7 +33,7 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.View; -import android.view.accessibility.AccessibilityEvent; +import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; import android.widget.Button; import android.widget.ImageView; @@ -42,10 +41,13 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.widget.LockPatternUtils; import com.android.systemui.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; /** * Contains the Biometric views (title, subtitle, icon, buttons, etc) and its controllers. @@ -97,6 +99,7 @@ public abstract class AuthBiometricView extends LinearLayout { int ACTION_BUTTON_NEGATIVE = 3; int ACTION_BUTTON_TRY_AGAIN = 4; int ACTION_ERROR = 5; + int ACTION_USE_DEVICE_CREDENTIAL = 6; /** * When an action has occurred. The caller will only invoke this when the callback should @@ -145,6 +148,14 @@ public abstract class AuthBiometricView extends LinearLayout { public int getDelayAfterError() { return BiometricPrompt.HIDE_DIALOG_DELAY; } + + public int getMediumToLargeAnimationDurationMs() { + return AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS; + } + + public int getAnimateCredentialStartDelayMs() { + return AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS; + } } private final Injector mInjector; @@ -154,8 +165,9 @@ public abstract class AuthBiometricView extends LinearLayout { private final int mTextColorHint; private AuthPanelController mPanelController; - private Bundle mBundle; + private Bundle mBiometricPromptBundle; private boolean mRequireConfirmation; + private int mUserId; @AuthDialog.DialogSize int mSize = AuthDialog.SIZE_UNKNOWN; private TextView mTitleView; @@ -212,6 +224,9 @@ public abstract class AuthBiometricView extends LinearLayout { } else if (mSize == AuthDialog.SIZE_SMALL) { Log.w(TAG, "Ignoring background click during small dialog"); return; + } else if (mSize == AuthDialog.SIZE_LARGE) { + Log.w(TAG, "Ignoring background click during large dialog"); + return; } mCallback.onAction(Callback.ACTION_USER_CANCELED); }; @@ -256,7 +271,7 @@ public abstract class AuthBiometricView extends LinearLayout { } public void setBiometricPromptBundle(Bundle bundle) { - mBundle = bundle; + mBiometricPromptBundle = bundle; } public void setCallback(Callback callback) { @@ -267,6 +282,10 @@ public abstract class AuthBiometricView extends LinearLayout { backgroundView.setOnClickListener(mBackgroundClickListener); } + public void setUserId(int userId) { + mUserId = userId; + } + public void setRequireConfirmation(boolean requireConfirmation) { mRequireConfirmation = requireConfirmation; } @@ -287,7 +306,7 @@ public abstract class AuthBiometricView extends LinearLayout { final int newHeight = mIconView.getHeight() + 2 * (int) iconPadding; mPanelController.updateForContentDimensions(mMediumWidth, newHeight, - false /* animate */); + 0 /* animateDurationMs */); mSize = newSize; } else if (mSize == AuthDialog.SIZE_SMALL && newSize == AuthDialog.SIZE_MEDIUM) { @@ -305,10 +324,8 @@ public abstract class AuthBiometricView extends LinearLayout { // Animate the text final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1); - opacityAnimator.setDuration(AuthDialog.ANIMATE_DURATION_MS); opacityAnimator.addUpdateListener((animation) -> { final float opacity = (float) animation.getAnimatedValue(); - mTitleView.setAlpha(opacity); mIndicatorView.setAlpha(opacity); mNegativeButton.setAlpha(opacity); @@ -324,7 +341,7 @@ public abstract class AuthBiometricView extends LinearLayout { // Choreograph together final AnimatorSet as = new AnimatorSet(); - as.setDuration(AuthDialog.ANIMATE_DURATION_MS); + as.setDuration(AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS); as.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { @@ -355,11 +372,73 @@ public abstract class AuthBiometricView extends LinearLayout { as.start(); // Animate the panel mPanelController.updateForContentDimensions(mMediumWidth, mMediumHeight, - true /* animate */); + AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS); } else if (newSize == AuthDialog.SIZE_MEDIUM) { mPanelController.updateForContentDimensions(mMediumWidth, mMediumHeight, - false /* animate */); + 0 /* animateDurationMs */); mSize = newSize; + } else if (newSize == AuthDialog.SIZE_LARGE) { + final boolean isManagedProfile = Utils.isManagedProfile(mContext, mUserId); + + // If it's a managed profile, animate the contents and panel down, since the credential + // contents will be shown on the same "layer" as the background. If it's not a managed + // profile, animate the contents up and expand the panel to full-screen - the credential + // contents will be shown on the same "layer" as the panel. + final float translationY = isManagedProfile ? + -getResources().getDimension( + R.dimen.biometric_dialog_animation_translation_offset) + : getResources().getDimension( + R.dimen.biometric_dialog_medium_to_large_translation_offset); + final AuthBiometricView biometricView = this; + + // Translate at full duration + final ValueAnimator translationAnimator = ValueAnimator.ofFloat( + biometricView.getY(), biometricView.getY() - translationY); + translationAnimator.setDuration(mInjector.getMediumToLargeAnimationDurationMs()); + translationAnimator.addUpdateListener((animation) -> { + final float translation = (float) animation.getAnimatedValue(); + biometricView.setTranslationY(translation); + }); + translationAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (biometricView.getParent() != null) { + ((ViewGroup) biometricView.getParent()).removeView(biometricView); + } + mSize = newSize; + } + }); + + // Opacity to 0 in half duration + final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(1, 0); + opacityAnimator.setDuration(mInjector.getMediumToLargeAnimationDurationMs() / 2); + opacityAnimator.addUpdateListener((animation) -> { + final float opacity = (float) animation.getAnimatedValue(); + biometricView.setAlpha(opacity); + }); + + if (!isManagedProfile) { + mPanelController.setUseFullScreen(true); + mPanelController.updateForContentDimensions( + mPanelController.getContainerWidth(), + mPanelController.getContainerHeight(), + mInjector.getMediumToLargeAnimationDurationMs()); + } + + // Start the animations together + AnimatorSet as = new AnimatorSet(); + List<Animator> animators = new ArrayList<>(); + animators.add(translationAnimator); + animators.add(opacityAnimator); + if (isManagedProfile) { + animators.add(mPanelController.getTranslationAnimator(translationY)); + animators.add(mPanelController.getAlphaAnimator(0)); + } + as.playTogether(animators); + as.setDuration(isManagedProfile ? mInjector.getMediumToLargeAnimationDurationMs() + : mInjector.getMediumToLargeAnimationDurationMs() * 2 / 3); + as.start(); } else { Log.e(TAG, "Unknown transition from: " + mSize + " to: " + newSize); } @@ -528,7 +607,11 @@ public abstract class AuthBiometricView extends LinearLayout { if (mState == STATE_PENDING_CONFIRMATION) { mCallback.onAction(Callback.ACTION_USER_CANCELED); } else { - mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE); + if (isDeviceCredentialAllowed()) { + startTransitionToCredentialUI(); + } else { + mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE); + } } }); @@ -544,6 +627,16 @@ public abstract class AuthBiometricView extends LinearLayout { }); } + /** + * Kicks off the animation process and invokes the callback. + */ + void startTransitionToCredentialUI() { + updateSize(AuthDialog.SIZE_LARGE); + mHandler.postDelayed(() -> { + mCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL); + }, mInjector.getAnimateCredentialStartDelayMs()); + } + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); @@ -556,11 +649,37 @@ public abstract class AuthBiometricView extends LinearLayout { */ @VisibleForTesting void onAttachedToWindowInternal() { - setText(mTitleView, mBundle.getString(BiometricPrompt.KEY_TITLE)); - setText(mNegativeButton, mBundle.getString(BiometricPrompt.KEY_NEGATIVE_TEXT)); + setText(mTitleView, mBiometricPromptBundle.getString(BiometricPrompt.KEY_TITLE)); + + final String negativeText; + if (isDeviceCredentialAllowed()) { + + final @Utils.CredentialType int credentialType = + Utils.getCredentialType(mContext, mUserId); + switch(credentialType) { + case Utils.CREDENTIAL_PIN: + negativeText = getResources().getString(R.string.biometric_dialog_use_pin); + break; + case Utils.CREDENTIAL_PATTERN: + negativeText = getResources().getString(R.string.biometric_dialog_use_pattern); + break; + case Utils.CREDENTIAL_PASSWORD: + negativeText = getResources().getString(R.string.biometric_dialog_use_password); + break; + default: + negativeText = getResources().getString(R.string.biometric_dialog_use_password); + break; + } - setTextOrHide(mSubtitleView, mBundle.getString(BiometricPrompt.KEY_SUBTITLE)); - setTextOrHide(mDescriptionView, mBundle.getString(BiometricPrompt.KEY_DESCRIPTION)); + } else { + negativeText = mBiometricPromptBundle.getString(BiometricPrompt.KEY_NEGATIVE_TEXT); + } + setText(mNegativeButton, negativeText); + + setTextOrHide(mSubtitleView, + mBiometricPromptBundle.getString(BiometricPrompt.KEY_SUBTITLE)); + setTextOrHide(mDescriptionView, + mBiometricPromptBundle.getString(BiometricPrompt.KEY_DESCRIPTION)); if (mSavedState == null) { updateState(STATE_AUTHENTICATING_ANIMATING_IN); @@ -655,4 +774,8 @@ public abstract class AuthBiometricView extends LinearLayout { } } } + + private boolean isDeviceCredentialAllowed() { + return Utils.isDeviceCredentialAllowed(mBiometricPromptBundle); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 6555c75f677a..a9359d4ff0db 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -24,7 +24,9 @@ import android.content.Context; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; +import android.hardware.biometrics.Authenticator; import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricPrompt; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -36,6 +38,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.Interpolator; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; @@ -72,17 +75,20 @@ public class AuthContainerView extends LinearLayout @interface ContainerState {} final Config mConfig; + private final Injector mInjector; private final IBinder mWindowToken = new Binder(); private final WindowManager mWindowManager; private final AuthPanelController mPanelController; private final Interpolator mLinearOutSlowIn; @VisibleForTesting final BiometricCallback mBiometricCallback; + private final CredentialCallback mCredentialCallback; - private final ViewGroup mContainerView; - private final AuthBiometricView mBiometricView; + @VisibleForTesting final FrameLayout mFrameLayout; + @VisibleForTesting @Nullable AuthBiometricView mBiometricView; + @VisibleForTesting @Nullable AuthCredentialView mCredentialView; private final ImageView mBackgroundView; - private final ScrollView mScrollView; + @VisibleForTesting final ScrollView mBiometricScrollView; private final View mPanelView; private final float mTranslationY; @@ -145,7 +151,31 @@ public class AuthContainerView extends LinearLayout public AuthContainerView build(int modalityMask) { mConfig.mModalityMask = modalityMask; - return new AuthContainerView(mConfig); + return new AuthContainerView(mConfig, new Injector()); + } + } + + public static class Injector { + ScrollView getBiometricScrollView(FrameLayout parent) { + return parent.findViewById(R.id.biometric_scrollview); + } + + FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) { + return (FrameLayout) factory.inflate( + R.layout.auth_container_view, root, false /* attachToRoot */); + } + + AuthPanelController getPanelController(Context context, View panelView, + boolean isManagedProfile) { + return new AuthPanelController(context, panelView, isManagedProfile); + } + + ImageView getBackgroundView(FrameLayout parent) { + return parent.findViewById(R.id.background); + } + + View getPanelView(FrameLayout parent) { + return parent.findViewById(R.id.panel); } } @@ -155,7 +185,7 @@ public class AuthContainerView extends LinearLayout public void onAction(int action) { switch (action) { case AuthBiometricView.Callback.ACTION_AUTHENTICATED: - animateAway(AuthDialogCallback.DISMISSED_AUTHENTICATED); + animateAway(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED); break; case AuthBiometricView.Callback.ACTION_USER_CANCELED: animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); @@ -169,17 +199,30 @@ public class AuthContainerView extends LinearLayout case AuthBiometricView.Callback.ACTION_ERROR: animateAway(AuthDialogCallback.DISMISSED_ERROR); break; + case AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL: + mConfig.mCallback.onDeviceCredentialPressed(); + addCredentialView(false /* animatePanel */, true /* animateContents */); + break; default: Log.e(TAG, "Unhandled action: " + action); } } } + final class CredentialCallback implements AuthCredentialView.Callback { + @Override + public void onCredentialMatched() { + animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED); + } + } + @VisibleForTesting - AuthContainerView(Config config) { + AuthContainerView(Config config, Injector injector) { super(config.mContext); mConfig = config; + mInjector = injector; + mWindowManager = mContext.getSystemService(WindowManager.class); mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class); @@ -187,51 +230,48 @@ public class AuthContainerView extends LinearLayout .getDimension(R.dimen.biometric_dialog_animation_translation_offset); mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN; mBiometricCallback = new BiometricCallback(); + mCredentialCallback = new CredentialCallback(); final LayoutInflater factory = LayoutInflater.from(mContext); - mContainerView = (ViewGroup) factory.inflate( - R.layout.auth_container_view, this, false /* attachToRoot */); - - mPanelView = mContainerView.findViewById(R.id.panel); - mPanelController = new AuthPanelController(mContext, mPanelView); - - // TODO: Update with new controllers if multi-modal authentication can occur simultaneously - if (config.mModalityMask == BiometricAuthenticator.TYPE_FINGERPRINT) { - mBiometricView = (AuthBiometricFingerprintView) - factory.inflate(R.layout.auth_biometric_fingerprint_view, null, false); - } else if (config.mModalityMask == BiometricAuthenticator.TYPE_FACE) { - mBiometricView = (AuthBiometricFaceView) - factory.inflate(R.layout.auth_biometric_face_view, null, false); - } else { - Log.e(TAG, "Unsupported modality mask: " + config.mModalityMask); - mBiometricView = null; - mBackgroundView = null; - mScrollView = null; - return; + mFrameLayout = mInjector.inflateContainerView(factory, this); + + final boolean isManagedProfile = Utils.isManagedProfile(mContext, mConfig.mUserId); + + mPanelView = mInjector.getPanelView(mFrameLayout); + mPanelController = mInjector.getPanelController(mContext, mPanelView, isManagedProfile); + + // Inflate biometric view only if necessary. + if (Utils.isBiometricAllowed(mConfig.mBiometricPromptBundle)) { + if (config.mModalityMask == BiometricAuthenticator.TYPE_FINGERPRINT) { + mBiometricView = (AuthBiometricFingerprintView) + factory.inflate(R.layout.auth_biometric_fingerprint_view, null, false); + } else if (config.mModalityMask == BiometricAuthenticator.TYPE_FACE) { + mBiometricView = (AuthBiometricFaceView) + factory.inflate(R.layout.auth_biometric_face_view, null, false); + } else { + Log.e(TAG, "Unsupported biometric modality: " + config.mModalityMask); + mBiometricView = null; + mBackgroundView = null; + mBiometricScrollView = null; + return; + } } - mBackgroundView = mContainerView.findViewById(R.id.background); + mBiometricScrollView = mInjector.getBiometricScrollView(mFrameLayout); + mBackgroundView = mInjector.getBackgroundView(mFrameLayout); + - UserManager userManager = mContext.getSystemService(UserManager.class); - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - if (userManager.isManagedProfile(mConfig.mUserId)) { + if (isManagedProfile) { final Drawable image = getResources().getDrawable(R.drawable.work_challenge_background, mContext.getTheme()); + final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); image.setColorFilter(dpm.getOrganizationColorForUser(mConfig.mUserId), PorterDuff.Mode.DARKEN); mBackgroundView.setScaleType(ImageView.ScaleType.CENTER_CROP); mBackgroundView.setImageDrawable(image); } - mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation); - mBiometricView.setPanelController(mPanelController); - mBiometricView.setBiometricPromptBundle(config.mBiometricPromptBundle); - mBiometricView.setCallback(mBiometricCallback); - mBiometricView.setBackgroundView(mBackgroundView); - - mScrollView = mContainerView.findViewById(R.id.scrollview); - mScrollView.addView(mBiometricView); - addView(mContainerView); + addView(mFrameLayout); setOnKeyListener((v, keyCode, event) -> { if (keyCode != KeyEvent.KEYCODE_BACK) { @@ -248,6 +288,53 @@ public class AuthContainerView extends LinearLayout } @Override + public boolean isAllowDeviceCredentials() { + return Utils.isDeviceCredentialAllowed(mConfig.mBiometricPromptBundle); + } + + private void addBiometricView() { + mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation); + mBiometricView.setPanelController(mPanelController); + mBiometricView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle); + mBiometricView.setCallback(mBiometricCallback); + mBiometricView.setBackgroundView(mBackgroundView); + mBiometricView.setUserId(mConfig.mUserId); + mBiometricScrollView.addView(mBiometricView); + } + + /** + * Adds the credential view. When going from biometric to credential view, the biometric + * view starts the panel expansion animation. If the credential view is being shown first, + * it should own the panel expansion. + * @param animatePanel if the credential view needs to own the panel expansion animation + */ + private void addCredentialView(boolean animatePanel, boolean animateContents) { + final LayoutInflater factory = LayoutInflater.from(mContext); + final int credentialType = Utils.getCredentialType(mContext, mConfig.mUserId); + switch (credentialType) { + case Utils.CREDENTIAL_PATTERN: + mCredentialView = (AuthCredentialView) factory.inflate( + R.layout.auth_credential_pattern_view, null, false); + break; + case Utils.CREDENTIAL_PIN: + case Utils.CREDENTIAL_PASSWORD: + mCredentialView = (AuthCredentialView) factory.inflate( + R.layout.auth_credential_password_view, null, false); + break; + default: + throw new IllegalStateException("Unknown credential type: " + credentialType); + } + + mCredentialView.setContainerView(this); + mCredentialView.setUser(mConfig.mUserId); + mCredentialView.setCallback(mCredentialCallback); + mCredentialView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle); + mCredentialView.setPanelController(mPanelController, animatePanel); + mCredentialView.setShouldAnimateContents(animateContents); + mFrameLayout.addView(mCredentialView); + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mPanelController.setContainerDimensions(getMeasuredWidth(), getMeasuredHeight()); @@ -256,8 +343,22 @@ public class AuthContainerView extends LinearLayout @Override public void onAttachedToWindow() { super.onAttachedToWindow(); + onAttachedToWindowInternal(); + } + + @VisibleForTesting + void onAttachedToWindowInternal() { mWakefulnessLifecycle.addObserver(this); + if (Utils.isBiometricAllowed(mConfig.mBiometricPromptBundle)) { + addBiometricView(); + } else if (Utils.isDeviceCredentialAllowed(mConfig.mBiometricPromptBundle)) { + addCredentialView(true /* animatePanel */, false /* animateContents */); + } else { + throw new IllegalStateException("Unknown configuration: " + + Utils.getAuthenticators(mConfig.mBiometricPromptBundle)); + } + if (mConfig.mSkipIntro) { mContainerState = STATE_SHOWING; } else { @@ -265,7 +366,7 @@ public class AuthContainerView extends LinearLayout // The background panel and content are different views since we need to be able to // animate them separately in other places. mPanelView.setY(mTranslationY); - mScrollView.setY(mTranslationY); + mBiometricScrollView.setY(mTranslationY); setAlpha(0f); postOnAnimation(() -> { @@ -276,12 +377,21 @@ public class AuthContainerView extends LinearLayout .withLayer() .withEndAction(this::onDialogAnimatedIn) .start(); - mScrollView.animate() + mBiometricScrollView.animate() .translationY(0) .setDuration(ANIMATION_DURATION_SHOW_MS) .setInterpolator(mLinearOutSlowIn) .withLayer() .start(); + if (mCredentialView != null && mCredentialView.isAttachedToWindow()) { + mCredentialView.setY(mTranslationY); + mCredentialView.animate() + .translationY(0) + .setDuration(ANIMATION_DURATION_SHOW_MS) + .setInterpolator(mLinearOutSlowIn) + .withLayer() + .start(); + } animate() .alpha(1f) .setDuration(ANIMATION_DURATION_SHOW_MS) @@ -305,7 +415,9 @@ public class AuthContainerView extends LinearLayout @Override public void show(WindowManager wm, @Nullable Bundle savedState) { - mBiometricView.restoreState(savedState); + if (mBiometricView != null) { + mBiometricView.restoreState(savedState); + } wm.addView(this, getLayoutParams(mWindowToken)); } @@ -346,7 +458,15 @@ public class AuthContainerView extends LinearLayout @Override public void onSaveState(@NonNull Bundle outState) { outState.putInt(AuthDialog.KEY_CONTAINER_STATE, mContainerState); - mBiometricView.onSaveState(outState); + // In the case where biometric and credential are both allowed, we can assume that + // biometric isn't showing if credential is showing since biometric is shown first. + outState.putBoolean(AuthDialog.KEY_BIOMETRIC_SHOWING, + mBiometricView != null && mCredentialView == null); + outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null); + + if (mBiometricView != null) { + mBiometricView.onSaveState(outState); + } } @Override @@ -354,6 +474,11 @@ public class AuthContainerView extends LinearLayout return mConfig.mOpPackageName; } + @Override + public void animateToCredentialUI() { + mBiometricView.startTransitionToCredentialUI(); + } + @VisibleForTesting void animateAway(int reason) { animateAway(true /* sendReason */, reason); @@ -391,12 +516,20 @@ public class AuthContainerView extends LinearLayout .withLayer() .withEndAction(endActionRunnable) .start(); - mScrollView.animate() + mBiometricScrollView.animate() .translationY(mTranslationY) .setDuration(ANIMATION_DURATION_AWAY_MS) .setInterpolator(mLinearOutSlowIn) .withLayer() .start(); + if (mCredentialView != null && mCredentialView.isAttachedToWindow()) { + mCredentialView.animate() + .translationY(mTranslationY) + .setDuration(ANIMATION_DURATION_AWAY_MS) + .setInterpolator(mLinearOutSlowIn) + .withLayer() + .start(); + } animate() .alpha(0f) .setDuration(ANIMATION_DURATION_AWAY_MS) @@ -431,7 +564,9 @@ public class AuthContainerView extends LinearLayout return; } mContainerState = STATE_SHOWING; - mBiometricView.onDialogAnimatedIn(); + if (mBiometricView != null) { + mBiometricView.onDialogAnimatedIn(); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index d10a3fede412..4c2afb0a14ca 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -23,6 +23,8 @@ import android.app.TaskStackListener; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.hardware.biometrics.Authenticator; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.os.Bundle; @@ -105,6 +107,15 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } @Override + public void onDeviceCredentialPressed() { + try { + mReceiver.onDeviceCredentialPressed(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException when handling credential button", e); + } + } + + @Override public void onDismissed(@DismissedReason int reason) { switch (reason) { case AuthDialogCallback.DISMISSED_USER_CANCELED: @@ -116,11 +127,12 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, break; case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE: - sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRMED); + sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED); break; - case AuthDialogCallback.DISMISSED_AUTHENTICATED: - sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED); + case AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED: + sendResultAndCleanUp( + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED); break; case AuthDialogCallback.DISMISSED_ERROR: @@ -131,6 +143,10 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED); break; + case AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED: + sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED); + break; + default: Log.e(TAG, "Unhandled reason: " + reason); break; @@ -185,16 +201,19 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } @Override - public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, - int type, boolean requireConfirmation, int userId, String opPackageName) { + public void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, + int biometricModality, boolean requireConfirmation, int userId, String opPackageName) { + final int authenticators = Utils.getAuthenticators(bundle); + if (DEBUG) { - Log.d(TAG, "showBiometricDialog, type: " + type + Log.d(TAG, "showAuthenticationDialog, authenticators: " + authenticators + + ", biometricModality: " + biometricModality + ", requireConfirmation: " + requireConfirmation); } SomeArgs args = SomeArgs.obtain(); args.arg1 = bundle; args.arg2 = receiver; - args.argi1 = type; + args.argi1 = biometricModality; args.arg3 = requireConfirmation; args.argi2 = userId; args.arg4 = opPackageName; @@ -204,6 +223,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, Log.w(TAG, "mCurrentDialog: " + mCurrentDialog); skipAnimation = true; } + showDialog(args, skipAnimation, null /* savedState */); } @@ -227,14 +247,21 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } @Override - public void onBiometricError(String error) { - if (DEBUG) Log.d(TAG, "onBiometricError: " + error); - mCurrentDialog.onError(error); + public void onBiometricError(int errorCode, String error) { + if (DEBUG) Log.d(TAG, "onBiometricError: " + errorCode + ", " + error); + + final boolean isLockout = errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT + || errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; + if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) { + mCurrentDialog.animateToCredentialUI(); + } else { + mCurrentDialog.onError(error); + } } @Override - public void hideBiometricDialog() { - if (DEBUG) Log.d(TAG, "hideBiometricDialog"); + public void hideAuthenticationDialog() { + if (DEBUG) Log.d(TAG, "hideAuthenticationDialog"); mCurrentDialog.dismissFromSystemServer(); } @@ -262,7 +289,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } if (DEBUG) { - Log.d(TAG, "showDialog, " + Log.d(TAG, "showDialog: " + args + " savedState: " + savedState + " mCurrentDialog: " + mCurrentDialog + " newDialog: " + newDialog @@ -306,6 +333,15 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, // to send its pending callback immediately. if (savedState.getInt(AuthDialog.KEY_CONTAINER_STATE) != AuthContainerView.STATE_ANIMATING_OUT) { + final boolean credentialShowing = + savedState.getBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING); + if (credentialShowing) { + // TODO: Clean this up + Bundle bundle = (Bundle) mCurrentDialogArgs.arg1; + bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, + Authenticator.TYPE_CREDENTIAL); + } + showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java new file mode 100644 index 000000000000..8df072e9b99e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.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.systemui.biometrics; + +import android.content.Context; +import android.text.InputType; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.TextView; + +import com.android.internal.widget.LockPatternChecker; +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.R; + +/** + * Pin and Password UI + */ +public class AuthCredentialPasswordView extends AuthCredentialView + implements TextView.OnEditorActionListener { + + private static final String TAG = "BiometricPrompt/AuthCredentialPasswordView"; + + private final InputMethodManager mImm; + private EditText mPasswordField; + + public AuthCredentialPasswordView(Context context, + AttributeSet attrs) { + super(context, attrs); + mImm = mContext.getSystemService(InputMethodManager.class); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mPasswordField = findViewById(R.id.lockPassword); + mPasswordField.setOnEditorActionListener(this); + mPasswordField.setOnKeyListener((v, keyCode, event) -> { + if (keyCode != KeyEvent.KEYCODE_BACK) { + return false; + } + if (event.getAction() == KeyEvent.ACTION_UP) { + mContainerView.animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); + } + return true; + }); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (mCredentialType == Utils.CREDENTIAL_PIN) { + mPasswordField.setInputType( + InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD); + } + + // Wait a bit to focus the field so the focusable flag on the window is already set then. + post(() -> { + mPasswordField.requestFocus(); + mImm.showSoftInput(mPasswordField, InputMethodManager.SHOW_IMPLICIT); + }); + } + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + // Check if this was the result of hitting the enter key + final boolean isSoftImeEvent = event == null + && (actionId == EditorInfo.IME_NULL + || actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT); + final boolean isKeyboardEnterKey = event != null + && KeyEvent.isConfirmKey(event.getKeyCode()) + && event.getAction() == KeyEvent.ACTION_DOWN; + if (isSoftImeEvent || isKeyboardEnterKey) { + checkPasswordAndUnlock(); + return true; + } + return false; + } + + private void checkPasswordAndUnlock() { + final byte[] password = LockPatternUtils.charSequenceToByteArray(mPasswordField.getText()); + if (password == null || password.length == 0) { + return; + } + + mPendingLockCheck = LockPatternChecker.checkPassword(mLockPatternUtils, + password, mUserId, this::onCredentialChecked); + } + + @Override + protected void onCredentialChecked(boolean matched, int timeoutMs) { + super.onCredentialChecked(matched, timeoutMs); + + if (matched) { + mImm.hideSoftInputFromWindow(getWindowToken(), 0 /* flags */); + } else { + mPasswordField.setText(""); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java new file mode 100644 index 000000000000..6c36f8263237 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java @@ -0,0 +1,102 @@ +/* + * 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.biometrics; + +import android.content.Context; +import android.util.AttributeSet; + +import com.android.internal.widget.LockPatternChecker; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockPatternView; +import com.android.systemui.R; + +import java.util.List; + +/** + * Pattern UI + */ +public class AuthCredentialPatternView extends AuthCredentialView { + + private LockPatternView mLockPatternView; + + private class UnlockPatternListener implements LockPatternView.OnPatternListener { + + @Override + public void onPatternStart() { + + } + + @Override + public void onPatternCleared() { + + } + + @Override + public void onPatternCellAdded(List<LockPatternView.Cell> pattern) { + + } + + @Override + public void onPatternDetected(List<LockPatternView.Cell> pattern) { + if (mPendingLockCheck != null) { + mPendingLockCheck.cancel(false); + } + + mLockPatternView.setEnabled(false); + + if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { + // Pattern size is less than the minimum, do not count it as a failed attempt. + onPatternChecked(false /* matched */, 0 /* timeoutMs */); + return; + } + + mPendingLockCheck = LockPatternChecker.checkPattern( + mLockPatternUtils, + pattern, + mUserId, + this::onPatternChecked); + } + + private void onPatternChecked(boolean matched, int timeoutMs) { + AuthCredentialPatternView.this.onCredentialChecked(matched, timeoutMs); + if (timeoutMs > 0) { + mLockPatternView.setEnabled(false); + } else { + mLockPatternView.setEnabled(true); + } + } + } + + @Override + protected void onErrorTimeoutFinish() { + super.onErrorTimeoutFinish(); + mLockPatternView.setEnabled(true); + } + + public AuthCredentialPatternView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mLockPatternView = findViewById(R.id.lockPattern); + mLockPatternView.setOnPatternListener(new UnlockPatternListener()); + mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(mUserId)); + mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java new file mode 100644 index 000000000000..8c8611e49dfb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java @@ -0,0 +1,265 @@ +/* + * 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.biometrics; + +import android.content.Context; +import android.hardware.biometrics.BiometricPrompt; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.CountDownTimer; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import android.view.accessibility.AccessibilityManager; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.Interpolators; +import com.android.systemui.R; + +/** + * Abstract base class for Pin, Pattern, or Password authentication, for + * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} + */ +public abstract class AuthCredentialView extends LinearLayout { + + private static final String TAG = "BiometricPrompt/AuthCredentialView"; + private static final int ERROR_DURATION_MS = 3000; + + private final AccessibilityManager mAccessibilityManager; + + protected final Handler mHandler; + + private Bundle mBiometricPromptBundle; + private AuthPanelController mPanelController; + private boolean mShouldAnimatePanel; + private boolean mShouldAnimateContents; + + private TextView mTitleView; + private TextView mSubtitleView; + private TextView mDescriptionView; + protected TextView mErrorView; + + protected @Utils.CredentialType int mCredentialType; + protected final LockPatternUtils mLockPatternUtils; + protected AuthContainerView mContainerView; + protected Callback mCallback; + protected AsyncTask<?, ?, ?> mPendingLockCheck; + protected int mUserId; + protected ErrorTimer mErrorTimer; + + interface Callback { + void onCredentialMatched(); + } + + protected static class ErrorTimer extends CountDownTimer { + private final TextView mErrorView; + private final Context mContext; + + /** + * @param millisInFuture The number of millis in the future from the call + * to {@link #start()} until the countdown is done and {@link + * #onFinish()} + * is called. + * @param countDownInterval The interval along the way to receive + * {@link #onTick(long)} callbacks. + */ + public ErrorTimer(Context context, long millisInFuture, long countDownInterval, + TextView errorView) { + super(millisInFuture, countDownInterval); + mErrorView = errorView; + mContext = context; + } + + @Override + public void onTick(long millisUntilFinished) { + final int secondsCountdown = (int) (millisUntilFinished / 1000); + mErrorView.setText(mContext.getString( + R.string.biometric_dialog_credential_too_many_attempts, secondsCountdown)); + } + + @Override + public void onFinish() { + mErrorView.setText(""); + } + } + + protected final Runnable mClearErrorRunnable = new Runnable() { + @Override + public void run() { + mErrorView.setText(""); + } + }; + + public AuthCredentialView(Context context, AttributeSet attrs) { + super(context, attrs); + + mLockPatternUtils = new LockPatternUtils(mContext); + mHandler = new Handler(Looper.getMainLooper()); + mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); + } + + protected void showError(String error) { + mHandler.removeCallbacks(mClearErrorRunnable); + mErrorView.setText(error); + mHandler.postDelayed(mClearErrorRunnable, ERROR_DURATION_MS); + } + + private void setTextOrHide(TextView view, String string) { + if (TextUtils.isEmpty(string)) { + view.setVisibility(View.GONE); + } else { + view.setText(string); + } + + Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this); + } + + private void setText(TextView view, String string) { + view.setText(string); + } + + void setUser(int user) { + mUserId = user; + } + + void setCallback(Callback callback) { + mCallback = callback; + } + + void setBiometricPromptBundle(Bundle bundle) { + mBiometricPromptBundle = bundle; + } + + void setPanelController(AuthPanelController panelController, boolean animatePanel) { + mPanelController = panelController; + mShouldAnimatePanel = animatePanel; + } + + void setShouldAnimateContents(boolean animateContents) { + mShouldAnimateContents = animateContents; + } + + void setContainerView(AuthContainerView containerView) { + mContainerView = containerView; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + mCredentialType = Utils.getCredentialType(mContext, mUserId); + + setText(mTitleView, mBiometricPromptBundle.getString(BiometricPrompt.KEY_TITLE)); + setTextOrHide(mSubtitleView, + mBiometricPromptBundle.getString(BiometricPrompt.KEY_SUBTITLE)); + setTextOrHide(mDescriptionView, + mBiometricPromptBundle.getString(BiometricPrompt.KEY_DESCRIPTION)); + + // Only animate this if we're transitioning from a biometric view. + if (mShouldAnimateContents) { + setTranslationY(getResources() + .getDimension(R.dimen.biometric_dialog_credential_translation_offset)); + setAlpha(0); + + postOnAnimation(() -> { + animate().translationY(0) + .setDuration(AuthDialog.ANIMATE_CREDENTIAL_INITIAL_DURATION_MS) + .alpha(1.f) + .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN) + .withLayer() + .start(); + }); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mErrorTimer != null) { + mErrorTimer.cancel(); + } + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mTitleView = findViewById(R.id.title); + mSubtitleView = findViewById(R.id.subtitle); + mDescriptionView = findViewById(R.id.description); + mErrorView = findViewById(R.id.error); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (mShouldAnimatePanel) { + // Credential view is always full screen. + mPanelController.setUseFullScreen(true); + mPanelController.updateForContentDimensions(mPanelController.getContainerWidth(), + mPanelController.getContainerHeight(), 0 /* animateDurationMs */); + mShouldAnimatePanel = false; + } + } + + protected void onErrorTimeoutFinish() {} + + protected void onCredentialChecked(boolean matched, int timeoutMs) { + if (matched) { + mClearErrorRunnable.run(); + mCallback.onCredentialMatched(); + } else { + if (timeoutMs > 0) { + mHandler.removeCallbacks(mClearErrorRunnable); + long deadline = mLockPatternUtils.setLockoutAttemptDeadline(mUserId, timeoutMs); + mErrorTimer = new ErrorTimer(mContext, + deadline - SystemClock.elapsedRealtime(), + LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS, + mErrorView) { + @Override + public void onFinish() { + onErrorTimeoutFinish(); + mClearErrorRunnable.run(); + } + }; + mErrorTimer.start(); + } else { + final int error; + switch (mCredentialType) { + case Utils.CREDENTIAL_PIN: + error = R.string.biometric_dialog_wrong_pin; + break; + case Utils.CREDENTIAL_PATTERN: + error = R.string.biometric_dialog_wrong_pattern; + break; + case Utils.CREDENTIAL_PASSWORD: + error = R.string.biometric_dialog_wrong_password; + break; + default: + error = R.string.biometric_dialog_wrong_password; + break; + } + showError(getResources().getString(error)); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java index edb29538874c..ca95f9d736fc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java @@ -31,6 +31,8 @@ import java.lang.annotation.RetentionPolicy; public interface AuthDialog { String KEY_CONTAINER_STATE = "container_state"; + String KEY_BIOMETRIC_SHOWING = "biometric_showing"; + String KEY_CREDENTIAL_SHOWING = "credential_showing"; String KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY = "try_agian_visibility"; String KEY_BIOMETRIC_STATE = "state"; @@ -40,17 +42,38 @@ public interface AuthDialog { String KEY_BIOMETRIC_DIALOG_SIZE = "size"; int SIZE_UNKNOWN = 0; + /** + * Minimal UI, showing only biometric icon. + */ int SIZE_SMALL = 1; + /** + * Normal-sized biometric UI, showing title, icon, buttons, etc. + */ int SIZE_MEDIUM = 2; + /** + * Full-screen credential UI. + */ int SIZE_LARGE = 3; @Retention(RetentionPolicy.SOURCE) @IntDef({SIZE_UNKNOWN, SIZE_SMALL, SIZE_MEDIUM, SIZE_LARGE}) @interface DialogSize {} /** - * Animation duration, e.g. small to medium dialog, icon translation, etc. + * Animation duration, from small to medium dialog, including back panel, icon translation, etc + */ + int ANIMATE_SMALL_TO_MEDIUM_DURATION_MS = 150; + /** + * Animation duration from medium to large dialog, including biometric fade out, back panel, etc + */ + int ANIMATE_MEDIUM_TO_LARGE_DURATION_MS = 450; + /** + * Delay before notifying {@link AuthCredentialView} to start animating in. + */ + int ANIMATE_CREDENTIAL_START_DELAY_MS = ANIMATE_MEDIUM_TO_LARGE_DURATION_MS * 2 / 3; + /** + * Animation duration when sliding in credential UI */ - int ANIMATE_DURATION_MS = 150; + int ANIMATE_CREDENTIAL_INITIAL_DURATION_MS = 150; /** * Show the dialog. @@ -101,4 +124,14 @@ public interface AuthDialog { * Get the client's package name */ String getOpPackageName(); + + /** + * Animate to credential UI. Typically called after biometric is locked out. + */ + void animateToCredentialUI(); + + /** + * @return true if device credential is allowed. + */ + boolean isAllowDeviceCredentials(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java index 70752f5f860e..12bb1228a53b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java @@ -27,17 +27,18 @@ public interface AuthDialogCallback { int DISMISSED_USER_CANCELED = 1; int DISMISSED_BUTTON_NEGATIVE = 2; int DISMISSED_BUTTON_POSITIVE = 3; - - int DISMISSED_AUTHENTICATED = 4; + int DISMISSED_BIOMETRIC_AUTHENTICATED = 4; int DISMISSED_ERROR = 5; int DISMISSED_BY_SYSTEM_SERVER = 6; + int DISMISSED_CREDENTIAL_AUTHENTICATED = 7; @IntDef({DISMISSED_USER_CANCELED, DISMISSED_BUTTON_NEGATIVE, DISMISSED_BUTTON_POSITIVE, - DISMISSED_AUTHENTICATED, + DISMISSED_BIOMETRIC_AUTHENTICATED, DISMISSED_ERROR, - DISMISSED_BY_SYSTEM_SERVER}) + DISMISSED_BY_SYSTEM_SERVER, + DISMISSED_CREDENTIAL_AUTHENTICATED}) @interface DismissedReason {} /** @@ -50,4 +51,9 @@ public interface AuthDialogCallback { * Invoked when the "try again" button is clicked */ void onTryAgainPressed(); + + /** + * Invoked when the "use password" button is clicked + */ + void onDeviceCredentialPressed(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java index 55ba0491dc1e..2b8b586961ff 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java @@ -16,15 +16,17 @@ package com.android.systemui.biometrics; +import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Color; import android.graphics.Outline; import android.util.Log; import android.view.View; import android.view.ViewOutlineProvider; +import android.view.animation.AccelerateDecelerateInterpolator; import com.android.systemui.R; -import com.android.systemui.biometrics.AuthDialog; /** * Controls the back panel and its animations for the BiometricPrompt UI. @@ -36,8 +38,9 @@ public class AuthPanelController extends ViewOutlineProvider { private final Context mContext; private final View mPanelView; - private final float mCornerRadius; - private final int mBiometricMargin; + private final boolean mIsManagedProfile; + + private boolean mUseFullScreen; private int mContainerWidth; private int mContainerHeight; @@ -45,14 +48,23 @@ public class AuthPanelController extends ViewOutlineProvider { private int mContentWidth; private int mContentHeight; + private float mCornerRadius; + private int mMargin; + @Override public void getOutline(View view, Outline outline) { final int left = (mContainerWidth - mContentWidth) / 2; final int right = mContainerWidth - left; + + // If the content fits within the container, shrink the height to wrap the content. + // Otherwise, set the outline to be the display size minus the margin - the content within + // is scrollable. final int top = mContentHeight < mContainerHeight - ? mContainerHeight - mContentHeight - mBiometricMargin - : mBiometricMargin; - final int bottom = mContainerHeight - mBiometricMargin; + ? mContainerHeight - mContentHeight - mMargin + : mMargin; + + // TODO(b/139954942) Likely don't need to "+1" after we resolve the navbar styling. + final int bottom = mContainerHeight - mMargin + 1; outline.setRoundRect(left, top, right, bottom, mCornerRadius); } @@ -64,11 +76,34 @@ public class AuthPanelController extends ViewOutlineProvider { mContainerHeight = containerHeight; } - public void updateForContentDimensions(int contentWidth, int contentHeight, boolean animate) { + public void setUseFullScreen(boolean fullScreen) { + mUseFullScreen = fullScreen; + } + + public ValueAnimator getTranslationAnimator(float relativeTranslationY) { + final ValueAnimator animator = ValueAnimator.ofFloat( + mPanelView.getY(), mPanelView.getY() - relativeTranslationY); + animator.addUpdateListener(animation -> { + final float translation = (float) animation.getAnimatedValue(); + mPanelView.setTranslationY(translation); + }); + return animator; + } + + public ValueAnimator getAlphaAnimator(float alpha) { + final ValueAnimator animator = ValueAnimator.ofFloat(mPanelView.getAlpha(), alpha); + animator.addUpdateListener(animation -> { + mPanelView.setAlpha((float) animation.getAnimatedValue()); + }); + return animator; + } + + public void updateForContentDimensions(int contentWidth, int contentHeight, + int animateDurationMs) { if (DEBUG) { Log.v(TAG, "Content Width: " + contentWidth + " Height: " + contentHeight - + " Animate: " + animate); + + " Animate: " + animateDurationMs); } if (mContainerWidth == 0 || mContainerHeight == 0) { @@ -76,27 +111,86 @@ public class AuthPanelController extends ViewOutlineProvider { return; } - if (animate) { + final int margin = mUseFullScreen ? 0 : (int) mContext.getResources() + .getDimension(R.dimen.biometric_dialog_border_padding); + final float cornerRadius = mUseFullScreen ? 0 : mContext.getResources() + .getDimension(R.dimen.biometric_dialog_corner_size); + + // When going to full-screen for managed profiles, fade away so the managed profile + // background behind this view becomes visible. + final boolean shouldFadeAway = mUseFullScreen && mIsManagedProfile; + final int alpha = shouldFadeAway ? 0 : 255; + final float elevation = shouldFadeAway ? 0 : + mContext.getResources().getDimension(R.dimen.biometric_dialog_elevation); + + if (animateDurationMs > 0) { + // Animate margin + ValueAnimator marginAnimator = ValueAnimator.ofInt(mMargin, margin); + marginAnimator.addUpdateListener((animation) -> { + mMargin = (int) animation.getAnimatedValue(); + }); + + // Animate corners + ValueAnimator cornerAnimator = ValueAnimator.ofFloat(mCornerRadius, cornerRadius); + cornerAnimator.addUpdateListener((animation) -> { + mCornerRadius = (float) animation.getAnimatedValue(); + }); + + // Animate height ValueAnimator heightAnimator = ValueAnimator.ofInt(mContentHeight, contentHeight); - heightAnimator.setDuration(AuthDialog.ANIMATE_DURATION_MS); heightAnimator.addUpdateListener((animation) -> { mContentHeight = (int) animation.getAnimatedValue(); mPanelView.invalidateOutline(); }); heightAnimator.start(); + + // Animate width + ValueAnimator widthAnimator = ValueAnimator.ofInt(mContentWidth, contentWidth); + widthAnimator.addUpdateListener((animation) -> { + mContentWidth = (int) animation.getAnimatedValue(); + }); + + // Animate background + ValueAnimator alphaAnimator = ValueAnimator.ofInt( + mPanelView.getBackground().getAlpha(), alpha); + alphaAnimator.addUpdateListener((animation) -> { + if (shouldFadeAway) { + mPanelView.getBackground().setAlpha((int) animation.getAnimatedValue()); + } + }); + + // Play together + AnimatorSet as = new AnimatorSet(); + as.setDuration(animateDurationMs); + as.setInterpolator(new AccelerateDecelerateInterpolator()); + as.playTogether(cornerAnimator, widthAnimator, marginAnimator, alphaAnimator); + as.start(); + } else { + mMargin = margin; + mCornerRadius = cornerRadius; mContentWidth = contentWidth; mContentHeight = contentHeight; + mPanelView.getBackground().setAlpha(alpha); mPanelView.invalidateOutline(); } } - AuthPanelController(Context context, View panelView) { + int getContainerWidth() { + return mContainerWidth; + } + + int getContainerHeight() { + return mContainerHeight; + } + + AuthPanelController(Context context, View panelView, boolean isManagedProfile) { mContext = context; mPanelView = panelView; + mIsManagedProfile = isManagedProfile; mCornerRadius = context.getResources() .getDimension(R.dimen.biometric_dialog_corner_size); - mBiometricMargin = (int) context.getResources() + mMargin = (int) context.getResources() .getDimension(R.dimen.biometric_dialog_border_padding); mPanelView.setOutlineProvider(this); mPanelView.setClipToOutline(true); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java index e00cf6abafaa..d6f830dd2e7a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java @@ -18,14 +18,36 @@ package com.android.systemui.biometrics; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE; +import android.annotation.IntDef; +import android.app.admin.DevicePolicyManager; import android.content.Context; +import android.hardware.biometrics.Authenticator; +import android.hardware.biometrics.BiometricPrompt; +import android.os.Bundle; +import android.os.UserManager; import android.util.DisplayMetrics; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.R; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + public class Utils { + + public static final int CREDENTIAL_PIN = 1; + public static final int CREDENTIAL_PATTERN = 2; + public static final int CREDENTIAL_PASSWORD = 3; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD}) + @interface CredentialType {} + + static float dpToPixels(Context context, float dp) { return dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT); @@ -46,4 +68,41 @@ public class Utils { view.sendAccessibilityEventUnchecked(event); view.notifySubtreeAccessibilityStateChanged(view, view, CONTENT_CHANGE_TYPE_SUBTREE); } + + static boolean isDeviceCredentialAllowed(Bundle biometricPromptBundle) { + final int authenticators = getAuthenticators(biometricPromptBundle); + return (authenticators & Authenticator.TYPE_CREDENTIAL) != 0; + } + + static boolean isBiometricAllowed(Bundle biometricPromptBundle) { + final int authenticators = getAuthenticators(biometricPromptBundle); + return (authenticators & Authenticator.TYPE_BIOMETRIC) != 0; + } + + static int getAuthenticators(Bundle biometricPromptBundle) { + return biometricPromptBundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED); + } + + static @CredentialType int getCredentialType(Context context, int userId) { + final LockPatternUtils lpu = new LockPatternUtils(context); + switch (lpu.getKeyguardStoredPasswordQuality(userId)) { + case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: + return CREDENTIAL_PATTERN; + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: + return CREDENTIAL_PIN; + case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: + case DevicePolicyManager.PASSWORD_QUALITY_MANAGED: + return CREDENTIAL_PASSWORD; + default: + return CREDENTIAL_PASSWORD; + } + } + + static boolean isManagedProfile(Context context, int userId) { + final UserManager userManager = context.getSystemService(UserManager.class); + return userManager.isManagedProfile(userId); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 134d4b87a159..36e04fe42ced 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -270,12 +270,13 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< default void onRotationProposal(int rotation, boolean isValid) { } - default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, - int type, boolean requireConfirmation, int userId, String opPackageName) { } + default void showAuthenticationDialog(Bundle bundle, + IBiometricServiceReceiverInternal receiver, int biometricModality, + boolean requireConfirmation, int userId, String opPackageName) { } default void onBiometricAuthenticated(boolean authenticated, String failureReason) { } default void onBiometricHelp(String message) { } - default void onBiometricError(String error) { } - default void hideBiometricDialog() { } + default void onBiometricError(int errorCode, String error) { } + default void hideAuthenticationDialog() { } /** * @see IStatusBar#onDisplayReady(int) @@ -740,13 +741,13 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } @Override - public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, - int type, boolean requireConfirmation, int userId, String opPackageName) { + public void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, + int biometricModality, boolean requireConfirmation, int userId, String opPackageName) { synchronized (mLock) { SomeArgs args = SomeArgs.obtain(); args.arg1 = bundle; args.arg2 = receiver; - args.argi1 = type; + args.argi1 = biometricModality; args.arg3 = requireConfirmation; args.argi2 = userId; args.arg4 = opPackageName; @@ -773,14 +774,14 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } @Override - public void onBiometricError(String error) { + public void onBiometricError(int errorCode, String error) { synchronized (mLock) { - mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, error).sendToTarget(); + mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, errorCode, 0, error).sendToTarget(); } } @Override - public void hideBiometricDialog() { + public void hideAuthenticationDialog() { synchronized (mLock) { mHandler.obtainMessage(MSG_BIOMETRIC_HIDE).sendToTarget(); } @@ -1032,10 +1033,10 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< mHandler.removeMessages(MSG_BIOMETRIC_AUTHENTICATED); SomeArgs someArgs = (SomeArgs) msg.obj; for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).showBiometricDialog( + mCallbacks.get(i).showAuthenticationDialog( (Bundle) someArgs.arg1, (IBiometricServiceReceiverInternal) someArgs.arg2, - someArgs.argi1 /* type */, + someArgs.argi1 /* biometricModality */, (boolean) someArgs.arg3 /* requireConfirmation */, someArgs.argi2 /* userId */, (String) someArgs.arg4 /* opPackageName */); @@ -1060,12 +1061,12 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< break; case MSG_BIOMETRIC_ERROR: for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onBiometricError((String) msg.obj); + mCallbacks.get(i).onBiometricError(msg.arg1, (String) msg.obj); } break; case MSG_BIOMETRIC_HIDE: for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).hideBiometricDialog(); + mCallbacks.get(i).hideAuthenticationDialog(); } break; case MSG_SHOW_CHARGING_ANIMATION: 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 90301c59dc68..148a1a87f305 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -20,7 +20,6 @@ import static android.service.notification.NotificationListenerService.REASON_ER import android.annotation.Nullable; import android.app.Notification; -import android.content.Context; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; @@ -124,8 +123,8 @@ public class NotificationEntryManager implements } @Inject - public NotificationEntryManager(Context context) { - mNotificationData = new NotificationData(context); + public NotificationEntryManager(NotificationData notificationData) { + mNotificationData = notificationData; } /** Adds a {@link NotificationEntryListener}. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt new file mode 100644 index 000000000000..480cb78efbba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt @@ -0,0 +1,73 @@ +/* + * 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 + +import android.content.Context +import android.provider.DeviceConfig + +import com.android.internal.annotations.VisibleForTesting +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT +import com.android.systemui.util.DeviceConfigProxy + +import javax.inject.Inject + +private var sUsePeopleFiltering: Boolean? = null + +/** + * Feature controller for the NOTIFICATIONS_USE_PEOPLE_FILTERING config. + */ +class NotificationSectionsFeatureManager @Inject constructor( + val proxy: DeviceConfigProxy, + val context: Context +) { + + fun isFilteringEnabled(): Boolean { + return usePeopleFiltering(proxy) + } + + fun getNotificationBuckets(): IntArray { + return when { + isFilteringEnabled() -> + intArrayOf(BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT) + NotificationUtils.useNewInterruptionModel(context) -> + intArrayOf(BUCKET_ALERTING, BUCKET_SILENT) + else -> + intArrayOf(BUCKET_ALERTING) + } + } + + fun getNumberOfBuckets(): Int { + return getNotificationBuckets().size + } + + @VisibleForTesting + fun clearCache() { + sUsePeopleFiltering = null + } +} + +private fun usePeopleFiltering(proxy: DeviceConfigProxy): Boolean { + if (sUsePeopleFiltering == null) { + sUsePeopleFiltering = proxy.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, NOTIFICATIONS_USE_PEOPLE_FILTERING, false) + } + + return sUsePeopleFiltering!! +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java index dfbbf987dd96..1af47dd0f4c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java @@ -36,7 +36,6 @@ public class NotificationUtils { private static final int[] sLocationOffset = new int[2]; @Nullable private static Boolean sUseNewInterruptionModel = null; - @Nullable private static Boolean sUsePeopleFiltering = null; public static boolean isGrayscale(ImageView v, ContrastColorUtil colorUtil) { Object isGrayscale = v.getTag(R.id.icon_is_grayscale); @@ -88,17 +87,4 @@ public class NotificationUtils { } return sUseNewInterruptionModel; } - - /** - * Caches and returns the value of the people filtering setting. Cannot change except through - * process restarts. - */ - public static boolean usePeopleFiltering(Context context) { - if (sUsePeopleFiltering == null) { - sUsePeopleFiltering = context.getResources().getBoolean( - R.bool.config_usePeopleFiltering); - } - - return sUsePeopleFiltering; - } } 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 aacb2dd0682f..cf0fbbb5d9c1 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 @@ -24,7 +24,6 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.Person; -import android.content.Context; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.SnoozeCriterion; @@ -35,7 +34,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.notification.NotificationFilter; -import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -46,6 +45,8 @@ import java.util.Comparator; import java.util.List; import java.util.Objects; +import javax.inject.Inject; + /** * The list of currently displaying notifications. */ @@ -73,8 +74,9 @@ public class NotificationData { private final Ranking mTmpRanking = new Ranking(); private final boolean mUsePeopleFiltering; - public NotificationData(Context context) { - mUsePeopleFiltering = NotificationUtils.usePeopleFiltering(context); + @Inject + public NotificationData(NotificationSectionsFeatureManager sectionsFeatureManager) { + mUsePeopleFiltering = sectionsFeatureManager.isFilteringEnabled(); } public void setHeadsUpManager(HeadsUpManager headsUpManager) { @@ -483,19 +485,6 @@ public class NotificationData { } /** - * Get the current set of buckets for notification entries, as defined here - */ - public static int[] getNotificationBuckets(Context context) { - if (NotificationUtils.usePeopleFiltering(context)) { - return new int[]{BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT}; - } else if (NotificationUtils.useNewInterruptionModel(context)) { - return new int[]{BUCKET_ALERTING, BUCKET_SILENT}; - } else { - return new int[]{BUCKET_ALERTING}; - } - } - - /** * Provides access to keyguard state and user settings dependent data. */ public interface KeyguardEnvironment { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java index ec0c6348fd89..b4f7b59349d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java @@ -16,10 +16,9 @@ package com.android.systemui.statusbar.notification.stack; -import android.content.Context; import android.util.MathUtils; -import com.android.systemui.statusbar.notification.collection.NotificationData; +import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -52,8 +51,8 @@ public class NotificationRoundnessManager implements OnHeadsUpChangedListener { @Inject NotificationRoundnessManager( KeyguardBypassController keyguardBypassController, - Context context) { - int numberOfSections = NotificationData.getNotificationBuckets(context).length; + NotificationSectionsFeatureManager sectionsFeatureManager) { + int numberOfSections = sectionsFeatureManager.getNumberOfBuckets(); mFirstInSectionViews = new ActivatableNotificationView[numberOfSections]; mLastInSectionViews = new ActivatableNotificationView[numberOfSections]; mTmpFirstInSectionViews = new ActivatableNotificationView[numberOfSections]; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index a67018ef9710..f5705c5f643b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -109,11 +109,11 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.ShadeViewRefactor; import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -517,7 +517,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd StatusBarStateController statusBarStateController, HeadsUpManagerPhone headsUpManager, KeyguardBypassController keyguardBypassController, - FalsingManager falsingManager) { + FalsingManager falsingManager, + NotificationSectionsFeatureManager sectionsFeatureManager) { super(context, attrs, 0, 0); Resources res = getResources(); @@ -531,7 +532,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mKeyguardBypassController = keyguardBypassController; mFalsingManager = falsingManager; - int[] buckets = NotificationData.getNotificationBuckets(context); + int[] buckets = sectionsFeatureManager.getNotificationBuckets(); mSectionsManager = new NotificationSectionsManager( this, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 68eca8d68d90..cac33044ea13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -117,6 +117,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private static final int DOZE_ANIMATION_STAGGER_DELAY = 48; private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250; + private final boolean mShowLeftAffordance; + private final boolean mShowCameraAffordance; + private KeyguardAffordanceView mRightAffordanceView; private KeyguardAffordanceView mLeftAffordanceView; private ViewGroup mIndicationArea; @@ -184,6 +187,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mShowLeftAffordance = getResources().getBoolean(R.bool.config_keyguardShowLeftAffordance); + mShowCameraAffordance = getResources() + .getBoolean(R.bool.config_keyguardShowCameraAffordance); } private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() { @@ -371,8 +377,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL // Things are not set up yet; reply hazy, ask again later return; } - mRightAffordanceView.setVisibility(!mDozing && mRightButton.getIcon().isVisible - ? View.VISIBLE : View.GONE); + mRightAffordanceView.setVisibility(!mDozing && mShowCameraAffordance + && mRightButton.getIcon().isVisible ? View.VISIBLE : View.GONE); } /** @@ -384,8 +390,12 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } private void updateLeftAffordanceIcon() { + if (!mShowLeftAffordance || mDozing) { + mLeftAffordanceView.setVisibility(GONE); + return; + } IconState state = mLeftButton.getIcon(); - mLeftAffordanceView.setVisibility(!mDozing && state.isVisible ? View.VISIBLE : View.GONE); + mLeftAffordanceView.setVisibility(state.isVisible ? View.VISIBLE : View.GONE); if (state.drawable != mLeftAffordanceView.getDrawable() || state.tint != mLeftAffordanceView.shouldTint()) { mLeftAffordanceView.setImageDrawable(state.drawable, state.tint); @@ -767,10 +777,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override public IconState getIcon() { mLeftIsVoiceAssist = canLaunchVoiceAssist(); - final boolean showAffordance = - getResources().getBoolean(R.bool.config_keyguardShowLeftAffordance); if (mLeftIsVoiceAssist) { - mIconState.isVisible = mUserSetupComplete && showAffordance; + mIconState.isVisible = mUserSetupComplete && mShowLeftAffordance; if (mLeftAssistIcon == null) { mIconState.drawable = mContext.getDrawable(R.drawable.ic_mic_26dp); } else { @@ -779,7 +787,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mIconState.contentDescription = mContext.getString( R.string.accessibility_voice_assist_button); } else { - mIconState.isVisible = mUserSetupComplete && showAffordance && isPhoneVisible(); + mIconState.isVisible = mUserSetupComplete && mShowLeftAffordance + && isPhoneVisible(); mIconState.drawable = mContext.getDrawable( com.android.internal.R.drawable.ic_phone); mIconState.contentDescription = mContext.getString( @@ -802,7 +811,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL public IconState getIcon() { boolean isCameraDisabled = (mStatusBar != null) && !mStatusBar.isCameraAllowedByAdmin(); mIconState.isVisible = !isCameraDisabled - && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance) + && mShowCameraAffordance && mUserSetupComplete && resolveCameraIntent() != null; mIconState.drawable = mContext.getDrawable(R.drawable.ic_camera_alt_24dp); 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 da5931a50ba8..b1825c8ce667 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -25,7 +25,6 @@ import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; -import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; import static com.android.systemui.Dependency.BG_HANDLER; import static com.android.systemui.Dependency.MAIN_HANDLER; @@ -3936,20 +3935,17 @@ public class StatusBar extends SystemUI implements DemoMode, } boolean isCameraAllowedByAdmin() { - // TODO(b/140060745) - return whitelistIpcs(() -> { - if (mDevicePolicyManager.getCameraDisabled(null, - mLockscreenUserManager.getCurrentUserId())) { - return false; - } else if (mStatusBarKeyguardViewManager == null - || (isKeyguardShowing() && isKeyguardSecure())) { - // Check if the admin has disabled the camera specifically for the keyguard - return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, - mLockscreenUserManager.getCurrentUserId()) - & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0; - } - return true; - }); + if (mDevicePolicyManager.getCameraDisabled(null, + mLockscreenUserManager.getCurrentUserId())) { + return false; + } else if (mStatusBarKeyguardViewManager == null + || (isKeyguardShowing() && isKeyguardSecure())) { + // Check if the admin has disabled the camera specifically for the keyguard + return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, + mLockscreenUserManager.getCurrentUserId()) + & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0; + } + return true; } private boolean isGoingToSleep() { diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java index cd1f0cc13297..47b56e097ec9 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java @@ -92,7 +92,7 @@ public class UsbPermissionActivity extends AlertActivity mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory); } else { boolean hasRecordPermission = - PermissionChecker.checkPermission( + PermissionChecker.checkPermissionForPreflight( this, android.Manifest.permission.RECORD_AUDIO, -1, aInfo.uid, mPackageName) == android.content.pm.PackageManager.PERMISSION_GRANTED; diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java index 7a09137b1ff8..2c85424bac79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.content.Context; +import android.hardware.biometrics.Authenticator; import android.hardware.biometrics.BiometricPrompt; import android.os.Bundle; import android.test.suitebuilder.annotation.SmallTest; @@ -70,7 +71,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() { - initDialog(mContext, mCallback, new MockInjector()); + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); // The onAuthenticated runnable is posted when authentication succeeds. mBiometricView.onAuthenticationSucceeded(); @@ -81,7 +82,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() { - initDialog(mContext, mCallback, new MockInjector()); + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); mBiometricView.setRequireConfirmation(true); mBiometricView.onAuthenticationSucceeded(); @@ -97,7 +98,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testPositiveButton_sendsActionAuthenticated() { Button button = new Button(mContext); - initDialog(mContext, mCallback, new MockInjector() { + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { @Override public Button getPositiveButton() { return button; @@ -114,7 +115,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testNegativeButton_beforeAuthentication_sendsActionButtonNegative() { Button button = new Button(mContext); - initDialog(mContext, mCallback, new MockInjector() { + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { @Override public Button getNegativeButton() { return button; @@ -131,7 +132,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testNegativeButton_whenPendingConfirmation_sendsActionUserCanceled() { Button button = new Button(mContext); - initDialog(mContext, mCallback, new MockInjector() { + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { @Override public Button getNegativeButton() { return button; @@ -149,7 +150,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testTryAgainButton_sendsActionTryAgain() { Button button = new Button(mContext); - initDialog(mContext, mCallback, new MockInjector() { + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { @Override public Button getTryAgainButton() { return button; @@ -165,7 +166,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testError_sendsActionError() { - initDialog(mContext, mCallback, new MockInjector()); + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); final String testError = "testError"; mBiometricView.onError(testError); waitForIdleSync(); @@ -176,7 +177,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testBackgroundClicked_sendsActionUserCanceled() { - initDialog(mContext, mCallback, new MockInjector()); + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); View view = new View(mContext); mBiometricView.setBackgroundView(view); @@ -186,7 +187,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testBackgroundClicked_afterAuthenticated_neverSendsUserCanceled() { - initDialog(mContext, mCallback, new MockInjector()); + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); View view = new View(mContext); mBiometricView.setBackgroundView(view); @@ -197,8 +198,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testBackgroundClicked_whenSmallDialog_neverSendsUserCanceled() { - initDialog(mContext, mCallback, new MockInjector()); - mBiometricView.setPanelController(mPanelController); + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); mBiometricView.updateSize(AuthDialog.SIZE_SMALL); View view = new View(mContext); @@ -213,7 +213,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { Button tryAgainButton = new Button(mContext); TextView indicatorView = new TextView(mContext); - initDialog(mContext, mCallback, new MockInjector() { + initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { @Override public Button getTryAgainButton() { return tryAgainButton; @@ -249,16 +249,18 @@ public class AuthBiometricViewTest extends SysuiTestCase { // Create new dialog and restore the previous state into it Button tryAgainButton2 = new Button(mContext); TextView indicatorView2 = new TextView(mContext); - initDialog(mContext, mCallback, state, new MockInjector() { - @Override - public Button getTryAgainButton() { - return tryAgainButton2; - } - @Override - public TextView getIndicatorView() { - return indicatorView2; - } - }); + initDialog(mContext, false /* allowDeviceCredential */, mCallback, state, + new MockInjector() { + @Override + public Button getTryAgainButton() { + return tryAgainButton2; + } + + @Override + public TextView getIndicatorView() { + return indicatorView2; + } + }); mBiometricView.setRequireConfirmation(requireConfirmation); waitForIdleSync(); @@ -271,26 +273,51 @@ public class AuthBiometricViewTest extends SysuiTestCase { // dialog size is known. } - private Bundle buildBiometricPromptBundle() { + @Test + public void testNegativeButton_whenDeviceCredentialAllowed() throws InterruptedException { + Button negativeButton = new Button(mContext); + initDialog(mContext, true /* allowDeviceCredential */, mCallback, new MockInjector() { + @Override + public Button getNegativeButton() { + return negativeButton; + } + }); + + negativeButton.performClick(); + waitForIdleSync(); + + verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL); + } + + private Bundle buildBiometricPromptBundle(boolean allowDeviceCredential) { Bundle bundle = new Bundle(); bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title"); - bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative"); + int authenticators = Authenticator.TYPE_BIOMETRIC; + if (allowDeviceCredential) { + authenticators |= Authenticator.TYPE_CREDENTIAL; + } else { + bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative"); + } + bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); return bundle; } - private void initDialog(Context context, AuthBiometricView.Callback callback, + private void initDialog(Context context, boolean allowDeviceCredential, + AuthBiometricView.Callback callback, Bundle savedState, MockInjector injector) { mBiometricView = new TestableBiometricView(context, null, injector); - mBiometricView.setBiometricPromptBundle(buildBiometricPromptBundle()); + mBiometricView.setBiometricPromptBundle(buildBiometricPromptBundle(allowDeviceCredential)); mBiometricView.setCallback(callback); mBiometricView.restoreState(savedState); mBiometricView.onFinishInflateInternal(); mBiometricView.onAttachedToWindowInternal(); + + mBiometricView.setPanelController(mPanelController); } - private void initDialog(Context context, AuthBiometricView.Callback callback, - MockInjector injector) { - initDialog(context, callback, null /* savedState */, injector); + private void initDialog(Context context, boolean allowDeviceCredential, + AuthBiometricView.Callback callback, MockInjector injector) { + initDialog(context, allowDeviceCredential, callback, null /* savedState */, injector); } private class MockInjector extends AuthBiometricView.Injector { @@ -338,6 +365,16 @@ public class AuthBiometricViewTest extends SysuiTestCase { public int getDelayAfterError() { return 0; // Keep this at 0 for tests to invoke callback immediately. } + + @Override + public int getMediumToLargeAnimationDurationMs() { + return 0; + } + + @Override + public int getAnimateCredentialStartDelayMs() { + return 0; + } } private class TestableBiometricView extends AuthBiometricView { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java index d4fc3f842e9d..990f74ae33c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java @@ -16,12 +16,30 @@ package com.android.systemui.biometrics; +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.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import android.content.Context; +import android.hardware.biometrics.Authenticator; +import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricPrompt; +import android.os.Bundle; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ScrollView; import com.android.systemui.SysuiTestCase; @@ -43,22 +61,21 @@ public class AuthContainerViewTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); - - AuthContainerView.Config config = new AuthContainerView.Config(); - config.mContext = mContext; - config.mCallback = mCallback; - mAuthContainer = new TestableAuthContainer(config); } @Test public void testActionAuthenticated_sendsDismissedAuthenticated() { + initializeContainer(Authenticator.TYPE_BIOMETRIC); + mAuthContainer.mBiometricCallback.onAction( AuthBiometricView.Callback.ACTION_AUTHENTICATED); - verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_AUTHENTICATED)); + verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED)); } @Test public void testActionUserCanceled_sendsDismissedUserCanceled() { + initializeContainer(Authenticator.TYPE_BIOMETRIC); + mAuthContainer.mBiometricCallback.onAction( AuthBiometricView.Callback.ACTION_USER_CANCELED); verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_USER_CANCELED)); @@ -66,6 +83,8 @@ public class AuthContainerViewTest extends SysuiTestCase { @Test public void testActionButtonNegative_sendsDismissedButtonNegative() { + initializeContainer(Authenticator.TYPE_BIOMETRIC); + mAuthContainer.mBiometricCallback.onAction( AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE); verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE)); @@ -73,6 +92,8 @@ public class AuthContainerViewTest extends SysuiTestCase { @Test public void testActionTryAgain_sendsTryAgain() { + initializeContainer(Authenticator.TYPE_BIOMETRIC); + mAuthContainer.mBiometricCallback.onAction( AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN); verify(mCallback).onTryAgainPressed(); @@ -80,14 +101,77 @@ public class AuthContainerViewTest extends SysuiTestCase { @Test public void testActionError_sendsDismissedError() { + initializeContainer(Authenticator.TYPE_BIOMETRIC); + mAuthContainer.mBiometricCallback.onAction( AuthBiometricView.Callback.ACTION_ERROR); verify(mCallback).onDismissed(AuthDialogCallback.DISMISSED_ERROR); } + @Test + public void testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() { + initializeContainer( + Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL); + + mAuthContainer.mBiometricCallback.onAction( + AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL); + verify(mCallback).onDeviceCredentialPressed(); + + // Credential view is attached to the frame layout + waitForIdleSync(); + assertNotNull(mAuthContainer.mCredentialView); + verify(mAuthContainer.mFrameLayout).addView(eq(mAuthContainer.mCredentialView)); + } + + @Test + public void testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() { + initializeContainer( + Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL); + + mAuthContainer.mBiometricView = mock(AuthBiometricView.class); + mAuthContainer.animateToCredentialUI(); + verify(mAuthContainer.mBiometricView).startTransitionToCredentialUI(); + } + + @Test + public void testShowBiometricUI() { + initializeContainer(Authenticator.TYPE_BIOMETRIC); + + assertNotEquals(null, mAuthContainer.mBiometricView); + + mAuthContainer.onAttachedToWindowInternal(); + verify(mAuthContainer.mBiometricScrollView).addView(mAuthContainer.mBiometricView); + // Credential view is not added + verify(mAuthContainer.mFrameLayout, never()).addView(any()); + } + + @Test + public void testShowCredentialUI_doesNotInflateBiometricUI() { + initializeContainer(Authenticator.TYPE_CREDENTIAL); + + mAuthContainer.onAttachedToWindowInternal(); + + assertNull(null, mAuthContainer.mBiometricView); + assertNotNull(mAuthContainer.mCredentialView); + verify(mAuthContainer.mFrameLayout).addView(mAuthContainer.mCredentialView); + } + + private void initializeContainer(int authenticators) { + AuthContainerView.Config config = new AuthContainerView.Config(); + config.mContext = mContext; + config.mCallback = mCallback; + config.mModalityMask |= BiometricAuthenticator.TYPE_FINGERPRINT; + + Bundle bundle = new Bundle(); + bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); + config.mBiometricPromptBundle = bundle; + + mAuthContainer = new TestableAuthContainer(config); + } + private class TestableAuthContainer extends AuthContainerView { TestableAuthContainer(AuthContainerView.Config config) { - super(config); + super(config, new MockInjector()); } @Override @@ -95,4 +179,32 @@ public class AuthContainerViewTest extends SysuiTestCase { mConfig.mCallback.onDismissed(reason); } } + + private final class MockInjector extends AuthContainerView.Injector { + @Override + public ScrollView getBiometricScrollView(FrameLayout parent) { + return mock(ScrollView.class); + } + + @Override + public FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) { + return mock(FrameLayout.class); + } + + @Override + public AuthPanelController getPanelController(Context context, View view, + boolean isManagedProfile) { + return mock(AuthPanelController.class); + } + + @Override + public ImageView getBackgroundView(FrameLayout parent) { + return mock(ImageView.class); + } + + @Override + public View getPanelView(FrameLayout parent) { + return mock(View.class); + } + } } 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 eb7be4fa6332..dcdb5c3f8e9e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -22,8 +22,11 @@ import static junit.framework.TestCase.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; 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.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -33,6 +36,8 @@ import android.app.IActivityTaskManager; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.hardware.biometrics.Authenticator; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.os.Bundle; @@ -69,7 +74,7 @@ public class AuthControllerTest extends SysuiTestCase { @Mock private AuthDialog mDialog2; - private TestableBiometricDialogImpl mBiometricDialogImpl; + private TestableAuthController mAuthController; @Before @@ -90,78 +95,90 @@ public class AuthControllerTest extends SysuiTestCase { when(mDialog1.getOpPackageName()).thenReturn("Dialog1"); when(mDialog2.getOpPackageName()).thenReturn("Dialog2"); - mBiometricDialogImpl = new TestableBiometricDialogImpl(new MockInjector()); - mBiometricDialogImpl.mContext = context; - mBiometricDialogImpl.mComponents = mContext.getComponents(); + when(mDialog1.isAllowDeviceCredentials()).thenReturn(false); + when(mDialog2.isAllowDeviceCredentials()).thenReturn(false); - mBiometricDialogImpl.start(); + mAuthController = new TestableAuthController(new MockInjector()); + mAuthController.mContext = context; + mAuthController.mComponents = mContext.getComponents(); + + mAuthController.start(); } // Callback tests @Test public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); - mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED); + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED); verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); } @Test public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); - mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE); + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE); verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE); } @Test public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); - mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE); - verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CONFIRMED); + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE); + verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED); } @Test public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); - mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_AUTHENTICATED); - verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED); + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED); + verify(mReceiver).onDialogDismissed( + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED); } @Test public void testSendsReasonError_whenDismissedByError() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); - mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_ERROR); + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_ERROR); verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR); } @Test - public void testSendsReasonDismissedBySystemServer_whenDismissedByServer() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); - mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER); + public void testSendsReasonServerRequested_whenDismissedByServer() throws Exception { + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER); verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED); } + @Test + public void testSendsReasonCredentialConfirmed_whenDeviceCredentialAuthenticated() + throws Exception { + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); + mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED); + verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED); + } + // Statusbar tests @Test public void testShowInvoked_whenSystemRequested() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); verify(mDialog1).show(any(), any()); } @Test - public void testOnAuthenticationSucceededInvoked_whenSystemRequested() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); - mBiometricDialogImpl.onBiometricAuthenticated(true, null /* failureReason */); + public void testOnAuthenticationSucceededInvoked_whenSystemRequested() { + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); + mAuthController.onBiometricAuthenticated(true, null /* failureReason */); verify(mDialog1).onAuthenticationSucceeded(); } @Test - public void testOnAuthenticationFailedInvoked_whenSystemRequested() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); + public void testOnAuthenticationFailedInvoked_whenSystemRequested() { + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); final String failureReason = "failure reason"; - mBiometricDialogImpl.onBiometricAuthenticated(false, failureReason); + mAuthController.onBiometricAuthenticated(false, failureReason); ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); verify(mDialog1).onAuthenticationFailed(captor.capture()); @@ -170,10 +187,10 @@ public class AuthControllerTest extends SysuiTestCase { } @Test - public void testOnHelpInvoked_whenSystemRequested() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); + public void testOnHelpInvoked_whenSystemRequested() { + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); final String helpMessage = "help"; - mBiometricDialogImpl.onBiometricHelp(helpMessage); + mAuthController.onBiometricHelp(helpMessage); ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); verify(mDialog1).onHelp(captor.capture()); @@ -182,10 +199,11 @@ public class AuthControllerTest extends SysuiTestCase { } @Test - public void testOnErrorInvoked_whenSystemRequested() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); + public void testOnErrorInvoked_whenSystemRequested() { + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); + final int error = 1; final String errMessage = "error message"; - mBiometricDialogImpl.onBiometricError(errMessage); + mAuthController.onBiometricError(error, errMessage); ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); verify(mDialog1).onError(captor.capture()); @@ -194,30 +212,82 @@ public class AuthControllerTest extends SysuiTestCase { } @Test - public void testDismissWithoutCallbackInvoked_whenSystemRequested() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); - mBiometricDialogImpl.hideBiometricDialog(); + public void testErrorLockout_whenCredentialAllowed_AnimatesToCredentialUI() { + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); + final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT; + final String errorString = "lockout"; + + when(mDialog1.isAllowDeviceCredentials()).thenReturn(true); + + mAuthController.onBiometricError(error, errorString); + verify(mDialog1, never()).onError(anyString()); + verify(mDialog1).animateToCredentialUI(); + } + + @Test + 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"; + + when(mDialog1.isAllowDeviceCredentials()).thenReturn(true); + + mAuthController.onBiometricError(error, errorString); + verify(mDialog1, never()).onError(anyString()); + verify(mDialog1).animateToCredentialUI(); + } + + @Test + public void testErrorLockout_whenCredentialNotAllowed_sendsOnError() { + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); + final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT; + final String errorString = "lockout"; + + when(mDialog1.isAllowDeviceCredentials()).thenReturn(false); + + mAuthController.onBiometricError(error, errorString); + verify(mDialog1).onError(eq(errorString)); + verify(mDialog1, never()).animateToCredentialUI(); + } + + @Test + 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"; + + when(mDialog1.isAllowDeviceCredentials()).thenReturn(false); + + mAuthController.onBiometricError(error, errorString); + verify(mDialog1).onError(eq(errorString)); + verify(mDialog1, never()).animateToCredentialUI(); + } + + @Test + public void testDismissWithoutCallbackInvoked_whenSystemRequested() { + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); + mAuthController.hideAuthenticationDialog(); verify(mDialog1).dismissFromSystemServer(); } @Test - public void testClientNotified_whenDismissedBySystemServer() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); - mBiometricDialogImpl.hideBiometricDialog(); + public void testClientNotified_whenDismissedBySystemServer() { + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); + mAuthController.hideAuthenticationDialog(); verify(mDialog1).dismissFromSystemServer(); - assertNotNull(mBiometricDialogImpl.mCurrentDialog); - assertNotNull(mBiometricDialogImpl.mReceiver); + assertNotNull(mAuthController.mCurrentDialog); + assertNotNull(mAuthController.mReceiver); } // Corner case tests @Test - public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); + public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() { + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); verify(mDialog1).show(any(), any()); - showDialog(BiometricPrompt.TYPE_FACE); + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); // First dialog should be dismissed without animation verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */); @@ -227,11 +297,20 @@ public class AuthControllerTest extends SysuiTestCase { } @Test - public void testConfigurationPersists_whenOnConfigurationChanged() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); + public void testConfigurationPersists_whenOnConfigurationChanged() { + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); verify(mDialog1).show(any(), any()); - mBiometricDialogImpl.onConfigurationChanged(new Configuration()); + // Return that the UI is in "showing" state + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + Bundle savedState = (Bundle) args[0]; + savedState.putInt( + AuthDialog.KEY_CONTAINER_STATE, AuthContainerView.STATE_SHOWING); + return null; // onSaveState returns void + }).when(mDialog1).onSaveState(any()); + + mAuthController.onConfigurationChanged(new Configuration()); ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class); verify(mDialog1).onSaveState(captor.capture()); @@ -248,37 +327,63 @@ public class AuthControllerTest extends SysuiTestCase { } @Test + public void testConfigurationPersists_whenBiometricFallbackToCredential() { + showDialog(Authenticator.TYPE_CREDENTIAL | Authenticator.TYPE_BIOMETRIC, + BiometricPrompt.TYPE_FACE); + verify(mDialog1).show(any(), any()); + + // Pretend that the UI is now showing device credential UI. + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + Bundle savedState = (Bundle) args[0]; + savedState.putInt( + AuthDialog.KEY_CONTAINER_STATE, AuthContainerView.STATE_SHOWING); + savedState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, true); + return null; // onSaveState returns void + }).when(mDialog1).onSaveState(any()); + + mAuthController.onConfigurationChanged(new Configuration()); + + // Check that the new dialog was initialized to the credential UI. + ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class); + verify(mDialog2).show(any(), captor.capture()); + assertEquals(Authenticator.TYPE_CREDENTIAL, + mAuthController.mLastBiometricPromptBundle + .getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)); + } + + @Test public void testClientNotified_whenTaskStackChangesDuringAuthentication() throws Exception { - showDialog(BiometricPrompt.TYPE_FACE); + showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE); List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>(); ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class); taskInfo.topActivity = mock(ComponentName.class); when(taskInfo.topActivity.getPackageName()).thenReturn("other_package"); tasks.add(taskInfo); - when(mBiometricDialogImpl.mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks); + when(mAuthController.mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks); - mBiometricDialogImpl.mTaskStackListener.onTaskStackChanged(); + mAuthController.mTaskStackListener.onTaskStackChanged(); waitForIdleSync(); - assertNull(mBiometricDialogImpl.mCurrentDialog); - assertNull(mBiometricDialogImpl.mReceiver); + assertNull(mAuthController.mCurrentDialog); + assertNull(mAuthController.mReceiver); verify(mDialog1).dismissWithoutCallback(true /* animate */); verify(mReceiver).onDialogDismissed(eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL)); } // Helpers - private void showDialog(int type) { - mBiometricDialogImpl.showBiometricDialog(createTestDialogBundle(), + private void showDialog(int authenticators, int biometricModality) { + mAuthController.showAuthenticationDialog(createTestDialogBundle(authenticators), mReceiver /* receiver */, - type, + biometricModality, true /* requireConfirmation */, 0 /* userId */, "testPackage"); } - private Bundle createTestDialogBundle() { + private Bundle createTestDialogBundle(int authenticators) { Bundle bundle = new Bundle(); bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title"); @@ -290,13 +395,16 @@ public class AuthControllerTest extends SysuiTestCase { // by user settings, and should be tested in BiometricService. bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true); + bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); + return bundle; } - private final class TestableBiometricDialogImpl extends AuthController { + private final class TestableAuthController extends AuthController { private int mBuildCount = 0; + private Bundle mLastBiometricPromptBundle; - public TestableBiometricDialogImpl(Injector injector) { + public TestableAuthController(Injector injector) { super(injector); } @@ -304,6 +412,9 @@ public class AuthControllerTest extends SysuiTestCase { protected AuthDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation, int userId, int type, String opPackageName, boolean skipIntro) { + + mLastBiometricPromptBundle = biometricPromptBundle; + AuthDialog dialog; if (mBuildCount == 0) { dialog = mDialog1; 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 b252a0d95f94..1bd01e166ddb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -367,12 +367,13 @@ public class CommandQueueTest extends SysuiTestCase { } @Test - public void testShowBiometricDialog() { + public void testShowAuthenticationDialog() { Bundle bundle = new Bundle(); String packageName = "test"; - mCommandQueue.showBiometricDialog(bundle, null /* receiver */, 1, true, 3, packageName); + mCommandQueue.showAuthenticationDialog(bundle, null /* receiver */, 1, true, 3, + packageName); waitForIdleSync(); - verify(mCallbacks).showBiometricDialog(eq(bundle), eq(null), eq(1), eq(true), eq(3), + verify(mCallbacks).showAuthenticationDialog(eq(bundle), eq(null), eq(1), eq(true), eq(3), eq(packageName)); } @@ -394,16 +395,17 @@ public class CommandQueueTest extends SysuiTestCase { @Test public void testOnBiometricError() { + final int errorCode = 1; String errorMessage = "test_error_message"; - mCommandQueue.onBiometricError(errorMessage); + mCommandQueue.onBiometricError(errorCode, errorMessage); waitForIdleSync(); - verify(mCallbacks).onBiometricError(eq(errorMessage)); + verify(mCallbacks).onBiometricError(eq(errorCode), eq(errorMessage)); } @Test - public void testHideBiometricDialog() { - mCommandQueue.hideBiometricDialog(); + public void testHideAuthenticationDialog() { + mCommandQueue.hideAuthenticationDialog(); waitForIdleSync(); - verify(mCallbacks).hideBiometricDialog(); + verify(mCallbacks).hideAuthenticationDialog(); } } 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 30e02e6b46d2..52f7e679b49c 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 @@ -41,7 +41,6 @@ import android.app.ActivityManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.content.Context; import android.content.Intent; import android.graphics.drawable.Icon; import android.os.Handler; @@ -145,8 +144,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { private class TestableNotificationEntryManager extends NotificationEntryManager { private final CountDownLatch mCountDownLatch; - TestableNotificationEntryManager(Context context) { - super(context); + TestableNotificationEntryManager() { + super(new NotificationData(mock(NotificationSectionsFeatureManager.class))); mCountDownLatch = new CountDownLatch(1); } @@ -250,7 +249,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.expandedIcon = mock(StatusBarIconView.class); - mEntryManager = new TestableNotificationEntryManager(mContext); + mEntryManager = new TestableNotificationEntryManager(); Dependency.get(InitController.class).executePostInitTasks(); mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager); mEntryManager.addNotificationEntryListener(mEntryListener); 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 8d496a72e3b2..6d275419ee94 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.NotificationEntry; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; +import com.android.systemui.util.DeviceConfigProxyFake; import org.junit.Before; import org.junit.Test; @@ -73,7 +74,9 @@ public class NotificationListControllerTest extends SysuiTestCase { private DeviceProvisionedListener mProvisionedListener; // TODO: Remove this once EntryManager no longer needs to be mocked - private NotificationData mNotificationData = new NotificationData(mContext); + private NotificationData mNotificationData = + new NotificationData(new NotificationSectionsFeatureManager( + new DeviceConfigProxyFake(), mContext)); private int mNextNotifId = 0; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt new file mode 100644 index 000000000000..b3d0d22445b6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt @@ -0,0 +1,69 @@ +/* + * 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 + +import android.provider.DeviceConfig +import android.provider.Settings +import android.provider.Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL +import android.testing.AndroidTestingRunner + +import androidx.test.filters.SmallTest + +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.DeviceConfigProxyFake + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class NotificationSectionsFeatureManagerTest : SysuiTestCase() { + var manager: NotificationSectionsFeatureManager? = null + val proxyFake = DeviceConfigProxyFake() + + @Before + public fun setup() { + Settings.Secure.putInt(mContext.getContentResolver(), + NOTIFICATION_NEW_INTERRUPTION_MODEL, 1) + manager = NotificationSectionsFeatureManager(proxyFake, mContext) + manager!!.clearCache() + } + + @Test + public fun testPeopleFilteringOff_newInterruptionModelOn() { + proxyFake.setProperty( + DeviceConfig.NAMESPACE_SYSTEMUI, NOTIFICATIONS_USE_PEOPLE_FILTERING, "false", false) + + assertFalse("People filtering should be disabled", manager!!.isFilteringEnabled()) + assertTrue("Expecting 2 buckets when people filtering is disabled", + manager!!.getNumberOfBuckets() == 2) + } + + @Test + public fun testPeopleFilteringOn_newInterruptionModelOn() { + proxyFake.setProperty( + DeviceConfig.NAMESPACE_SYSTEMUI, NOTIFICATIONS_USE_PEOPLE_FILTERING, "true", false) + + assertTrue("People filtering should be enabled", manager!!.isFilteringEnabled()) + assertTrue("Expecting 3 buckets when people filtering is enabled", + manager!!.getNumberOfBuckets() == 3) + } +} 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 657ec61dfd11..5fbacb1d7adf 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 @@ -50,7 +50,6 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Person; -import android.content.Context; import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; @@ -78,6 +77,7 @@ import com.android.systemui.statusbar.NotificationEntryBuilder; import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; +import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -139,7 +139,8 @@ public class NotificationDataTest extends SysuiTestCase { mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment); when(mEnvironment.isDeviceProvisioned()).thenReturn(true); when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true); - mNotificationData = new TestableNotificationData(mContext); + mNotificationData = new TestableNotificationData( + mock(NotificationSectionsFeatureManager.class)); mNotificationData.updateRanking(mock(NotificationListenerService.RankingMap.class)); mRow = new NotificationTestHelper(getContext()).createRow(); Dependency.get(InitController.class).executePostInitTasks(); @@ -631,8 +632,8 @@ public class NotificationDataTest extends SysuiTestCase { } public static class TestableNotificationData extends NotificationData { - public TestableNotificationData(Context context) { - super(context); + public TestableNotificationData(NotificationSectionsFeatureManager sectionsFeatureManager) { + super(sectionsFeatureManager); } public static final String OVERRIDE_RANK = "r"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java index addceb5def6e..3f467eae1d57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java @@ -32,10 +32,12 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationTestHelper; +import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.util.DeviceConfigProxy; import org.junit.Assert; import org.junit.Before; @@ -64,7 +66,9 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mRoundnessManager = new NotificationRoundnessManager(mBypassController, mContext); + mRoundnessManager = new NotificationRoundnessManager( + mBypassController, + new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext)); com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper(); NotificationTestHelper testHelper = new NotificationTestHelper(getContext()); mFirst = testHelper.createRow(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 31054260eb15..11ae0cc34787 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -55,7 +55,6 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; -import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -65,6 +64,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -79,6 +79,7 @@ import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarTest.TestableNotificationEntryManager; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.util.DeviceConfigProxyFake; import org.junit.After; import org.junit.Before; @@ -144,11 +145,10 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mDependency.injectMockDependency(ShadeController.class); when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController); - mEntryManager = new TestableNotificationEntryManager(mContext); + mEntryManager = new TestableNotificationEntryManager(mNotificationData); mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); Dependency.get(InitController.class).executePostInitTasks(); - mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager, - mNotificationData); + mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager); NotificationShelf notificationShelf = mock(NotificationShelf.class); @@ -166,7 +166,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mock(SysuiStatusBarStateController.class), mHeadsUpManager, mKeyguardBypassController, - new FalsingManagerFake()); + new FalsingManagerFake(), + new NotificationSectionsFeatureManager(new DeviceConfigProxyFake(), mContext)); mStackScroller = spy(mStackScrollerInternal); mStackScroller.setShelf(notificationShelf); mStackScroller.setStatusBar(mBar); 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 144935212c27..2631ced77972 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 @@ -236,7 +236,7 @@ public class StatusBarTest extends SysuiTestCase { mMetricsLogger = new FakeMetricsLogger(); TestableNotificationEntryManager entryManager = new TestableNotificationEntryManager( - mContext); + mNotificationData); NotificationLogger notificationLogger = new NotificationLogger(mNotificationListener, Dependency.get(UiOffloadThread.class), entryManager, mStatusBarStateController, mExpansionStateLogger); @@ -354,7 +354,7 @@ public class StatusBarTest extends SysuiTestCase { mStatusBar.putComponent(StatusBar.class, mStatusBar); Dependency.get(InitController.class).executePostInitTasks(); entryManager.setUpForTest(mock(NotificationPresenter.class), mStackScroller, - mHeadsUpManager, mNotificationData); + mHeadsUpManager); entryManager.addNotificationEntryListener(mEntryListener); notificationLogger.setUpWithContainer(mStackScroller); } @@ -865,16 +865,14 @@ public class StatusBarTest extends SysuiTestCase { public static class TestableNotificationEntryManager extends NotificationEntryManager { - public TestableNotificationEntryManager(Context context) { - super(context); + public TestableNotificationEntryManager(NotificationData notificationData) { + super(notificationData); } public void setUpForTest(NotificationPresenter presenter, NotificationListContainer listContainer, - HeadsUpManagerPhone headsUpManager, - NotificationData notificationData) { + HeadsUpManagerPhone headsUpManager) { super.setUpWithPresenter(presenter, listContainer, headsUpManager); - mNotificationData = notificationData; } } diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 4f021ad3cee0..514eb77c3201 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -350,9 +350,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ protected abstract boolean hasRightsToCurrentUserLocked(); + @Nullable @Override - public List<AccessibilityWindowInfo> getWindows() { - ensureWindowsAvailableTimed(Display.DEFAULT_DISPLAY); + public AccessibilityWindowInfo.WindowListSparseArray getWindows() { synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return null; @@ -362,38 +362,39 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ if (!permissionGranted) { return null; } - List<AccessibilityWindowInfo> internalWindowList = - mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); - if (internalWindowList == null) { - return null; - } if (!mSecurityPolicy.checkAccessibilityAccess(this)) { return null; } - List<AccessibilityWindowInfo> returnedWindowList = new ArrayList<>(); - final int windowCount = internalWindowList.size(); - for (int i = 0; i < windowCount; i++) { - AccessibilityWindowInfo window = internalWindowList.get(i); - AccessibilityWindowInfo windowClone = - AccessibilityWindowInfo.obtain(window); - windowClone.setConnectionId(mId); - returnedWindowList.add(windowClone); + final AccessibilityWindowInfo.WindowListSparseArray allWindows = + new AccessibilityWindowInfo.WindowListSparseArray(); + final ArrayList<Integer> displayList = mA11yWindowManager.getDisplayListLocked(); + final int displayListCounts = displayList.size(); + if (displayListCounts > 0) { + for (int i = 0; i < displayListCounts; i++) { + final int displayId = displayList.get(i); + ensureWindowsAvailableTimedLocked(displayId); + + final List<AccessibilityWindowInfo> windowList = getWindowsByDisplayLocked( + displayId); + if (windowList != null) { + allWindows.put(displayId, windowList); + } + } } - return returnedWindowList; + return allWindows; } } @Override public AccessibilityWindowInfo getWindow(int windowId) { - int displayId = Display.INVALID_DISPLAY; synchronized (mLock) { + int displayId = Display.INVALID_DISPLAY; if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) { displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked( mSystemSupport.getCurrentUserIdLocked(), windowId); } - } - ensureWindowsAvailableTimed(displayId); - synchronized (mLock) { + ensureWindowsAvailableTimedLocked(displayId); + if (!hasRightsToCurrentUserLocked()) { return null; } @@ -1316,35 +1317,33 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ * * @param displayId The logical display id. */ - private void ensureWindowsAvailableTimed(int displayId) { - synchronized (mLock) { - if (mA11yWindowManager.getWindowListLocked(displayId) != null) { - return; - } - // If we have no registered callback, update the state we - // we may have to register one but it didn't happen yet. - if (!mA11yWindowManager.isTrackingWindowsLocked(displayId)) { - // Invokes client change to make sure tracking window enabled. - mSystemSupport.onClientChangeLocked(false); - } - // We have no windows but do not care about them, done. - if (!mA11yWindowManager.isTrackingWindowsLocked(displayId)) { + private void ensureWindowsAvailableTimedLocked(int displayId) { + if (mA11yWindowManager.getWindowListLocked(displayId) != null) { + return; + } + // If we have no registered callback, update the state we + // we may have to register one but it didn't happen yet. + if (!mA11yWindowManager.isTrackingWindowsLocked(displayId)) { + // Invokes client change to make sure tracking window enabled. + mSystemSupport.onClientChangeLocked(false); + } + // We have no windows but do not care about them, done. + if (!mA11yWindowManager.isTrackingWindowsLocked(displayId)) { + return; + } + + // Wait for the windows with a timeout. + final long startMillis = SystemClock.uptimeMillis(); + while (mA11yWindowManager.getWindowListLocked(displayId) == null) { + final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; + final long remainMillis = WAIT_WINDOWS_TIMEOUT_MILLIS - elapsedMillis; + if (remainMillis <= 0) { return; } - - // Wait for the windows with a timeout. - final long startMillis = SystemClock.uptimeMillis(); - while (mA11yWindowManager.getWindowListLocked(displayId) == null) { - final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; - final long remainMillis = WAIT_WINDOWS_TIMEOUT_MILLIS - elapsedMillis; - if (remainMillis <= 0) { - return; - } - try { - mLock.wait(remainMillis); - } catch (InterruptedException ie) { - /* ignore */ - } + try { + mLock.wait(remainMillis); + } catch (InterruptedException ie) { + /* ignore */ } } } @@ -1442,6 +1441,24 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ interrogatingPid, interrogatingTid); } + private List<AccessibilityWindowInfo> getWindowsByDisplayLocked(int displayId) { + final List<AccessibilityWindowInfo> internalWindowList = + mA11yWindowManager.getWindowListLocked(displayId); + if (internalWindowList == null) { + return null; + } + final List<AccessibilityWindowInfo> returnedWindowList = new ArrayList<>(); + final int windowCount = internalWindowList.size(); + for (int i = 0; i < windowCount; i++) { + AccessibilityWindowInfo window = internalWindowList.get(i); + AccessibilityWindowInfo windowClone = + AccessibilityWindowInfo.obtain(window); + windowClone.setConnectionId(mId); + returnedWindowList.add(windowClone); + } + return returnedWindowList; + } + public ComponentName getComponentName() { return mComponentName; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index 0038a27db384..cb858ac11b00 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -1472,11 +1472,27 @@ public class AccessibilityWindowManager { public int getDisplayIdByUserIdAndWindowIdLocked(int userId, int windowId) { final IBinder windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId); final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken); - return displayId; } /** + * Returns the display list including all displays which are tracking windows. + * + * @return The display list. + */ + public ArrayList<Integer> getDisplayListLocked() { + final ArrayList<Integer> displayList = new ArrayList<>(); + final int count = mDisplayWindowsObservers.size(); + for (int i = 0; i < count; i++) { + final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i); + if (observer != null) { + displayList.add(observer.mDisplayId); + } + } + return displayList; + } + + /** * Gets current input focused window token from window manager, and returns its windowId. * * @param userId The userId diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java index 7e8fb295d036..3dfe59e142a6 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java @@ -408,9 +408,6 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen cancelGesture(); } - public boolean firstTapDetected() { - return mFirstTapDetected; - } @Override public void onLongPress(MotionEvent e) { diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index c60e35e2cc6d..b62e260aacad 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -411,24 +411,12 @@ public class TouchExplorer extends BaseEventStreamTransformation // we resent the delayed callback and wait again. mSendHoverEnterAndMoveDelayed.cancel(); mSendHoverExitDelayed.cancel(); - // If a touch exploration gesture is in progress send events for its end. if (mState.isTouchExploring()) { sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } - // Avoid duplicated TYPE_TOUCH_INTERACTION_START event when 2nd tap of double - // tap. - if (!mGestureDetector.firstTapDetected() && mState.isClear()) { - mSendTouchExplorationEndDelayed.forceSendAndRemove(); - mSendTouchInteractionEndDelayed.forceSendAndRemove(); - mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START); - } else { - // Let gesture to handle to avoid duplicated TYPE_TOUCH_INTERACTION_END event. - mSendTouchInteractionEndDelayed.cancel(); - } - - if (!mGestureDetector.firstTapDetected() && !mState.isTouchExploring()) { + if (mState.isClear()) { if (!mSendHoverEnterAndMoveDelayed.isPending()) { // Queue a delayed transition to STATE_TOUCH_EXPLORING. // If we do not detect that this is a gesture, delegation or drag the transition @@ -441,6 +429,12 @@ public class TouchExplorer extends BaseEventStreamTransformation // Cache the event until we discern exploration from gesturing. mSendHoverEnterAndMoveDelayed.addEvent(event, rawEvent); } + mSendTouchExplorationEndDelayed.forceSendAndRemove(); + mSendTouchInteractionEndDelayed.forceSendAndRemove(); + mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START); + } else { + // Avoid duplicated TYPE_TOUCH_INTERACTION_START event when 2nd tap of double tap. + mSendTouchInteractionEndDelayed.cancel(); } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index f91cf0cc1255..0f8a3b54acc5 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -274,8 +274,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE; static final int READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK = - PhoneStateListener.LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER - | PhoneStateListener.LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER; + PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL + | PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS; private static final int MSG_USER_SWITCHED = 1; private static final int MSG_UPDATE_DEFAULT_SUB = 2; @@ -1932,7 +1932,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mOutgoingCallEmergencyNumber[phoneId] = emergencyNumber; for (Record r : mRecords) { if (r.matchPhoneStateListenerEvent( - PhoneStateListener.LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER) + PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onOutgoingEmergencyCall(emergencyNumber); @@ -1957,7 +1957,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mOutgoingSmsEmergencyNumber[phoneId] = emergencyNumber; for (Record r : mRecords) { if (r.matchPhoneStateListenerEvent( - PhoneStateListener.LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER) + PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS) && idMatch(r.subId, subId, phoneId)) { try { r.callback.onOutgoingEmergencySms(emergencyNumber); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ee98af4020ad..7cbd1fcebb94 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -9736,6 +9736,10 @@ public class ActivityManagerService extends IActivityManager.Stub sb.append("Foreground: ") .append(process.isInterestingToUserLocked() ? "Yes" : "No") .append("\n"); + if (process.startTime > 0) { + long runtimeMillis = SystemClock.elapsedRealtime() - process.startTime; + sb.append("Process-Runtime: ").append(runtimeMillis).append("\n"); + } } if (activityShortComponentName != null) { sb.append("Activity: ").append(activityShortComponentName).append("\n"); diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING index 21d4925722d0..bc4707f04724 100644 --- a/services/core/java/com/android/server/am/TEST_MAPPING +++ b/services/core/java/com/android/server/am/TEST_MAPPING @@ -33,7 +33,6 @@ }, { "name": "FrameworksMockingServicesTests", - "file_patterns": ["AppCompactor\\.java"], "options": [ { "include-filter": "com.android.server.am." diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 8e4474c462ab..175419117898 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1184,7 +1184,7 @@ class UserController implements Handler.Callback { updateStartedUserArrayLU(); } needStart = true; - t.traceBegin("updateStateStopping"); + t.traceEnd(); } if (uss.state == UserState.STATE_BOOTING) { diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 3d341ef67d6f..4f1db3c96faf 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -26,19 +26,17 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; import android.app.ActivityManager; import android.app.IActivityManager; -import android.app.KeyguardManager; import android.app.UserSwitchObserver; import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManager; import android.database.ContentObserver; +import android.hardware.biometrics.Authenticator; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.BiometricsProtoEnums; -import android.hardware.biometrics.IBiometricConfirmDeviceCredentialCallback; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -95,11 +93,8 @@ public class BiometricService extends SystemService { private static final int MSG_ON_READY_FOR_AUTHENTICATION = 8; private static final int MSG_AUTHENTICATE = 9; private static final int MSG_CANCEL_AUTHENTICATION = 10; - private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS = 11; - private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR = 12; - private static final int MSG_REGISTER_CANCELLATION_CALLBACK = 13; - private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 14; - + 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, @@ -132,19 +127,19 @@ public class BiometricService extends SystemService { */ static final int STATE_AUTH_PENDING_CONFIRM = 5; /** - * Biometric authentication was canceled, but the device is now showing ConfirmDeviceCredential - */ - static final int STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC = 6; - /** * Biometric authenticated, waiting for SysUI to finish animation */ - static final int STATE_AUTHENTICATED_PENDING_SYSUI = 7; + static final int STATE_AUTHENTICATED_PENDING_SYSUI = 6; /** * Biometric error, waiting for SysUI to finish animation */ - static final int STATE_ERROR_PENDING_SYSUI = 8; + static final int STATE_ERROR_PENDING_SYSUI = 7; + /** + * Device credential in AuthController is showing + */ + static final int STATE_SHOWING_DEVICE_CREDENTIAL = 8; - final class AuthSession implements IBinder.DeathRecipient { + final class AuthSession { // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from // <Biometric>Services before we can start authenticating. Pairs that have been returned // are moved to mModalitiesMatched. @@ -184,14 +179,10 @@ public class BiometricService extends SystemService { // Timestamp when hardware authentication occurred private long mAuthenticatedTimeMs; - // TODO(b/123378871): Remove when moved. - private IBiometricConfirmDeviceCredentialCallback mConfirmDeviceCredentialCallback; - AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId, int userId, IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, int callingUid, int callingPid, int callingUserId, - int modality, boolean requireConfirmation, - IBiometricConfirmDeviceCredentialCallback callback) { + int modality, boolean requireConfirmation) { mModalitiesWaiting = modalities; mToken = token; mSessionId = sessionId; @@ -204,25 +195,12 @@ public class BiometricService extends SystemService { mCallingUserId = callingUserId; mModality = modality; mRequireConfirmation = requireConfirmation; - mConfirmDeviceCredentialCallback = callback; - - if (isFromConfirmDeviceCredential()) { - try { - token.linkToDeath(this, 0 /* flags */); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to link to death", e); - } - } } boolean isCrypto() { return mSessionId != 0; } - boolean isFromConfirmDeviceCredential() { - return mBundle.getBoolean(BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false); - } - boolean containsCookie(int cookie) { if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) { return true; @@ -233,23 +211,8 @@ public class BiometricService extends SystemService { return false; } - // TODO(b/123378871): Remove when moved. - @Override - public void binderDied() { - mHandler.post(() -> { - Slog.e(TAG, "Binder died, killing ConfirmDeviceCredential"); - if (mConfirmDeviceCredentialCallback == null) { - Slog.e(TAG, "Callback is null"); - return; - } - - try { - mConfirmDeviceCredentialCallback.cancel(); - mConfirmDeviceCredentialCallback = null; - } catch (RemoteException e) { - Slog.e(TAG, "Unable to send cancel", e); - } - }); + boolean isAllowDeviceCredential() { + return Utils.isDeviceCredentialAllowed(mBundle); } } @@ -275,7 +238,7 @@ public class BiometricService extends SystemService { // Get and cache the available authenticator (manager) classes. Used since aidl doesn't support // polymorphism :/ - final ArrayList<Authenticator> mAuthenticators = new ArrayList<>(); + final ArrayList<AuthenticatorWrapper> mAuthenticators = new ArrayList<>(); // The current authentication session, null if idle/done. We need to track both the current // and pending sessions since errors may be sent to either. @@ -284,14 +247,6 @@ public class BiometricService extends SystemService { @VisibleForTesting AuthSession mPendingAuthSession; - // TODO(b/123378871): Remove when moved. - // When BiometricPrompt#setAllowDeviceCredentials is set to true, we need to store the - // client (app) receiver. BiometricService internally launches CDCA which invokes - // BiometricService to start authentication (normal path). When auth is success/rejected, - // CDCA will use an aidl method to poke BiometricService - the result will then be forwarded - // to this receiver. - private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver; - @VisibleForTesting final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override @@ -361,8 +316,7 @@ public class BiometricService extends SystemService { (Bundle) args.arg5 /* bundle */, args.argi2 /* callingUid */, args.argi3 /* callingPid */, - args.argi4 /* callingUserId */, - (IBiometricConfirmDeviceCredentialCallback) args.arg6 /* callback */); + args.argi4 /* callingUserId */); args.recycle(); break; } @@ -376,28 +330,13 @@ public class BiometricService extends SystemService { break; } - case MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS: { - handleOnConfirmDeviceCredentialSuccess(); - break; - } - - case MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR: { - SomeArgs args = (SomeArgs) msg.obj; - handleOnConfirmDeviceCredentialError( - args.argi1 /* error */, - (String) args.arg1 /* errorMsg */); - args.recycle(); + case MSG_ON_AUTHENTICATION_TIMED_OUT: { + handleAuthenticationTimedOut((String) msg.obj /* errorMessage */); break; } - case MSG_REGISTER_CANCELLATION_CALLBACK: { - handleRegisterCancellationCallback( - (IBiometricConfirmDeviceCredentialCallback) msg.obj /* callback */); - break; - } - - case MSG_ON_AUTHENTICATION_TIMED_OUT: { - handleAuthenticationTimedOut((String) msg.obj /* errorMessage */); + case MSG_ON_DEVICE_CREDENTIAL_PRESSED: { + handleOnDeviceCredentialPressed(); break; } @@ -408,11 +347,11 @@ public class BiometricService extends SystemService { } }; - private final class Authenticator { + private final class AuthenticatorWrapper { final int mType; final BiometricAuthenticator mAuthenticator; - Authenticator(int type, BiometricAuthenticator authenticator) { + AuthenticatorWrapper(int type, BiometricAuthenticator authenticator) { mType = type; mAuthenticator = authenticator; } @@ -620,6 +559,11 @@ public class BiometricService extends SystemService { public void onTryAgainPressed() { mHandler.sendEmptyMessage(MSG_ON_TRY_AGAIN_PRESSED); } + + @Override + public void onDeviceCredentialPressed() { + mHandler.sendEmptyMessage(MSG_ON_DEVICE_CREDENTIAL_PRESSED); + } }; @@ -642,18 +586,12 @@ public class BiometricService extends SystemService { @Override // Binder call public void authenticate(IBinder token, long sessionId, int userId, - IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, - IBiometricConfirmDeviceCredentialCallback callback) + IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle) throws RemoteException { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int callingUserId = UserHandle.getCallingUserId(); - // TODO(b/123378871): Remove when moved. - if (callback != null) { - checkInternalPermission(); - } - // In the BiometricServiceBase, check do the AppOps and foreground check. if (userId == callingUserId) { // Check the USE_BIOMETRIC permission here. @@ -670,12 +608,12 @@ public class BiometricService extends SystemService { return; } - final boolean isFromConfirmDeviceCredential = - bundle.getBoolean(BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false); - if (isFromConfirmDeviceCredential) { + if (bundle.get(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED) != null) { checkInternalPermission(); } + Utils.combineAuthenticatorBundles(bundle); + // Check the usage of this in system server. Need to remove this check if it becomes // a public API. final boolean useDefaultTitle = @@ -689,39 +627,6 @@ public class BiometricService extends SystemService { } } - // Launch CDC instead if necessary. CDC will return results through an AIDL call, since - // we can't get activity results. Store the receiver somewhere so we can forward the - // result back to the client. - // TODO(b/123378871): Remove when moved. - if (bundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL)) { - mHandler.post(() -> { - final KeyguardManager kgm = getContext().getSystemService( - KeyguardManager.class); - if (!kgm.isDeviceSecure()) { - try { - receiver.onError( - BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL, - getContext().getString( - R.string.biometric_error_device_not_secured)); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - } - return; - } - mConfirmDeviceCredentialReceiver = receiver; - // Use this so we don't need to duplicate logic.. - final Intent intent = kgm.createConfirmDeviceCredentialIntent(null /* title */, - null /* description */, userId); - // Then give it the bundle to do magic behavior.. - intent.putExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE, bundle); - // Create a new task with this activity located at the root. - intent.setFlags( - Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); - getContext().startActivityAsUser(intent, UserHandle.CURRENT); - }); - return; - } - SomeArgs args = SomeArgs.obtain(); args.arg1 = token; args.arg2 = sessionId; @@ -732,41 +637,11 @@ public class BiometricService extends SystemService { args.argi2 = callingUid; args.argi3 = callingPid; args.argi4 = callingUserId; - args.arg6 = callback; mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget(); } @Override // Binder call - public void onConfirmDeviceCredentialSuccess() { - checkInternalPermission(); - - mHandler.sendEmptyMessage(MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS); - } - - @Override // Binder call - public void onConfirmDeviceCredentialError(int error, String message) { - checkInternalPermission(); - - SomeArgs args = SomeArgs.obtain(); - args.argi1 = error; - args.arg1 = message; - mHandler.obtainMessage(MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR, args).sendToTarget(); - } - - @Override // Binder call - public void registerCancellationCallback( - IBiometricConfirmDeviceCredentialCallback callback) { - // TODO(b/123378871): Remove when moved. - // This callback replaces the one stored in the current session. If the session is null - // we can ignore this, since it means ConfirmDeviceCredential was launched by something - // else (not BiometricPrompt) - checkInternalPermission(); - - mHandler.obtainMessage(MSG_REGISTER_CANCELLATION_CALLBACK, callback).sendToTarget(); - } - - @Override // Binder call public void cancelAuthentication(IBinder token, String opPackageName) throws RemoteException { checkPermission(); @@ -972,8 +847,8 @@ public class BiometricService extends SystemService { // Cache the authenticators for (int featureId : FEATURE_ID) { if (hasFeature(featureId)) { - Authenticator authenticator = - new Authenticator(featureId, getAuthenticator(featureId)); + AuthenticatorWrapper authenticator = + new AuthenticatorWrapper(featureId, getAuthenticator(featureId)); mAuthenticators.add(authenticator); } } @@ -1011,7 +886,7 @@ public class BiometricService extends SystemService { int modality = TYPE_NONE; int firstHwAvailable = TYPE_NONE; - for (Authenticator authenticatorWrapper : mAuthenticators) { + for (AuthenticatorWrapper authenticatorWrapper : mAuthenticators) { modality = authenticatorWrapper.getType(); BiometricAuthenticator authenticator = authenticatorWrapper.getAuthenticator(); if (authenticator.isHardwareDetected()) { @@ -1108,7 +983,7 @@ public class BiometricService extends SystemService { } private void logDialogDismissed(int reason) { - if (reason == BiometricPrompt.DISMISSED_REASON_CONFIRMED) { + if (reason == BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED) { // Explicit auth, authentication confirmed. // Latency in this case is authenticated -> confirmed. <Biometric>Service // should have the first half (first acquired -> authenticated). @@ -1254,49 +1129,6 @@ public class BiometricService extends SystemService { } } - private void handleOnConfirmDeviceCredentialSuccess() { - if (mConfirmDeviceCredentialReceiver == null) { - Slog.w(TAG, "handleOnConfirmDeviceCredentialSuccess null!"); - return; - } - try { - mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded(); - if (mCurrentAuthSession != null) { - mCurrentAuthSession = null; - } - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException", e); - } - mConfirmDeviceCredentialReceiver = null; - } - - private void handleOnConfirmDeviceCredentialError(int error, String message) { - if (mConfirmDeviceCredentialReceiver == null) { - Slog.w(TAG, "handleOnConfirmDeviceCredentialError null! Error: " - + error + " " + message); - return; - } - try { - mConfirmDeviceCredentialReceiver.onError(error, message); - if (mCurrentAuthSession != null) { - mCurrentAuthSession = null; - } - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException", e); - } - mConfirmDeviceCredentialReceiver = null; - } - - private void handleRegisterCancellationCallback( - IBiometricConfirmDeviceCredentialCallback callback) { - if (mCurrentAuthSession == null) { - Slog.d(TAG, "Current auth session null"); - return; - } - Slog.d(TAG, "Updating cancel callback"); - mCurrentAuthSession.mConfirmDeviceCredentialCallback = callback; - } - private void handleOnError(int cookie, int error, String message) { Slog.d(TAG, "handleOnError: " + error + " cookie: " + cookie); // Errors can either be from the current auth session or the pending auth session. @@ -1307,34 +1139,34 @@ public class BiometricService extends SystemService { // of their intended receivers. try { if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) { - mCurrentAuthSession.mErrorEscrow = error; mCurrentAuthSession.mErrorStringEscrow = message; - if (mCurrentAuthSession.isFromConfirmDeviceCredential()) { - // If we were invoked by ConfirmDeviceCredential, do not delete the current - // auth session since we still need to respond to cancel signal while - if (DEBUG) Slog.d(TAG, "From CDC, transition to CANCELED_SHOWING_CDC state"); - - // Send the error to ConfirmDeviceCredential so that it goes to Pin/Pattern/Pass - // screen - mCurrentAuthSession.mClientReceiver.onError(error, message); - mCurrentAuthSession.mState = STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC; - mStatusBarService.hideBiometricDialog(); - } else if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) { - mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI; - if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { - mStatusBarService.hideBiometricDialog(); + if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) { + final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT + || error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; + if (mCurrentAuthSession.isAllowDeviceCredential() && errorLockout) { + // SystemUI handles transition from biometric to device credential. + mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL; + mStatusBarService.onBiometricError(error, message); } else { - mStatusBarService.onBiometricError(message); + mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI; + if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { + mStatusBarService.hideAuthenticationDialog(); + } else { + mStatusBarService.onBiometricError(error, message); + } } } 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); - mStatusBarService.hideBiometricDialog(); + mStatusBarService.hideAuthenticationDialog(); mCurrentAuthSession = null; + } else if (mCurrentAuthSession.mState == STATE_SHOWING_DEVICE_CREDENTIAL) { + Slog.d(TAG, "Biometric canceled, ignoring from state: " + + mCurrentAuthSession.mState); } else { Slog.e(TAG, "Impossible session error state: " + mCurrentAuthSession.mState); @@ -1342,12 +1174,38 @@ public class BiometricService extends SystemService { } else if (mPendingAuthSession != null && mPendingAuthSession.containsCookie(cookie)) { if (mPendingAuthSession.mState == STATE_AUTH_CALLED) { - mPendingAuthSession.mClientReceiver.onError(error, message); - mPendingAuthSession = null; + // If any error is received while preparing the auth session (lockout, etc), + // and if device credential is allowed, just show the credential UI. + if (mPendingAuthSession.isAllowDeviceCredential()) { + int authenticators = mPendingAuthSession.mBundle + .getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0); + // Disallow biometric and notify SystemUI to show the authentication prompt. + authenticators &= ~Authenticator.TYPE_BIOMETRIC; + mPendingAuthSession.mBundle.putInt( + BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, + authenticators); + + mCurrentAuthSession = mPendingAuthSession; + mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL; + mPendingAuthSession = null; + + mStatusBarService.showAuthenticationDialog( + mCurrentAuthSession.mBundle, + mInternalReceiver, + 0 /* biometricModality */, + false /* requireConfirmation */, + mCurrentAuthSession.mUserId, + mCurrentAuthSession.mOpPackageName); + } else { + mPendingAuthSession.mClientReceiver.onError(error, message); + mPendingAuthSession = null; + } } else { Slog.e(TAG, "Impossible pending session error state: " + mPendingAuthSession.mState); } + } else { + Slog.e(TAG, "Unknown cookie: " + cookie); } } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); @@ -1385,9 +1243,12 @@ public class BiometricService extends SystemService { try { switch (reason) { - case BiometricPrompt.DISMISSED_REASON_CONFIRMED: - case BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED: - mKeyStore.addAuthToken(mCurrentAuthSession.mTokenEscrow); + case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED: + case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED: + case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED: + if (mCurrentAuthSession.mTokenEscrow != null) { + mKeyStore.addAuthToken(mCurrentAuthSession.mTokenEscrow); + } mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); break; @@ -1439,12 +1300,37 @@ public class BiometricService extends SystemService { mCurrentAuthSession.mCallingUid, mCurrentAuthSession.mCallingPid, mCurrentAuthSession.mCallingUserId, - mCurrentAuthSession.mModality, - mCurrentAuthSession.mConfirmDeviceCredentialCallback); + mCurrentAuthSession.mModality); } + private void handleOnDeviceCredentialPressed() { + Slog.d(TAG, "onDeviceCredentialPressed"); + if (mCurrentAuthSession == null) { + Slog.e(TAG, "Auth session null"); + return; + } + + // 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 */); + + mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL; + } + + /** + * Invoked when each service has notified that its client is ready to be started. When + * all biometrics are ready, this invokes the SystemUI dialog through StatusBar. + */ private void handleOnReadyForAuthentication(int cookie, boolean requireConfirmation, int userId) { + if (mPendingAuthSession == null) { + // Only should happen if a biometric was locked out when authenticate() was invoked. + // In that case, if device credentials are allowed, the UI is already showing. If not + // allowed, the error has already been returned to the caller. + Slog.w(TAG, "Pending auth session null"); + return; + } + Iterator it = mPendingAuthSession.mModalitiesWaiting.entrySet().iterator(); while (it.hasNext()) { Map.Entry<Integer, Integer> pair = (Map.Entry) it.next(); @@ -1486,7 +1372,7 @@ public class BiometricService extends SystemService { } if (!continuing) { - mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle, + mStatusBarService.showAuthenticationDialog(mCurrentAuthSession.mBundle, mInternalReceiver, modality, requireConfirmation, userId, mCurrentAuthSession.mOpPackageName); } @@ -1498,16 +1384,21 @@ public class BiometricService extends SystemService { private void handleAuthenticate(IBinder token, long sessionId, int userId, IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, - int callingUid, int callingPid, int callingUserId, - IBiometricConfirmDeviceCredentialCallback callback) { + int callingUid, int callingPid, int callingUserId) { mHandler.post(() -> { final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId); final int modality = result.first; final int error = result.second; - // Check for errors, notify callback, and return - if (error != BiometricConstants.BIOMETRIC_SUCCESS) { + final boolean credentialAllowed = Utils.isDeviceCredentialAllowed(bundle); + + if (error != BiometricConstants.BIOMETRIC_SUCCESS && credentialAllowed) { + // If there's a problem but device credential is allowed, only show credential UI. + bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, + Authenticator.TYPE_CREDENTIAL); + } 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); @@ -1535,7 +1426,7 @@ public class BiometricService extends SystemService { // Start preparing for authentication. Authentication starts when // all modalities requested have invoked onReadyForAuthentication. authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, - callingUid, callingPid, callingUserId, modality, callback); + callingUid, callingPid, callingUserId, modality); }); } @@ -1550,8 +1441,7 @@ public class BiometricService extends SystemService { */ private void authenticateInternal(IBinder token, long sessionId, int userId, IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, - int callingUid, int callingPid, int callingUserId, int modality, - IBiometricConfirmDeviceCredentialCallback callback) { + int callingUid, int callingPid, int callingUserId, int modality) { try { boolean requireConfirmation = bundle.getBoolean( BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */); @@ -1565,27 +1455,49 @@ public class BiometricService extends SystemService { // 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); - final HashMap<Integer, Integer> authenticators = new HashMap<>(); - authenticators.put(modality, cookie); - mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId, - receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, - modality, requireConfirmation, callback); - 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"); + + ", 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); } - if ((modality & TYPE_FACE) != 0) { - mFaceService.prepareForAuthentication(requireConfirmation, - token, sessionId, userId, mInternalReceiver, opPackageName, - cookie, callingUid, callingPid, callingUserId); + 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; + mPendingAuthSession = null; + + mStatusBarService.showAuthenticationDialog( + mCurrentAuthSession.mBundle, + mInternalReceiver, + 0 /* biometricModality */, + false /* requireConfirmation */, + mCurrentAuthSession.mUserId, + 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); + } } } catch (RemoteException e) { Slog.e(TAG, "Unable to start authentication", e); @@ -1598,20 +1510,7 @@ public class BiometricService extends SystemService { return; } - if (mCurrentAuthSession != null - && mCurrentAuthSession.mState == STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC) { - if (DEBUG) Slog.d(TAG, "Cancel received while ConfirmDeviceCredential showing"); - try { - mCurrentAuthSession.mConfirmDeviceCredentialCallback.cancel(); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to cancel ConfirmDeviceCredential", e); - } - - // TODO(b/123378871): Remove when moved. Piggy back on this for now to clean up. - handleOnConfirmDeviceCredentialError(BiometricConstants.BIOMETRIC_ERROR_CANCELED, - getContext().getString(R.string.biometric_error_canceled)); - } else if (mCurrentAuthSession != null - && mCurrentAuthSession.mState != STATE_AUTH_STARTED) { + if (mCurrentAuthSession != null && mCurrentAuthSession.mState != STATE_AUTH_STARTED) { // We need to check the current authenticators state. If we're pending confirm // or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client, // since we won't be getting an onError from the driver. @@ -1624,24 +1523,12 @@ public class BiometricService extends SystemService { ); mCurrentAuthSession = null; - mStatusBarService.hideBiometricDialog(); + mStatusBarService.hideAuthenticationDialog(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } } else { - boolean fromCDC = false; - if (mCurrentAuthSession != null) { - fromCDC = mCurrentAuthSession.mBundle.getBoolean( - BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false); - } - - if (fromCDC) { - if (DEBUG) Slog.d(TAG, "Cancelling from CDC"); - cancelInternal(token, opPackageName, false /* fromClient */); - } else { - cancelInternal(token, opPackageName, true /* fromClient */); - } - + cancelInternal(token, opPackageName, true /* fromClient */); } } diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 4fa29ac541f9..ed5f9de01bcf 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -17,10 +17,15 @@ package com.android.server.biometrics; import android.content.Context; +import android.hardware.biometrics.Authenticator; +import android.hardware.biometrics.BiometricPrompt; import android.os.Build; +import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; +import com.android.internal.annotations.VisibleForTesting; + public class Utils { public static boolean isDebugEnabled(Context context, int targetUserId) { if (targetUserId == UserHandle.USER_NULL) { @@ -38,4 +43,43 @@ public class Utils { } return true; } + + /** + * Combine {@link BiometricPrompt#KEY_ALLOW_DEVICE_CREDENTIAL} with + * {@link BiometricPrompt#KEY_AUTHENTICATORS_ALLOWED}, as the former is not flexible + * enough. + */ + public static void combineAuthenticatorBundles(Bundle bundle) { + boolean biometricEnabled = true; // enabled by default + boolean credentialEnabled = bundle.getBoolean( + BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false); + if (bundle.get(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED) != null) { + final int authenticatorFlags = + bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED); + biometricEnabled = (authenticatorFlags & Authenticator.TYPE_BIOMETRIC) != 0; + // Using both KEY_ALLOW_DEVICE_CREDENTIAL and KEY_AUTHENTICATORS_ALLOWED together + // is not supported. Default to overwriting. + credentialEnabled = (authenticatorFlags & Authenticator.TYPE_CREDENTIAL) != 0; + } + + bundle.remove(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL); + + int authenticators = 0; + if (biometricEnabled) { + authenticators |= Authenticator.TYPE_BIOMETRIC; + } + if (credentialEnabled) { + authenticators |= Authenticator.TYPE_CREDENTIAL; + } + bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); + } + + /** + * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)} + * @return true if device credential allowed. + */ + public static boolean isDeviceCredentialAllowed(Bundle bundle) { + final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED); + return (authenticators & Authenticator.TYPE_CREDENTIAL) != 0; + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 4f4baab204e6..61d4d4bf9ac1 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -850,36 +850,25 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { mAddress, Constants.ADDR_BROADCAST, systemAudioStatusOn)); if (systemAudioStatusOn) { + // If TV sends out SAM Request with a path of a non-CEC device, which should not show + // up in the CEC device list and not under the current AVR device, the AVR would switch + // to ARC. int sourcePhysicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - if (sourcePhysicalAddress != getActiveSource().physicalAddress) { - // If the Active Source recorded by the current device is not synced up with TV, - // update the Active Source internally. - if (sourcePhysicalAddress == mService.getPhysicalAddress()) { - // If the active path is the current device itself, update with local info - if (mService.playback() != null) { - setActiveSource(mService.playback().mAddress, sourcePhysicalAddress); - } else { - setActiveSource(mAddress, sourcePhysicalAddress); - } - } else { - // If it's not the current device, look for the device info from the list - for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) { - if (info.getPhysicalAddress() == sourcePhysicalAddress) { - setActiveSource(info.getLogicalAddress(), info.getPhysicalAddress()); - break; - } - } - } - // If the Active path from TV's System Audio Mode request does not belong to any - // device in the local device list, record the new Active physicalAddress with an - // unregistered logical address first. Then query the Active Source again. - if (sourcePhysicalAddress != getActiveSource().physicalAddress) { - setActiveSource(Constants.ADDR_UNREGISTERED, sourcePhysicalAddress); - mService.sendCecCommand( - HdmiCecMessageBuilder.buildRequestActiveSource(mAddress)); + if (HdmiUtils.getLocalPortFromPhysicalAddress( + sourcePhysicalAddress, getDeviceInfo().getPhysicalAddress()) + != HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) { + return true; + } + boolean isDeviceInCecDeviceList = false; + for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) { + if (info.getPhysicalAddress() == sourcePhysicalAddress) { + isDeviceInCecDeviceList = true; + break; } } - switchInputOnReceivingNewActivePath(sourcePhysicalAddress); + if (!isDeviceInCecDeviceList) { + switchInputOnReceivingNewActivePath(sourcePhysicalAddress); + } } return true; } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index c3354e1d7e0f..1794df3b602e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -311,6 +311,11 @@ public class HdmiControlService extends SystemService { private IHdmiControlCallback mDisplayStatusCallback = null; @Nullable + // Save callback when the device is still under logcial address allocation + // Invoke once new local device is ready. + private IHdmiControlCallback mOtpCallbackPendingAddressAllocation = null; + + @Nullable private HdmiCecController mCecController; // HDMI port information. Stored in the unmodifiable list to keep the static information @@ -785,17 +790,21 @@ public class HdmiControlService extends SystemService { // Address allocation completed for all devices. Notify each device. if (allocatingDevices.size() == ++finished[0]) { mAddressAllocated = true; - // Reinvoke the saved display status callback once the local device is ready. - if (mDisplayStatusCallback != null) { - queryDisplayStatus(mDisplayStatusCallback); - mDisplayStatusCallback = null; - } if (initiatedBy != INITIATED_BY_HOTPLUG) { // In case of the hotplug we don't call onInitializeCecComplete() // since we reallocate the logical address only. onInitializeCecComplete(initiatedBy); } notifyAddressAllocated(allocatedDevices, initiatedBy); + // Reinvoke the saved display status callback once the local device is ready. + if (mDisplayStatusCallback != null) { + queryDisplayStatus(mDisplayStatusCallback); + mDisplayStatusCallback = null; + } + if (mOtpCallbackPendingAddressAllocation != null) { + oneTouchPlay(mOtpCallbackPendingAddressAllocation); + mOtpCallbackPendingAddressAllocation = null; + } mCecMessageBuffer.processMessages(); } } @@ -2246,8 +2255,16 @@ public class HdmiControlService extends SystemService { } @ServiceThreadOnly - private void oneTouchPlay(final IHdmiControlCallback callback) { + @VisibleForTesting + protected void oneTouchPlay(final IHdmiControlCallback callback) { assertRunOnServiceThread(); + if (!mAddressAllocated) { + mOtpCallbackPendingAddressAllocation = callback; + Slog.d(TAG, "Local device is under address allocation. " + + "Save OTP callback for later process."); + return; + } + HdmiCecLocalDeviceSource source = playback(); if (source == null) { source = audioSystem(); diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java index c8fc5fc96e59..4962af176f18 100644 --- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java +++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java @@ -77,7 +77,6 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress)); broadcastActiveSource(); queryDevicePowerStatus(); - mState = STATE_WAITING_FOR_REPORT_POWER_STATUS; addTimer(mState, HdmiConfig.TIMEOUT_MS); return true; } @@ -99,6 +98,7 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { } private void queryDevicePowerStatus() { + mState = STATE_WAITING_FOR_REPORT_POWER_STATUS; sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), mTargetAddress)); } diff --git a/services/core/java/com/android/server/locksettings/TEST_MAPPING b/services/core/java/com/android/server/locksettings/TEST_MAPPING index c1cba5f7f22d..56f5cc034f05 100644 --- a/services/core/java/com/android/server/locksettings/TEST_MAPPING +++ b/services/core/java/com/android/server/locksettings/TEST_MAPPING @@ -1,5 +1,5 @@ { - "presubmit": [ + "presubmit-devicepolicy": [ { "name": "CtsDevicePolicyManagerTestCases", "options": [ diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index a8ba82a74065..3341991aa0ae 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -94,7 +94,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER; -import static com.android.server.notification.PreferencesHelper.DEFAULT_ALLOW_BUBBLE; import static com.android.server.utils.PriorityDump.PRIORITY_ARG; import static com.android.server.utils.PriorityDump.PRIORITY_ARG_CRITICAL; import static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL; @@ -1428,10 +1427,8 @@ public class NotificationManagerService extends SystemService { private final class SettingsObserver extends ContentObserver { private final Uri NOTIFICATION_BADGING_URI = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING); - private final Uri NOTIFICATION_BUBBLES_URI_GLOBAL + private final Uri NOTIFICATION_BUBBLES_URI = Settings.Global.getUriFor(Settings.Global.NOTIFICATION_BUBBLES); - private final Uri NOTIFICATION_BUBBLES_URI_SECURE - = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES); private final Uri NOTIFICATION_LIGHT_PULSE_URI = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE); private final Uri NOTIFICATION_RATE_LIMIT_URI @@ -1449,9 +1446,7 @@ public class NotificationManagerService extends SystemService { false, this, UserHandle.USER_ALL); resolver.registerContentObserver(NOTIFICATION_RATE_LIMIT_URI, false, this, UserHandle.USER_ALL); - resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI_GLOBAL, - false, this, UserHandle.USER_ALL); - resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI_SECURE, + resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI, false, this, UserHandle.USER_ALL); update(null); } @@ -1478,41 +1473,9 @@ public class NotificationManagerService extends SystemService { if (uri == null || NOTIFICATION_BADGING_URI.equals(uri)) { mPreferencesHelper.updateBadgingEnabled(); } - // In QPR we moved the setting to Global rather than Secure so that the setting - // applied to work profiles. Unfortunately we need to maintain both to pass CTS without - // a change to CTS outside of a normal letter release. - if (uri == null || NOTIFICATION_BUBBLES_URI_GLOBAL.equals(uri)) { - syncBubbleSettings(resolver, NOTIFICATION_BUBBLES_URI_GLOBAL); + if (uri == null || NOTIFICATION_BUBBLES_URI.equals(uri)) { mPreferencesHelper.updateBubblesEnabled(); } - if (NOTIFICATION_BUBBLES_URI_SECURE.equals(uri)) { - syncBubbleSettings(resolver, NOTIFICATION_BUBBLES_URI_SECURE); - } - } - - private void syncBubbleSettings(ContentResolver resolver, Uri settingToFollow) { - boolean followSecureSetting = settingToFollow.equals(NOTIFICATION_BUBBLES_URI_SECURE); - - int secureSettingValue = Settings.Secure.getInt(resolver, - Settings.Secure.NOTIFICATION_BUBBLES, DEFAULT_ALLOW_BUBBLE ? 1 : 0); - int globalSettingValue = Settings.Global.getInt(resolver, - Settings.Global.NOTIFICATION_BUBBLES, DEFAULT_ALLOW_BUBBLE ? 1 : 0); - - if (globalSettingValue == secureSettingValue) { - return; - } - - if (followSecureSetting) { - // Global => secure - Settings.Global.putInt(resolver, - Settings.Global.NOTIFICATION_BUBBLES, - secureSettingValue); - } else { - // Secure => Global - Settings.Secure.putInt(resolver, - Settings.Secure.NOTIFICATION_BADGING, - globalSettingValue); - } } } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 83bea9dd1efc..9e7b46485d4c 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -738,6 +738,25 @@ public final class OverlayManagerService extends SystemService { } @Override + public void invalidateCachesForOverlay(@Nullable String packageName, int userId) + throws RemoteException { + if (packageName == null) { + return; + } + + enforceChangeOverlayPackagesPermission("invalidateCachesForOverlay"); + userId = handleIncomingUser(userId, "invalidateCachesForOverlay"); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + mImpl.removeIdmapForOverlay(packageName, userId); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public void onShellCommand(@NonNull final FileDescriptor in, @NonNull final FileDescriptor out, @NonNull final FileDescriptor err, @NonNull final String[] args, @NonNull final ShellCallback callback, diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 934511bf88d1..019c9528f8ab 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -651,6 +651,11 @@ final class OverlayManagerServiceImpl { return mDefaultOverlays; } + void removeIdmapForOverlay(String packageName, int userId) { + final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId); + removeIdmapIfPossible(oi); + } + List<String> getEnabledOverlayPackageNames(@NonNull final String targetPackageName, final int userId) { final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName, diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 8897eca85d7a..3439d3841973 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -609,13 +609,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, - int type, boolean requireConfirmation, int userId, String opPackageName) { + public void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, + int biometricModality, boolean requireConfirmation, int userId, String opPackageName) { enforceBiometricDialog(); if (mBar != null) { try { - mBar.showBiometricDialog(bundle, receiver, type, requireConfirmation, userId, - opPackageName); + mBar.showAuthenticationDialog(bundle, receiver, biometricModality, + requireConfirmation, userId, opPackageName); } catch (RemoteException ex) { } } @@ -644,22 +644,22 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void onBiometricError(String error) { + public void onBiometricError(int errorCode, String error) { enforceBiometricDialog(); if (mBar != null) { try { - mBar.onBiometricError(error); + mBar.onBiometricError(errorCode, error); } catch (RemoteException ex) { } } } @Override - public void hideBiometricDialog() { + public void hideAuthenticationDialog() { enforceBiometricDialog(); if (mBar != null) { try { - mBar.hideBiometricDialog(); + mBar.hideAuthenticationDialog(); } catch (RemoteException ex) { } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c3ff28530b7f..b118cdfeb84d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -11902,10 +11902,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { try { int uid = packageManager.getPackageUidAsUser(packageName, user.getIdentifier()); - - // TODO: Prevent noting the app-op - granted = PermissionChecker.checkPermission(mContext, permission, -1, - uid, packageName); + if (PermissionChecker.checkPermissionForPreflight(mContext, permission, + PermissionChecker.PID_UNKNOWN, uid, packageName) + != PermissionChecker.PERMISSION_GRANTED) { + granted = PackageManager.PERMISSION_DENIED; + } else { + granted = PackageManager.PERMISSION_GRANTED; + } } catch (NameNotFoundException e) { throw new RemoteException( "Cannot check if " + permission + "is a runtime permission", e, diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 1685b04cf2ca..8b3c85e8a7f3 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -82,6 +82,7 @@ import android.os.Build; import android.os.IBinder; import android.os.PowerManagerInternal; import android.os.SystemClock; +import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; @@ -103,6 +104,7 @@ import java.util.ArrayList; * Build/Install/Run: * atest MockingOomAdjusterTests */ +@Presubmit public class MockingOomAdjusterTests { private static final int MOCKAPP_PID = 12345; private static final int MOCKAPP_UID = 12345; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index 73dcb98abc8c..43f251a9281f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -46,6 +46,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID; import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -140,6 +141,8 @@ public class AbstractAccessibilityServiceConnectionTest { private final List<AccessibilityWindowInfo> mA11yWindowInfosOnSecondDisplay = new ArrayList<>(); private Callable[] mFindA11yNodesFunctions; private Callable<Boolean> mPerformA11yAction; + private ArrayList<Integer> mDisplayList = new ArrayList<>(Arrays.asList( + Display.DEFAULT_DISPLAY, SECONDARY_DISPLAY_ID)); // To mock package-private class. @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = @@ -184,6 +187,7 @@ public class AbstractAccessibilityServiceConnectionTest { addA11yWindowInfo(mA11yWindowInfos, PIP_WINDOWID, true, Display.DEFAULT_DISPLAY); addA11yWindowInfo(mA11yWindowInfosOnSecondDisplay, WINDOWID_ONSECONDDISPLAY, false, SECONDARY_DISPLAY_ID); + when(mMockA11yWindowManager.getDisplayListLocked()).thenReturn(mDisplayList); when(mMockA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)) .thenReturn(mA11yWindowInfos); when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID)) @@ -285,7 +289,14 @@ public class AbstractAccessibilityServiceConnectionTest { @Test public void getWindows() { - assertThat(mServiceConnection.getWindows(), is(mA11yWindowInfos)); + final AccessibilityWindowInfo.WindowListSparseArray allWindows = + mServiceConnection.getWindows(); + + assertEquals(2, allWindows.size()); + assertThat(allWindows.get(Display.DEFAULT_DISPLAY), is(mA11yWindowInfos)); + assertEquals(2, allWindows.get(Display.DEFAULT_DISPLAY).size()); + assertThat(allWindows.get(SECONDARY_DISPLAY_ID), is(mA11yWindowInfosOnSecondDisplay)); + assertEquals(1, allWindows.get(SECONDARY_DISPLAY_ID).size()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java index b7b5a4eaacfa..b5e5deb9ff59 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java @@ -67,6 +67,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -104,6 +105,9 @@ public class AccessibilityWindowManagerTest { // List of callback, mapping from displayId -> callback. private final SparseArray<WindowsForAccessibilityCallback> mCallbackOfWindows = new SparseArray<>(); + // List of display ID. + private final ArrayList<Integer> mExpectedDisplayList = new ArrayList<>(Arrays.asList( + Display.DEFAULT_DISPLAY, SECONDARY_DISPLAY_ID)); private final MessageCapturingHandler mHandler = new MessageCapturingHandler(null); @@ -692,6 +696,15 @@ public class AccessibilityWindowManagerTest { assertTrue(windowId >= 0); } + @Test + public void getDisplayList() throws RemoteException { + // Starts tracking window of second display. + startTrackingPerDisplay(SECONDARY_DISPLAY_ID); + + final ArrayList<Integer> displayList = mA11yWindowManager.getDisplayListLocked(); + assertTrue(displayList.equals(mExpectedDisplayList)); + } + private void startTrackingPerDisplay(int displayId) throws RemoteException { ArrayList<WindowInfo> windowInfosForDisplay = new ArrayList<>(); // Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy 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 ccf3a908364a..4aeeb0af1bf8 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -39,6 +39,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.hardware.biometrics.Authenticator; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; @@ -55,6 +56,9 @@ import android.os.Handler; import android.os.IBinder; import android.security.KeyStore; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + import com.android.internal.R; import com.android.internal.statusbar.IStatusBarService; @@ -66,9 +70,6 @@ import org.mockito.MockitoAnnotations; import java.util.List; -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; - @SmallTest public class BiometricServiceTest { @@ -82,6 +83,7 @@ public class BiometricServiceTest { private static final String ERROR_CANCELED = "error_canceled"; private static final String ERROR_UNABLE_TO_PROCESS = "error_unable_to_process"; private static final String ERROR_USER_CANCELED = "error_user_canceled"; + private static final String ERROR_LOCKOUT = "error_lockout"; private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty"; @@ -179,7 +181,8 @@ public class BiometricServiceTest { mBiometricService = new BiometricService(mContext, new MockInjector()); mBiometricService.onStart(); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + false /* allowDeviceCredential */); waitForIdle(); verify(mReceiver1).onError( eq(BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT), eq(ERROR_HW_UNAVAILABLE)); @@ -193,7 +196,8 @@ public class BiometricServiceTest { mBiometricService = new BiometricService(mContext, new MockInjector()); mBiometricService.onStart(); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + false /* allowDeviceCredential */); waitForIdle(); verify(mReceiver1).onError( eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS), any()); @@ -208,7 +212,8 @@ public class BiometricServiceTest { mBiometricService = new BiometricService(mContext, new MockInjector()); mBiometricService.onStart(); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + false /* allowDeviceCredential */); waitForIdle(); verify(mReceiver1).onError( eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(ERROR_HW_UNAVAILABLE)); @@ -226,7 +231,8 @@ public class BiometricServiceTest { // Disabled in user settings receives onError when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(false); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + false /* allowDeviceCredential */); waitForIdle(); verify(mReceiver1).onError( eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(ERROR_HW_UNAVAILABLE)); @@ -236,7 +242,8 @@ public class BiometricServiceTest { when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true); when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt())) .thenReturn(true); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + false /* allowDeviceCredential */); waitForIdle(); verify(mReceiver1, never()).onError(anyInt(), any(String.class)); verify(mBiometricService.mFaceService).prepareForAuthentication( @@ -255,7 +262,8 @@ public class BiometricServiceTest { resetReceiver(); when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt())) .thenReturn(false); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + false /* allowDeviceCredential */); waitForIdle(); verify(mBiometricService.mFaceService).prepareForAuthentication( eq(false) /* requireConfirmation */, @@ -277,7 +285,8 @@ public class BiometricServiceTest { mBiometricService.onStart(); // Start testing the happy path - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + false /* allowDeviceCredential */); waitForIdle(); // Creates a pending auth session with the correct initial states @@ -311,7 +320,7 @@ public class BiometricServiceTest { .startPreparedClient(cookieCaptor.getValue()); // StatusBar showBiometricDialog invoked - verify(mBiometricService.mStatusBarService).showBiometricDialog( + verify(mBiometricService.mStatusBarService).showAuthenticationDialog( eq(mBiometricService.mCurrentAuthSession.mBundle), any(IBiometricServiceReceiverInternal.class), eq(BiometricAuthenticator.TYPE_FINGERPRINT), @@ -333,7 +342,7 @@ public class BiometricServiceTest { // SystemUI sends callback with dismissed reason mBiometricService.mInternalReceiver.onDialogDismissed( - BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED); + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED); waitForIdle(); // HAT sent to keystore verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class)); @@ -344,10 +353,32 @@ public class BiometricServiceTest { } @Test + public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, + true /* requireConfirmation */, true /* allowDeviceCredential */); + waitForIdle(); + + assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL, + mBiometricService.mCurrentAuthSession.mState); + assertEquals(Authenticator.TYPE_CREDENTIAL, + mBiometricService.mCurrentAuthSession.mBundle + .getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)); + verify(mBiometricService.mStatusBarService).showAuthenticationDialog( + eq(mBiometricService.mCurrentAuthSession.mBundle), + any(IBiometricServiceReceiverInternal.class), + eq(0 /* biometricModality */), + anyBoolean() /* requireConfirmation */, + anyInt() /* userId */, + eq(TEST_PACKAGE_NAME)); + } + + @Test public void testAuthenticate_happyPathWithConfirmation() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, - true /* requireConfirmation */); + true /* requireConfirmation */, false /* allowDeviceCredential */); // Test authentication succeeded goes to PENDING_CONFIRMATION and that the HAT is not // sent to KeyStore yet @@ -362,7 +393,7 @@ public class BiometricServiceTest { // SystemUI sends confirm, HAT is sent to keystore and client is notified. mBiometricService.mInternalReceiver.onDialogDismissed( - BiometricPrompt.DISMISSED_REASON_CONFIRMED); + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED); waitForIdle(); verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class)); verify(mReceiver1).onAuthenticationSucceeded(); @@ -373,7 +404,7 @@ public class BiometricServiceTest { throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */); + false /* requireConfirmation */, false /* allowDeviceCredential */); mBiometricService.mInternalReceiver.onAuthenticationFailed(); waitForIdle(); @@ -390,7 +421,7 @@ public class BiometricServiceTest { throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */); + false /* requireConfirmation */, false /* allowDeviceCredential */); mBiometricService.mInternalReceiver.onAuthenticationFailed(); waitForIdle(); @@ -406,13 +437,14 @@ public class BiometricServiceTest { public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */); + false /* requireConfirmation */, false /* allowDeviceCredential */); // Create a new pending auth session but don't start it yet. HAL contract is that previous // one must get ERROR_CANCELED. Simulate that here by creating the pending auth session, // sending ERROR_CANCELED to the current auth session, and then having the second one // onReadyForAuthentication. - invokeAuthenticate(mBiometricService.mImpl, mReceiver2, false /* requireConfirmation */); + invokeAuthenticate(mBiometricService.mImpl, mReceiver2, false /* requireConfirmation */, + false /* allowDeviceCredential */); waitForIdle(); assertEquals(mBiometricService.mCurrentAuthSession.mState, @@ -430,7 +462,7 @@ public class BiometricServiceTest { verify(mReceiver2, never()).onError(anyInt(), any(String.class)); // SystemUI dialog closed - verify(mBiometricService.mStatusBarService).hideBiometricDialog(); + verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(); // After SystemUI notifies that the animation has completed mBiometricService.mInternalReceiver @@ -446,7 +478,7 @@ public class BiometricServiceTest { public void testErrorHalTimeout_whenAuthenticating_entersPausedState() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */); + false /* requireConfirmation */, false /* allowDeviceCredential */); mBiometricService.mInternalReceiver.onError( getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), @@ -479,7 +511,7 @@ public class BiometricServiceTest { resetStatusBar(); startPendingAuthSession(mBiometricService); waitForIdle(); - verify(mBiometricService.mStatusBarService, never()).showBiometricDialog( + verify(mBiometricService.mStatusBarService, never()).showAuthenticationDialog( any(Bundle.class), any(IBiometricServiceReceiverInternal.class), anyInt(), @@ -492,7 +524,7 @@ public class BiometricServiceTest { public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, - false /* requireCOnfirmation */); + false /* requireCOnfirmation */, false /* allowDeviceCredential */); mBiometricService.mInternalReceiver.onError( getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), @@ -509,7 +541,7 @@ public class BiometricServiceTest { eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED), eq(ERROR_CANCELED)); // Dialog is hidden immediately - verify(mBiometricService.mStatusBarService).hideBiometricDialog(); + verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(); // Auth session is over assertNull(mBiometricService.mCurrentAuthSession); } @@ -522,7 +554,7 @@ public class BiometricServiceTest { // session is done. setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */); + false /* requireConfirmation */, false /* allowDeviceCredential */); mBiometricService.mInternalReceiver.onError( getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), @@ -533,9 +565,10 @@ public class BiometricServiceTest { // Sends error to SystemUI and does not notify client yet assertEquals(mBiometricService.mCurrentAuthSession.mState, BiometricService.STATE_ERROR_PENDING_SYSUI); - verify(mBiometricService.mStatusBarService) - .onBiometricError(eq(ERROR_UNABLE_TO_PROCESS)); - verify(mBiometricService.mStatusBarService, never()).hideBiometricDialog(); + verify(mBiometricService.mStatusBarService).onBiometricError( + eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS), + eq(ERROR_UNABLE_TO_PROCESS)); + verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog(); verify(mReceiver1, never()).onError(anyInt(), anyString()); // SystemUI animation completed, client is notified, auth session is over @@ -549,11 +582,177 @@ public class BiometricServiceTest { } @Test + public void testErrorFromHal_whilePreparingAuthentication_credentialAllowed() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */, true /* allowDeviceCredential */); + waitForIdle(); + + mBiometricService.mInternalReceiver.onError( + getCookieForPendingSession(mBiometricService.mPendingAuthSession), + BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, + ERROR_LOCKOUT); + waitForIdle(); + + // Pending auth session becomes current auth session, since device credential should + // be shown now. + assertNull(mBiometricService.mPendingAuthSession); + assertNotNull(mBiometricService.mCurrentAuthSession); + assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL, + mBiometricService.mCurrentAuthSession.mState); + assertEquals(Authenticator.TYPE_CREDENTIAL, + mBiometricService.mCurrentAuthSession.mBundle.getInt( + BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)); + verify(mBiometricService.mStatusBarService).showAuthenticationDialog( + eq(mBiometricService.mCurrentAuthSession.mBundle), + any(IBiometricServiceReceiverInternal.class), + eq(0 /* biometricModality */), + anyBoolean() /* requireConfirmation */, + anyInt() /* userId */, + eq(TEST_PACKAGE_NAME)); + } + + @Test + public void testErrorFromHal_whilePreparingAuthentication_credentialNotAllowed() + throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */, false /* allowDeviceCredential */); + waitForIdle(); + + mBiometricService.mInternalReceiver.onError( + getCookieForPendingSession(mBiometricService.mPendingAuthSession), + BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, + ERROR_LOCKOUT); + waitForIdle(); + + // Error is sent to client + assertNull(mBiometricService.mPendingAuthSession); + assertNull(mBiometricService.mCurrentAuthSession); + } + + @Test + public void testCombineAuthenticatorBundle_keyAllowDeviceCredentialAlwaysRemoved() { + Bundle bundle; + int authenticators; + + // In: + // KEY_ALLOW_DEVICE_CREDENTIAL = true + // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL + // Out: + // KEY_ALLOW_DEVICE_CREDENTIAL = null + // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL + bundle = new Bundle(); + bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true); + authenticators = Authenticator.TYPE_CREDENTIAL | Authenticator.TYPE_BIOMETRIC; + bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); + Utils.combineAuthenticatorBundles(bundle); + assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL)); + assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)); + + // In: + // KEY_ALLOW_DEVICE_CREDENTIAL = true + // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC + // Out: + // KEY_ALLOW_DEVICE_CREDENTIAL = null + // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL + bundle = new Bundle(); + bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true); + authenticators = Authenticator.TYPE_BIOMETRIC; + bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); + Utils.combineAuthenticatorBundles(bundle); + assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL)); + assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)); + + // In: + // KEY_ALLOW_DEVICE_CREDENTIAL = null + // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL + // Out: + // KEY_ALLOW_DEVICE_CREDENTIAL = null + // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL + bundle = new Bundle(); + authenticators = Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL; + bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); + Utils.combineAuthenticatorBundles(bundle); + assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL)); + assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)); + } + + @Test + public void testErrorFromHal_whileShowingDeviceCredential_doesntNotifySystemUI() + throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */, true /* allowDeviceCredential */); + + mBiometricService.mInternalReceiver.onDeviceCredentialPressed(); + waitForIdle(); + + assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL, + mBiometricService.mCurrentAuthSession.mState); + verify(mReceiver1, never()).onError(anyInt(), anyString()); + + mBiometricService.mInternalReceiver.onError( + getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + BiometricConstants.BIOMETRIC_ERROR_CANCELED, + ERROR_CANCELED); + waitForIdle(); + + assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL, + mBiometricService.mCurrentAuthSession.mState); + verify(mReceiver1, never()).onError(anyInt(), anyString()); + } + + @Test + public void testLockout_whileAuthenticating_credentialAllowed() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */, true /* allowDeviceCredential */); + + assertEquals(BiometricService.STATE_AUTH_STARTED, + mBiometricService.mCurrentAuthSession.mState); + + mBiometricService.mInternalReceiver.onError( + getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, + ERROR_LOCKOUT); + waitForIdle(); + + assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL, + mBiometricService.mCurrentAuthSession.mState); + verify(mBiometricService.mStatusBarService).onBiometricError( + eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT), + eq(ERROR_LOCKOUT)); + } + + @Test + public void testLockout_whenAuthenticating_credentialNotAllowed() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */, false /* allowDeviceCredential */); + + assertEquals(BiometricService.STATE_AUTH_STARTED, + mBiometricService.mCurrentAuthSession.mState); + + mBiometricService.mInternalReceiver.onError( + getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS, + ERROR_UNABLE_TO_PROCESS); + waitForIdle(); + + assertEquals(BiometricService.STATE_ERROR_PENDING_SYSUI, + mBiometricService.mCurrentAuthSession.mState); + verify(mBiometricService.mStatusBarService).onBiometricError( + eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS), + eq(ERROR_UNABLE_TO_PROCESS)); + } + + @Test public void testDismissedReasonUserCancel_whileAuthenticating_cancelsHalAuthentication() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */); + false /* requireConfirmation */, false /* allowDeviceCredential */); mBiometricService.mInternalReceiver .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); @@ -575,7 +774,7 @@ public class BiometricServiceTest { public void testDismissedReasonNegative_whilePaused_doesntInvokeHalCancel() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */); + false /* requireConfirmation */, false /* allowDeviceCredential */); mBiometricService.mInternalReceiver.onError( getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), @@ -598,7 +797,7 @@ public class BiometricServiceTest { public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */); + false /* requireConfirmation */, false /* allowDeviceCredential */); mBiometricService.mInternalReceiver.onError( getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), @@ -621,7 +820,7 @@ public class BiometricServiceTest { public void testDismissedReasonUserCancel_whenPendingConfirmation() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, - true /* requireConfirmation */); + true /* requireConfirmation */, false /* allowDeviceCredential */); mBiometricService.mInternalReceiver.onAuthenticationSucceeded( true /* requireConfirmation */, @@ -648,7 +847,7 @@ public class BiometricServiceTest { public void testAcquire_whenAuthenticating_sentToSystemUI() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */); + false /* requireConfirmation */, false /* allowDeviceCredential */); mBiometricService.mInternalReceiver.onAcquired( FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY, @@ -698,9 +897,10 @@ public class BiometricServiceTest { } private void invokeAuthenticateAndStart(IBiometricService.Stub service, - IBiometricServiceReceiver receiver, boolean requireConfirmation) throws Exception { + IBiometricServiceReceiver receiver, boolean requireConfirmation, + boolean allowDeviceCredential) throws Exception { // Request auth, creates a pending session - invokeAuthenticate(service, receiver, requireConfirmation); + invokeAuthenticate(service, receiver, requireConfirmation, allowDeviceCredential); waitForIdle(); startPendingAuthSession(mBiometricService); @@ -720,20 +920,25 @@ public class BiometricServiceTest { } private static void invokeAuthenticate(IBiometricService.Stub service, - IBiometricServiceReceiver receiver, boolean requireConfirmation) throws Exception { + IBiometricServiceReceiver receiver, boolean requireConfirmation, + boolean allowDeviceCredential) throws Exception { service.authenticate( new Binder() /* token */, 0 /* sessionId */, 0 /* userId */, receiver, TEST_PACKAGE_NAME /* packageName */, - createTestBiometricPromptBundle(requireConfirmation), - null /* IBiometricConfirmDeviceCredentialCallback */); + createTestBiometricPromptBundle(requireConfirmation, allowDeviceCredential)); } - private static Bundle createTestBiometricPromptBundle(boolean requireConfirmation) { + private static Bundle createTestBiometricPromptBundle(boolean requireConfirmation, + boolean allowDeviceCredential) { final Bundle bundle = new Bundle(); bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, requireConfirmation); + + if (allowDeviceCredential) { + bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true); + } return bundle; } @@ -742,6 +947,11 @@ public class BiometricServiceTest { return session.mModalitiesMatched.values().iterator().next(); } + private static int getCookieForPendingSession(BiometricService.AuthSession session) { + assertEquals(session.mModalitiesWaiting.values().size(), 1); + return session.mModalitiesWaiting.values().iterator().next(); + } + private static void waitForIdle() { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index 11bd29d8a163..ad3e040aca72 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -28,6 +28,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -114,6 +115,7 @@ public class ArcTerminationActionFromAvrTest { } @Test + @Ignore("b/120845532") public void testSendMessage_notSuccess() { mSendCecCommandSuccess = false; mShouldDispatchReportArcTerminated = false; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index a89198ae3708..0a1899be8c1f 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -232,6 +232,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { } @Test + @Ignore("b/120845532") public void handleGiveSystemAudioModeStatus_originalOff() throws Exception { HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildReportSystemAudioMode( @@ -301,6 +302,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { } @Test + @Ignore("b/120845532") public void handleSetSystemAudioMode_setOn_orignalOff() throws Exception { mMusicMute = true; HdmiCecMessage messageSet = @@ -328,6 +330,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { } @Test + @Ignore("b/120845532") public void handleSystemAudioModeRequest_turnOffByTv() throws Exception { assertThat(mMusicMute).isFalse(); // Check if feature correctly turned off @@ -497,6 +500,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { } @Test + @Ignore("b/120845532") public void handleRequestArcTerminate_arcIsNotOn() throws Exception { assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse(); HdmiCecMessage message = @@ -671,6 +675,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { } @Test + @Ignore("b/120845532") public void handleReportPhysicalAddress_differentPath_addDevice() { assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE); HdmiDeviceInfo oldDevice = new HdmiDeviceInfo( diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index b8799c3f16f7..0062a1751802 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -99,6 +99,7 @@ public class HdmiCecLocalDevicePlaybackTest { } @Test + @Ignore("b/120845532") public void handleSetSystemAudioModeOn_audioSystemBroadcast() { mHdmiControlService.setSystemAudioActivated(false); assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isFalse(); @@ -110,6 +111,7 @@ public class HdmiCecLocalDevicePlaybackTest { } @Test + @Ignore("b/120845532") public void handleSetSystemAudioModeOff_audioSystemToPlayback() { mHdmiCecLocalDevicePlayback.mService.setSystemAudioActivated(true); assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isTrue(); @@ -123,6 +125,7 @@ public class HdmiCecLocalDevicePlaybackTest { } @Test + @Ignore("b/120845532") public void handleSystemAudioModeStatusOn_DirectltToLocalDeviceFromAudioSystem() { mHdmiControlService.setSystemAudioActivated(false); assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isFalse(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java new file mode 100644 index 000000000000..c6cf9b116a1d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java @@ -0,0 +1,212 @@ +/* + * 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.hdmi; + +import static android.os.SystemClock.sleep; +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.assertEquals; + +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiPortInfo; +import android.hardware.hdmi.IHdmiControlCallback; +import android.os.Looper; +import android.os.SystemProperties; +import android.os.test.TestLooper; +import android.util.Slog; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import java.util.ArrayList; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for {@link HdmiControlServiceBinderAPITest} class. + */ +@SmallTest +@RunWith(JUnit4.class) +public class HdmiControlServiceBinderAPITest { + + private class HdmiCecLocalDeviceMyDevice extends HdmiCecLocalDevice { + + private boolean mCanGoToStandby; + private boolean mIsStandby; + private boolean mIsDisabled; + + protected HdmiCecLocalDeviceMyDevice(HdmiControlService service, int deviceType) { + super(service, deviceType); + } + + @Override + protected void onAddressAllocated(int logicalAddress, int reason) { + } + + @Override + protected int getPreferredAddress() { + return 0; + } + + @Override + protected void setPreferredAddress(int addr) { + } + + @Override + protected boolean canGoToStandby() { + return mCanGoToStandby; + } + + @Override + protected void disableDevice( + boolean initiatedByCec, final PendingActionClearedCallback originalCallback) { + mIsDisabled = true; + originalCallback.onCleared(this); + } + + @Override + protected void onStandby(boolean initiatedByCec, int standbyAction) { + mIsStandby = true; + } + + protected boolean isStandby() { + return mIsStandby; + } + + protected boolean isDisabled() { + return mIsDisabled; + } + + protected void setCanGoToStandby(boolean canGoToStandby) { + mCanGoToStandby = canGoToStandby; + } + } + + private static final String TAG = "HdmiControlServiceBinderAPITest"; + private HdmiControlService mHdmiControlService; + private HdmiCecController mHdmiCecController; + private HdmiCecLocalDevicePlayback mPlaybackDevice; + private FakeNativeWrapper mNativeWrapper; + private Looper mMyLooper; + private TestLooper mTestLooper = new TestLooper(); + private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); + private HdmiPortInfo[] mHdmiPortInfo; + private int mResult; + private int mPowerStatus; + + @Before + public void SetUp() { + mHdmiControlService = + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { + @Override + void sendCecCommand(HdmiCecMessage command) { + switch (command.getOpcode()) { + case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS: + HdmiCecMessage message = + HdmiCecMessageBuilder.buildReportPowerStatus( + Constants.ADDR_TV, + Constants.ADDR_PLAYBACK_1, + HdmiControlManager.POWER_STATUS_ON); + handleCecCommand(message); + break; + default: + return; + } + } + + @Override + boolean isPowerStandby() { + return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY; + } + }; + mMyLooper = mTestLooper.getLooper(); + + mPlaybackDevice = new HdmiCecLocalDevicePlayback(mHdmiControlService) { + @Override + void setIsActiveSource(boolean on) { + mIsActiveSource = on; + } + + @Override + protected void wakeUpIfActiveSource() {} + + @Override + protected void setPreferredAddress(int addr) {} + + @Override + protected int getPreferredAddress() { + return Constants.ADDR_PLAYBACK_1; + } + }; + mPlaybackDevice.init(); + + mHdmiControlService.setIoLooper(mMyLooper); + + mNativeWrapper = new FakeNativeWrapper(); + mHdmiCecController = + HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper); + mHdmiControlService.setCecController(mHdmiCecController); + mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); + mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); + + mLocalDevices.add(mPlaybackDevice); + mHdmiPortInfo = new HdmiPortInfo[1]; + mHdmiPortInfo[0] = + new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false); + mNativeWrapper.setPortInfo(mHdmiPortInfo); + mHdmiControlService.initPortInfo(); + mResult = -1; + mPowerStatus = HdmiControlManager.POWER_STATUS_ON; + + mTestLooper.dispatchAll(); + } + + @Test + public void oneTouchPlay_addressNotAllocated() { + assertThat(mHdmiControlService.isAddressAllocated()).isFalse(); + mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + mResult = result; + } + }); + assertEquals(mResult, -1); + assertThat(mPlaybackDevice.mIsActiveSource).isFalse(); + + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + assertThat(mHdmiControlService.isAddressAllocated()).isTrue(); + assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS); + assertThat(mPlaybackDevice.mIsActiveSource).isTrue(); + } + + @Test + public void oneTouchPlay_addressAllocated() { + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + assertThat(mHdmiControlService.isAddressAllocated()).isTrue(); + mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + mResult = result; + } + }); + assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS); + assertThat(mPlaybackDevice.mIsActiveSource).isTrue(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java index 440a49ab81fc..6dcff3574faf 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java @@ -221,6 +221,7 @@ public class SystemAudioInitiationActionFromAvrTest { } @Test + @Ignore("b/120845532") public void testTvSupport() { resetTestVariables(); mShouldDispatchActiveSource = true; diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java index 806c71a7a9b8..6d5b994a63bb 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java @@ -132,6 +132,7 @@ public class UserManagerServiceUserInfoTest { user.profileBadge = 2; user.partial = true; user.guestToRemove = true; + user.preCreated = true; return user; } @@ -147,5 +148,6 @@ public class UserManagerServiceUserInfoTest { assertEquals("profile badge not preseved", one.profileBadge, two.profileBadge); assertEquals("partial not preseved", one.partial, two.partial); assertEquals("guestToRemove not preseved", one.guestToRemove, two.guestToRemove); + assertEquals("preCreated not preseved", one.preCreated, two.preCreated); } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 59d07357ff56..34eb3f16bf2a 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -97,6 +97,8 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.Arrays; @@ -159,7 +161,7 @@ public class UsageStatsService extends SystemService implements int mUsageSource; /** Manages the standby state of apps. */ - AppStandbyController mAppStandby; + AppStandbyInternal mAppStandby; /** Manages app time limit observers */ AppTimeLimitController mAppTimeLimit; @@ -208,7 +210,9 @@ public class UsageStatsService extends SystemService implements mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mHandler = new H(BackgroundThread.get().getLooper()); - mAppStandby = new AppStandbyController(getContext(), BackgroundThread.get().getLooper()); + mAppStandby = AppStandbyInternal.newAppStandbyController( + UsageStatsService.class.getClassLoader(), getContext(), + BackgroundThread.get().getLooper()); mAppTimeLimit = new AppTimeLimitController( new AppTimeLimitController.TimeLimitCallbackListener() { @@ -1010,7 +1014,7 @@ public class UsageStatsService extends SystemService implements pw.println("Flushed stats to disk"); return; } else if ("is-app-standby-enabled".equals(arg)) { - pw.println(mAppStandby.mAppIdleEnabled); + pw.println(mAppStandby.isAppIdleEnabled()); return; } else if ("apptimelimit".equals(arg)) { if (i + 1 >= args.length) { diff --git a/telephony/java/android/telephony/CellBroadcastService.java b/telephony/java/android/telephony/CellBroadcastService.java new file mode 100644 index 000000000000..d5e447e6c73d --- /dev/null +++ b/telephony/java/android/telephony/CellBroadcastService.java @@ -0,0 +1,112 @@ +/* + * 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.telephony; + +import android.annotation.CallSuper; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +/** + * A service which exposes the cell broadcast handling module to the system. + * <p> + * To extend this class, you must declare the service in your manifest file to require the + * {@link android.Manifest.permission#BIND_CELL_BROADCAST_SERVICE} permission and include an intent + * filter with the {@link #CELL_BROADCAST_SERVICE_INTERFACE}. + * Implementations of this service should run in the phone process and with its UID. + * <p> + * For example: + * <pre>{@code + * <manifest xmlns:android="http://schemas.android.com/apk/res/android" + * android:sharedUserId="android.uid.phone"> + * <service android:name=".MyCellBroadcastService" + * android:label="@string/service_name" + * android:process="com.android.phone" + * android:exported="true" + * android:permission="android.permission.BIND_CELL_BROADCAST_SERVICE"> + * <intent-filter> + * <action android:name="android.telephony.CellBroadcastService" /> + * </intent-filter> + * </service> + * </manifest> + * }</pre> + * @hide + */ +@SystemApi +public abstract class CellBroadcastService extends Service { + + public static final String CELL_BROADCAST_SERVICE_INTERFACE = + "android.telephony.CellBroadcastService"; + + private final ICellBroadcastService.Stub mStubWrapper; + + public CellBroadcastService() { + mStubWrapper = new ICellBroadcastServiceWrapper(); + } + + /** + * Handle a GSM cell broadcast SMS message forwarded from the system. + * @param slotIndex the index of the slot which received the message + * @param message the SMS PDU + */ + public abstract void onGsmCellBroadcastSms(int slotIndex, byte[] message); + + /** + * Handle a CDMA cell broadcast SMS message forwarded from the system. + * @param slotIndex the index of the slot which received the message + * @param message the SMS PDU + */ + public abstract void onCdmaCellBroadcastSms(int slotIndex, byte[] message); + + /** + * If overriding this method, call through to the super method for any unknown actions. + * {@inheritDoc} + */ + @Override + @CallSuper + public IBinder onBind(Intent intent) { + return mStubWrapper; + } + + /** + * A wrapper around ICellBroadcastService that forwards calls to implementations of + * {@link CellBroadcastService}. + * @hide + */ + public class ICellBroadcastServiceWrapper extends ICellBroadcastService.Stub { + /** + * Handle a GSM cell broadcast SMS. + * @param slotIndex the index of the slot which received the broadcast + * @param message the SMS message PDU + */ + @Override + public void handleGsmCellBroadcastSms(int slotIndex, byte[] message) { + CellBroadcastService.this.onGsmCellBroadcastSms(slotIndex, message); + } + + /** + * Handle a CDMA cell broadcast SMS. + * @param slotIndex the index of the slot which received the broadcast + * @param message the SMS message PDU + */ + @Override + public void handleCdmaCellBroadcastSms(int slotIndex, byte[] message) { + CellBroadcastService.this.onCdmaCellBroadcastSms(slotIndex, message); + } + } +} diff --git a/telephony/java/android/telephony/ICellBroadcastService.aidl b/telephony/java/android/telephony/ICellBroadcastService.aidl new file mode 100644 index 000000000000..eff64a2e35ba --- /dev/null +++ b/telephony/java/android/telephony/ICellBroadcastService.aidl @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +/** + * Service bound to by the system to allow custom handling of cell broadcast messages. + * <p> + * @see android.telephony.CellBroadcastService + * @hide + */ +interface ICellBroadcastService { + + /** @see android.telephony.CellBroadcastService#onGsmCellBroadcastSms */ + oneway void handleGsmCellBroadcastSms(int slotId, in byte[] message); + + /** @see android.telephony.CellBroadcastService#onCdmaCellBroadcastSms */ + oneway void handleCdmaCellBroadcastSms(int slotId, in byte[] message); +} diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index c2028f900508..1ba0a41024ed 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -374,7 +374,7 @@ public class PhoneStateListener { @SystemApi @TestApi @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) - public static final int LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER = 0x10000000; + public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 0x10000000; /** * Listen for the emergency number placed from an outgoing SMS. @@ -387,7 +387,7 @@ public class PhoneStateListener { @SystemApi @TestApi @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) - public static final int LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER = 0x20000000; + public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 0x20000000; /* * Subscription used to listen to the phone state changes @@ -878,6 +878,7 @@ public class PhoneStateListener { * @hide */ @SystemApi + @TestApi public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber) { // default implementation empty } @@ -889,6 +890,7 @@ public class PhoneStateListener { * @hide */ @SystemApi + @TestApi public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber) { // default implementation empty } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java index 6eea118787a7..c65c45fa015b 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java @@ -461,7 +461,11 @@ public class GsmSmsCbMessage { } } - static final class GeoFencingTriggerMessage { + /** + * Part of a GSM SMS cell broadcast message which may trigger geo-fencing logic. + * @hide + */ + public static final class GeoFencingTriggerMessage { /** * Indicate the list of active alerts share their warning area coordinates which means the * broadcast area is the union of the broadcast areas of the active alerts in this list. @@ -476,6 +480,11 @@ public class GsmSmsCbMessage { this.cbIdentifiers = cbIdentifiers; } + /** + * Whether the trigger message indicates that the broadcast areas are shared between all + * active alerts. + * @return true if broadcast areas are to be shared + */ boolean shouldShareBroadcastArea() { return type == TYPE_ACTIVE_ALERT_SHARE_WAC; } diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java index 6bbff4b91ee7..cbe521182667 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java @@ -74,22 +74,22 @@ public class SmsCbHeader { /** * Length of SMS-CB header */ - static final int PDU_HEADER_LENGTH = 6; + public static final int PDU_HEADER_LENGTH = 6; /** * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1 */ - static final int FORMAT_GSM = 1; + public static final int FORMAT_GSM = 1; /** * UMTS pdu format, as defined in 3gpp TS 23.041, section 9.4.2 */ - static final int FORMAT_UMTS = 2; + public static final int FORMAT_UMTS = 2; /** - * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3 + * ETWS pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3 */ - static final int FORMAT_ETWS_PRIMARY = 3; + public static final int FORMAT_ETWS_PRIMARY = 3; /** * Message type value as defined in 3gpp TS 25.324, section 11.1. @@ -230,43 +230,43 @@ public class SmsCbHeader { } @UnsupportedAppUsage - int getGeographicalScope() { + public int getGeographicalScope() { return mGeographicalScope; } @UnsupportedAppUsage - int getSerialNumber() { + public int getSerialNumber() { return mSerialNumber; } @UnsupportedAppUsage - int getServiceCategory() { + public int getServiceCategory() { return mMessageIdentifier; } - int getDataCodingScheme() { + public int getDataCodingScheme() { return mDataCodingScheme; } - DataCodingScheme getDataCodingSchemeStructedData() { + public DataCodingScheme getDataCodingSchemeStructedData() { return mDataCodingSchemeStructedData; } @UnsupportedAppUsage - int getPageIndex() { + public int getPageIndex() { return mPageIndex; } @UnsupportedAppUsage - int getNumberOfPages() { + public int getNumberOfPages() { return mNrOfPages; } - SmsCbEtwsInfo getEtwsInfo() { + public SmsCbEtwsInfo getEtwsInfo() { return mEtwsInfo; } - SmsCbCmasInfo getCmasInfo() { + public SmsCbCmasInfo getCmasInfo() { return mCmasInfo; } @@ -274,7 +274,7 @@ public class SmsCbHeader { * Return whether this broadcast is an emergency (PWS) message type. * @return true if this message is emergency type; false otherwise */ - boolean isEmergencyMessage() { + public boolean isEmergencyMessage() { return mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER && mMessageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER; } @@ -292,7 +292,7 @@ public class SmsCbHeader { * Return whether this broadcast is an ETWS primary notification. * @return true if this message is an ETWS primary notification; false otherwise */ - boolean isEtwsPrimaryNotification() { + public boolean isEtwsPrimaryNotification() { return mFormat == FORMAT_ETWS_PRIMARY; } @@ -300,7 +300,7 @@ public class SmsCbHeader { * Return whether this broadcast is in UMTS format. * @return true if this message is in UMTS format; false otherwise */ - boolean isUmtsFormat() { + public boolean isUmtsFormat() { return mFormat == FORMAT_UMTS; } diff --git a/tools/codegen/src/com/android/codegen/Main.kt b/tools/codegen/src/com/android/codegen/Main.kt index c91eca94a9c0..039f7b2fc627 100755 --- a/tools/codegen/src/com/android/codegen/Main.kt +++ b/tools/codegen/src/com/android/codegen/Main.kt @@ -95,7 +95,13 @@ In addition, for any field mMyField(or myField) of type FieldType you can define you can use with final fields. Version: $CODEGEN_VERSION -Questions? Feedback? Contact: eugenesusla@ + +Questions? Feedback? +Contact: eugenesusla@ +Bug/feature request: http://go/codegen-bug + +Slides: http://go/android-codegen +In-depth example: http://go/SampleDataClass """ fun main(args: Array<String>) { |