diff options
282 files changed, 7841 insertions, 4461 deletions
diff --git a/Android.bp b/Android.bp index 12bc90680dd5..69d654fbddbc 100644 --- a/Android.bp +++ b/Android.bp @@ -403,7 +403,6 @@ java_defaults { "unsupportedappusage", "framework-media-stubs-systemapi", "framework-mediaprovider-stubs-systemapi", - "framework-tethering", "framework-telephony-stubs", ], @@ -472,6 +471,7 @@ java_library { "framework-permission-stubs-systemapi", "framework-wifi-stubs", "ike-stubs", + "framework-tethering-stubs", ], installable: true, javac_shard_size: 150, @@ -496,6 +496,7 @@ java_library { "//frameworks/base/apex/blobstore/framework", "//frameworks/base/apex/jobscheduler/framework", "//frameworks/base/apex/statsd/service", + "//frameworks/base/packages/Tethering/tests/unit", ], } @@ -523,8 +524,7 @@ java_library { "framework-statsd", "framework-wifi-stubs", "ike-stubs", - // TODO(b/147200698): should be the stub of framework-tethering - "framework-tethering", + "framework-tethering-stubs", // TODO (b/147688669) should be framework-telephony-stubs "framework-telephony", // TODO(jiyong): add stubs for APEXes here diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java index a7b69c420c59..8e0ea9888e4c 100644 --- a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java +++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java @@ -102,7 +102,7 @@ public class BlobStorePerfTests { durations.clear(); collectDigestDurationsFromTrace(parser, durations); - // get and delete blobId + // TODO: get and delete blobId before next iteration. } } finally { mAtraceUtils.stopTrace(); diff --git a/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java b/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java deleted file mode 100644 index 236f548cf6dd..000000000000 --- a/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package android.os; - -import android.content.ComponentName; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.perftests.utils.BenchmarkState; -import android.perftests.utils.PerfStatusReporter; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@LargeTest -public class PackageManagerPerfTest { - private static final String PERMISSION_NAME_EXISTS = - "com.android.perftests.core.TestPermission"; - private static final String PERMISSION_NAME_DOESNT_EXIST = - "com.android.perftests.core.TestBadPermission"; - private static final ComponentName TEST_ACTIVITY = - new ComponentName("com.android.perftests.core", - "android.perftests.utils.PerfTestActivity"); - - @Rule - public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - - @Test - public void testCheckPermissionExists() { - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager(); - final String packageName = TEST_ACTIVITY.getPackageName(); - - while (state.keepRunning()) { - int ret = pm.checkPermission(PERMISSION_NAME_EXISTS, packageName); - } - } - - @Test - public void testCheckPermissionDoesntExist() { - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager(); - final String packageName = TEST_ACTIVITY.getPackageName(); - - while (state.keepRunning()) { - int ret = pm.checkPermission(PERMISSION_NAME_DOESNT_EXIST, packageName); - } - } - - @Test - public void testQueryIntentActivities() { - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager(); - final Intent intent = new Intent("com.android.perftests.core.PERFTEST"); - - while (state.keepRunning()) { - pm.queryIntentActivities(intent, 0); - } - } - - @Test - public void testGetPackageInfo() throws Exception { - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager(); - final String packageName = TEST_ACTIVITY.getPackageName(); - - while (state.keepRunning()) { - pm.getPackageInfo(packageName, 0); - } - } - - @Test - public void testGetApplicationInfo() throws Exception { - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager(); - final String packageName = TEST_ACTIVITY.getPackageName(); - - while (state.keepRunning()) { - pm.getApplicationInfo(packageName, 0); - } - } - - @Test - public void testGetActivityInfo() throws Exception { - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager(); - - while (state.keepRunning()) { - pm.getActivityInfo(TEST_ACTIVITY, 0); - } - } -} diff --git a/apct-tests/perftests/packagemanager/Android.bp b/apct-tests/perftests/packagemanager/Android.bp new file mode 100644 index 000000000000..17033e048c7d --- /dev/null +++ b/apct-tests/perftests/packagemanager/Android.bp @@ -0,0 +1,21 @@ +android_test { + name: "PackageManagerPerfTests", + + srcs: ["src/**/*.java"], + + static_libs: [ + "platform-compat-test-rules", + "androidx.appcompat_appcompat", + "androidx.test.rules", + "androidx.test.ext.junit", + "androidx.annotation_annotation", + "apct-perftests-utils", + ], + + libs: ["android.test.base"], + + platform_apis: true, + + test_suites: ["device-tests"], + +} diff --git a/apct-tests/perftests/packagemanager/AndroidManifest.xml b/apct-tests/perftests/packagemanager/AndroidManifest.xml new file mode 100644 index 000000000000..520f4b55d931 --- /dev/null +++ b/apct-tests/perftests/packagemanager/AndroidManifest.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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.packagemanager"> + + <permission android:name="com.android.perftests.packagemanager.TestPermission" /> + <uses-permission android:name="com.android.perftests.packagemanager.TestPermission" /> + + <queries> + <package android:name="com.android.perftests.appenumeration0" /> + <package android:name="com.android.perftests.appenumeration1" /> + <package android:name="com.android.perftests.appenumeration2" /> + <package android:name="com.android.perftests.appenumeration3" /> + <package android:name="com.android.perftests.appenumeration4" /> + <package android:name="com.android.perftests.appenumeration5" /> + <package android:name="com.android.perftests.appenumeration6" /> + <package android:name="com.android.perftests.appenumeration7" /> + <package android:name="com.android.perftests.appenumeration8" /> + <package android:name="com.android.perftests.appenumeration9" /> + <package android:name="com.android.perftests.appenumeration10" /> + <package android:name="com.android.perftests.appenumeration11" /> + <package android:name="com.android.perftests.appenumeration12" /> + <package android:name="com.android.perftests.appenumeration13" /> + <package android:name="com.android.perftests.appenumeration14" /> + <package android:name="com.android.perftests.appenumeration15" /> + <package android:name="com.android.perftests.appenumeration16" /> + <package android:name="com.android.perftests.appenumeration17" /> + <package android:name="com.android.perftests.appenumeration18" /> + <package android:name="com.android.perftests.appenumeration19" /> + <package android:name="com.android.perftests.appenumeration20" /> + <package android:name="com.android.perftests.appenumeration21" /> + <package android:name="com.android.perftests.appenumeration22" /> + <package android:name="com.android.perftests.appenumeration23" /> + <package android:name="com.android.perftests.appenumeration24" /> + <package android:name="com.android.perftests.appenumeration25" /> + <package android:name="com.android.perftests.appenumeration26" /> + <package android:name="com.android.perftests.appenumeration27" /> + <package android:name="com.android.perftests.appenumeration28" /> + <package android:name="com.android.perftests.appenumeration29" /> + <package android:name="com.android.perftests.appenumeration30" /> + <package android:name="com.android.perftests.appenumeration31" /> + <package android:name="com.android.perftests.appenumeration32" /> + <package android:name="com.android.perftests.appenumeration33" /> + <package android:name="com.android.perftests.appenumeration34" /> + <package android:name="com.android.perftests.appenumeration35" /> + <package android:name="com.android.perftests.appenumeration36" /> + <package android:name="com.android.perftests.appenumeration37" /> + <package android:name="com.android.perftests.appenumeration38" /> + <package android:name="com.android.perftests.appenumeration39" /> + <package android:name="com.android.perftests.appenumeration40" /> + <package android:name="com.android.perftests.appenumeration41" /> + <package android:name="com.android.perftests.appenumeration42" /> + <package android:name="com.android.perftests.appenumeration43" /> + <package android:name="com.android.perftests.appenumeration44" /> + <package android:name="com.android.perftests.appenumeration45" /> + <package android:name="com.android.perftests.appenumeration46" /> + <package android:name="com.android.perftests.appenumeration47" /> + <package android:name="com.android.perftests.appenumeration48" /> + <package android:name="com.android.perftests.appenumeration49" /> + </queries> + + <application> + <uses-library android:name="android.test.runner" /> + <activity android:name="android.perftests.utils.PerfTestActivity"> + <intent-filter> + <action android:name="com.android.perftests.packagemanager.PERFTEST" /> + </intent-filter> + </activity> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.perftests.packagemanager"/> + +</manifest> diff --git a/apct-tests/perftests/packagemanager/AndroidTest.xml b/apct-tests/perftests/packagemanager/AndroidTest.xml new file mode 100644 index 000000000000..c112d87d83e1 --- /dev/null +++ b/apct-tests/perftests/packagemanager/AndroidTest.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="Runs PackageManagerPerfTests metric instrumentation."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-metric-instrumentation" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="PackageManagerPerfTests.apk" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="force-queryable" value="false" /> + <option name="test-file-name" value="QueriesAll0.apk" /> + <option name="test-file-name" value="QueriesAll1.apk" /> + <option name="test-file-name" value="QueriesAll2.apk" /> + <option name="test-file-name" value="QueriesAll3.apk" /> + <option name="test-file-name" value="QueriesAll4.apk" /> + <option name="test-file-name" value="QueriesAll5.apk" /> + <option name="test-file-name" value="QueriesAll6.apk" /> + <option name="test-file-name" value="QueriesAll7.apk" /> + <option name="test-file-name" value="QueriesAll8.apk" /> + <option name="test-file-name" value="QueriesAll9.apk" /> + <option name="test-file-name" value="QueriesAll10.apk" /> + <option name="test-file-name" value="QueriesAll11.apk" /> + <option name="test-file-name" value="QueriesAll12.apk" /> + <option name="test-file-name" value="QueriesAll13.apk" /> + <option name="test-file-name" value="QueriesAll14.apk" /> + <option name="test-file-name" value="QueriesAll15.apk" /> + <option name="test-file-name" value="QueriesAll16.apk" /> + <option name="test-file-name" value="QueriesAll17.apk" /> + <option name="test-file-name" value="QueriesAll18.apk" /> + <option name="test-file-name" value="QueriesAll19.apk" /> + <option name="test-file-name" value="QueriesAll20.apk" /> + <option name="test-file-name" value="QueriesAll21.apk" /> + <option name="test-file-name" value="QueriesAll22.apk" /> + <option name="test-file-name" value="QueriesAll23.apk" /> + <option name="test-file-name" value="QueriesAll24.apk" /> + <option name="test-file-name" value="QueriesAll25.apk" /> + <option name="test-file-name" value="QueriesAll26.apk" /> + <option name="test-file-name" value="QueriesAll27.apk" /> + <option name="test-file-name" value="QueriesAll28.apk" /> + <option name="test-file-name" value="QueriesAll29.apk" /> + <option name="test-file-name" value="QueriesAll30.apk" /> + <option name="test-file-name" value="QueriesAll31.apk" /> + <option name="test-file-name" value="QueriesAll32.apk" /> + <option name="test-file-name" value="QueriesAll33.apk" /> + <option name="test-file-name" value="QueriesAll34.apk" /> + <option name="test-file-name" value="QueriesAll35.apk" /> + <option name="test-file-name" value="QueriesAll36.apk" /> + <option name="test-file-name" value="QueriesAll37.apk" /> + <option name="test-file-name" value="QueriesAll38.apk" /> + <option name="test-file-name" value="QueriesAll39.apk" /> + <option name="test-file-name" value="QueriesAll40.apk" /> + <option name="test-file-name" value="QueriesAll41.apk" /> + <option name="test-file-name" value="QueriesAll42.apk" /> + <option name="test-file-name" value="QueriesAll43.apk" /> + <option name="test-file-name" value="QueriesAll44.apk" /> + <option name="test-file-name" value="QueriesAll45.apk" /> + <option name="test-file-name" value="QueriesAll46.apk" /> + <option name="test-file-name" value="QueriesAll47.apk" /> + <option name="test-file-name" value="QueriesAll48.apk" /> + <option name="test-file-name" value="QueriesAll49.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.perftests.packagemanager" /> + <option name="hidden-api-checks" value="false"/> + </test> + + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="directory-keys" value="/data/local/PackageManagerPerfTests" /> + <option name="collect-on-run-ended-only" value="true" /> + </metrics_collector> +</configuration> diff --git a/apct-tests/perftests/packagemanager/apps/query-all/Android.bp b/apct-tests/perftests/packagemanager/apps/query-all/Android.bp new file mode 100644 index 000000000000..3cb1589bc376 --- /dev/null +++ b/apct-tests/perftests/packagemanager/apps/query-all/Android.bp @@ -0,0 +1,314 @@ +// Copyright (C) 2020 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: "QueriesAll0", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration0", + ] +} +android_test_helper_app { + name: "QueriesAll1", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration1", + ] +} +android_test_helper_app { + name: "QueriesAll2", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration2", + ] +} +android_test_helper_app { + name: "QueriesAll3", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration3", + ] +} +android_test_helper_app { + name: "QueriesAll4", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration4", + ] +} +android_test_helper_app { + name: "QueriesAll5", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration5", + ] +} +android_test_helper_app { + name: "QueriesAll6", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration6", + ] +} +android_test_helper_app { + name: "QueriesAll7", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration7", + ] +} +android_test_helper_app { + name: "QueriesAll8", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration8", + ] +} +android_test_helper_app { + name: "QueriesAll9", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration9", + ] +} +android_test_helper_app { + name: "QueriesAll10", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration10", + ] +} +android_test_helper_app { + name: "QueriesAll11", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration11", + ] +} +android_test_helper_app { + name: "QueriesAll12", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration12", + ] +} +android_test_helper_app { + name: "QueriesAll13", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration13", + ] +} +android_test_helper_app { + name: "QueriesAll14", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration14", + ] +} +android_test_helper_app { + name: "QueriesAll15", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration15", + ] +} +android_test_helper_app { + name: "QueriesAll16", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration16", + ] +} +android_test_helper_app { + name: "QueriesAll17", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration17", + ] +} +android_test_helper_app { + name: "QueriesAll18", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration18", + ] +} +android_test_helper_app { + name: "QueriesAll19", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration19", + ] +} +android_test_helper_app { + name: "QueriesAll20", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration20", + ] +} +android_test_helper_app { + name: "QueriesAll21", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration21", + ] +} +android_test_helper_app { + name: "QueriesAll22", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration22", + ] +} +android_test_helper_app { + name: "QueriesAll23", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration23", + ] +} +android_test_helper_app { + name: "QueriesAll24", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration24", + ] +} +android_test_helper_app { + name: "QueriesAll25", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration25", + ] +} +android_test_helper_app { + name: "QueriesAll26", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration26", + ] +} +android_test_helper_app { + name: "QueriesAll27", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration27", + ] +} +android_test_helper_app { + name: "QueriesAll28", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration28", + ] +} +android_test_helper_app { + name: "QueriesAll29", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration29", + ] +} +android_test_helper_app { + name: "QueriesAll30", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration30", + ] +} +android_test_helper_app { + name: "QueriesAll31", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration31", + ] +} +android_test_helper_app { + name: "QueriesAll32", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration32", + ] +} +android_test_helper_app { + name: "QueriesAll33", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration33", + ] +} +android_test_helper_app { + name: "QueriesAll34", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration34", + ] +} +android_test_helper_app { + name: "QueriesAll35", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration35", + ] +} +android_test_helper_app { + name: "QueriesAll36", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration36", + ] +} +android_test_helper_app { + name: "QueriesAll37", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration37", + ] +} +android_test_helper_app { + name: "QueriesAll38", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration38", + ] +} +android_test_helper_app { + name: "QueriesAll39", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration39", + ] +} +android_test_helper_app { + name: "QueriesAll40", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration40", + ] +} +android_test_helper_app { + name: "QueriesAll41", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration41", + ] +} +android_test_helper_app { + name: "QueriesAll42", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration42", + ] +} +android_test_helper_app { + name: "QueriesAll43", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration43", + ] +} +android_test_helper_app { + name: "QueriesAll44", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration44", + ] +} +android_test_helper_app { + name: "QueriesAll45", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration45", + ] +} +android_test_helper_app { + name: "QueriesAll46", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration46", + ] +} +android_test_helper_app { + name: "QueriesAll47", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration47", + ] +} +android_test_helper_app { + name: "QueriesAll48", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration48", + ] +} +android_test_helper_app { + name: "QueriesAll49", + aaptflags: [ + "--rename-manifest-package com.android.perftests.appenumeration49", + ] +} diff --git a/apct-tests/perftests/packagemanager/apps/query-all/AndroidManifest.xml b/apct-tests/perftests/packagemanager/apps/query-all/AndroidManifest.xml new file mode 100644 index 000000000000..e2cfa0430a32 --- /dev/null +++ b/apct-tests/perftests/packagemanager/apps/query-all/AndroidManifest.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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.appenumeration"> + + <application android:hasCode="false" > + <activity android:name="android.perftests.utils.PerfTestActivity"> + <intent-filter> + <action android:name="com.android.perftests.packagemanager.PERFTEST" /> + </intent-filter> + </activity> + </application> + + <queries> + <package android:name="com.android.perftests.appenumeration0" /> + <package android:name="com.android.perftests.appenumeration1" /> + <package android:name="com.android.perftests.appenumeration2" /> + <package android:name="com.android.perftests.appenumeration3" /> + <package android:name="com.android.perftests.appenumeration4" /> + <package android:name="com.android.perftests.appenumeration5" /> + <package android:name="com.android.perftests.appenumeration6" /> + <package android:name="com.android.perftests.appenumeration7" /> + <package android:name="com.android.perftests.appenumeration8" /> + <package android:name="com.android.perftests.appenumeration9" /> + <package android:name="com.android.perftests.appenumeration10" /> + <package android:name="com.android.perftests.appenumeration11" /> + <package android:name="com.android.perftests.appenumeration12" /> + <package android:name="com.android.perftests.appenumeration13" /> + <package android:name="com.android.perftests.appenumeration14" /> + <package android:name="com.android.perftests.appenumeration15" /> + <package android:name="com.android.perftests.appenumeration16" /> + <package android:name="com.android.perftests.appenumeration17" /> + <package android:name="com.android.perftests.appenumeration18" /> + <package android:name="com.android.perftests.appenumeration19" /> + <package android:name="com.android.perftests.appenumeration20" /> + <package android:name="com.android.perftests.appenumeration21" /> + <package android:name="com.android.perftests.appenumeration22" /> + <package android:name="com.android.perftests.appenumeration23" /> + <package android:name="com.android.perftests.appenumeration24" /> + <package android:name="com.android.perftests.appenumeration25" /> + <package android:name="com.android.perftests.appenumeration26" /> + <package android:name="com.android.perftests.appenumeration27" /> + <package android:name="com.android.perftests.appenumeration28" /> + <package android:name="com.android.perftests.appenumeration29" /> + <package android:name="com.android.perftests.appenumeration30" /> + <package android:name="com.android.perftests.appenumeration31" /> + <package android:name="com.android.perftests.appenumeration32" /> + <package android:name="com.android.perftests.appenumeration33" /> + <package android:name="com.android.perftests.appenumeration34" /> + <package android:name="com.android.perftests.appenumeration35" /> + <package android:name="com.android.perftests.appenumeration36" /> + <package android:name="com.android.perftests.appenumeration37" /> + <package android:name="com.android.perftests.appenumeration38" /> + <package android:name="com.android.perftests.appenumeration39" /> + <package android:name="com.android.perftests.appenumeration40" /> + <package android:name="com.android.perftests.appenumeration41" /> + <package android:name="com.android.perftests.appenumeration42" /> + <package android:name="com.android.perftests.appenumeration43" /> + <package android:name="com.android.perftests.appenumeration44" /> + <package android:name="com.android.perftests.appenumeration45" /> + <package android:name="com.android.perftests.appenumeration46" /> + <package android:name="com.android.perftests.appenumeration47" /> + <package android:name="com.android.perftests.appenumeration48" /> + <package android:name="com.android.perftests.appenumeration49" /> + </queries> + +</manifest>
\ No newline at end of file diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageManagerPerfTest.java b/apct-tests/perftests/packagemanager/src/android/os/PackageManagerPerfTest.java new file mode 100644 index 000000000000..d7428cf0ab8a --- /dev/null +++ b/apct-tests/perftests/packagemanager/src/android/os/PackageManagerPerfTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import android.compat.testing.PlatformCompatChangeRule; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class PackageManagerPerfTest { + private static final String PERMISSION_NAME_EXISTS = + "com.android.perftests.packagemanager.TestPermission"; + private static final String PERMISSION_NAME_DOESNT_EXIST = + "com.android.perftests.packagemanager.TestBadPermission"; + private static final String OTHER_PACKAGE_NAME = "com.android.perftests.appenumeration0"; + private static final ComponentName TEST_ACTIVITY = + new ComponentName(OTHER_PACKAGE_NAME, + "android.perftests.utils.PerfTestActivity"); + + @Rule + public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @Rule + public final PlatformCompatChangeRule mPlatformCompatChangeRule = + new PlatformCompatChangeRule(); + + public PackageManagerPerfTest() throws PackageManager.NameNotFoundException { + final Context context = InstrumentationRegistry.getInstrumentation().getContext(); + } + + @Test + @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY) + public void testCheckPermissionExists() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final PackageManager pm = + InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager(); + final String packageName = TEST_ACTIVITY.getPackageName(); + + while (state.keepRunning()) { + int ret = pm.checkPermission(PERMISSION_NAME_EXISTS, packageName); + } + } + + @Test + @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY) + public void testCheckPermissionExistsWithFiltering() { + testCheckPermissionExists(); + } + + @Test + @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY) + public void testCheckPermissionDoesntExist() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final PackageManager pm = + InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager(); + final String packageName = TEST_ACTIVITY.getPackageName(); + + while (state.keepRunning()) { + int ret = pm.checkPermission(PERMISSION_NAME_DOESNT_EXIST, packageName); + } + } + + @Test + @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY) + public void testCheckPermissionDoesntExistWithFiltering() { + testCheckPermissionDoesntExist(); + } + + @Test + @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY) + public void testQueryIntentActivities() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final PackageManager pm = + InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager(); + final Intent intent = new Intent("com.android.perftests.core.PERFTEST"); + + while (state.keepRunning()) { + pm.queryIntentActivities(intent, 0); + } + } + + @Test + @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY) + public void testQueryIntentActivitiesWithFiltering() { + testQueryIntentActivities(); + } + + @Test + @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY) + public void testGetPackageInfo() throws Exception { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final PackageManager pm = + InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager(); + + while (state.keepRunning()) { + pm.getPackageInfo(OTHER_PACKAGE_NAME, 0); + } + } + + @Test + @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY) + public void testGetPackageInfoWithFiltering() throws Exception { + testGetPackageInfo(); + } + + @Test + @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY) + public void testGetApplicationInfo() throws Exception { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final PackageManager pm = + InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager(); + + while (state.keepRunning()) { + pm.getApplicationInfo(OTHER_PACKAGE_NAME, 0); + } + } + + @Test + @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY) + public void testGetApplicationInfoWithFiltering() throws Exception { + testGetApplicationInfo(); + } + + @Test + @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY) + public void testGetActivityInfo() throws Exception { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final PackageManager pm = + InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager(); + + while (state.keepRunning()) { + pm.getActivityInfo(TEST_ACTIVITY, 0); + } + } + + @Test + @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY) + public void testGetActivityInfoWithFiltering() throws Exception { + testGetActivityInfo(); + } + + @Test + @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY) + public void testGetInstalledPackages() throws Exception { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final PackageManager pm = + InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager(); + + while (state.keepRunning()) { + pm.getInstalledPackages(0); + } + } + + @Test + @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY) + public void testGetInstalledPackagesWithFiltering() throws Exception { + testGetInstalledPackages(); + } +} diff --git a/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassificationManagerPerfTest.java b/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassificationManagerPerfTest.java index bd3b6737f505..f61ea8549236 100644 --- a/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassificationManagerPerfTest.java +++ b/apct-tests/perftests/textclassifier/src/android/view/textclassifier/TextClassificationManagerPerfTest.java @@ -18,35 +18,60 @@ package android.view.textclassifier; import android.content.Context; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; -import android.perftests.utils.SettingsHelper; -import android.provider.Settings; +import android.provider.DeviceConfig; import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; @LargeTest public class TextClassificationManagerPerfTest { + private static final String WRITE_DEVICE_CONFIG_PERMISSION = + "android.permission.WRITE_DEVICE_CONFIG"; @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + private String mOriginalSystemTextclassifierStatus; + + @BeforeClass + public static void setUpClass() { + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .adoptShellPermissionIdentity( + WRITE_DEVICE_CONFIG_PERMISSION); + } + + @AfterClass + public static void tearDownClass() { + InstrumentationRegistry + .getInstrumentation() + .getUiAutomation() + .dropShellPermissionIdentity(); + } + + @Before + public void setUp() { + // Saves config original value. + mOriginalSystemTextclassifierStatus = DeviceConfig.getProperty( + DeviceConfig.NAMESPACE_TEXTCLASSIFIER, "system_textclassifier_enabled"); + } + @After public void tearDown() { - SettingsHelper.delete( - SettingsHelper.NAMESPACE_GLOBAL, Settings.Global.TEXT_CLASSIFIER_CONSTANTS); + // Restores config original value. + enableSystemTextclassifier(mOriginalSystemTextclassifierStatus); } @Test public void testGetTextClassifier_systemTextClassifierDisabled() { Context context = InstrumentationRegistry.getTargetContext(); - SettingsHelper.set( - SettingsHelper.NAMESPACE_GLOBAL, - Settings.Global.TEXT_CLASSIFIER_CONSTANTS, - "system_textclassifier_enabled=false"); + enableSystemTextclassifier(String.valueOf(false)); TextClassificationManager textClassificationManager = context.getSystemService(TextClassificationManager.class); BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); @@ -59,10 +84,7 @@ public class TextClassificationManagerPerfTest { @Test public void testGetTextClassifier_systemTextClassifierEnabled() { Context context = InstrumentationRegistry.getTargetContext(); - SettingsHelper.set( - SettingsHelper.NAMESPACE_GLOBAL, - Settings.Global.TEXT_CLASSIFIER_CONSTANTS, - "system_textclassifier_enabled=true"); + enableSystemTextclassifier(String.valueOf(true)); TextClassificationManager textClassificationManager = context.getSystemService(TextClassificationManager.class); BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); @@ -71,4 +93,9 @@ public class TextClassificationManagerPerfTest { textClassificationManager.invalidateForTesting(); } } + + private void enableSystemTextclassifier(String enabled) { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + "system_textclassifier_enabled", enabled, /* makeDefault */ false); + } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 69f4748548a7..939164edd13e 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -494,9 +494,10 @@ public class JobSchedulerService extends com.android.server.SystemService private static final String DEPRECATED_KEY_BG_LOW_JOB_COUNT = "bg_low_job_count"; private static final String DEPRECATED_KEY_BG_CRITICAL_JOB_COUNT = "bg_critical_job_count"; - private static final String KEY_MAX_STANDARD_RESCHEDULE_COUNT + private static final String DEPRECATED_KEY_MAX_STANDARD_RESCHEDULE_COUNT = "max_standard_reschedule_count"; - private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count"; + private static final String DEPRECATED_KEY_MAX_WORK_RESCHEDULE_COUNT = + "max_work_reschedule_count"; private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time"; private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time"; private static final String DEPRECATED_KEY_STANDBY_HEARTBEAT_TIME = @@ -525,8 +526,6 @@ public class JobSchedulerService extends com.android.server.SystemService private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS; private static final float DEFAULT_HEAVY_USE_FACTOR = .9f; private static final float DEFAULT_MODERATE_USE_FACTOR = .5f; - private static final int DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT = Integer.MAX_VALUE; - private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE; private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS; private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS; private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f; @@ -640,16 +639,6 @@ public class JobSchedulerService extends com.android.server.SystemService "screen_off_job_concurrency_increase_delay_ms", 30_000); /** - * The maximum number of times we allow a job to have itself rescheduled before - * giving up on it, for standard jobs. - */ - int MAX_STANDARD_RESCHEDULE_COUNT = DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT; - /** - * The maximum number of times we allow a job to have itself rescheduled before - * giving up on it, for jobs that are executing work. - */ - int MAX_WORK_RESCHEDULE_COUNT = DEFAULT_MAX_WORK_RESCHEDULE_COUNT; - /** * The minimum backoff time to allow for linear backoff. */ long MIN_LINEAR_BACKOFF_TIME = DEFAULT_MIN_LINEAR_BACKOFF_TIME; @@ -735,10 +724,6 @@ public class JobSchedulerService extends com.android.server.SystemService SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.parse(mParser); - MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT, - DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT); - MAX_WORK_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_WORK_RESCHEDULE_COUNT, - DEFAULT_MAX_WORK_RESCHEDULE_COUNT); MIN_LINEAR_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_LINEAR_BACKOFF_TIME, DEFAULT_MIN_LINEAR_BACKOFF_TIME); MIN_EXP_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_EXP_BACKOFF_TIME, @@ -790,8 +775,6 @@ public class JobSchedulerService extends com.android.server.SystemService SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dump(pw, ""); - pw.printPair(KEY_MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT).println(); - pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println(); pw.printPair(KEY_MIN_LINEAR_BACKOFF_TIME, MIN_LINEAR_BACKOFF_TIME).println(); pw.printPair(KEY_MIN_EXP_BACKOFF_TIME, MIN_EXP_BACKOFF_TIME).println(); pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println(); @@ -827,8 +810,6 @@ public class JobSchedulerService extends com.android.server.SystemService SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dumpProto(proto, ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS); - proto.write(ConstantsProto.MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT); - proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT); proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME); proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME); proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC); @@ -1390,18 +1371,8 @@ public class JobSchedulerService extends com.android.server.SystemService // Effective standby bucket can change after this in some situations so use // the real bucket so that the job is tracked by the controllers. if (js.getStandbyBucket() == RESTRICTED_INDEX) { - js.addDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW); - js.addDynamicConstraint(JobStatus.CONSTRAINT_CHARGING); - js.addDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY); - js.addDynamicConstraint(JobStatus.CONSTRAINT_IDLE); - mRestrictiveControllers.get(j).startTrackingRestrictedJobLocked(js); } else { - js.removeDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW); - js.removeDynamicConstraint(JobStatus.CONSTRAINT_CHARGING); - js.removeDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY); - js.removeDynamicConstraint(JobStatus.CONSTRAINT_IDLE); - mRestrictiveControllers.get(j).stopTrackingRestrictedJobLocked(js); } } @@ -1738,19 +1709,6 @@ public class JobSchedulerService extends com.android.server.SystemService final int backoffAttempts = failureToReschedule.getNumFailures() + 1; long delayMillis; - if (failureToReschedule.hasWorkLocked()) { - if (backoffAttempts > mConstants.MAX_WORK_RESCHEDULE_COUNT) { - Slog.w(TAG, "Not rescheduling " + failureToReschedule + ": attempt #" - + backoffAttempts + " > work limit " - + mConstants.MAX_STANDARD_RESCHEDULE_COUNT); - return null; - } - } else if (backoffAttempts > mConstants.MAX_STANDARD_RESCHEDULE_COUNT) { - Slog.w(TAG, "Not rescheduling " + failureToReschedule + ": attempt #" - + backoffAttempts + " > std limit " + mConstants.MAX_STANDARD_RESCHEDULE_COUNT); - return null; - } - switch (job.getBackoffPolicy()) { case JobInfo.BACKOFF_POLICY_LINEAR: { long backoff = initialBackoffMillis; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index f706260edec2..1e89158ca4bb 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -17,6 +17,8 @@ package com.android.server.job.controllers; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; +import static com.android.server.job.JobSchedulerService.NEVER_INDEX; +import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import android.app.AppGlobals; @@ -63,26 +65,36 @@ import java.util.function.Predicate; * @hide */ public final class JobStatus { - static final String TAG = "JobSchedulerService"; + private static final String TAG = "JobScheduler.JobStatus"; static final boolean DEBUG = JobSchedulerService.DEBUG; public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE; public static final long NO_EARLIEST_RUNTIME = 0L; - public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0 - public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2 - public static final int CONSTRAINT_BATTERY_NOT_LOW = - JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1 + static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0 + static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2 + static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1 static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3 static final int CONSTRAINT_TIMING_DELAY = 1<<31; static final int CONSTRAINT_DEADLINE = 1<<30; - public static final int CONSTRAINT_CONNECTIVITY = 1 << 28; + static final int CONSTRAINT_CONNECTIVITY = 1 << 28; static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26; static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint /** + * The additional set of dynamic constraints that must be met if the job's effective bucket is + * {@link JobSchedulerService#RESTRICTED_INDEX}. Connectivity can be ignored if the job doesn't + * need network. + */ + private static final int DYNAMIC_RESTRICTED_CONSTRAINTS = + CONSTRAINT_BATTERY_NOT_LOW + | CONSTRAINT_CHARGING + | CONSTRAINT_CONNECTIVITY + | CONSTRAINT_IDLE; + + /** * The constraints that we want to log to statsd. * * Constraints that can be inferred from other atoms have been excluded to avoid logging too @@ -419,7 +431,11 @@ public final class JobStatus { this.requiredConstraints = requiredConstraints; mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST; mReadyNotDozing = (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0; - mReadyDynamicSatisfied = true; + if (standbyBucket == RESTRICTED_INDEX) { + addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS); + } else { + mReadyDynamicSatisfied = true; + } mLastSuccessfulRunTime = lastSuccessfulRunTime; mLastFailedRunTime = lastFailedRunTime; @@ -727,6 +743,14 @@ public final class JobStatus { } public void setStandbyBucket(int newBucket) { + if (newBucket == RESTRICTED_INDEX) { + // Adding to the bucket. + addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS); + } else if (standbyBucket == RESTRICTED_INDEX) { + // Removing from the RESTRICTED bucket. + removeDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS); + } + standbyBucket = newBucket; } @@ -1054,6 +1078,11 @@ public final class JobStatus { if (old == state) { return false; } + if (DEBUG) { + Slog.v(TAG, + "Constraint " + constraint + " is " + (!state ? "NOT " : "") + "satisfied for " + + toShortString()); + } satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0); mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST; mReadyDynamicSatisfied = @@ -1086,38 +1115,40 @@ public final class JobStatus { } /** - * Indicates that this job cannot run without the specified constraint. This is evaluated + * Indicates that this job cannot run without the specified constraints. This is evaluated * separately from the job's explicitly requested constraints and MUST be satisfied before * the job can run if the app doesn't have quota. - * */ - public void addDynamicConstraint(int constraint) { - if (constraint == CONSTRAINT_WITHIN_QUOTA) { + private void addDynamicConstraints(int constraints) { + if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) { + // Quota should never be used as a dynamic constraint. Slog.wtf(TAG, "Tried to set quota as a dynamic constraint"); - return; + constraints &= ~CONSTRAINT_WITHIN_QUOTA; } // Connectivity and content trigger are special since they're only valid to add if the // job has requested network or specific content URIs. Adding these constraints to jobs // that don't need them doesn't make sense. - if ((constraint == CONSTRAINT_CONNECTIVITY && !hasConnectivityConstraint()) - || (constraint == CONSTRAINT_CONTENT_TRIGGER && !hasContentTriggerConstraint())) { - return; + if (!hasConnectivityConstraint()) { + constraints &= ~CONSTRAINT_CONNECTIVITY; + } + if (!hasContentTriggerConstraint()) { + constraints &= ~CONSTRAINT_CONTENT_TRIGGER; } - mDynamicConstraints |= constraint; + mDynamicConstraints |= constraints; mReadyDynamicSatisfied = mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints); } /** - * Removes a dynamic constraint from a job, meaning that the requirement is not required for + * Removes dynamic constraints from a job, meaning that the requirements are not required for * the job to run (if the job itself hasn't requested the constraint. This is separate from * the job's explicitly requested constraints and does not remove those requested constraints. * */ - public void removeDynamicConstraint(int constraint) { - mDynamicConstraints &= ~constraint; + private void removeDynamicConstraints(int constraints) { + mDynamicConstraints &= ~constraints; mReadyDynamicSatisfied = mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints); } @@ -1193,7 +1224,11 @@ public final class JobStatus { private boolean isReady(int satisfiedConstraints) { // Quota and dynamic constraints trump all other constraints. - if (!mReadyWithinQuota && !mReadyDynamicSatisfied) { + // NEVER jobs are not supposed to run at all. Since we're using quota to allow parole + // sessions (exempt from dynamic restrictions), we need the additional check to ensure + // that NEVER jobs don't run. + // TODO: cleanup quota and standby bucket management so we don't need the additional checks + if ((!mReadyWithinQuota && !mReadyDynamicSatisfied) || standbyBucket == NEVER_INDEX) { return false; } // Deadline constraint trumps other constraints besides quota and dynamic (except for diff --git a/apex/sdkextensions/framework/java/android/os/ext/test/Test.java b/apex/sdkextensions/framework/java/android/os/ext/test/Test.java new file mode 100644 index 000000000000..1715f498be1e --- /dev/null +++ b/apex/sdkextensions/framework/java/android/os/ext/test/Test.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.ext.test; + +import android.annotation.SystemApi; + +/** + * This class exists temporarily to verify SDK updates are working properly. + * @deprecated Do not use. + */ +@Deprecated +public class Test { + + public Test() { } + + /** @hide */ + public void testA() {} + + /** @hide */ + public void testB() {} + + /** @hide */ + public void testC() {} + + /** @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public void testD() {} + + public void testE() {} + + /** @hide */ + @SystemApi + public void testF() {} + + /** @hide */ + @SystemApi + public void testG() {} + +} diff --git a/api/current.txt b/api/current.txt index bdc98146ac7e..fb73f2ac6f84 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2873,7 +2873,7 @@ package android.accessibilityservice { method public void onSystemActionsChanged(); method public final boolean performGlobalAction(int); method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); - method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.Bitmap>); + method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.accessibilityservice.AccessibilityService.ScreenshotResult>); field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14 field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13 field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a @@ -2952,6 +2952,12 @@ package android.accessibilityservice { method public void onMagnificationChanged(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController, @NonNull android.graphics.Region, float, float, float); } + public static final class AccessibilityService.ScreenshotResult { + method @Nullable public android.graphics.ColorSpace getColorSpace(); + method @NonNull public android.hardware.HardwareBuffer getHardwareBuffer(); + method public long getTimestamp(); + } + public static final class AccessibilityService.SoftKeyboardController { method public void addOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener); method public void addOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener, @Nullable android.os.Handler); @@ -4009,7 +4015,7 @@ package android.app { method public android.util.Size getAppTaskThumbnailSize(); method public java.util.List<android.app.ActivityManager.AppTask> getAppTasks(); method public android.content.pm.ConfigurationInfo getDeviceConfigurationInfo(); - method @Nullable public java.util.List<android.app.ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String, @IntRange(from=0) int, @IntRange(from=0) int); + method @NonNull public java.util.List<android.app.ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String, @IntRange(from=0) int, @IntRange(from=0) int); method public int getLargeMemoryClass(); method public int getLauncherLargeIconDensity(); method public int getLauncherLargeIconSize(); @@ -4549,12 +4555,13 @@ package android.app { method public int getPackageUid(); method public int getPid(); method @NonNull public String getProcessName(); - method public int getPss(); + method public long getPss(); method public int getRealUid(); method public int getReason(); - method public int getRss(); + method public long getRss(); method public int getStatus(); method public long getTimestamp(); + method @NonNull public android.os.UserHandle getUserHandle(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.ApplicationExitInfo> CREATOR; field public static final int REASON_ANR = 6; // 0x6 @@ -6810,7 +6817,7 @@ package android.app.admin { ctor public DevicePolicyKeyguardService(); method @Nullable public void dismiss(); method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @Nullable public android.view.SurfaceControl onSurfaceReady(@Nullable android.os.IBinder); + method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage onSurfaceReady(@Nullable android.os.IBinder); } public class DevicePolicyManager { @@ -29696,7 +29703,7 @@ package android.net { method public long getReportTimestamp(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.ConnectivityReport> CREATOR; - field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttemped"; + field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttempted"; field public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = "networkProbesSucceeded"; field public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult"; field public static final int NETWORK_PROBE_DNS = 4; // 0x4 @@ -31289,6 +31296,7 @@ package android.net.wifi { method public boolean isP2pSupported(); method public boolean isPreferredNetworkOffloadSupported(); method @Deprecated public boolean isScanAlwaysAvailable(); + method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isScanThrottleEnabled(); method public boolean isStaApConcurrencySupported(); method public boolean isTdlsSupported(); method public boolean isWapiSupported(); @@ -31761,7 +31769,7 @@ package android.net.wifi.p2p { field public static final int GROUP_OWNER_INTENT_MAX = 15; // 0xf field public static final int GROUP_OWNER_INTENT_MIN = 0; // 0x0 field public String deviceAddress; - field public int groupOwnerIntent; + field @IntRange(from=0, to=15) public int groupOwnerIntent; field public android.net.wifi.WpsInfo wps; } @@ -31953,14 +31961,14 @@ package android.net.wifi.p2p { method public int getDeviceType(); method public int getMaxThroughput(); method public boolean isContentProtectionSupported(); + method public boolean isEnabled(); method public boolean isSessionAvailable(); - method public boolean isWfdEnabled(); method public void setContentProtectionSupported(boolean); - method public void setControlPort(int); + method public void setControlPort(@IntRange(from=0) int); method public boolean setDeviceType(int); - method public void setMaxThroughput(int); + method public void setEnabled(boolean); + method public void setMaxThroughput(@IntRange(from=0) int); method public void setSessionAvailable(boolean); - method public void setWfdEnabled(boolean); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.p2p.WifiP2pWfdInfo> CREATOR; field public static final int DEVICE_TYPE_PRIMARY_SINK = 1; // 0x1 @@ -37039,6 +37047,15 @@ package android.os { } +package android.os.ext.test { + + @Deprecated public class Test { + ctor @Deprecated public Test(); + method @Deprecated public void testE(); + } + +} + package android.os.health { public class HealthStats { @@ -43988,7 +44005,7 @@ package android.service.quicksettings { field public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE"; field public static final String ACTION_QS_TILE_PREFERENCES = "android.service.quicksettings.action.QS_TILE_PREFERENCES"; field public static final String META_DATA_ACTIVE_TILE = "android.service.quicksettings.ACTIVE_TILE"; - field public static final String META_DATA_BOOLEAN_TILE = "android.service.quicksettings.BOOLEAN_TILE"; + field public static final String META_DATA_TOGGLEABLE_TILE = "android.service.quicksettings.TOGGLEABLE_TILE"; } } @@ -60828,7 +60845,6 @@ package android.widget { method public void setTypeface(@Nullable android.graphics.Typeface, int); method public void setTypeface(@Nullable android.graphics.Typeface); method public void setWidth(int); - field public static final int ACCESSIBILITY_ACTION_IME_ENTER = 16908372; // 0x1020054 field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0 field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1 } diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index 90531b16a516..59aa145ce1e9 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -1,4 +1,133 @@ // Signature format: 2.0 +package android.net { + + public final class TetheredClient implements android.os.Parcelable { + ctor public TetheredClient(@NonNull android.net.MacAddress, @NonNull java.util.Collection<android.net.TetheredClient.AddressInfo>, int); + method public int describeContents(); + method @NonNull public java.util.List<android.net.TetheredClient.AddressInfo> getAddresses(); + method @NonNull public android.net.MacAddress getMacAddress(); + method public int getTetheringType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient> CREATOR; + } + + public static final class TetheredClient.AddressInfo implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.net.LinkAddress getAddress(); + method @Nullable public String getHostname(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient.AddressInfo> CREATOR; + } + + public class TetheringConstants { + ctor public TetheringConstants(); + field public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; + field public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback"; + field public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType"; + field public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; + field public static final String EXTRA_SET_ALARM = "extraSetAlarm"; + } + + public class TetheringManager { + ctor public TetheringManager(@NonNull android.content.Context, @NonNull java.util.function.Supplier<android.os.IBinder>); + method public int getLastTetherError(@NonNull String); + method @NonNull public String[] getTetherableBluetoothRegexs(); + method @NonNull public String[] getTetherableIfaces(); + method @NonNull public String[] getTetherableUsbRegexs(); + method @NonNull public String[] getTetherableWifiRegexs(); + method @NonNull public String[] getTetheredIfaces(); + method @NonNull public String[] getTetheringErroredIfaces(); + method public boolean isTetheringSupported(); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback); + method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener); + method public void requestLatestTetheringEntitlementResult(int, @NonNull android.os.ResultReceiver, boolean); + method @Deprecated public int setUsbTethering(boolean); + method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback); + method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback); + method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering(); + method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int); + method @Deprecated public int tether(@NonNull String); + method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback); + method @Deprecated public int untether(@NonNull String); + field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED"; + field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY"; + field public static final String EXTRA_ACTIVE_TETHER = "tetherArray"; + field public static final String EXTRA_AVAILABLE_TETHER = "availableArray"; + field public static final String EXTRA_ERRORED_TETHER = "erroredArray"; + field public static final int TETHERING_BLUETOOTH = 2; // 0x2 + field public static final int TETHERING_ETHERNET = 5; // 0x5 + field public static final int TETHERING_INVALID = -1; // 0xffffffff + field public static final int TETHERING_NCM = 4; // 0x4 + field public static final int TETHERING_USB = 1; // 0x1 + field public static final int TETHERING_WIFI = 0; // 0x0 + field public static final int TETHERING_WIFI_P2P = 3; // 0x3 + field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc + field public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9; // 0x9 + field public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8; // 0x8 + field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd + field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa + field public static final int TETHER_ERROR_MASTER_ERROR = 5; // 0x5 + field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf + field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe + field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 + field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb + field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2 + field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6 + field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4 + field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1 + field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3 + field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7 + } + + public static interface TetheringManager.OnTetheringEntitlementResultListener { + method public void onTetheringEntitlementResult(int); + } + + public abstract static class TetheringManager.StartTetheringCallback { + ctor public TetheringManager.StartTetheringCallback(); + method public void onTetheringFailed(int); + method public void onTetheringStarted(); + } + + public abstract static class TetheringManager.TetheringEventCallback { + ctor public TetheringManager.TetheringEventCallback(); + method public void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>); + method public void onError(@NonNull String, int); + method @Deprecated public void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps); + method public void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>); + method public void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>); + method public void onTetheringSupported(boolean); + method public void onUpstreamChanged(@Nullable android.net.Network); + } + + @Deprecated public static class TetheringManager.TetheringInterfaceRegexps { + ctor @Deprecated public TetheringManager.TetheringInterfaceRegexps(@NonNull String[], @NonNull String[], @NonNull String[]); + method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableBluetoothRegexs(); + method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableUsbRegexs(); + method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs(); + } + + public static class TetheringManager.TetheringRequest { + } + + public static class TetheringManager.TetheringRequest.Builder { + ctor public TetheringManager.TetheringRequest.Builder(int); + method @NonNull public android.net.TetheringManager.TetheringRequest build(); + method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean); + method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setSilentProvisioning(boolean); + method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder useStaticIpv4Addresses(@NonNull android.net.LinkAddress); + } + +} + +package android.os.ext.test { + + @Deprecated public class Test { + method @Deprecated public void testD(); + } + +} + package android.util { public final class Log { diff --git a/api/system-current.txt b/api/system-current.txt index ff9391d9877d..bf87e55bd017 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -585,10 +585,6 @@ package android.app { field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR; } - public final class ApplicationExitInfo implements android.os.Parcelable { - method @NonNull public android.os.UserHandle getUserHandle(); - } - public class BroadcastOptions { method public static android.app.BroadcastOptions makeBasic(); method @RequiresPermission("android.permission.START_ACTIVITIES_FROM_BACKGROUND") public void setBackgroundActivityStartsAllowed(boolean); @@ -2212,6 +2208,7 @@ package android.content.pm { field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES"; field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS"; field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio"; + field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery"; field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow"; field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock"; field public static final int FLAGS_PERMISSION_RESERVED_PERMISSIONCONTROLLER = -268435456; // 0xf0000000 @@ -3704,8 +3701,8 @@ package android.hardware.soundtrigger { public static final class SoundTrigger.ModuleProperties implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); - field public static final int CAPABILITY_ECHO_CANCELLATION = 1; // 0x1 - field public static final int CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2 + field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1 + field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2 field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> CREATOR; field public final int audioCapabilities; field @NonNull public final String description; @@ -4215,15 +4212,15 @@ package android.media { method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioAttributes.Builder setSystemUsage(int); } - public final class AudioDeviceAddress implements android.os.Parcelable { - ctor public AudioDeviceAddress(@NonNull android.media.AudioDeviceInfo); - ctor public AudioDeviceAddress(int, int, @NonNull String); + public final class AudioDevice implements android.os.Parcelable { + ctor public AudioDevice(@NonNull android.media.AudioDeviceInfo); + ctor public AudioDevice(int, int, @NonNull String); method public int describeContents(); method @NonNull public String getAddress(); method public int getRole(); method public int getType(); method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioDeviceAddress> CREATOR; + field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioDevice> CREATOR; field public static final int ROLE_INPUT = 1; // 0x1 field public static final int ROLE_OUTPUT = 2; // 0x2 } @@ -4260,11 +4257,11 @@ package android.media { method @IntRange(from=0) public int getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies(); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups(); - method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAddress> getDevicesForAttributes(@NonNull android.media.AudioAttributes); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDevice> getDevicesForAttributes(@NonNull android.media.AudioAttributes); method @IntRange(from=0) public int getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); - method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAddress getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); + method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDevice getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages(); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method public boolean isAudioServerRunning(); @@ -4278,7 +4275,7 @@ package android.media { method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) int); method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy); - method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAddress); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDevice); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy); @@ -4374,7 +4371,7 @@ package android.media { package android.media.audiofx { public class AudioEffect { - ctor @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public AudioEffect(@NonNull java.util.UUID, @NonNull android.media.AudioDeviceAddress); + ctor @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public AudioEffect(@NonNull java.util.UUID, @NonNull android.media.AudioDevice); } } @@ -7692,6 +7689,7 @@ package android.net.wifi { method @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(int); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMacRandomizationSettingPasspointEnabled(@NonNull String, boolean); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMeteredOverridePasspoint(@NonNull String, int); + method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setScanThrottleEnabled(boolean); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public boolean setSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setVerboseLoggingEnabled(boolean); method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration); @@ -7836,23 +7834,23 @@ package android.net.wifi { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int); } - public final class WifiOemConfigStoreMigrationHook { - method @Nullable public static android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData load(); + public final class WifiOemMigrationHook { + method @Nullable public static android.net.wifi.WifiOemMigrationHook.ConfigStoreMigrationData loadFromConfigStore(); } - public static final class WifiOemConfigStoreMigrationHook.MigrationData implements android.os.Parcelable { + public static final class WifiOemMigrationHook.ConfigStoreMigrationData implements android.os.Parcelable { method public int describeContents(); method @Nullable public java.util.List<android.net.wifi.WifiConfiguration> getUserSavedNetworkConfigurations(); method @Nullable public android.net.wifi.SoftApConfiguration getUserSoftApConfiguration(); method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData> CREATOR; + field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiOemMigrationHook.ConfigStoreMigrationData> CREATOR; } - public static final class WifiOemConfigStoreMigrationHook.MigrationData.Builder { - ctor public WifiOemConfigStoreMigrationHook.MigrationData.Builder(); - method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData build(); - method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSavedNetworkConfigurations(@NonNull java.util.List<android.net.wifi.WifiConfiguration>); - method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration); + public static final class WifiOemMigrationHook.ConfigStoreMigrationData.Builder { + ctor public WifiOemMigrationHook.ConfigStoreMigrationData.Builder(); + method @NonNull public android.net.wifi.WifiOemMigrationHook.ConfigStoreMigrationData build(); + method @NonNull public android.net.wifi.WifiOemMigrationHook.ConfigStoreMigrationData.Builder setUserSavedNetworkConfigurations(@NonNull java.util.List<android.net.wifi.WifiConfiguration>); + method @NonNull public android.net.wifi.WifiOemMigrationHook.ConfigStoreMigrationData.Builder setUserSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration); } public class WifiScanner { @@ -8110,7 +8108,7 @@ package android.net.wifi.p2p { public final class WifiP2pGroupList implements android.os.Parcelable { method public int describeContents(); - method @NonNull public java.util.Collection<android.net.wifi.p2p.WifiP2pGroup> getGroupList(); + method @NonNull public java.util.List<android.net.wifi.p2p.WifiP2pGroup> getGroupList(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.p2p.WifiP2pGroupList> CREATOR; } @@ -8118,12 +8116,13 @@ package android.net.wifi.p2p { public class WifiP2pManager { method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.OVERRIDE_WIFI_CONFIG}) public void deletePersistentGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, int, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void factoryReset(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener); - method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void listen(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, boolean, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public void requestPersistentGroupInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pManager.PersistentGroupInfoListener); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.OVERRIDE_WIFI_CONFIG}) public void setDeviceName(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull String, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener); method @RequiresPermission(allOf={android.Manifest.permission.CONNECTIVITY_INTERNAL, android.Manifest.permission.CONFIGURE_WIFI_DISPLAY}) public void setMiracastMode(int); method @RequiresPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) public void setWfdInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pWfdInfo, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.OVERRIDE_WIFI_CONFIG}) public void setWifiP2pChannels(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, int, int, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void startListening(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void stopListening(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener); field public static final String ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED = "android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED"; field public static final int MIRACAST_DISABLED = 0; // 0x0 field public static final int MIRACAST_SINK = 2; // 0x2 @@ -9143,6 +9142,15 @@ package android.os.ext { } +package android.os.ext.test { + + @Deprecated public class Test { + method @Deprecated public void testF(); + method @Deprecated public void testG(); + } + +} + package android.os.image { public class DynamicSystemClient { @@ -12423,7 +12431,6 @@ package android.telephony { field public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // 0x0 field public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; // 0xffffffff field public static final int CHANGE_ICC_LOCK_SUCCESS = 2147483647; // 0x7fffffff - field public static final int DEFAULT_PREFERRED_NETWORK_MODE = 0; // 0x0 field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION"; field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID"; field @Deprecated public static final String EXTRA_APN_PROTOCOL = "apnProto"; @@ -14129,6 +14136,10 @@ package android.view.contentcapture { method public boolean isContentCaptureFeatureEnabled(); } + public abstract class ContentCaptureSession implements java.lang.AutoCloseable { + field public static final int NO_SESSION_ID = 0; // 0x0 + } + public final class ViewNode extends android.app.assist.AssistStructure.ViewNode { method @Nullable public android.view.autofill.AutofillId getParentAutofillId(); } diff --git a/api/test-current.txt b/api/test-current.txt index e1f83822609d..826c27546aad 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -2539,6 +2539,7 @@ package android.os { } public class UserManager { + method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS"}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle); method public static boolean isSplitSystemUser(); field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED"; } diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h index de1dbc90eb2d..c643b0e8800c 100644 --- a/cmds/idmap2/include/idmap2/ResourceUtils.h +++ b/cmds/idmap2/include/idmap2/ResourceUtils.h @@ -37,6 +37,10 @@ typedef uint16_t EntryId; // eeee in 0xpptteeee namespace utils { +// Returns whether the Res_value::data_type represents a dynamic or regular resource reference. +bool IsReference(uint8_t data_type); + +// Converts the Res_value::data_type to a human-readable string representation. StringPiece DataTypeToString(uint8_t data_type); struct OverlayManifestInfo { diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp index 407478945151..43cfec3f9cf9 100644 --- a/cmds/idmap2/libidmap2/ResourceMapping.cpp +++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp @@ -27,6 +27,7 @@ #include "idmap2/ResourceUtils.h" using android::base::StringPrintf; +using android::idmap2::utils::IsReference; using android::idmap2::utils::ResToTypeEntryName; namespace android::idmap2 { @@ -200,8 +201,7 @@ Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManage // Only rewrite resources defined within the overlay package to their corresponding target // resource ids at runtime. bool rewrite_overlay_reference = - (overlay_resource->dataType == Res_value::TYPE_REFERENCE || - overlay_resource->dataType == Res_value::TYPE_DYNAMIC_REFERENCE) + IsReference(overlay_resource->dataType) ? overlay_package_id == EXTRACT_PACKAGE(overlay_resource->data) : false; @@ -331,8 +331,13 @@ Result<ResourceMapping> ResourceMapping::FromApkAssets(const ApkAssets& target_a std::unique_ptr<uint8_t[]> string_pool_data; Result<ResourceMapping> resource_mapping = {{}}; if (overlay_info.resource_mapping != 0U) { + // Use the dynamic reference table to find the assigned resource id of the map xml. + const auto& ref_table = overlay_asset_manager.GetDynamicRefTableForCookie(0); + uint32_t resource_mapping_id = overlay_info.resource_mapping; + ref_table->lookupResourceId(&resource_mapping_id); + // Load the overlay resource mappings from the file specified using android:resourcesMap. - auto asset = OpenNonAssetFromResource(overlay_info.resource_mapping, overlay_asset_manager); + auto asset = OpenNonAssetFromResource(resource_mapping_id, overlay_asset_manager); if (!asset) { return Error("failed opening xml for android:resourcesMap: %s", asset.GetErrorMessage().c_str()); @@ -404,8 +409,7 @@ Result<Unit> ResourceMapping::AddMapping(ResourceId target_resource, target_map_.insert(std::make_pair(target_resource, TargetValue{data_type, data_value})); - if (rewrite_overlay_reference && - (data_type == Res_value::TYPE_REFERENCE || data_type == Res_value::TYPE_DYNAMIC_REFERENCE)) { + if (rewrite_overlay_reference && IsReference(data_type)) { overlay_map_.insert(std::make_pair(data_value, target_resource)); } @@ -421,8 +425,7 @@ void ResourceMapping::RemoveMapping(ResourceId target_resource) { const TargetValue value = target_iter->second; target_map_.erase(target_iter); - if (value.data_type != Res_value::TYPE_REFERENCE && - value.data_type != Res_value::TYPE_DYNAMIC_REFERENCE) { + if (!IsReference(value.data_type)) { return; } diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp index a5df746ca733..98d026bc70dc 100644 --- a/cmds/idmap2/libidmap2/ResourceUtils.cpp +++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp @@ -33,6 +33,10 @@ using android::util::Utf16ToUtf8; namespace android::idmap2::utils { +bool IsReference(uint8_t data_type) { + return data_type == Res_value::TYPE_REFERENCE || data_type == Res_value::TYPE_DYNAMIC_REFERENCE; +} + StringPiece DataTypeToString(uint8_t data_type) { switch (data_type) { case Res_value::TYPE_NULL: @@ -133,7 +137,7 @@ Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path, } if (auto result_value = overlay_it->GetAttributeValue("resourcesMap")) { - if ((*result_value).dataType == Res_value::TYPE_REFERENCE) { + if (IsReference((*result_value).dataType)) { info.resource_mapping = (*result_value).data; } else { return Error("android:resourcesMap is not a reference in AndroidManifest.xml of %s", diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp index f55acee029dc..8af4037be954 100644 --- a/cmds/idmap2/tests/FileUtilsTests.cpp +++ b/cmds/idmap2/tests/FileUtilsTests.cpp @@ -56,12 +56,12 @@ TEST(FileUtilsTests, FindFilesFindApkFilesRecursive) { return type == DT_REG && path.size() > 4 && path.compare(path.size() - 4, 4, ".apk") == 0; }); ASSERT_THAT(v, NotNull()); - ASSERT_EQ(v->size(), 10U); + ASSERT_EQ(v->size(), 11U); ASSERT_EQ(std::set<std::string>(v->begin(), v->end()), std::set<std::string>( {root + "/target/target.apk", root + "/target/target-no-overlayable.apk", root + "/overlay/overlay.apk", root + "/overlay/overlay-no-name.apk", - root + "/overlay/overlay-no-name-static.apk", + root + "/overlay/overlay-no-name-static.apk", root + "/overlay/overlay-shared.apk", root + "/overlay/overlay-static-1.apk", root + "/overlay/overlay-static-2.apk", root + "/signature-overlay/signature-overlay.apk", root + "/system-overlay/system-overlay.apk", diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp index 4bc625565144..a2c156063757 100644 --- a/cmds/idmap2/tests/IdmapTests.cpp +++ b/cmds/idmap2/tests/IdmapTests.cpp @@ -247,6 +247,43 @@ TEST(IdmapTests, CreateIdmapDataFromApkAssets) { ASSERT_OVERLAY_ENTRY(overlay_entries[3], 0x7f020002, 0x7f02000f); } +TEST(IdmapTests, CreateIdmapDataFromApkAssetsSharedLibOverlay) { + std::string target_apk_path = GetTestDataPath() + "/target/target.apk"; + std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-shared.apk"; + + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::POLICY_PUBLIC, + /* enforce_overlayable */ true); + ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage(); + auto& idmap = *idmap_result; + ASSERT_THAT(idmap, NotNull()); + + const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); + ASSERT_EQ(dataBlocks.size(), 1U); + + const std::unique_ptr<const IdmapData>& data = dataBlocks[0]; + ASSERT_THAT(data, NotNull()); + + const auto& target_entries = data->GetTargetEntries(); + ASSERT_EQ(target_entries.size(), 4U); + ASSERT_TARGET_ENTRY(target_entries[0], 0x7f010000, Res_value::TYPE_DYNAMIC_REFERENCE, 0x00010000); + ASSERT_TARGET_ENTRY(target_entries[1], 0x7f02000c, Res_value::TYPE_DYNAMIC_REFERENCE, 0x00020000); + ASSERT_TARGET_ENTRY(target_entries[2], 0x7f02000e, Res_value::TYPE_DYNAMIC_REFERENCE, 0x00020001); + ASSERT_TARGET_ENTRY(target_entries[3], 0x7f02000f, Res_value::TYPE_DYNAMIC_REFERENCE, 0x00020002); + + const auto& overlay_entries = data->GetOverlayEntries(); + ASSERT_EQ(target_entries.size(), 4U); + ASSERT_OVERLAY_ENTRY(overlay_entries[0], 0x00010000, 0x7f010000); + ASSERT_OVERLAY_ENTRY(overlay_entries[1], 0x00020000, 0x7f02000c); + ASSERT_OVERLAY_ENTRY(overlay_entries[2], 0x00020001, 0x7f02000e); + ASSERT_OVERLAY_ENTRY(overlay_entries[3], 0x00020002, 0x7f02000f); +} + TEST(IdmapTests, CreateIdmapDataDoNotRewriteNonOverlayResourceId) { OverlayManifestInfo info{}; info.target_package = "test.target"; diff --git a/cmds/idmap2/tests/data/overlay/build b/cmds/idmap2/tests/data/overlay/build index b921b0d3d3ad..114b099598fa 100755 --- a/cmds/idmap2/tests/data/overlay/build +++ b/cmds/idmap2/tests/data/overlay/build @@ -51,4 +51,12 @@ aapt2 link \ -o overlay-static-2.apk \ compiled.flata +aapt2 link \ + --no-resource-removal \ + --shared-lib \ + -I "$FRAMEWORK_RES_APK" \ + --manifest AndroidManifest.xml \ + -o overlay-shared.apk \ + compiled.flata + rm compiled.flata diff --git a/cmds/idmap2/tests/data/overlay/overlay-shared.apk b/cmds/idmap2/tests/data/overlay/overlay-shared.apk Binary files differnew file mode 100644 index 000000000000..93dcc82f9358 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/overlay-shared.apk diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 3a2472efb601..70f309add5df 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -118,22 +118,20 @@ cc_defaults { ], static_libs: [ - "android.frameworks.stats@1.0", "libbase", "libcutils", "libprotoutil", "libstatslog", "libstatsmetadata", "libsysutils", + "libutils", ], shared_libs: [ "libbinder", - "libhidlbase", "libincident", "liblog", "libservices", "libstatssocket", - "libutils", ], } @@ -222,8 +220,6 @@ cc_binary { shared_libs: ["libgtest_prod"], - vintf_fragments: ["android.frameworks.stats@1.0-service.xml"], - init_rc: ["statsd.rc"], } diff --git a/cmds/statsd/android.frameworks.stats@1.0-service.xml b/cmds/statsd/android.frameworks.stats@1.0-service.xml deleted file mode 100644 index bb02f66a28b1..000000000000 --- a/cmds/statsd/android.frameworks.stats@1.0-service.xml +++ /dev/null @@ -1,11 +0,0 @@ -<manifest version="1.0" type="framework"> - <hal> - <name>android.frameworks.stats</name> - <transport>hwbinder</transport> - <version>1.0</version> - <interface> - <name>IStats</name> - <instance>default</instance> - </interface> - </hal> -</manifest> diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 0256e3617dd7..a06e59c8e409 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -1354,7 +1354,6 @@ Status StatsService::sendWatchdogRollbackOccurredAtom(const int32_t rollbackType return Status::ok(); } - Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) { ENFORCE_UID(AID_SYSTEM); // TODO: add verifier permission @@ -1372,103 +1371,6 @@ Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experiment return Status::ok(); } -hardware::Return<void> StatsService::reportSpeakerImpedance( - const SpeakerImpedance& speakerImpedance) { - android::util::stats_write(android::util::SPEAKER_IMPEDANCE_REPORTED, - speakerImpedance.speakerLocation, speakerImpedance.milliOhms); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportHardwareFailed(const HardwareFailed& hardwareFailed) { - android::util::stats_write(android::util::HARDWARE_FAILED, int32_t(hardwareFailed.hardwareType), - hardwareFailed.hardwareLocation, int32_t(hardwareFailed.errorCode)); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportPhysicalDropDetected( - const PhysicalDropDetected& physicalDropDetected) { - android::util::stats_write(android::util::PHYSICAL_DROP_DETECTED, - int32_t(physicalDropDetected.confidencePctg), physicalDropDetected.accelPeak, - physicalDropDetected.freefallDuration); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportChargeCycles(const ChargeCycles& chargeCycles) { - std::vector<int32_t> buckets = chargeCycles.cycleBucket; - int initialSize = buckets.size(); - for (int i = 0; i < 10 - initialSize; i++) { - buckets.push_back(-1); // Push -1 for buckets that do not exist. - } - android::util::stats_write(android::util::CHARGE_CYCLES_REPORTED, buckets[0], buckets[1], - buckets[2], buckets[3], buckets[4], buckets[5], buckets[6], buckets[7], buckets[8], - buckets[9]); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportBatteryHealthSnapshot( - const BatteryHealthSnapshotArgs& batteryHealthSnapshotArgs) { - android::util::stats_write(android::util::BATTERY_HEALTH_SNAPSHOT, - int32_t(batteryHealthSnapshotArgs.type), batteryHealthSnapshotArgs.temperatureDeciC, - batteryHealthSnapshotArgs.voltageMicroV, batteryHealthSnapshotArgs.currentMicroA, - batteryHealthSnapshotArgs.openCircuitVoltageMicroV, - batteryHealthSnapshotArgs.resistanceMicroOhm, batteryHealthSnapshotArgs.levelPercent); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportSlowIo(const SlowIo& slowIo) { - android::util::stats_write(android::util::SLOW_IO, int32_t(slowIo.operation), slowIo.count); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportBatteryCausedShutdown( - const BatteryCausedShutdown& batteryCausedShutdown) { - android::util::stats_write(android::util::BATTERY_CAUSED_SHUTDOWN, - batteryCausedShutdown.voltageMicroV); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportUsbPortOverheatEvent( - const UsbPortOverheatEvent& usbPortOverheatEvent) { - android::util::stats_write(android::util::USB_PORT_OVERHEAT_EVENT_REPORTED, - usbPortOverheatEvent.plugTemperatureDeciC, usbPortOverheatEvent.maxTemperatureDeciC, - usbPortOverheatEvent.timeToOverheat, usbPortOverheatEvent.timeToHysteresis, - usbPortOverheatEvent.timeToInactive); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportSpeechDspStat( - const SpeechDspStat& speechDspStat) { - android::util::stats_write(android::util::SPEECH_DSP_STAT_REPORTED, - speechDspStat.totalUptimeMillis, speechDspStat.totalDowntimeMillis, - speechDspStat.totalCrashCount, speechDspStat.totalRecoverCount); - - return hardware::Void(); -} - -hardware::Return<void> StatsService::reportVendorAtom(const VendorAtom& vendorAtom) { - std::string reverseDomainName = (std::string) vendorAtom.reverseDomainName; - if (vendorAtom.atomId < 100000 || vendorAtom.atomId >= 200000) { - ALOGE("Atom ID %ld is not a valid vendor atom ID", (long) vendorAtom.atomId); - return hardware::Void(); - } - if (reverseDomainName.length() > 50) { - ALOGE("Vendor atom reverse domain name %s is too long.", reverseDomainName.c_str()); - return hardware::Void(); - } - LogEvent event(getWallClockSec() * NS_PER_SEC, getElapsedRealtimeNs(), vendorAtom); - mProcessor->OnLogEvent(&event); - - return hardware::Void(); -} - void StatsService::binderDied(const wp <IBinder>& who) { ALOGW("statscompanion service died"); StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec()); diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index af3016f86773..82a5a5305df4 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -27,8 +27,6 @@ #include "shell/ShellSubscriber.h" #include "statscompanion_util.h" -#include <android/frameworks/stats/1.0/IStats.h> -#include <android/frameworks/stats/1.0/types.h> #include <android/os/BnStatsd.h> #include <android/os/IPendingIntentRef.h> #include <android/os/IStatsCompanionService.h> @@ -41,7 +39,6 @@ using namespace android; using namespace android::binder; -using namespace android::frameworks::stats::V1_0; using namespace android::os; using namespace std; @@ -49,10 +46,7 @@ namespace android { namespace os { namespace statsd { -using android::hardware::Return; - class StatsService : public BnStatsd, - public IStats, public IBinder::DeathRecipient { public: StatsService(const sp<Looper>& handlerLooper, std::shared_ptr<LogEventQueue> queue); @@ -207,61 +201,6 @@ public: */ virtual Status getRegisteredExperimentIds(std::vector<int64_t>* expIdsOut); - /** - * Binder call to get SpeakerImpedance atom. - */ - virtual Return<void> reportSpeakerImpedance(const SpeakerImpedance& speakerImpedance) override; - - /** - * Binder call to get HardwareFailed atom. - */ - virtual Return<void> reportHardwareFailed(const HardwareFailed& hardwareFailed) override; - - /** - * Binder call to get PhysicalDropDetected atom. - */ - virtual Return<void> reportPhysicalDropDetected( - const PhysicalDropDetected& physicalDropDetected) override; - - /** - * Binder call to get ChargeCyclesReported atom. - */ - virtual Return<void> reportChargeCycles(const ChargeCycles& chargeCycles) override; - - /** - * Binder call to get BatteryHealthSnapshot atom. - */ - virtual Return<void> reportBatteryHealthSnapshot( - const BatteryHealthSnapshotArgs& batteryHealthSnapshotArgs) override; - - /** - * Binder call to get SlowIo atom. - */ - virtual Return<void> reportSlowIo(const SlowIo& slowIo) override; - - /** - * Binder call to get BatteryCausedShutdown atom. - */ - virtual Return<void> reportBatteryCausedShutdown( - const BatteryCausedShutdown& batteryCausedShutdown) override; - - /** - * Binder call to get UsbPortOverheatEvent atom. - */ - virtual Return<void> reportUsbPortOverheatEvent( - const UsbPortOverheatEvent& usbPortOverheatEvent) override; - - /** - * Binder call to get Speech DSP state atom. - */ - virtual Return<void> reportSpeechDspStat( - const SpeechDspStat& speechDspStat) override; - - /** - * Binder call to get vendor atom. - */ - virtual Return<void> reportVendorAtom(const VendorAtom& vendorAtom) override; - /** IBinder::DeathRecipient */ virtual void binderDied(const wp<IBinder>& who) override; diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index e8fc603088a1..103eb0c78bd5 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -195,37 +195,6 @@ LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requi } LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, - const VendorAtom& vendorAtom) { - mLogdTimestampNs = wallClockTimestampNs; - mElapsedTimestampNs = elapsedTimestampNs; - mTagId = vendorAtom.atomId; - mLogUid = AID_STATSD; - - mValues.push_back( - FieldValue(Field(mTagId, getSimpleField(1)), Value(vendorAtom.reverseDomainName))); - for (int i = 0; i < (int)vendorAtom.values.size(); i++) { - switch (vendorAtom.values[i].getDiscriminator()) { - case VendorAtom::Value::hidl_discriminator::intValue: - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)), - Value(vendorAtom.values[i].intValue()))); - break; - case VendorAtom::Value::hidl_discriminator::longValue: - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)), - Value(vendorAtom.values[i].longValue()))); - break; - case VendorAtom::Value::hidl_discriminator::floatValue: - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)), - Value(vendorAtom.values[i].floatValue()))); - break; - case VendorAtom::Value::hidl_discriminator::stringValue: - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)), - Value(vendorAtom.values[i].stringValue()))); - break; - } - } -} - -LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const InstallTrainInfo& trainInfo) { mLogdTimestampNs = wallClockTimestampNs; mElapsedTimestampNs = elapsedTimestampNs; diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index e4b784e069ca..5509c093a6f6 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -18,7 +18,6 @@ #include "FieldValue.h" -#include <android/frameworks/stats/1.0/types.h> #include <android/util/ProtoOutputStream.h> #include <private/android_logger.h> #include <stats_event_list.h> @@ -27,8 +26,6 @@ #include <string> #include <vector> -using namespace android::frameworks::stats::V1_0; - namespace android { namespace os { namespace statsd { @@ -103,9 +100,6 @@ public: const std::vector<uint8_t>& experimentIds, int32_t userId); explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, - const VendorAtom& vendorAtom); - - explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const InstallTrainInfo& installTrainInfo); ~LogEvent(); diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp index 58bfeb337da4..140ef4e9cea9 100644 --- a/cmds/statsd/src/main.cpp +++ b/cmds/statsd/src/main.cpp @@ -23,7 +23,6 @@ #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> #include <binder/ProcessState.h> -#include <hidl/HidlTransportSupport.h> #include <utils/Looper.h> #include <stdio.h> @@ -75,8 +74,6 @@ int main(int /*argc*/, char** /*argv*/) { ps->giveThreadPoolName(); IPCThreadState::self()->disableBackgroundScheduling(true); - ::android::hardware::configureRpcThreadpool(4 /*threads*/, false /*willJoin*/); - std::shared_ptr<LogEventQueue> eventQueue = std::make_shared<LogEventQueue>(2000 /*buffer limit. Buffer is NOT pre-allocated*/); @@ -89,12 +86,6 @@ int main(int /*argc*/, char** /*argv*/) { return -1; } - auto ret = gStatsService->registerAsService(); - if (ret != ::android::OK) { - ALOGE("Failed to add service as HIDL service"); - return 1; // or handle error - } - registerSigHandler(); gStatsService->sayHiToStatsCompanion(); diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 11f7f465cca5..b65f68e177ca 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -28,7 +28,9 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.graphics.Bitmap; +import android.graphics.ColorSpace; import android.graphics.Region; +import android.hardware.HardwareBuffer; import android.os.Binder; import android.os.Build; import android.os.Handler; @@ -585,7 +587,12 @@ public abstract class AccessibilityService extends Service { private FingerprintGestureController mFingerprintGestureController; /** @hide */ - public static final String KEY_ACCESSIBILITY_SCREENSHOT = "screenshot"; + public static final String KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER = + "screenshot_hardwareBuffer"; + + /** @hide */ + public static final String KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID = + "screenshot_colorSpaceId"; /** * Callback for {@link android.view.accessibility.AccessibilityEvent}s. @@ -1888,8 +1895,9 @@ public abstract class AccessibilityService extends Service { } /** - * Takes a screenshot of the specified display and returns it by {@link Bitmap.Config#HARDWARE} - * format. + * Takes a screenshot of the specified display and returns it via an + * {@link AccessibilityService.ScreenshotResult}. You can use {@link Bitmap#wrapHardwareBuffer} + * to construct the bitmap from the ScreenshotResult's payload. * <p> * <strong>Note:</strong> In order to take screenshot your service has * to declare the capability to take screenshot by setting the @@ -1907,7 +1915,7 @@ public abstract class AccessibilityService extends Service { * @return {@code true} if the taking screenshot accepted, {@code false} if not. */ public boolean takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor, - @NonNull Consumer<Bitmap> callback) { + @NonNull Consumer<ScreenshotResult> callback) { Preconditions.checkNotNull(executor, "executor cannot be null"); Preconditions.checkNotNull(callback, "callback cannot be null"); final IAccessibilityServiceConnection connection = @@ -1917,14 +1925,22 @@ public abstract class AccessibilityService extends Service { return false; } try { - connection.takeScreenshotWithCallback(displayId, new RemoteCallback((result) -> { - final Bitmap screenshot = result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT); - final long identity = Binder.clearCallingIdentity(); - try { - executor.execute(() -> callback.accept(screenshot)); - } finally { - Binder.restoreCallingIdentity(identity); + connection.takeScreenshot(displayId, new RemoteCallback((result) -> { + if (result == null) { + sendScreenshotResult(executor, callback, null); + return; } + final HardwareBuffer hardwareBuffer = + result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER); + final int colorSpaceId = + result.getInt(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID); + ColorSpace colorSpace = null; + if (colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length) { + colorSpace = ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]); + } + ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer, + colorSpace, System.currentTimeMillis()); + sendScreenshotResult(executor, callback, screenshot); })); } catch (RemoteException re) { throw new RuntimeException(re); @@ -2323,4 +2339,67 @@ public abstract class AccessibilityService extends Service { this.handler = handler; } } + + private void sendScreenshotResult(Executor executor, Consumer<ScreenshotResult> callback, + ScreenshotResult screenshot) { + final ScreenshotResult result = screenshot; + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.accept(result)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * Class including hardwareBuffer, colorSpace, and timestamp to be the result for + * {@link AccessibilityService#takeScreenshot} API. + * <p> + * <strong>Note:</strong> colorSpace would be null if the name of this colorSpace isn't at + * {@link ColorSpace.Named}. + * </p> + */ + public static final class ScreenshotResult { + private final @NonNull HardwareBuffer mHardwareBuffer; + private final @Nullable ColorSpace mColorSpace; + private final long mTimestamp; + + private ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer, + @Nullable ColorSpace colorSpace, long timestamp) { + Preconditions.checkNotNull(hardwareBuffer, "hardwareBuffer cannot be null"); + mHardwareBuffer = hardwareBuffer; + mColorSpace = colorSpace; + mTimestamp = timestamp; + } + + /** + * Gets the colorSpace identifying a specific organization of colors of the screenshot. + * + * @return the colorSpace or {@code null} if the name of colorSpace isn't at + * {@link ColorSpace.Named} + */ + @Nullable + public ColorSpace getColorSpace() { + return mColorSpace; + } + + /** + * Gets the hardwareBuffer representing a memory buffer of the screenshot. + * + * @return the hardwareBuffer + */ + @NonNull + public HardwareBuffer getHardwareBuffer() { + return mHardwareBuffer; + } + + /** + * Gets the timestamp of taking the screenshot. + * + * @return the timestamp from {@link System#currentTimeMillis()} + */ + public long getTimestamp() { + return mTimestamp; + }; + } } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 5db4dd7470d8..9177d4d27491 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -110,7 +110,5 @@ interface IAccessibilityServiceConnection { int getWindowIdForLeashToken(IBinder token); - Bitmap takeScreenshot(int displayId); - - void takeScreenshotWithCallback(int displayId, in RemoteCallback callback); + void takeScreenshot(int displayId, in RemoteCallback callback); } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index db9aa18dbd5a..7ee44053d4d5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -94,6 +94,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Locale; @@ -3555,13 +3556,13 @@ public class ActivityManager { * @return a list of {@link ApplicationExitInfo} records matching the criteria, sorted in * the order from most recent to least recent. */ - @Nullable + @NonNull public List<ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String packageName, @IntRange(from = 0) int pid, @IntRange(from = 0) int maxNum) { try { ParceledListSlice<ApplicationExitInfo> r = getService().getHistoricalProcessExitReasons( packageName, pid, maxNum, mContext.getUserId()); - return r == null ? null : r.getList(); + return r == null ? Collections.emptyList() : r.getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java index 4bf5f07e86be..c55453e94960 100644 --- a/core/java/android/app/ApplicationExitInfo.java +++ b/core/java/android/app/ApplicationExitInfo.java @@ -19,7 +19,6 @@ package android.app; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import android.app.ActivityManager.RunningAppProcessInfo.Importance; import android.icu.text.SimpleDateFormat; import android.os.Parcel; @@ -245,12 +244,12 @@ public final class ApplicationExitInfo implements Parcelable { /** * @see {@link #getPss} */ - private int mPss; + private long mPss; /** * @see {@link #getRss} */ - private int mRss; + private long mRss; /** * @see {@link #getTimestamp} @@ -385,7 +384,7 @@ public final class ApplicationExitInfo implements Parcelable { * it's NOT the exact memory information prior to its death; and it'll be zero * if the process died before system had a chance to take the sample. </p> */ - public int getPss() { + public long getPss() { return mPss; } @@ -396,12 +395,13 @@ public final class ApplicationExitInfo implements Parcelable { * it's NOT the exact memory information prior to its death; and it'll be zero * if the process died before system had a chance to take the sample. </p> */ - public int getRss() { + public long getRss() { return mRss; } /** - * The timestamp of the process's death, in milliseconds since the epoch. + * The timestamp of the process's death, in milliseconds since the epoch, + * as returned by {@link System#currentTimeMillis System.currentTimeMillis()}. */ public long getTimestamp() { return mTimestamp; @@ -409,6 +409,9 @@ public final class ApplicationExitInfo implements Parcelable { /** * The human readable description of the process's death, given by the system; could be null. + * + * <p class="note">Note: only intended to be human-readable and the system provides no + * guarantees that the format is stable across devices or Android releases.</p> */ public @Nullable String getDescription() { return mDescription; @@ -416,10 +419,7 @@ public final class ApplicationExitInfo implements Parcelable { /** * Return the user id of the record on a multi-user system. - * - * @hide */ - @SystemApi public @NonNull UserHandle getUserHandle() { return UserHandle.of(UserHandle.getUserId(mRealUid)); } @@ -546,7 +546,7 @@ public final class ApplicationExitInfo implements Parcelable { * * @hide */ - public void setPss(final int pss) { + public void setPss(final long pss) { mPss = pss; } @@ -555,7 +555,7 @@ public final class ApplicationExitInfo implements Parcelable { * * @hide */ - public void setRss(final int rss) { + public void setRss(final long rss) { mRss = rss; } @@ -630,8 +630,8 @@ public final class ApplicationExitInfo implements Parcelable { dest.writeInt(mSubReason); dest.writeInt(mStatus); dest.writeInt(mImportance); - dest.writeInt(mPss); - dest.writeInt(mRss); + dest.writeLong(mPss); + dest.writeLong(mRss); dest.writeLong(mTimestamp); dest.writeString(mDescription); } @@ -669,8 +669,8 @@ public final class ApplicationExitInfo implements Parcelable { mSubReason = in.readInt(); mStatus = in.readInt(); mImportance = in.readInt(); - mPss = in.readInt(); - mRss = in.readInt(); + mPss = in.readLong(); + mRss = in.readLong(); mTimestamp = in.readLong(); mDescription = in.readString(); } @@ -848,10 +848,10 @@ public final class ApplicationExitInfo implements Parcelable { mImportance = proto.readInt(ApplicationExitInfoProto.IMPORTANCE); break; case (int) ApplicationExitInfoProto.PSS: - mPss = proto.readInt(ApplicationExitInfoProto.PSS); + mPss = proto.readLong(ApplicationExitInfoProto.PSS); break; case (int) ApplicationExitInfoProto.RSS: - mRss = proto.readInt(ApplicationExitInfoProto.RSS); + mRss = proto.readLong(ApplicationExitInfoProto.RSS); break; case (int) ApplicationExitInfoProto.TIMESTAMP: mTimestamp = proto.readLong(ApplicationExitInfoProto.TIMESTAMP); @@ -891,8 +891,8 @@ public final class ApplicationExitInfo implements Parcelable { result = 31 * result + mSubReason; result = 31 * result + mImportance; result = 31 * result + mStatus; - result = 31 * result + mPss; - result = 31 * result + mRss; + result = 31 * result + (int) mPss; + result = 31 * result + (int) mRss; result = 31 * result + Long.hashCode(mTimestamp); result = 31 * result + Objects.hashCode(mProcessName); result = 31 * result + Objects.hashCode(mDescription); diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl index bfc42ef8848d..5d5956e4dca4 100644 --- a/core/java/android/app/ITaskOrganizerController.aidl +++ b/core/java/android/app/ITaskOrganizerController.aidl @@ -51,6 +51,9 @@ interface ITaskOrganizerController { /** Deletes a persistent root task in WM */ boolean deleteRootTask(IWindowContainer task); + /** Gets direct child tasks (ordered from top-to-bottom) */ + List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent); + /** Get the root task which contains the current ime target */ IWindowContainer getImeTarget(int display); diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java index 36ae450d342b..d279983a5793 100644 --- a/core/java/android/app/WindowContext.java +++ b/core/java/android/app/WindowContext.java @@ -60,12 +60,12 @@ public class WindowContext extends ContextWrapper { if (token != null && !isWindowToken(token)) { throw new IllegalArgumentException("Token must be registered to server."); } + mToken = token != null ? token : new Binder(); - final ContextImpl contextImpl = createBaseWindowContext(base, token); + final ContextImpl contextImpl = createBaseWindowContext(base, mToken); attachBaseContext(contextImpl); contextImpl.setOuterContext(this); - mToken = token != null ? token : new Binder(); mDisplayId = getDisplayId(); mWindowManager = new WindowManagerImpl(this); mWindowManager.setDefaultToken(mToken); diff --git a/core/java/android/app/admin/DevicePolicyKeyguardService.java b/core/java/android/app/admin/DevicePolicyKeyguardService.java index c2a76c5014c0..2ac5ebfc7c84 100644 --- a/core/java/android/app/admin/DevicePolicyKeyguardService.java +++ b/core/java/android/app/admin/DevicePolicyKeyguardService.java @@ -22,7 +22,7 @@ import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; -import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; /** * Client interface for providing the SystemUI with secondary lockscreen information. @@ -43,14 +43,14 @@ public class DevicePolicyKeyguardService extends Service { @Override public void onSurfaceReady(@Nullable IBinder hostInputToken, IKeyguardCallback callback) { mCallback = callback; - SurfaceControl surfaceControl = + SurfaceControlViewHost.SurfacePackage surfacePackage = DevicePolicyKeyguardService.this.onSurfaceReady(hostInputToken); if (mCallback != null) { try { - mCallback.onSurfaceControlCreated(surfaceControl); + mCallback.onRemoteContentReady(surfacePackage); } catch (RemoteException e) { - Log.e(TAG, "Failed to return created SurfaceControl", e); + Log.e(TAG, "Failed to return created SurfacePackage", e); } } } @@ -65,11 +65,11 @@ public class DevicePolicyKeyguardService extends Service { /** * Called by keyguard once the host surface for the secondary lockscreen is ready to display * remote content. - * @return the {@link SurfaceControl} for the Surface the secondary lockscreen content is - * attached to. + * @return the {@link SurfaceControlViewHost.SurfacePackage} for the Surface the + * secondary lockscreen content is attached to. */ @Nullable - public SurfaceControl onSurfaceReady(@Nullable IBinder hostInputToken) { + public SurfaceControlViewHost.SurfacePackage onSurfaceReady(@Nullable IBinder hostInputToken) { return null; } diff --git a/core/java/android/app/admin/IKeyguardCallback.aidl b/core/java/android/app/admin/IKeyguardCallback.aidl index 81e7d4dee902..856033dc8129 100644 --- a/core/java/android/app/admin/IKeyguardCallback.aidl +++ b/core/java/android/app/admin/IKeyguardCallback.aidl @@ -15,13 +15,13 @@ */ package android.app.admin; -import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; /** * Internal IPC interface for informing the keyguard of events on the secondary lockscreen. * @hide */ interface IKeyguardCallback { - oneway void onSurfaceControlCreated(in SurfaceControl remoteSurfaceControl); + oneway void onRemoteContentReady(in SurfaceControlViewHost.SurfacePackage surfacePackage); oneway void onDismiss(); } diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java index 6743a3f86944..9735f8157a57 100644 --- a/core/java/android/content/pm/PackageInfoLite.java +++ b/core/java/android/content/pm/PackageInfoLite.java @@ -74,6 +74,11 @@ public class PackageInfoLite implements Parcelable { public boolean multiArch; /** + * The android:debuggable flag from the package manifest. + */ + public boolean debuggable; + + /** * Specifies the recommended install location. Can be one of * {@link PackageHelper#RECOMMEND_INSTALL_INTERNAL} to install on internal storage, * {@link PackageHelper#RECOMMEND_INSTALL_EXTERNAL} to install on external media, @@ -108,6 +113,7 @@ public class PackageInfoLite implements Parcelable { dest.writeInt(recommendedInstallLocation); dest.writeInt(installLocation); dest.writeInt(multiArch ? 1 : 0); + dest.writeInt(debuggable ? 1 : 0); if (verifiers == null || verifiers.length == 0) { dest.writeInt(0); @@ -139,6 +145,7 @@ public class PackageInfoLite implements Parcelable { recommendedInstallLocation = source.readInt(); installLocation = source.readInt(); multiArch = (source.readInt() != 0); + debuggable = (source.readInt() != 0); final int verifiersLength = source.readInt(); if (verifiersLength == 0) { diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 5a0bcf04c87f..9f7f4821a11e 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -60,6 +60,7 @@ import android.os.PersistableBundle; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.os.incremental.IncrementalManager; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.util.AndroidException; @@ -2997,6 +2998,18 @@ public abstract class PackageManager { public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has + * the requisite kernel support to support incremental delivery aka Incremental FileSystem. + * + * @see IncrementalManager#isEnabled + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_INCREMENTAL_DELIVERY = + "android.software.incremental_delivery"; + + /** * Extra field name for the URI to a verification file. Passed to a package * verifier. * diff --git a/core/java/android/content/rollback/RollbackManager.java b/core/java/android/content/rollback/RollbackManager.java index 73b8a48d9153..7ebeb212b64a 100644 --- a/core/java/android/content/rollback/RollbackManager.java +++ b/core/java/android/content/rollback/RollbackManager.java @@ -216,6 +216,10 @@ public final class RollbackManager { * across device reboot, by simulating what happens on reboot without * actually rebooting the device. * + * Note rollbacks in the process of enabling will be lost after calling + * this method since they are not persisted yet. Don't call this method + * in the middle of the install process. + * * @throws SecurityException if the caller does not have appropriate permissions. * * @hide diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java index a30fd6b51e76..dbf33cade60c 100644 --- a/core/java/android/hardware/soundtrigger/ConversionUtil.java +++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java @@ -17,7 +17,6 @@ package android.hardware.soundtrigger; import android.annotation.Nullable; -import android.hardware.soundtrigger.ModelParams; import android.media.AudioFormat; import android.media.audio.common.AudioConfig; import android.media.soundtrigger_middleware.AudioCapabilities; @@ -333,20 +332,22 @@ class ConversionUtil { public static int aidl2apiAudioCapabilities(int aidlCapabilities) { int result = 0; if ((aidlCapabilities & AudioCapabilities.ECHO_CANCELLATION) != 0) { - result |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION; + result |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION; } if ((aidlCapabilities & AudioCapabilities.NOISE_SUPPRESSION) != 0) { - result |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION; + result |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION; } return result; } public static int api2aidlAudioCapabilities(int apiCapabilities) { int result = 0; - if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION) != 0) { + if ((apiCapabilities & SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION) + != 0) { result |= AudioCapabilities.ECHO_CANCELLATION; } - if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION) != 0) { + if ((apiCapabilities & SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION) + != 0) { result |= AudioCapabilities.NOISE_SUPPRESSION; } return result; diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index fc4c02243b9c..a74871d29041 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -97,8 +97,8 @@ public class SoundTrigger { */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = { - CAPABILITY_ECHO_CANCELLATION, - CAPABILITY_NOISE_SUPPRESSION + AUDIO_CAPABILITY_ECHO_CANCELLATION, + AUDIO_CAPABILITY_NOISE_SUPPRESSION }) public @interface AudioCapabilities {} @@ -106,12 +106,12 @@ public class SoundTrigger { * If set the underlying module supports AEC. * Describes bit field {@link ModuleProperties#audioCapabilities} */ - public static final int CAPABILITY_ECHO_CANCELLATION = 0x1; + public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 0x1; /** * If set, the underlying module supports noise suppression. * Describes bit field {@link ModuleProperties#audioCapabilities} */ - public static final int CAPABILITY_NOISE_SUPPRESSION = 0x2; + public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 0x2; /** Unique module ID provided by the native service */ public final int id; diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java index b128ea7f3e39..3c39d1558e87 100644 --- a/core/java/android/net/ConnectivityDiagnosticsManager.java +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -202,7 +202,7 @@ public class ConnectivityDiagnosticsManager { */ @NetworkProbe public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = - "networkProbesAttemped"; + "networkProbesAttempted"; /** @hide */ @StringDef(prefix = {"KEY_"}, value = { diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index f644f148a5ad..a3059486a35a 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2423,14 +2423,14 @@ public class ConnectivityManager { /** * Get the set of tethered dhcp ranges. * - * @return an array of 0 or more {@code String} of tethered dhcp ranges. - * @deprecated This API just return the default value which is not used in DhcpServer. + * @deprecated This method is not supported. + * TODO: remove this function when all of clients are removed. * {@hide} */ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) @Deprecated public String[] getTetheredDhcpRanges() { - return getTetheringManager().getTetheredDhcpRanges(); + throw new UnsupportedOperationException("getTetheredDhcpRanges is not supported"); } /** diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 25584f156084..3508b7031dd3 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -101,6 +101,8 @@ public final class Trace { public static final long TRACE_TAG_NNAPI = 1L << 25; /** @hide */ public static final long TRACE_TAG_RRO = 1L << 26; + /** @hide */ + public static final long TRACE_TAG_APEX_MANAGER = 1L << 18; private static final long TRACE_TAG_NOT_READY = 1L << 63; private static final int MAX_SECTION_NAME_LEN = 127; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 6ed1d2c345f8..b202053b5d65 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2325,10 +2325,12 @@ public class UserManager { * @param restrictionKey the string key representing the restriction * @param userHandle the UserHandle of the user for whom to retrieve the restrictions. */ + @TestApi @UnsupportedAppUsage - @RequiresPermission(Manifest.permission.MANAGE_USERS) - public boolean hasBaseUserRestriction(@UserRestrictionKey String restrictionKey, - UserHandle userHandle) { + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) + public boolean hasBaseUserRestriction(@UserRestrictionKey @NonNull String restrictionKey, + @NonNull UserHandle userHandle) { try { return mService.hasBaseUserRestriction(restrictionKey, userHandle.getIdentifier()); } catch (RemoteException re) { diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 35fa37a491de..0024ac7a6b2e 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -261,6 +261,13 @@ public final class IncrementalManager { } /** + * Checks if Incremental is enabled + */ + public static boolean isEnabled() { + return nativeIsEnabled(); + } + + /** * Checks if path is mounted on Incremental File System. */ public static boolean isIncrementalPath(@NonNull String path) { @@ -268,5 +275,6 @@ public final class IncrementalManager { } /* Native methods */ + private static native boolean nativeIsEnabled(); private static native boolean nativeIsIncrementalPath(@NonNull String path); } diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index 707426a22889..0edd01330fdd 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -345,9 +345,11 @@ public abstract class ContentCaptureService extends Service { } /** - * Notifies the service of {@link SnapshotData snapshot data} associated with a session. + * Notifies the service of {@link SnapshotData snapshot data} associated with an activity. * - * @param sessionId the session's Id + * @param sessionId the session's Id. This may also be + * {@link ContentCaptureSession#NO_SESSION_ID} if no content capture session + * exists for the activity being snapshotted * @param snapshotData the data */ public void onActivitySnapshot(@NonNull ContentCaptureSessionId sessionId, diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java index d0675ed4b99a..b4b5819c7d5f 100644 --- a/core/java/android/service/quicksettings/TileService.java +++ b/core/java/android/service/quicksettings/TileService.java @@ -126,22 +126,22 @@ public class TileService extends Service { = "android.service.quicksettings.ACTIVE_TILE"; /** - * Meta-data for a tile to support {@code BooleanState}. + * Meta-data for a tile to mark is toggleable. * <p> - * BooleanState is for tiles that should support switch tile behavior in accessibility. This is + * Toggleable tiles support switch tile behavior in accessibility. This is * the behavior of most of the framework tiles. * - * To make a TileService support BooleanState, set this meta-data to true on the TileService's - * manifest declaration. + * To indicate that a TileService is toggleable, set this meta-data to true on the + * TileService's manifest declaration. * <pre class="prettyprint"> * {@literal - * <meta-data android:name="android.service.quicksettings.BOOLEAN_TILE" + * <meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE" * android:value="true" /> * } * </pre> */ - public static final String META_DATA_BOOLEAN_TILE = - "android.service.quicksettings.BOOLEAN_TILE"; + public static final String META_DATA_TOGGLEABLE_TILE = + "android.service.quicksettings.TOGGLEABLE_TILE"; /** * Used to notify SysUI that Listening has be requested. diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index b9b33d0214e3..a88d389178bf 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -181,14 +181,14 @@ public class AlwaysOnHotwordDetector { * Returned by {@link #getSupportedAudioCapabilities()} */ public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = - SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION; + SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION; /** * If set, the underlying module supports noise suppression. * Returned by {@link #getSupportedAudioCapabilities()} */ public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = - SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION; + SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION; /** @hide */ @Retention(RetentionPolicy.SOURCE) diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index 64635098f7f4..1d3e6d035bfe 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -66,46 +66,48 @@ public class ApkSignatureVerifier { public static PackageParser.SigningDetails verify(String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion) throws PackageParserException { + return verifySignatures(apkPath, minSignatureSchemeVersion, true); + } + + /** + * Returns the certificates associated with each signer for the given APK without verification. + * This method is dangerous and should not be used, unless the caller is absolutely certain the + * APK is trusted. + * + * @throws PackageParserException if there was a problem collecting certificates. + */ + public static PackageParser.SigningDetails unsafeGetCertsWithoutVerification( + String apkPath, int minSignatureSchemeVersion) + throws PackageParserException { + return verifySignatures(apkPath, minSignatureSchemeVersion, false); + } + + /** + * Verifies the provided APK using all allowed signing schemas. + * @return the certificates associated with each signer. + * @param verifyFull whether to verify all contents of this APK or just collect certificates. + * @throws PackageParserException if there was a problem collecting certificates + */ + private static PackageParser.SigningDetails verifySignatures(String apkPath, + @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull) + throws PackageParserException { if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) { // V3 and before are older than the requested minimum signing version throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "No signature found in package of version " + minSignatureSchemeVersion - + " or newer for package " + apkPath); + + " or newer for package " + apkPath); } // first try v3 - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3"); try { - ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner = - ApkSignatureSchemeV3Verifier.verify(apkPath); - Certificate[][] signerCerts = new Certificate[][] { vSigner.certs }; - Signature[] signerSigs = convertToSignatures(signerCerts); - Signature[] pastSignerSigs = null; - if (vSigner.por != null) { - // populate proof-of-rotation information - pastSignerSigs = new Signature[vSigner.por.certs.size()]; - for (int i = 0; i < pastSignerSigs.length; i++) { - pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded()); - pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i)); - } - } - return new PackageParser.SigningDetails( - signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3, - pastSignerSigs); + return verifyV3Signature(apkPath, verifyFull); } catch (SignatureNotFoundException e) { // not signed with v3, try older if allowed if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "No APK Signature Scheme v3 signature in package " + apkPath, e); } - } catch (Exception e) { - // APK Signature Scheme v2 signature found but did not verify - throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "Failed to collect certificates from " + apkPath - + " using APK Signature Scheme v3", e); - } finally { - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } // redundant, protective version check @@ -117,26 +119,14 @@ public class ApkSignatureVerifier { } // try v2 - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2"); try { - Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath); - Signature[] signerSigs = convertToSignatures(signerCerts); - - return new PackageParser.SigningDetails( - signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V2); + return verifyV2Signature(apkPath, verifyFull); } catch (SignatureNotFoundException e) { // not signed with v2, try older if allowed if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "No APK Signature Scheme v2 signature in package " + apkPath, e); } - } catch (Exception e) { - // APK Signature Scheme v2 signature found but did not verify - throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "Failed to collect certificates from " + apkPath - + " using APK Signature Scheme v2", e); - } finally { - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } // redundant, protective version check @@ -148,14 +138,83 @@ public class ApkSignatureVerifier { } // v2 didn't work, try jarsigner - return verifyV1Signature(apkPath, true); + return verifyV1Signature(apkPath, verifyFull); } /** - * Verifies the provided APK and returns the certificates associated with each signer. + * Verifies the provided APK using V3 schema. * * @param verifyFull whether to verify all contents of this APK or just collect certificates. + * @return the certificates associated with each signer. + * @throws SignatureNotFoundException if there are no V3 signatures in the APK + * @throws PackageParserException if there was a problem collecting certificates + */ + private static PackageParser.SigningDetails verifyV3Signature(String apkPath, + boolean verifyFull) throws SignatureNotFoundException, PackageParserException { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV3" : "certsOnlyV3"); + try { + ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner = + verifyFull ? ApkSignatureSchemeV3Verifier.verify(apkPath) + : ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification( + apkPath); + Certificate[][] signerCerts = new Certificate[][]{vSigner.certs}; + Signature[] signerSigs = convertToSignatures(signerCerts); + Signature[] pastSignerSigs = null; + if (vSigner.por != null) { + // populate proof-of-rotation information + pastSignerSigs = new Signature[vSigner.por.certs.size()]; + for (int i = 0; i < pastSignerSigs.length; i++) { + pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded()); + pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i)); + } + } + return new PackageParser.SigningDetails(signerSigs, + SignatureSchemeVersion.SIGNING_BLOCK_V3, pastSignerSigs); + } catch (SignatureNotFoundException e) { + throw e; + } catch (Exception e) { + // APK Signature Scheme v3 signature found but did not verify + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "Failed to collect certificates from " + apkPath + + " using APK Signature Scheme v3", e); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + } + + /** + * Verifies the provided APK using V2 schema. * + * @param verifyFull whether to verify all contents of this APK or just collect certificates. + * @return the certificates associated with each signer. + * @throws SignatureNotFoundException if there are no V2 signatures in the APK + * @throws PackageParserException if there was a problem collecting certificates + */ + private static PackageParser.SigningDetails verifyV2Signature(String apkPath, + boolean verifyFull) throws SignatureNotFoundException, PackageParserException { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV2" : "certsOnlyV2"); + try { + Certificate[][] signerCerts = verifyFull ? ApkSignatureSchemeV2Verifier.verify(apkPath) + : ApkSignatureSchemeV2Verifier.unsafeGetCertsWithoutVerification(apkPath); + Signature[] signerSigs = convertToSignatures(signerCerts); + return new PackageParser.SigningDetails(signerSigs, + SignatureSchemeVersion.SIGNING_BLOCK_V2); + } catch (SignatureNotFoundException e) { + throw e; + } catch (Exception e) { + // APK Signature Scheme v2 signature found but did not verify + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "Failed to collect certificates from " + apkPath + + " using APK Signature Scheme v2", e); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + } + + /** + * Verifies the provided APK using JAR schema. + * @return the certificates associated with each signer. + * @param verifyFull whether to verify all contents of this APK or just collect certificates. * @throws PackageParserException if there was a problem collecting certificates */ private static PackageParser.SigningDetails verifyV1Signature( @@ -277,7 +336,7 @@ public class ApkSignatureVerifier { * * @throws CertificateEncodingException if it is unable to create a Signature object. */ - public static Signature[] convertToSignatures(Certificate[][] certs) + private static Signature[] convertToSignatures(Certificate[][] certs) throws CertificateEncodingException { final Signature[] res = new Signature[certs.length]; for (int i = 0; i < certs.length; i++) { @@ -296,99 +355,29 @@ public class ApkSignatureVerifier { } /** - * Returns the certificates associated with each signer for the given APK without verification. - * This method is dangerous and should not be used, unless the caller is absolutely certain the - * APK is trusted. - * - * @throws PackageParserException if the APK's signature failed to verify. - * or greater is not found, except in the case of no JAR signature. + * Returns the minimum signature scheme version required for an app targeting the specified + * {@code targetSdk}. */ - public static PackageParser.SigningDetails unsafeGetCertsWithoutVerification( - String apkPath, int minSignatureSchemeVersion) - throws PackageParserException { - - if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) { - // V3 and before are older than the requested minimum signing version - throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "No signature found in package of version " + minSignatureSchemeVersion - + " or newer for package " + apkPath); - } - - // first try v3 - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV3"); - try { - ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner = - ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath); - Certificate[][] signerCerts = new Certificate[][] { vSigner.certs }; - Signature[] signerSigs = convertToSignatures(signerCerts); - Signature[] pastSignerSigs = null; - if (vSigner.por != null) { - // populate proof-of-rotation information - pastSignerSigs = new Signature[vSigner.por.certs.size()]; - for (int i = 0; i < pastSignerSigs.length; i++) { - pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded()); - pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i)); - } - } - return new PackageParser.SigningDetails( - signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3, - pastSignerSigs); - } catch (SignatureNotFoundException e) { - // not signed with v3, try older if allowed - if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) { - throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "No APK Signature Scheme v3 signature in package " + apkPath, e); - } - } catch (Exception e) { - // APK Signature Scheme v3 signature found but did not verify - throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "Failed to collect certificates from " + apkPath - + " using APK Signature Scheme v3", e); - } finally { - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } - - // redundant, protective version check - if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) { - // V2 and before are older than the requested minimum signing version - throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "No signature found in package of version " + minSignatureSchemeVersion - + " or newer for package " + apkPath); + public static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk) { + if (targetSdk >= Build.VERSION_CODES.R) { + return SignatureSchemeVersion.SIGNING_BLOCK_V2; } + return SignatureSchemeVersion.JAR; + } - // first try v2 - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV2"); - try { - Certificate[][] signerCerts = - ApkSignatureSchemeV2Verifier.unsafeGetCertsWithoutVerification(apkPath); - Signature[] signerSigs = convertToSignatures(signerCerts); - return new PackageParser.SigningDetails(signerSigs, - SignatureSchemeVersion.SIGNING_BLOCK_V2); - } catch (SignatureNotFoundException e) { - // not signed with v2, try older if allowed - if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) { - throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "No APK Signature Scheme v2 signature in package " + apkPath, e); - } - } catch (Exception e) { - // APK Signature Scheme v2 signature found but did not verify - throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "Failed to collect certificates from " + apkPath - + " using APK Signature Scheme v2", e); - } finally { - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } + /** + * Result of a successful APK verification operation. + */ + public static class Result { + public final Certificate[][] certs; + public final Signature[] sigs; + public final int signatureSchemeVersion; - // redundant, protective version check - if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) { - // V1 and is older than the requested minimum signing version - throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "No signature found in package of version " + minSignatureSchemeVersion - + " or newer for package " + apkPath); + public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) { + this.certs = certs; + this.sigs = sigs; + this.signatureSchemeVersion = signingVersion; } - - // v2 didn't work, try jarsigner - return verifyV1Signature(apkPath, false); } /** @@ -416,7 +405,7 @@ public class ApkSignatureVerifier { */ public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory) throws IOException, SignatureNotFoundException, SecurityException, DigestException, - NoSuchAlgorithmException { + NoSuchAlgorithmException { // first try v3 try { return ApkSignatureSchemeV3Verifier.generateApkVerity(apkPath, bufferFactory); @@ -446,30 +435,4 @@ public class ApkSignatureVerifier { return null; } } - - /** - * Returns the minimum signature scheme version required for an app targeting the specified - * {@code targetSdk}. - */ - public static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdk) { - if (targetSdk >= Build.VERSION_CODES.R) { - return SignatureSchemeVersion.SIGNING_BLOCK_V2; - } - return SignatureSchemeVersion.JAR; - } - - /** - * Result of a successful APK verification operation. - */ - public static class Result { - public final Certificate[][] certs; - public final Signature[] sigs; - public final int signatureSchemeVersion; - - public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) { - this.certs = certs; - this.sigs = sigs; - this.signatureSchemeVersion = signingVersion; - } - } } diff --git a/core/java/android/view/SurfaceControlViewHost.aidl b/core/java/android/view/SurfaceControlViewHost.aidl new file mode 100644 index 000000000000..3b31ab834a51 --- /dev/null +++ b/core/java/android/view/SurfaceControlViewHost.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2020, 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.view; + +parcelable SurfaceControlViewHost.SurfacePackage;
\ No newline at end of file diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 159b93eb12dd..2c30d29d8da2 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -222,26 +222,26 @@ public final class ViewRootImpl implements ViewParent, * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static int sNewInsetsMode = - SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, 0); + public static final int NEW_INSETS_MODE_NONE = 0; /** * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static final int NEW_INSETS_MODE_NONE = 0; + public static final int NEW_INSETS_MODE_IME = 1; /** * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static final int NEW_INSETS_MODE_IME = 1; + public static final int NEW_INSETS_MODE_FULL = 2; /** * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static final int NEW_INSETS_MODE_FULL = 2; + public static int sNewInsetsMode = + SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, NEW_INSETS_MODE_IME); /** * Set this system property to true to force the view hierarchy to render diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java index 33f21f211a1a..cf34b0bad78d 100644 --- a/core/java/android/view/WindowContainerTransaction.java +++ b/core/java/android/view/WindowContainerTransaction.java @@ -16,6 +16,8 @@ package android.view; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.WindowConfiguration; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -25,6 +27,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; +import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -36,10 +40,14 @@ import java.util.Map; public class WindowContainerTransaction implements Parcelable { private final ArrayMap<IBinder, Change> mChanges = new ArrayMap<>(); + // Flat list because re-order operations are order-dependent + private final ArrayList<HierarchyOp> mHierarchyOps = new ArrayList<>(); + public WindowContainerTransaction() {} protected WindowContainerTransaction(Parcel in) { in.readMap(mChanges, null /* loader */); + in.readList(mHierarchyOps, null /* loader */); } private Change getOrCreateChange(IBinder token) { @@ -97,10 +105,39 @@ public class WindowContainerTransaction implements Parcelable { return this; } + /** + * Reparents a container into another one. The effect of a {@code null} parent can vary. For + * example, reparenting a stack to {@code null} will reparent it to its display. + * + * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to + * the bottom. + */ + public WindowContainerTransaction reparent(@NonNull IWindowContainer child, + @Nullable IWindowContainer parent, boolean onTop) { + mHierarchyOps.add(new HierarchyOp(child.asBinder(), + parent == null ? null : parent.asBinder(), onTop)); + return this; + } + + /** + * Reorders a container within its parent. + * + * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to + * the bottom. + */ + public WindowContainerTransaction reorder(@NonNull IWindowContainer child, boolean onTop) { + mHierarchyOps.add(new HierarchyOp(child.asBinder(), onTop)); + return this; + } + public Map<IBinder, Change> getChanges() { return mChanges; } + public List<HierarchyOp> getHierarchyOps() { + return mHierarchyOps; + } + @Override public String toString() { return "WindowContainerTransaction { changes = " + mChanges + " }"; @@ -109,6 +146,7 @@ public class WindowContainerTransaction implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeMap(mChanges); + dest.writeList(mHierarchyOps); } @Override @@ -249,4 +287,88 @@ public class WindowContainerTransaction implements Parcelable { } }; } + + /** + * Holds information about a reparent/reorder operation in the hierarchy. This is separate from + * Changes because they must be executed in the same order that they are added. + */ + public static class HierarchyOp implements Parcelable { + private final IBinder mContainer; + + // If this is same as mContainer, then only change position, don't reparent. + private final IBinder mReparent; + + // Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom. + private final boolean mToTop; + + public HierarchyOp(@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) { + mContainer = container; + mReparent = reparent; + mToTop = toTop; + } + + public HierarchyOp(@NonNull IBinder container, boolean toTop) { + mContainer = container; + mReparent = container; + mToTop = toTop; + } + + protected HierarchyOp(Parcel in) { + mContainer = in.readStrongBinder(); + mReparent = in.readStrongBinder(); + mToTop = in.readBoolean(); + } + + public boolean isReparent() { + return mContainer != mReparent; + } + + @Nullable + public IBinder getNewParent() { + return mReparent; + } + + @NonNull + public IBinder getContainer() { + return mContainer; + } + + public boolean getToTop() { + return mToTop; + } + + @Override + public String toString() { + if (isReparent()) { + return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ") + + mReparent + "}"; + } else { + return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}"; + } + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongBinder(mContainer); + dest.writeStrongBinder(mReparent); + dest.writeBoolean(mToTop); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<HierarchyOp> CREATOR = new Creator<HierarchyOp>() { + @Override + public HierarchyOp createFromParcel(Parcel in) { + return new HierarchyOp(in); + } + + @Override + public HierarchyOp[] newArray(int size) { + return new HierarchyOp[size]; + } + }; + } } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 05cf3edb4525..bf2de14e6811 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -4854,8 +4854,11 @@ public class AccessibilityNodeInfo implements Parcelable { new AccessibilityAction(R.id.accessibilityActionPressAndHold); /** - * Action to send ime action. A node should expose this action only for views that are - * currently with input focus and editable. + * Action to send an ime action which is from + * {@link android.view.inputmethod.EditorInfo#actionId}. This action would be + * {@link android.view.inputmethod.EditorInfo#IME_ACTION_UNSPECIFIED} if no specific + * actionId defined. A node should expose this action only for views that are currently + * with input focus and editable. */ @NonNull public static final AccessibilityAction ACTION_IME_ENTER = new AccessibilityAction(R.id.accessibilityActionImeEnter); diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 232d96ba7f43..2134dab7986b 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -22,6 +22,7 @@ import android.annotation.CallSuper; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.util.DebugUtils; import android.util.Log; import android.view.View; @@ -50,7 +51,11 @@ public abstract class ContentCaptureSession implements AutoCloseable { private static final Random sIdGenerator = new Random(); - /** @hide */ + /** + * ID used to indicate that a session does not exist + * @hide + */ + @SystemApi public static final int NO_SESSION_ID = 0; /** diff --git a/core/java/android/view/inputmethod/InlineSuggestionInfo.java b/core/java/android/view/inputmethod/InlineSuggestionInfo.java index 703b64f8224f..024de4de92e7 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java +++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java @@ -80,7 +80,6 @@ public final class InlineSuggestionInfo implements Parcelable { @NonNull InlinePresentationSpec presentationSpec, @NonNull @Source String source, @Nullable String[] autofillHints) { - // TODO(b/147394280): Add CTS test for the type field. return new InlineSuggestionInfo(presentationSpec, source, autofillHints, TYPE_SUGGESTION); } diff --git a/core/java/android/webkit/WebResourceRequest.java b/core/java/android/webkit/WebResourceRequest.java index 964b6f8e259d..0b307e63ca45 100644 --- a/core/java/android/webkit/WebResourceRequest.java +++ b/core/java/android/webkit/WebResourceRequest.java @@ -32,10 +32,10 @@ public interface WebResourceRequest { Uri getUrl(); /** - * Gets whether the request was made for the main frame. + * Gets whether the request was made in order to fetch the main frame's document. * - * @return whether the request was made for the main frame. Will be {@code false} for iframes, - * for example. + * @return whether the request was made for the main frame document. Will be + * {@code false} for subresources or iframes, for example. */ boolean isForMainFrame(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index cbfa05caac6e..469ab2ee8f4a 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -422,9 +422,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ static final int PROCESS_TEXT_REQUEST_CODE = 100; - // Accessibility action to send IME custom action for CTS testing. - public static final int ACCESSIBILITY_ACTION_IME_ENTER = R.id.accessibilityActionImeEnter; - /** * Return code of {@link #doKeyDown}. */ @@ -11758,7 +11755,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } AccessibilityNodeInfo.AccessibilityAction action = new AccessibilityNodeInfo.AccessibilityAction( - ACCESSIBILITY_ACTION_IME_ENTER, imeActionLabel); + R.id.accessibilityActionImeEnter, imeActionLabel); info.addAction(action); } } @@ -12072,7 +12069,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } } return true; - case ACCESSIBILITY_ACTION_IME_ENTER: { + case R.id.accessibilityActionImeEnter: { if (isFocused() && isTextEditable()) { final int imeActionId = (arguments != null) ? arguments.getInt( AccessibilityNodeInfo.ACTION_ARGUMENT_IME_ACTION_ID_INT, diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java index 82eb55a30a4e..5f35622cad51 100644 --- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java +++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java @@ -471,6 +471,7 @@ public class AccessibilityButtonChooserActivity extends Activity { holder.mSwitchItem.setVisibility(View.GONE); holder.mItemContainer.setVisibility(isLaunchMenuMode ? View.GONE : View.VISIBLE); holder.mItemView.setEnabled(enabledState); + holder.mItemView.setClickable(!enabledState); } private void updateInvisibleActionItemVisibility(@NonNull Context context, @@ -485,6 +486,7 @@ public class AccessibilityButtonChooserActivity extends Activity { holder.mItemContainer.setVisibility((mShortcutMenuMode == ShortcutMenuMode.EDIT) ? View.VISIBLE : View.GONE); holder.mItemView.setEnabled(true); + holder.mItemView.setClickable(false); } private void updateIntuitiveActionItemVisibility(@NonNull Context context, @@ -504,6 +506,7 @@ public class AccessibilityButtonChooserActivity extends Activity { holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled); holder.mItemContainer.setVisibility(View.VISIBLE); holder.mItemView.setEnabled(true); + holder.mItemView.setClickable(false); } private void updateBounceActionItemVisibility(@NonNull Context context, @@ -518,6 +521,7 @@ public class AccessibilityButtonChooserActivity extends Activity { holder.mSwitchItem.setVisibility(View.GONE); holder.mItemContainer.setVisibility(View.VISIBLE); holder.mItemView.setEnabled(true); + holder.mItemView.setClickable(false); } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index bbb751359bab..a934de328989 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1487,7 +1487,6 @@ public class ResolverActivity extends Activity implements for (int i = 0; i < tabWidget.getChildCount(); i++) { TextView title = tabWidget.getChildAt(i).findViewById(android.R.id.title); title.setTextColor(getColor(R.color.resolver_tabs_inactive_color)); - title.setAllCaps(false); } } diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index 22fe31eeda38..a0de51d4d211 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -73,6 +73,7 @@ public class NativeLibraryHelper { private final CloseGuard mGuard = CloseGuard.get(); private volatile boolean mClosed; + final String[] apkPaths; final long[] apkHandles; final boolean multiArch; final boolean extractNativeLibs; @@ -103,9 +104,11 @@ public class NativeLibraryHelper { private static Handle create(List<String> codePaths, boolean multiArch, boolean extractNativeLibs, boolean debuggable) throws IOException { final int size = codePaths.size(); + final String[] apkPaths = new String[size]; final long[] apkHandles = new long[size]; for (int i = 0; i < size; i++) { final String path = codePaths.get(i); + apkPaths[i] = path; apkHandles[i] = nativeOpenApk(path); if (apkHandles[i] == 0) { // Unwind everything we've opened so far @@ -116,7 +119,7 @@ public class NativeLibraryHelper { } } - return new Handle(apkHandles, multiArch, extractNativeLibs, debuggable); + return new Handle(apkPaths, apkHandles, multiArch, extractNativeLibs, debuggable); } public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException { @@ -127,11 +130,13 @@ public class NativeLibraryHelper { throw new IOException("Unable to open APK " + path + " from fd " + fd); } - return new Handle(apkHandles, lite.multiArch, lite.extractNativeLibs, lite.debuggable); + return new Handle(new String[]{path}, apkHandles, lite.multiArch, + lite.extractNativeLibs, lite.debuggable); } - Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs, - boolean debuggable) { + Handle(String[] apkPaths, long[] apkHandles, boolean multiArch, + boolean extractNativeLibs, boolean debuggable) { + this.apkPaths = apkPaths; this.apkHandles = apkHandles; this.multiArch = multiArch; this.extractNativeLibs = extractNativeLibs; @@ -313,40 +318,58 @@ public class NativeLibraryHelper { } public static int copyNativeBinariesForSupportedAbi(Handle handle, File libraryRoot, - String[] abiList, boolean useIsaSubdir) throws IOException { - createNativeLibrarySubdir(libraryRoot); - + String[] abiList, boolean useIsaSubdir, boolean isIncremental) throws IOException { /* * If this is an internal application or our nativeLibraryPath points to * the app-lib directory, unpack the libraries if necessary. */ int abi = findSupportedAbi(handle, abiList); - if (abi >= 0) { - /* - * If we have a matching instruction set, construct a subdir under the native - * library root that corresponds to this instruction set. - */ - final String instructionSet = VMRuntime.getInstructionSet(abiList[abi]); - final File subDir; - if (useIsaSubdir) { - final File isaSubdir = new File(libraryRoot, instructionSet); - createNativeLibrarySubdir(isaSubdir); - subDir = isaSubdir; - } else { - subDir = libraryRoot; - } + if (abi < 0) { + return abi; + } - int copyRet = copyNativeBinaries(handle, subDir, abiList[abi]); - if (copyRet != PackageManager.INSTALL_SUCCEEDED) { - return copyRet; + /* + * If we have a matching instruction set, construct a subdir under the native + * library root that corresponds to this instruction set. + */ + final String supportedAbi = abiList[abi]; + final String instructionSet = VMRuntime.getInstructionSet(supportedAbi); + final File subDir; + if (useIsaSubdir) { + subDir = new File(libraryRoot, instructionSet); + } else { + subDir = libraryRoot; + } + + if (isIncremental) { + int res = + incrementalConfigureNativeBinariesForSupportedAbi(handle, subDir, supportedAbi); + if (res != PackageManager.INSTALL_SUCCEEDED) { + // TODO(b/133435829): the caller of this function expects that we return the index + // to the supported ABI. However, any non-negative integer can be a valid index. + // We should fix this function and make sure it doesn't accidentally return an error + // code that can also be a valid index. + return res; } + return abi; + } + + // For non-incremental, use regular extraction and copy + createNativeLibrarySubdir(libraryRoot); + if (subDir != libraryRoot) { + createNativeLibrarySubdir(subDir); + } + + int copyRet = copyNativeBinaries(handle, subDir, supportedAbi); + if (copyRet != PackageManager.INSTALL_SUCCEEDED) { + return copyRet; } return abi; } public static int copyNativeBinariesWithOverride(Handle handle, File libraryRoot, - String abiOverride) { + String abiOverride, boolean isIncremental) { try { if (handle.multiArch) { // Warn if we've set an abiOverride for multi-lib packages.. @@ -359,7 +382,8 @@ public class NativeLibraryHelper { int copyRet = PackageManager.NO_NATIVE_LIBRARIES; if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, - Build.SUPPORTED_32_BIT_ABIS, true /* use isa specific subdirs */); + Build.SUPPORTED_32_BIT_ABIS, true /* use isa specific subdirs */, + isIncremental); if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES && copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) { Slog.w(TAG, "Failure copying 32 bit native libraries; copyRet=" +copyRet); @@ -369,7 +393,8 @@ public class NativeLibraryHelper { if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, - Build.SUPPORTED_64_BIT_ABIS, true /* use isa specific subdirs */); + Build.SUPPORTED_64_BIT_ABIS, true /* use isa specific subdirs */, + isIncremental); if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES && copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) { Slog.w(TAG, "Failure copying 64 bit native libraries; copyRet=" +copyRet); @@ -392,7 +417,7 @@ public class NativeLibraryHelper { } int copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, abiList, - true /* use isa specific subdirs */); + true /* use isa specific subdirs */, isIncremental); if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) { Slog.w(TAG, "Failure copying native libraries [errorCode=" + copyRet + "]"); return copyRet; @@ -449,29 +474,15 @@ public class NativeLibraryHelper { * Service will create native library directories and set up native library binary files in the * same structure as they are in non-incremental installations. * - * @param pkg The package to be installed, including all the APK files. - * @param handle The pointer to an zip archive. - * @param libraryRoot The root directory of the native library files, e.g., lib/ - * @param abiList The list of ABIs that are supported by the current device. - * @param useIsaSubdir Whether or not to set up a sub dir for the ISA. - * @return ABI code if installation succeeds or error code if installation fails. + * @param handle The Handle object that contains all apk paths. + * @param libSubDir The target directory to put the native library files, e.g., lib/ or lib/arm + * @param abi The abi that is supported by the current device. + * @return Integer code if installation succeeds or fails. */ - public static int configureNativeBinariesForSupportedAbi(AndroidPackage pkg, Handle handle, - File libraryRoot, String[] abiList, boolean useIsaSubdir) { - int abi = findSupportedAbi(handle, abiList); - if (abi < 0) { - Slog.e(TAG, "Failed to find find matching ABI."); - return abi; - } - - // Currently only support installations that have pre-configured native library files - // TODO(b/136132412): implement this after incfs supports file mapping - if (!libraryRoot.exists()) { - Slog.e(TAG, "Incremental installation currently does not configure native libs"); - return INSTALL_FAILED_NO_MATCHING_ABIS; - } - - return abi; + private static int incrementalConfigureNativeBinariesForSupportedAbi(Handle handle, + File libSubDir, String abi) { + // TODO(b/136132412): implement this + return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; } // We don't care about the other return values for now. diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 74b481c938c3..26d2f19b13b6 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -29,6 +29,7 @@ import android.os.FileUtils; import android.os.Process; import android.os.SystemProperties; import android.os.Trace; +import android.os.incremental.IncrementalManager; import android.os.storage.StorageManager; import android.permission.PermissionManager.SplitPermissionInfo; import android.text.TextUtils; @@ -1156,6 +1157,10 @@ public class SystemConfig { addFeature(PackageManager.FEATURE_RAM_NORMAL, 0); } + if (IncrementalManager.isEnabled()) { + addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY, 0); + } + for (String featureName : mUnavailableFeatures) { removeFeature(featureName); } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 35eb0fc986d7..3d0926d61789 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -153,7 +153,7 @@ cc_library_shared { "android_util_MemoryIntArray.cpp", "android_util_Process.cpp", "android_util_jar_StrictJarFile.cpp", - "android_media_AudioDeviceAddress.cpp", + "android_media_AudioDevice.cpp", "android_media_AudioEffectDescriptor.cpp", "android_media_AudioRecord.cpp", "android_media_AudioSystem.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 657336e6910a..d0e8fd355199 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -86,7 +86,7 @@ extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env); extern int register_android_hardware_UsbRequest(JNIEnv *env); extern int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env); -extern int register_android_media_AudioDeviceAddress(JNIEnv *env); +extern int register_android_media_AudioDevice(JNIEnv* env); extern int register_android_media_AudioEffectDescriptor(JNIEnv *env); extern int register_android_media_AudioRecord(JNIEnv *env); extern int register_android_media_AudioSystem(JNIEnv *env); @@ -631,6 +631,8 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p char heaptargetutilizationOptsBuf[sizeof("-XX:HeapTargetUtilization=")-1 + PROPERTY_VALUE_MAX]; char foregroundHeapGrowthMultiplierOptsBuf[ sizeof("-XX:ForegroundHeapGrowthMultiplier=")-1 + PROPERTY_VALUE_MAX]; + char finalizerTimeoutMsOptsBuf[sizeof("-XX:FinalizerTimeoutMs=")-1 + PROPERTY_VALUE_MAX]; + char threadSuspendTimeoutOptsBuf[sizeof("-XX:ThreadSuspendTimeout=")-1 + PROPERTY_VALUE_MAX]; char cachePruneBuf[sizeof("-Xzygote-max-boot-retry=")-1 + PROPERTY_VALUE_MAX]; char dex2oatXmsImageFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX]; char dex2oatXmxImageFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX]; @@ -784,7 +786,15 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p parseRuntimeOption("dalvik.vm.foreground-heap-growth-multiplier", foregroundHeapGrowthMultiplierOptsBuf, "-XX:ForegroundHeapGrowthMultiplier="); - + /* + * Finalizer and thread suspend timeouts. + */ + parseRuntimeOption("dalvik.vm.finalizer-timeout-ms", + finalizerTimeoutMsOptsBuf, + "-XX:FinalizerTimeoutMs="); + parseRuntimeOption("dalvik.vm.thread-suspend-timeout-ms", + threadSuspendTimeoutOptsBuf, + "-XX:ThreadSuspendTimeout="); /* * JIT related options. */ @@ -1424,140 +1434,140 @@ static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env } static const RegJNIRec gRegJNI[] = { - REG_JNI(register_com_android_internal_os_RuntimeInit), - REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit), - REG_JNI(register_android_os_SystemClock), - REG_JNI(register_android_util_EventLog), - REG_JNI(register_android_util_Log), - REG_JNI(register_android_util_MemoryIntArray), - REG_JNI(register_android_util_StatsLog), - REG_JNI(register_android_util_StatsLogInternal), - REG_JNI(register_android_app_admin_SecurityLog), - REG_JNI(register_android_content_AssetManager), - REG_JNI(register_android_content_StringBlock), - REG_JNI(register_android_content_XmlBlock), - REG_JNI(register_android_content_res_ApkAssets), - REG_JNI(register_android_text_AndroidCharacter), - REG_JNI(register_android_text_Hyphenator), - REG_JNI(register_android_view_InputDevice), - REG_JNI(register_android_view_KeyCharacterMap), - REG_JNI(register_android_os_Process), - REG_JNI(register_android_os_SystemProperties), - REG_JNI(register_android_os_Binder), - REG_JNI(register_android_os_Parcel), - REG_JNI(register_android_os_HidlMemory), - REG_JNI(register_android_os_HidlSupport), - REG_JNI(register_android_os_HwBinder), - REG_JNI(register_android_os_HwBlob), - REG_JNI(register_android_os_HwParcel), - REG_JNI(register_android_os_HwRemoteBinder), - REG_JNI(register_android_os_NativeHandle), - REG_JNI(register_android_os_storage_StorageManager), - REG_JNI(register_android_os_VintfObject), - REG_JNI(register_android_os_VintfRuntimeInfo), - REG_JNI(register_android_service_DataLoaderService), - REG_JNI(register_android_view_DisplayEventReceiver), - REG_JNI(register_android_view_RenderNodeAnimator), - REG_JNI(register_android_view_InputApplicationHandle), - REG_JNI(register_android_view_InputWindowHandle), - REG_JNI(register_android_view_Surface), - REG_JNI(register_android_view_SurfaceControl), - REG_JNI(register_android_view_SurfaceSession), - REG_JNI(register_android_view_CompositionSamplingListener), - REG_JNI(register_android_view_TextureView), - REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper), - REG_JNI(register_com_google_android_gles_jni_EGLImpl), - REG_JNI(register_com_google_android_gles_jni_GLImpl), - REG_JNI(register_android_opengl_jni_EGL14), - REG_JNI(register_android_opengl_jni_EGL15), - REG_JNI(register_android_opengl_jni_EGLExt), - REG_JNI(register_android_opengl_jni_GLES10), - REG_JNI(register_android_opengl_jni_GLES10Ext), - REG_JNI(register_android_opengl_jni_GLES11), - REG_JNI(register_android_opengl_jni_GLES11Ext), - REG_JNI(register_android_opengl_jni_GLES20), - REG_JNI(register_android_opengl_jni_GLES30), - REG_JNI(register_android_opengl_jni_GLES31), - REG_JNI(register_android_opengl_jni_GLES31Ext), - REG_JNI(register_android_opengl_jni_GLES32), - REG_JNI(register_android_graphics_classes), - REG_JNI(register_android_graphics_BLASTBufferQueue), - REG_JNI(register_android_graphics_GraphicBuffer), - REG_JNI(register_android_database_CursorWindow), - REG_JNI(register_android_database_SQLiteConnection), - REG_JNI(register_android_database_SQLiteGlobal), - REG_JNI(register_android_database_SQLiteDebug), - REG_JNI(register_android_os_Debug), - REG_JNI(register_android_os_FileObserver), - REG_JNI(register_android_os_GraphicsEnvironment), - REG_JNI(register_android_os_MessageQueue), - REG_JNI(register_android_os_SELinux), - REG_JNI(register_android_os_Trace), - REG_JNI(register_android_os_UEventObserver), - REG_JNI(register_android_net_LocalSocketImpl), - REG_JNI(register_android_net_NetworkUtils), - REG_JNI(register_android_os_MemoryFile), - REG_JNI(register_android_os_SharedMemory), - REG_JNI(register_android_os_incremental_IncrementalManager), - REG_JNI(register_com_android_internal_os_ClassLoaderFactory), - REG_JNI(register_com_android_internal_os_Zygote), - REG_JNI(register_com_android_internal_os_ZygoteInit), - REG_JNI(register_com_android_internal_util_VirtualRefBasePtr), - REG_JNI(register_android_hardware_Camera), - REG_JNI(register_android_hardware_camera2_CameraMetadata), - REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice), - REG_JNI(register_android_hardware_camera2_legacy_PerfMeasurement), - REG_JNI(register_android_hardware_camera2_DngCreator), - REG_JNI(register_android_hardware_HardwareBuffer), - REG_JNI(register_android_hardware_SensorManager), - REG_JNI(register_android_hardware_SerialPort), - REG_JNI(register_android_hardware_UsbDevice), - REG_JNI(register_android_hardware_UsbDeviceConnection), - REG_JNI(register_android_hardware_UsbRequest), - REG_JNI(register_android_hardware_location_ActivityRecognitionHardware), - REG_JNI(register_android_media_AudioDeviceAddress), - REG_JNI(register_android_media_AudioEffectDescriptor), - REG_JNI(register_android_media_AudioSystem), - REG_JNI(register_android_media_AudioRecord), - REG_JNI(register_android_media_AudioTrack), - REG_JNI(register_android_media_AudioAttributes), - REG_JNI(register_android_media_AudioProductStrategies), - REG_JNI(register_android_media_AudioVolumeGroups), - REG_JNI(register_android_media_AudioVolumeGroupChangeHandler), - REG_JNI(register_android_media_MediaMetrics), - REG_JNI(register_android_media_MicrophoneInfo), - REG_JNI(register_android_media_RemoteDisplay), - REG_JNI(register_android_media_ToneGenerator), - REG_JNI(register_android_media_midi), - - REG_JNI(register_android_opengl_classes), - REG_JNI(register_android_server_NetworkManagementSocketTagger), - REG_JNI(register_android_ddm_DdmHandleNativeHeap), - REG_JNI(register_android_backup_BackupDataInput), - REG_JNI(register_android_backup_BackupDataOutput), - REG_JNI(register_android_backup_FileBackupHelperBase), - REG_JNI(register_android_backup_BackupHelperDispatcher), - REG_JNI(register_android_app_backup_FullBackup), - REG_JNI(register_android_app_Activity), - REG_JNI(register_android_app_ActivityThread), - REG_JNI(register_android_app_NativeActivity), - REG_JNI(register_android_util_jar_StrictJarFile), - REG_JNI(register_android_view_InputChannel), - REG_JNI(register_android_view_InputEventReceiver), - REG_JNI(register_android_view_InputEventSender), - REG_JNI(register_android_view_InputQueue), - REG_JNI(register_android_view_KeyEvent), - REG_JNI(register_android_view_MotionEvent), - REG_JNI(register_android_view_PointerIcon), - REG_JNI(register_android_view_VelocityTracker), - - REG_JNI(register_android_content_res_ObbScanner), - REG_JNI(register_android_content_res_Configuration), - - REG_JNI(register_android_animation_PropertyValuesHolder), - REG_JNI(register_android_security_Scrypt), - REG_JNI(register_com_android_internal_content_NativeLibraryHelper), - REG_JNI(register_com_android_internal_os_FuseAppLoop), + REG_JNI(register_com_android_internal_os_RuntimeInit), + REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit), + REG_JNI(register_android_os_SystemClock), + REG_JNI(register_android_util_EventLog), + REG_JNI(register_android_util_Log), + REG_JNI(register_android_util_MemoryIntArray), + REG_JNI(register_android_util_StatsLog), + REG_JNI(register_android_util_StatsLogInternal), + REG_JNI(register_android_app_admin_SecurityLog), + REG_JNI(register_android_content_AssetManager), + REG_JNI(register_android_content_StringBlock), + REG_JNI(register_android_content_XmlBlock), + REG_JNI(register_android_content_res_ApkAssets), + REG_JNI(register_android_text_AndroidCharacter), + REG_JNI(register_android_text_Hyphenator), + REG_JNI(register_android_view_InputDevice), + REG_JNI(register_android_view_KeyCharacterMap), + REG_JNI(register_android_os_Process), + REG_JNI(register_android_os_SystemProperties), + REG_JNI(register_android_os_Binder), + REG_JNI(register_android_os_Parcel), + REG_JNI(register_android_os_HidlMemory), + REG_JNI(register_android_os_HidlSupport), + REG_JNI(register_android_os_HwBinder), + REG_JNI(register_android_os_HwBlob), + REG_JNI(register_android_os_HwParcel), + REG_JNI(register_android_os_HwRemoteBinder), + REG_JNI(register_android_os_NativeHandle), + REG_JNI(register_android_os_storage_StorageManager), + REG_JNI(register_android_os_VintfObject), + REG_JNI(register_android_os_VintfRuntimeInfo), + REG_JNI(register_android_service_DataLoaderService), + REG_JNI(register_android_view_DisplayEventReceiver), + REG_JNI(register_android_view_RenderNodeAnimator), + REG_JNI(register_android_view_InputApplicationHandle), + REG_JNI(register_android_view_InputWindowHandle), + REG_JNI(register_android_view_Surface), + REG_JNI(register_android_view_SurfaceControl), + REG_JNI(register_android_view_SurfaceSession), + REG_JNI(register_android_view_CompositionSamplingListener), + REG_JNI(register_android_view_TextureView), + REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper), + REG_JNI(register_com_google_android_gles_jni_EGLImpl), + REG_JNI(register_com_google_android_gles_jni_GLImpl), + REG_JNI(register_android_opengl_jni_EGL14), + REG_JNI(register_android_opengl_jni_EGL15), + REG_JNI(register_android_opengl_jni_EGLExt), + REG_JNI(register_android_opengl_jni_GLES10), + REG_JNI(register_android_opengl_jni_GLES10Ext), + REG_JNI(register_android_opengl_jni_GLES11), + REG_JNI(register_android_opengl_jni_GLES11Ext), + REG_JNI(register_android_opengl_jni_GLES20), + REG_JNI(register_android_opengl_jni_GLES30), + REG_JNI(register_android_opengl_jni_GLES31), + REG_JNI(register_android_opengl_jni_GLES31Ext), + REG_JNI(register_android_opengl_jni_GLES32), + REG_JNI(register_android_graphics_classes), + REG_JNI(register_android_graphics_BLASTBufferQueue), + REG_JNI(register_android_graphics_GraphicBuffer), + REG_JNI(register_android_database_CursorWindow), + REG_JNI(register_android_database_SQLiteConnection), + REG_JNI(register_android_database_SQLiteGlobal), + REG_JNI(register_android_database_SQLiteDebug), + REG_JNI(register_android_os_Debug), + REG_JNI(register_android_os_FileObserver), + REG_JNI(register_android_os_GraphicsEnvironment), + REG_JNI(register_android_os_MessageQueue), + REG_JNI(register_android_os_SELinux), + REG_JNI(register_android_os_Trace), + REG_JNI(register_android_os_UEventObserver), + REG_JNI(register_android_net_LocalSocketImpl), + REG_JNI(register_android_net_NetworkUtils), + REG_JNI(register_android_os_MemoryFile), + REG_JNI(register_android_os_SharedMemory), + REG_JNI(register_android_os_incremental_IncrementalManager), + REG_JNI(register_com_android_internal_os_ClassLoaderFactory), + REG_JNI(register_com_android_internal_os_Zygote), + REG_JNI(register_com_android_internal_os_ZygoteInit), + REG_JNI(register_com_android_internal_util_VirtualRefBasePtr), + REG_JNI(register_android_hardware_Camera), + REG_JNI(register_android_hardware_camera2_CameraMetadata), + REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice), + REG_JNI(register_android_hardware_camera2_legacy_PerfMeasurement), + REG_JNI(register_android_hardware_camera2_DngCreator), + REG_JNI(register_android_hardware_HardwareBuffer), + REG_JNI(register_android_hardware_SensorManager), + REG_JNI(register_android_hardware_SerialPort), + REG_JNI(register_android_hardware_UsbDevice), + REG_JNI(register_android_hardware_UsbDeviceConnection), + REG_JNI(register_android_hardware_UsbRequest), + REG_JNI(register_android_hardware_location_ActivityRecognitionHardware), + REG_JNI(register_android_media_AudioDevice), + REG_JNI(register_android_media_AudioEffectDescriptor), + REG_JNI(register_android_media_AudioSystem), + REG_JNI(register_android_media_AudioRecord), + REG_JNI(register_android_media_AudioTrack), + REG_JNI(register_android_media_AudioAttributes), + REG_JNI(register_android_media_AudioProductStrategies), + REG_JNI(register_android_media_AudioVolumeGroups), + REG_JNI(register_android_media_AudioVolumeGroupChangeHandler), + REG_JNI(register_android_media_MediaMetrics), + REG_JNI(register_android_media_MicrophoneInfo), + REG_JNI(register_android_media_RemoteDisplay), + REG_JNI(register_android_media_ToneGenerator), + REG_JNI(register_android_media_midi), + + REG_JNI(register_android_opengl_classes), + REG_JNI(register_android_server_NetworkManagementSocketTagger), + REG_JNI(register_android_ddm_DdmHandleNativeHeap), + REG_JNI(register_android_backup_BackupDataInput), + REG_JNI(register_android_backup_BackupDataOutput), + REG_JNI(register_android_backup_FileBackupHelperBase), + REG_JNI(register_android_backup_BackupHelperDispatcher), + REG_JNI(register_android_app_backup_FullBackup), + REG_JNI(register_android_app_Activity), + REG_JNI(register_android_app_ActivityThread), + REG_JNI(register_android_app_NativeActivity), + REG_JNI(register_android_util_jar_StrictJarFile), + REG_JNI(register_android_view_InputChannel), + REG_JNI(register_android_view_InputEventReceiver), + REG_JNI(register_android_view_InputEventSender), + REG_JNI(register_android_view_InputQueue), + REG_JNI(register_android_view_KeyEvent), + REG_JNI(register_android_view_MotionEvent), + REG_JNI(register_android_view_PointerIcon), + REG_JNI(register_android_view_VelocityTracker), + + REG_JNI(register_android_content_res_ObbScanner), + REG_JNI(register_android_content_res_Configuration), + + REG_JNI(register_android_animation_PropertyValuesHolder), + REG_JNI(register_android_security_Scrypt), + REG_JNI(register_com_android_internal_content_NativeLibraryHelper), + REG_JNI(register_com_android_internal_os_FuseAppLoop), }; /* diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index 8fc6afa0f386..2a56fd6d6d17 100644 --- a/core/jni/android/graphics/FontFamily.cpp +++ b/core/jni/android/graphics/FontFamily.cpp @@ -34,9 +34,9 @@ #include <hwui/MinikinSkia.h> #include <hwui/Typeface.h> -#include <utils/FatVector.h> #include <minikin/FontFamily.h> #include <minikin/LocaleList.h> +#include <ui/FatVector.h> #include <memory> @@ -109,7 +109,7 @@ static jlong FontFamily_getFamilyReleaseFunc(CRITICAL_JNI_PARAMS) { static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, int ttcIndex, jint weight, jint italic) { - uirenderer::FatVector<SkFontArguments::Axis, 2> skiaAxes; + FatVector<SkFontArguments::Axis, 2> skiaAxes; for (const auto& axis : builder->axes) { skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value}); } diff --git a/core/jni/android/graphics/fonts/Font.cpp b/core/jni/android/graphics/fonts/Font.cpp index bb0654d255c0..8d84e870d205 100644 --- a/core/jni/android/graphics/fonts/Font.cpp +++ b/core/jni/android/graphics/fonts/Font.cpp @@ -33,8 +33,8 @@ #include <hwui/MinikinSkia.h> #include <hwui/Typeface.h> -#include <utils/FatVector.h> #include <minikin/FontFamily.h> +#include <ui/FatVector.h> #include <memory> @@ -157,7 +157,7 @@ static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jo sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize, release_global_ref, reinterpret_cast<void*>(fontRef))); - uirenderer::FatVector<SkFontArguments::Axis, 2> skiaAxes; + FatVector<SkFontArguments::Axis, 2> skiaAxes; for (const auto& axis : builder->axes) { skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value}); } diff --git a/core/jni/android_media_AudioDeviceAddress.cpp b/core/jni/android_media_AudioDevice.cpp index 5f39f7efb6a2..f6a0e4be1066 100644 --- a/core/jni/android_media_AudioDeviceAddress.cpp +++ b/core/jni/android_media_AudioDevice.cpp @@ -14,40 +14,38 @@ * limitations under the License. */ -#include "core_jni_helpers.h" -#include "android_media_AudioDeviceAddress.h" +#include "android_media_AudioDevice.h" #include "android_media_AudioErrors.h" +#include "core_jni_helpers.h" #include <media/AudioDeviceTypeAddr.h> using namespace android; -static jclass gAudioDeviceAddressClass; -static jmethodID gAudioDeviceAddressCstor; +static jclass gAudioDeviceClass; +static jmethodID gAudioDeviceCstor; namespace android { -jint createAudioDeviceAddressFromNative( - JNIEnv *env, jobject *jAudioDeviceAddress, - const AudioDeviceTypeAddr *devTypeAddr) { +jint createAudioDeviceFromNative(JNIEnv *env, jobject *jAudioDevice, + const AudioDeviceTypeAddr *devTypeAddr) { jint jStatus = (jint)AUDIO_JAVA_SUCCESS; jint jNativeType = (jint)devTypeAddr->mType; ScopedLocalRef<jstring> jAddress(env, env->NewStringUTF(devTypeAddr->mAddress.data())); - *jAudioDeviceAddress = env->NewObject(gAudioDeviceAddressClass, gAudioDeviceAddressCstor, - jNativeType, jAddress.get()); + *jAudioDevice = + env->NewObject(gAudioDeviceClass, gAudioDeviceCstor, jNativeType, jAddress.get()); return jStatus; } -} +} // namespace android -int register_android_media_AudioDeviceAddress(JNIEnv *env) -{ - jclass audioDeviceTypeAddressClass = FindClassOrDie(env, "android/media/AudioDeviceAddress"); - gAudioDeviceAddressClass = MakeGlobalRefOrDie(env, audioDeviceTypeAddressClass); - gAudioDeviceAddressCstor = GetMethodIDOrDie(env, audioDeviceTypeAddressClass, "<init>", - "(ILjava/lang/String;)V"); +int register_android_media_AudioDevice(JNIEnv *env) { + jclass audioDeviceTypeAddressClass = FindClassOrDie(env, "android/media/AudioDevice"); + gAudioDeviceClass = MakeGlobalRefOrDie(env, audioDeviceTypeAddressClass); + gAudioDeviceCstor = + GetMethodIDOrDie(env, audioDeviceTypeAddressClass, "<init>", "(ILjava/lang/String;)V"); return 0; } diff --git a/core/jni/android_media_AudioDeviceAddress.h b/core/jni/android_media_AudioDevice.h index c66b17978776..fc92334db194 100644 --- a/core/jni/android_media_AudioDeviceAddress.h +++ b/core/jni/android_media_AudioDevice.h @@ -14,20 +14,20 @@ * limitations under the License. */ -#ifndef ANDROID_MEDIA_AUDIODEVICEADDRESS_H -#define ANDROID_MEDIA_AUDIODEVICEADDRESS_H +#ifndef ANDROID_MEDIA_AUDIODEVICE_H +#define ANDROID_MEDIA_AUDIODEVICE_H -#include <system/audio.h> #include <media/AudioDeviceTypeAddr.h> +#include <system/audio.h> #include "jni.h" namespace android { -// Create a Java AudioDeviceAddress instance from a C++ AudioDeviceTypeAddress +// Create a Java AudioDevice instance from a C++ AudioDeviceTypeAddress -extern jint createAudioDeviceAddressFromNative(JNIEnv *env, jobject *jAudioDeviceAddress, - const AudioDeviceTypeAddr *devTypeAddr); +extern jint createAudioDeviceFromNative(JNIEnv *env, jobject *jAudioDevice, + const AudioDeviceTypeAddr *devTypeAddr); } // namespace android #endif
\ No newline at end of file diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 0156e23e94b6..b4590f4f8b23 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -26,12 +26,6 @@ #include <nativehelper/JNIHelp.h> #include "core_jni_helpers.h" -#include "android_media_AudioAttributes.h" -#include "android_media_AudioDeviceAddress.h" -#include "android_media_AudioEffectDescriptor.h" -#include "android_media_AudioErrors.h" -#include "android_media_AudioFormat.h" -#include "android_media_MicrophoneInfo.h" #include <audiomanager/AudioManager.h> #include <media/AudioPolicy.h> #include <media/AudioSystem.h> @@ -39,6 +33,12 @@ #include <nativehelper/ScopedLocalRef.h> #include <system/audio.h> #include <system/audio_policy.h> +#include "android_media_AudioAttributes.h" +#include "android_media_AudioDevice.h" +#include "android_media_AudioEffectDescriptor.h" +#include "android_media_AudioErrors.h" +#include "android_media_AudioFormat.h" +#include "android_media_MicrophoneInfo.h" // ---------------------------------------------------------------------------- @@ -2349,7 +2349,7 @@ android_media_AudioSystem_getPreferredDeviceForStrategy(JNIEnv *env, jobject thi jint strategy, jobjectArray jDeviceArray) { if (jDeviceArray == nullptr || env->GetArrayLength(jDeviceArray) != 1) { - ALOGE("%s invalid array to store AudioDeviceAddress", __FUNCTION__); + ALOGE("%s invalid array to store AudioDevice", __FUNCTION__); return (jint)AUDIO_JAVA_BAD_VALUE; } @@ -2359,10 +2359,10 @@ android_media_AudioSystem_getPreferredDeviceForStrategy(JNIEnv *env, jobject thi if (status != NO_ERROR) { return (jint) status; } - jobject jAudioDeviceAddress = NULL; - jint jStatus = createAudioDeviceAddressFromNative(env, &jAudioDeviceAddress, &elDevice); + jobject jAudioDevice = NULL; + jint jStatus = createAudioDeviceFromNative(env, &jAudioDevice, &elDevice); if (jStatus == AUDIO_JAVA_SUCCESS) { - env->SetObjectArrayElement(jDeviceArray, 0, jAudioDeviceAddress); + env->SetObjectArrayElement(jDeviceArray, 0, jAudioDevice); } return jStatus; } @@ -2377,7 +2377,7 @@ android_media_AudioSystem_getDevicesForAttributes(JNIEnv *env, jobject thiz, // with reverse JNI to make the array grow as need as this would be less efficient, and some // components call this method often if (jDeviceArray == nullptr || maxResultSize == 0) { - ALOGE("%s invalid array to store AudioDeviceAddress", __FUNCTION__); + ALOGE("%s invalid array to store AudioDevice", __FUNCTION__); return (jint)AUDIO_JAVA_BAD_VALUE; } @@ -2398,105 +2398,133 @@ android_media_AudioSystem_getDevicesForAttributes(JNIEnv *env, jobject thiz, return AUDIO_JAVA_INVALID_OPERATION; } size_t index = 0; - jobject jAudioDeviceAddress = NULL; + jobject jAudioDevice = NULL; for (const auto& device : devices) { - jStatus = createAudioDeviceAddressFromNative(env, &jAudioDeviceAddress, &device); + jStatus = createAudioDeviceFromNative(env, &jAudioDevice, &device); if (jStatus != AUDIO_JAVA_SUCCESS) { return jStatus; } - env->SetObjectArrayElement(jDeviceArray, index++, jAudioDeviceAddress); + env->SetObjectArrayElement(jDeviceArray, index++, jAudioDevice); } return jStatus; } // ---------------------------------------------------------------------------- -static const JNINativeMethod gMethods[] = { - {"setParameters", "(Ljava/lang/String;)I", (void *)android_media_AudioSystem_setParameters}, - {"getParameters", "(Ljava/lang/String;)Ljava/lang/String;", (void *)android_media_AudioSystem_getParameters}, - {"muteMicrophone", "(Z)I", (void *)android_media_AudioSystem_muteMicrophone}, - {"isMicrophoneMuted", "()Z", (void *)android_media_AudioSystem_isMicrophoneMuted}, - {"isStreamActive", "(II)Z", (void *)android_media_AudioSystem_isStreamActive}, - {"isStreamActiveRemotely","(II)Z", (void *)android_media_AudioSystem_isStreamActiveRemotely}, - {"isSourceActive", "(I)Z", (void *)android_media_AudioSystem_isSourceActive}, - {"newAudioSessionId", "()I", (void *)android_media_AudioSystem_newAudioSessionId}, - {"newAudioPlayerId", "()I", (void *)android_media_AudioSystem_newAudioPlayerId}, - {"newAudioRecorderId", "()I", (void *)android_media_AudioSystem_newAudioRecorderId}, - {"setDeviceConnectionState", "(IILjava/lang/String;Ljava/lang/String;I)I", (void *)android_media_AudioSystem_setDeviceConnectionState}, - {"getDeviceConnectionState", "(ILjava/lang/String;)I", (void *)android_media_AudioSystem_getDeviceConnectionState}, - {"handleDeviceConfigChange", "(ILjava/lang/String;Ljava/lang/String;I)I", (void *)android_media_AudioSystem_handleDeviceConfigChange}, - {"setPhoneState", "(I)I", (void *)android_media_AudioSystem_setPhoneState}, - {"setForceUse", "(II)I", (void *)android_media_AudioSystem_setForceUse}, - {"getForceUse", "(I)I", (void *)android_media_AudioSystem_getForceUse}, - {"initStreamVolume", "(III)I", (void *)android_media_AudioSystem_initStreamVolume}, - {"setStreamVolumeIndex","(III)I", (void *)android_media_AudioSystem_setStreamVolumeIndex}, - {"getStreamVolumeIndex","(II)I", (void *)android_media_AudioSystem_getStreamVolumeIndex}, - {"setVolumeIndexForAttributes","(Landroid/media/AudioAttributes;II)I", (void *)android_media_AudioSystem_setVolumeIndexForAttributes}, - {"getVolumeIndexForAttributes","(Landroid/media/AudioAttributes;I)I", (void *)android_media_AudioSystem_getVolumeIndexForAttributes}, - {"getMinVolumeIndexForAttributes","(Landroid/media/AudioAttributes;)I", (void *)android_media_AudioSystem_getMinVolumeIndexForAttributes}, - {"getMaxVolumeIndexForAttributes","(Landroid/media/AudioAttributes;)I", (void *)android_media_AudioSystem_getMaxVolumeIndexForAttributes}, - {"setMasterVolume", "(F)I", (void *)android_media_AudioSystem_setMasterVolume}, - {"getMasterVolume", "()F", (void *)android_media_AudioSystem_getMasterVolume}, - {"setMasterMute", "(Z)I", (void *)android_media_AudioSystem_setMasterMute}, - {"getMasterMute", "()Z", (void *)android_media_AudioSystem_getMasterMute}, - {"setMasterMono", "(Z)I", (void *)android_media_AudioSystem_setMasterMono}, - {"getMasterMono", "()Z", (void *)android_media_AudioSystem_getMasterMono}, - {"setMasterBalance", "(F)I", (void *)android_media_AudioSystem_setMasterBalance}, - {"getMasterBalance", "()F", (void *)android_media_AudioSystem_getMasterBalance}, - {"getDevicesForStream", "(I)I", (void *)android_media_AudioSystem_getDevicesForStream}, - {"getPrimaryOutputSamplingRate", "()I", (void *)android_media_AudioSystem_getPrimaryOutputSamplingRate}, - {"getPrimaryOutputFrameCount", "()I", (void *)android_media_AudioSystem_getPrimaryOutputFrameCount}, - {"getOutputLatency", "(I)I", (void *)android_media_AudioSystem_getOutputLatency}, - {"setLowRamDevice", "(ZJ)I", (void *)android_media_AudioSystem_setLowRamDevice}, - {"checkAudioFlinger", "()I", (void *)android_media_AudioSystem_checkAudioFlinger}, - {"listAudioPorts", "(Ljava/util/ArrayList;[I)I", - (void *)android_media_AudioSystem_listAudioPorts}, - {"createAudioPatch", "([Landroid/media/AudioPatch;[Landroid/media/AudioPortConfig;[Landroid/media/AudioPortConfig;)I", - (void *)android_media_AudioSystem_createAudioPatch}, - {"releaseAudioPatch", "(Landroid/media/AudioPatch;)I", - (void *)android_media_AudioSystem_releaseAudioPatch}, - {"listAudioPatches", "(Ljava/util/ArrayList;[I)I", - (void *)android_media_AudioSystem_listAudioPatches}, - {"setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I", - (void *)android_media_AudioSystem_setAudioPortConfig}, - {"startAudioSource", "(Landroid/media/AudioPortConfig;Landroid/media/AudioAttributes;)I", - (void *)android_media_AudioSystem_startAudioSource}, - {"stopAudioSource", "(I)I", (void *)android_media_AudioSystem_stopAudioSource}, - {"getAudioHwSyncForSession", "(I)I", - (void *)android_media_AudioSystem_getAudioHwSyncForSession}, - {"registerPolicyMixes", "(Ljava/util/ArrayList;Z)I", - (void *)android_media_AudioSystem_registerPolicyMixes}, - {"setUidDeviceAffinities", "(I[I[Ljava/lang/String;)I", - (void *)android_media_AudioSystem_setUidDeviceAffinities}, - {"removeUidDeviceAffinities", "(I)I", - (void *)android_media_AudioSystem_removeUidDeviceAffinities}, - {"native_register_dynamic_policy_callback", "()V", - (void *)android_media_AudioSystem_registerDynPolicyCallback}, - {"native_register_recording_callback", "()V", - (void *)android_media_AudioSystem_registerRecordingCallback}, - {"systemReady", "()I", (void *)android_media_AudioSystem_systemReady}, - {"getStreamVolumeDB", "(III)F", (void *)android_media_AudioSystem_getStreamVolumeDB}, - {"native_is_offload_supported", "(IIIII)Z", (void *)android_media_AudioSystem_isOffloadSupported}, - {"getMicrophones", "(Ljava/util/ArrayList;)I", (void *)android_media_AudioSystem_getMicrophones}, - {"getSurroundFormats", "(Ljava/util/Map;Z)I", (void *)android_media_AudioSystem_getSurroundFormats}, - {"setSurroundFormatEnabled", "(IZ)I", (void *)android_media_AudioSystem_setSurroundFormatEnabled}, - {"setAssistantUid", "(I)I", (void *)android_media_AudioSystem_setAssistantUid}, - {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids}, - {"isHapticPlaybackSupported", "()Z", (void *)android_media_AudioSystem_isHapticPlaybackSupported}, - {"getHwOffloadEncodingFormatsSupportedForA2DP", "(Ljava/util/ArrayList;)I", - (void*)android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP}, - {"setSupportedSystemUsages", "([I)I", (void *)android_media_AudioSystem_setSupportedSystemUsages}, - {"setAllowedCapturePolicy", "(II)I", (void *)android_media_AudioSystem_setAllowedCapturePolicy}, - {"setRttEnabled", "(Z)I", (void *)android_media_AudioSystem_setRttEnabled}, - {"setAudioHalPids", "([I)I", (void *)android_media_AudioSystem_setAudioHalPids}, - {"isCallScreeningModeSupported", "()Z", (void *)android_media_AudioSystem_isCallScreeningModeSupported}, - {"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setPreferredDeviceForStrategy}, - {"removePreferredDeviceForStrategy", "(I)I", (void *)android_media_AudioSystem_removePreferredDeviceForStrategy}, - {"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getPreferredDeviceForStrategy}, - {"getDevicesForAttributes", "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getDevicesForAttributes}, - {"setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I", (void *)android_media_AudioSystem_setUserIdDeviceAffinities}, - {"removeUserIdDeviceAffinities", "(I)I", (void *)android_media_AudioSystem_removeUserIdDeviceAffinities} -}; +static const JNINativeMethod gMethods[] = + {{"setParameters", "(Ljava/lang/String;)I", + (void *)android_media_AudioSystem_setParameters}, + {"getParameters", "(Ljava/lang/String;)Ljava/lang/String;", + (void *)android_media_AudioSystem_getParameters}, + {"muteMicrophone", "(Z)I", (void *)android_media_AudioSystem_muteMicrophone}, + {"isMicrophoneMuted", "()Z", (void *)android_media_AudioSystem_isMicrophoneMuted}, + {"isStreamActive", "(II)Z", (void *)android_media_AudioSystem_isStreamActive}, + {"isStreamActiveRemotely", "(II)Z", + (void *)android_media_AudioSystem_isStreamActiveRemotely}, + {"isSourceActive", "(I)Z", (void *)android_media_AudioSystem_isSourceActive}, + {"newAudioSessionId", "()I", (void *)android_media_AudioSystem_newAudioSessionId}, + {"newAudioPlayerId", "()I", (void *)android_media_AudioSystem_newAudioPlayerId}, + {"newAudioRecorderId", "()I", (void *)android_media_AudioSystem_newAudioRecorderId}, + {"setDeviceConnectionState", "(IILjava/lang/String;Ljava/lang/String;I)I", + (void *)android_media_AudioSystem_setDeviceConnectionState}, + {"getDeviceConnectionState", "(ILjava/lang/String;)I", + (void *)android_media_AudioSystem_getDeviceConnectionState}, + {"handleDeviceConfigChange", "(ILjava/lang/String;Ljava/lang/String;I)I", + (void *)android_media_AudioSystem_handleDeviceConfigChange}, + {"setPhoneState", "(I)I", (void *)android_media_AudioSystem_setPhoneState}, + {"setForceUse", "(II)I", (void *)android_media_AudioSystem_setForceUse}, + {"getForceUse", "(I)I", (void *)android_media_AudioSystem_getForceUse}, + {"initStreamVolume", "(III)I", (void *)android_media_AudioSystem_initStreamVolume}, + {"setStreamVolumeIndex", "(III)I", (void *)android_media_AudioSystem_setStreamVolumeIndex}, + {"getStreamVolumeIndex", "(II)I", (void *)android_media_AudioSystem_getStreamVolumeIndex}, + {"setVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;II)I", + (void *)android_media_AudioSystem_setVolumeIndexForAttributes}, + {"getVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;I)I", + (void *)android_media_AudioSystem_getVolumeIndexForAttributes}, + {"getMinVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;)I", + (void *)android_media_AudioSystem_getMinVolumeIndexForAttributes}, + {"getMaxVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;)I", + (void *)android_media_AudioSystem_getMaxVolumeIndexForAttributes}, + {"setMasterVolume", "(F)I", (void *)android_media_AudioSystem_setMasterVolume}, + {"getMasterVolume", "()F", (void *)android_media_AudioSystem_getMasterVolume}, + {"setMasterMute", "(Z)I", (void *)android_media_AudioSystem_setMasterMute}, + {"getMasterMute", "()Z", (void *)android_media_AudioSystem_getMasterMute}, + {"setMasterMono", "(Z)I", (void *)android_media_AudioSystem_setMasterMono}, + {"getMasterMono", "()Z", (void *)android_media_AudioSystem_getMasterMono}, + {"setMasterBalance", "(F)I", (void *)android_media_AudioSystem_setMasterBalance}, + {"getMasterBalance", "()F", (void *)android_media_AudioSystem_getMasterBalance}, + {"getDevicesForStream", "(I)I", (void *)android_media_AudioSystem_getDevicesForStream}, + {"getPrimaryOutputSamplingRate", "()I", + (void *)android_media_AudioSystem_getPrimaryOutputSamplingRate}, + {"getPrimaryOutputFrameCount", "()I", + (void *)android_media_AudioSystem_getPrimaryOutputFrameCount}, + {"getOutputLatency", "(I)I", (void *)android_media_AudioSystem_getOutputLatency}, + {"setLowRamDevice", "(ZJ)I", (void *)android_media_AudioSystem_setLowRamDevice}, + {"checkAudioFlinger", "()I", (void *)android_media_AudioSystem_checkAudioFlinger}, + {"listAudioPorts", "(Ljava/util/ArrayList;[I)I", + (void *)android_media_AudioSystem_listAudioPorts}, + {"createAudioPatch", + "([Landroid/media/AudioPatch;[Landroid/media/AudioPortConfig;[Landroid/media/" + "AudioPortConfig;)I", + (void *)android_media_AudioSystem_createAudioPatch}, + {"releaseAudioPatch", "(Landroid/media/AudioPatch;)I", + (void *)android_media_AudioSystem_releaseAudioPatch}, + {"listAudioPatches", "(Ljava/util/ArrayList;[I)I", + (void *)android_media_AudioSystem_listAudioPatches}, + {"setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I", + (void *)android_media_AudioSystem_setAudioPortConfig}, + {"startAudioSource", "(Landroid/media/AudioPortConfig;Landroid/media/AudioAttributes;)I", + (void *)android_media_AudioSystem_startAudioSource}, + {"stopAudioSource", "(I)I", (void *)android_media_AudioSystem_stopAudioSource}, + {"getAudioHwSyncForSession", "(I)I", + (void *)android_media_AudioSystem_getAudioHwSyncForSession}, + {"registerPolicyMixes", "(Ljava/util/ArrayList;Z)I", + (void *)android_media_AudioSystem_registerPolicyMixes}, + {"setUidDeviceAffinities", "(I[I[Ljava/lang/String;)I", + (void *)android_media_AudioSystem_setUidDeviceAffinities}, + {"removeUidDeviceAffinities", "(I)I", + (void *)android_media_AudioSystem_removeUidDeviceAffinities}, + {"native_register_dynamic_policy_callback", "()V", + (void *)android_media_AudioSystem_registerDynPolicyCallback}, + {"native_register_recording_callback", "()V", + (void *)android_media_AudioSystem_registerRecordingCallback}, + {"systemReady", "()I", (void *)android_media_AudioSystem_systemReady}, + {"getStreamVolumeDB", "(III)F", (void *)android_media_AudioSystem_getStreamVolumeDB}, + {"native_is_offload_supported", "(IIIII)Z", + (void *)android_media_AudioSystem_isOffloadSupported}, + {"getMicrophones", "(Ljava/util/ArrayList;)I", + (void *)android_media_AudioSystem_getMicrophones}, + {"getSurroundFormats", "(Ljava/util/Map;Z)I", + (void *)android_media_AudioSystem_getSurroundFormats}, + {"setSurroundFormatEnabled", "(IZ)I", + (void *)android_media_AudioSystem_setSurroundFormatEnabled}, + {"setAssistantUid", "(I)I", (void *)android_media_AudioSystem_setAssistantUid}, + {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids}, + {"isHapticPlaybackSupported", "()Z", + (void *)android_media_AudioSystem_isHapticPlaybackSupported}, + {"getHwOffloadEncodingFormatsSupportedForA2DP", "(Ljava/util/ArrayList;)I", + (void *)android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP}, + {"setSupportedSystemUsages", "([I)I", + (void *)android_media_AudioSystem_setSupportedSystemUsages}, + {"setAllowedCapturePolicy", "(II)I", + (void *)android_media_AudioSystem_setAllowedCapturePolicy}, + {"setRttEnabled", "(Z)I", (void *)android_media_AudioSystem_setRttEnabled}, + {"setAudioHalPids", "([I)I", (void *)android_media_AudioSystem_setAudioHalPids}, + {"isCallScreeningModeSupported", "()Z", + (void *)android_media_AudioSystem_isCallScreeningModeSupported}, + {"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I", + (void *)android_media_AudioSystem_setPreferredDeviceForStrategy}, + {"removePreferredDeviceForStrategy", "(I)I", + (void *)android_media_AudioSystem_removePreferredDeviceForStrategy}, + {"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDevice;)I", + (void *)android_media_AudioSystem_getPreferredDeviceForStrategy}, + {"getDevicesForAttributes", + "(Landroid/media/AudioAttributes;[Landroid/media/AudioDevice;)I", + (void *)android_media_AudioSystem_getDevicesForAttributes}, + {"setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I", + (void *)android_media_AudioSystem_setUserIdDeviceAffinities}, + {"removeUserIdDeviceAffinities", "(I)I", + (void *)android_media_AudioSystem_removeUserIdDeviceAffinities}}; static const JNINativeMethod gEventHandlerMethods[] = { {"native_setup", diff --git a/core/jni/android_os_incremental_IncrementalManager.cpp b/core/jni/android_os_incremental_IncrementalManager.cpp index 698062a2651e..d41e98241b07 100644 --- a/core/jni/android_os_incremental_IncrementalManager.cpp +++ b/core/jni/android_os_incremental_IncrementalManager.cpp @@ -26,6 +26,10 @@ namespace android { +static jboolean nativeIsEnabled(JNIEnv* env, jobject clazz) { + return IncFs_IsEnabled(); +} + static jboolean nativeIsIncrementalPath(JNIEnv* env, jobject clazz, jstring javaPath) { @@ -34,8 +38,8 @@ static jboolean nativeIsIncrementalPath(JNIEnv* env, } static const JNINativeMethod method_table[] = { - {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", - (void*)nativeIsIncrementalPath}, + {"nativeIsEnabled", "()Z", (void*)nativeIsEnabled}, + {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", (void*)nativeIsIncrementalPath}, }; int register_android_os_incremental_IncrementalManager(JNIEnv* env) { diff --git a/core/proto/android/app/appexitinfo.proto b/core/proto/android/app/appexitinfo.proto index e23f150fab25..6a4922000805 100644 --- a/core/proto/android/app/appexitinfo.proto +++ b/core/proto/android/app/appexitinfo.proto @@ -178,8 +178,8 @@ message ApplicationExitInfoProto { } optional Importance importance = 10; - optional int32 pss = 11; - optional int32 rss = 12; + optional int64 pss = 11; + optional int64 rss = 12; optional int64 timestamp = 13; optional string description = 14; } diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index fc0a2ef6041a..c14d99c5d3fb 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2578,4 +2578,7 @@ enum PageId { // CATEGORY: SETTINGS // OS: R CONNECTION_DEVICE_ADVANCED_NFC = 1828; + + // OPEN: Settings -> Apps & Notifications -> Special App Access + INTERACT_ACROSS_PROFILES = 1829; } diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index 303d62dbb30a..546c5a092a50 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -219,12 +219,8 @@ message ConstantsProto { // The maximum number of background jobs we allow when the system is in a // critical memory state. optional int32 bg_critical_job_count = 14; - // The maximum number of times we allow a job to have itself rescheduled - // before giving up on it, for standard jobs. - optional int32 max_standard_reschedule_count = 15; - // The maximum number of times we allow a job to have itself rescheduled - // before giving up on it, for jobs that are executing work. - optional int32 max_work_reschedule_count = 16; + reserved 15; // max_standard_reschedule_count + reserved 16; // max_work_reschedule_count // The minimum backoff time to allow for linear backoff. optional int64 min_linear_backoff_time_ms = 17; // The minimum backoff time to allow for exponential backoff. diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index de0d172ecf04..ff696715e94d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1535,7 +1535,7 @@ @hide --> <permission android:name="android.permission.ACCESS_CONTEXT_HUB" - android:protectionLevel="signature" /> + android:protectionLevel="signature|privileged" /> <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB"/> <!-- @SystemApi Allows an application to create mock location providers for testing. @@ -1998,17 +1998,17 @@ <permission android:name="android.permission.REMOTE_AUDIO_PLAYBACK" android:protectionLevel="signature" /> - <!-- @SystemApi Allows TvInputService to access underlying TV input hardware such as + <!-- Allows TvInputService to access underlying TV input hardware such as built-in tuners and HDMI-in's. - @hide This should only be used by OEM's TvInputService's. - --> + <p>This should only be used by OEM's TvInputService's. + @hide @SystemApi --> <permission android:name="android.permission.TV_INPUT_HARDWARE" android:protectionLevel="signature|privileged|vendorPrivileged" /> - <!-- @SystemApi Allows to capture a frame of TV input hardware such as + <!-- Allows to capture a frame of TV input hardware such as built-in tuners and HDMI-in's. - @hide <p>Not for use by third-party applications. - --> + <p>Not for use by third-party applications. + @hide @SystemApi --> <permission android:name="android.permission.CAPTURE_TV_INPUT" android:protectionLevel="signature|privileged" /> @@ -3433,10 +3433,10 @@ <permission android:name="android.permission.READ_CONTENT_RATING_SYSTEMS" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi Allows an application to notify TV inputs by sending broadcasts. + <!-- Allows an application to notify TV inputs by sending broadcasts. <p>Protection level: signature|privileged <p>Not for use by third-party applications. - @hide --> + @hide @SystemApi --> <permission android:name="android.permission.NOTIFY_TV_INPUTS" android:protectionLevel="signature|privileged" /> diff --git a/core/res/res/drawable/tab_indicator_resolver.xml b/core/res/res/drawable/tab_indicator_resolver.xml new file mode 100644 index 000000000000..ff16d81a6383 --- /dev/null +++ b/core/res/res/drawable/tab_indicator_resolver.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 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. +--> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" + android:paddingMode="nest"> + <item> + <ripple android:color="@color/tab_highlight_material"> + <item android:id="@id/mask"> + <color android:color="@color/white" /> + </item> + </ripple> + </item> + <item android:gravity="bottom"> + <shape android:shape="rectangle" + android:tint="@color/resolver_tabs_active_color"> + <size android:height="2dp" /> + <solid android:color="@color/tab_indicator_material" /> + </shape> + </item> + <item android:bottom="2dp" + android:drawable="@color/transparent" /> +</layer-list> diff --git a/core/res/res/layout/accessibility_button_chooser_item.xml b/core/res/res/layout/accessibility_button_chooser_item.xml index d6fd7aa30e6e..d19e313055ae 100644 --- a/core/res/res/layout/accessibility_button_chooser_item.xml +++ b/core/res/res/layout/accessibility_button_chooser_item.xml @@ -20,7 +20,6 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:clickable="true" android:gravity="center" android:paddingStart="16dp" android:paddingEnd="16dp" diff --git a/core/res/res/layout/tab_indicator_resolver.xml b/core/res/res/layout/tab_indicator_resolver.xml new file mode 100644 index 000000000000..2038da656ee3 --- /dev/null +++ b/core/res/res/layout/tab_indicator_resolver.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="?android:attr/actionBarSize" + android:orientation="horizontal" + style="@android:style/Widget.Material.Resolver.Tab"> + + <ImageView + android:id="@android:id/icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:visibility="gone" /> + + <TextView + android:id="@android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:textAllCaps="false" + style="@android:style/Widget.Material.TabText" /> + +</LinearLayout> diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml index c5e72f012a21..33caeb65c2f8 100644 --- a/core/res/res/values-night/colors.xml +++ b/core/res/res/values-night/colors.xml @@ -32,4 +32,6 @@ <color name="chooser_row_divider">@color/list_divider_color_dark</color> <color name="chooser_gradient_background">@color/loading_gradient_background_color_dark</color> <color name="chooser_gradient_highlight">@color/loading_gradient_highlight_color_dark</color> + + <color name="resolver_tabs_active_color">#FF8AB4F8</color> </resources> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index cef21db1e0f8..81ec27841aa7 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -1679,6 +1679,15 @@ easier. <item name="listPreferredItemPaddingStart">?attr/dialogPreferredPadding</item> <item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item> <item name="navigationBarColor">@android:color/transparent</item> + <item name="tabWidgetStyle">@style/Widget.DeviceDefault.Resolver.TabWidget</item> + </style> + + <style name="Widget.DeviceDefault.Resolver.TabWidget" parent="Widget.DeviceDefault.TabWidget"> + <item name="tabLayout">@layout/tab_indicator_resolver</item> + </style> + + <style name="Widget.Material.Resolver.Tab" parent="Widget.Material.Tab"> + <item name="background">@drawable/tab_indicator_resolver</item> </style> <style name="Theme.DeviceDefault.Resolver" parent="Theme.DeviceDefault.ResolverCommon"> diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 8b8e9ea4c6ee..b71c5800161e 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -19,7 +19,6 @@ package android.view.accessibility; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceConnection; import android.content.pm.ParceledListSlice; -import android.graphics.Bitmap; import android.graphics.Region; import android.os.Bundle; import android.os.IBinder; @@ -157,9 +156,5 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon return -1; } - public Bitmap takeScreenshot(int displayId) { - return null; - } - - public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {} + public void takeScreenshot(int displayId, RemoteCallback callback) {} } diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 6761435a8171..31e45558139d 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -27,7 +27,6 @@ #include "DamageAccumulator.h" #include "pipeline/skia/SkiaDisplayList.h" #endif -#include "utils/FatVector.h" #include "utils/MathUtils.h" #include "utils/StringUtils.h" #include "utils/TraceUtils.h" @@ -37,6 +36,7 @@ #include <atomic> #include <sstream> #include <string> +#include <ui/FatVector.h> namespace android { namespace uirenderer { diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index d55e5b0ce836..c0ec2174bb35 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -27,6 +27,8 @@ #include <androidfw/ResourceTypes.h> +#include <ui/FatVector.h> + #include "AnimatorManager.h" #include "CanvasTransform.h" #include "Debug.h" @@ -35,7 +37,6 @@ #include "RenderProperties.h" #include "pipeline/skia/SkiaDisplayList.h" #include "pipeline/skia/SkiaLayer.h" -#include "utils/FatVector.h" #include <vector> diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index 4b2857f6c290..afd82aca07c5 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -24,6 +24,19 @@ using namespace android; +sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const { + const skcms_ICCProfile* encodedProfile = mCodec->getICCProfile(); + if (encodedProfile) { + // If the profile maps directly to an SkColorSpace, that SkColorSpace + // will be returned. Otherwise, nullptr will be returned. In either + // case, using this SkColorSpace results in doing no color correction. + return SkColorSpace::Make(*encodedProfile); + } + + // The image has no embedded color profile, and should be treated as SRGB. + return SkColorSpace::MakeSRGB(); +} + ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker) : mCodec(std::move(codec)) , mPeeker(std::move(peeker)) @@ -31,7 +44,7 @@ ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChu , mDecodeSize(mTargetSize) , mOutColorType(mCodec->computeOutputColorType(kN32_SkColorType)) , mUnpremultipliedRequired(false) - , mOutColorSpace(mCodec->computeOutputColorSpace(mOutColorType, nullptr)) + , mOutColorSpace(getDefaultColorSpace()) , mSampleSize(1) { } diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h index 0c99f84cbb72..a1b51573db3f 100644 --- a/libs/hwui/hwui/ImageDecoder.h +++ b/libs/hwui/hwui/ImageDecoder.h @@ -43,6 +43,7 @@ public: bool setUnpremultipliedRequired(bool unpremultipliedRequired); + sk_sp<SkColorSpace> getDefaultColorSpace() const; void setOutColorSpace(sk_sp<SkColorSpace> cs); // The size is the final size after scaling and cropping. diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h index cfc0f9b258da..d669f84c5ee2 100644 --- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h +++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h @@ -21,7 +21,7 @@ #include <SkCanvas.h> #include <SkDrawable.h> -#include <utils/FatVector.h> +#include <ui/FatVector.h> namespace android { namespace uirenderer { diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index cae3e3b5188c..206b58f62ea7 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -27,7 +27,6 @@ #include "pipeline/skia/SkiaOpenGLPipeline.h" #include "pipeline/skia/SkiaVulkanPipeline.h" #include "renderstate/RenderState.h" -#include "utils/FatVector.h" #include "utils/TimeUtils.h" #include "utils/TraceUtils.h" @@ -40,6 +39,8 @@ #include <utils/Mutex.h> #include <thread> +#include <ui/FatVector.h> + namespace android { namespace uirenderer { namespace renderthread { diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index a5355fc3499d..ba70afc8b8d2 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -23,13 +23,13 @@ #include <GrContext.h> #include <GrTypes.h> #include <android/sync.h> +#include <ui/FatVector.h> #include <vk/GrVkExtensions.h> #include <vk/GrVkTypes.h> #include "Properties.h" #include "RenderThread.h" #include "renderstate/RenderState.h" -#include "utils/FatVector.h" #include "utils/TraceUtils.h" namespace android { diff --git a/libs/hwui/tests/unit/FatVectorTests.cpp b/libs/hwui/tests/unit/FatVectorTests.cpp index 8523e6c9e973..6585a6249b44 100644 --- a/libs/hwui/tests/unit/FatVectorTests.cpp +++ b/libs/hwui/tests/unit/FatVectorTests.cpp @@ -15,7 +15,7 @@ */ #include <gtest/gtest.h> -#include <utils/FatVector.h> +#include <ui/FatVector.h> #include <tests/common/TestUtils.h> diff --git a/libs/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h deleted file mode 100644 index 8cc4d1010ab6..000000000000 --- a/libs/hwui/utils/FatVector.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2015, The Android Open Source Project - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef ANDROID_FAT_VECTOR_H -#define ANDROID_FAT_VECTOR_H - -#include "utils/Macros.h" - -#include <stddef.h> -#include <stdlib.h> -#include <utils/Log.h> -#include <type_traits> - -#include <vector> - -namespace android { -namespace uirenderer { - -template <typename T, size_t SIZE> -class InlineStdAllocator { -public: - struct Allocation { - PREVENT_COPY_AND_ASSIGN(Allocation); - - public: - Allocation(){}; - // char array instead of T array, so memory is uninitialized, with no destructors run - char array[sizeof(T) * SIZE]; - bool inUse = false; - }; - - typedef T value_type; // needed to implement std::allocator - typedef T* pointer; // needed to implement std::allocator - - explicit InlineStdAllocator(Allocation& allocation) : mAllocation(allocation) {} - InlineStdAllocator(const InlineStdAllocator& other) : mAllocation(other.mAllocation) {} - ~InlineStdAllocator() {} - - T* allocate(size_t num, const void* = 0) { - if (!mAllocation.inUse && num <= SIZE) { - mAllocation.inUse = true; - return (T*)mAllocation.array; - } else { - return (T*)malloc(num * sizeof(T)); - } - } - - void deallocate(pointer p, size_t num) { - if (p == (T*)mAllocation.array) { - mAllocation.inUse = false; - } else { - // 'free' instead of delete here - destruction handled separately - free(p); - } - } - Allocation& mAllocation; -}; - -/** - * std::vector with SIZE elements preallocated into an internal buffer. - * - * Useful for avoiding the cost of malloc in cases where only SIZE or - * fewer elements are needed in the common case. - */ -template <typename T, size_t SIZE> -class FatVector : public std::vector<T, InlineStdAllocator<T, SIZE>> { -public: - FatVector() - : std::vector<T, InlineStdAllocator<T, SIZE>>( - InlineStdAllocator<T, SIZE>(mAllocation)) { - this->reserve(SIZE); - } - - explicit FatVector(size_t capacity) : FatVector() { this->resize(capacity); } - -private: - typename InlineStdAllocator<T, SIZE>::Allocation mAllocation; -}; - -} // namespace uirenderer -} // namespace android - -#endif // ANDROID_FAT_VECTOR_H diff --git a/media/java/android/media/AudioDeviceAddress.aidl b/media/java/android/media/AudioDevice.aidl index 6a1a7f79247c..02071e5f0c13 100644 --- a/media/java/android/media/AudioDeviceAddress.aidl +++ b/media/java/android/media/AudioDevice.aidl @@ -15,4 +15,4 @@ package android.media; -parcelable AudioDeviceAddress; +parcelable AudioDevice; diff --git a/media/java/android/media/AudioDeviceAddress.java b/media/java/android/media/AudioDevice.java index 3d8fc373006e..31ecc7b4ead4 100644 --- a/media/java/android/media/AudioDeviceAddress.java +++ b/media/java/android/media/AudioDevice.java @@ -28,7 +28,7 @@ import java.util.Objects; /** * @hide - * Class to represent device type (speaker, headset...), address and role (input, output) + * Class to represent device type (speaker, headset...), address (if known) and role (input, output) * of an audio device. * <p>Unlike {@link AudioDeviceInfo}, the device * doesn't need to be connected to be uniquely identified, it can @@ -39,7 +39,7 @@ import java.util.Objects; * permission, APIs using one rely on MODIFY_AUDIO_ROUTING. */ @SystemApi -public final class AudioDeviceAddress implements Parcelable { +public final class AudioDevice implements Parcelable { /** * A role identifying input devices, such as microphones. @@ -78,7 +78,7 @@ public final class AudioDeviceAddress implements Parcelable { * type and address. */ @SystemApi - public AudioDeviceAddress(@NonNull AudioDeviceInfo deviceInfo) { + public AudioDevice(@NonNull AudioDeviceInfo deviceInfo) { Objects.requireNonNull(deviceInfo); mRole = deviceInfo.isSink() ? ROLE_OUTPUT : ROLE_INPUT; mType = deviceInfo.getType(); @@ -93,7 +93,7 @@ public final class AudioDeviceAddress implements Parcelable { * @param address the address of the device, or an empty string for devices without one */ @SystemApi - public AudioDeviceAddress(@Role int role, @AudioDeviceInfo.AudioDeviceType int type, + public AudioDevice(@Role int role, @AudioDeviceInfo.AudioDeviceType int type, @NonNull String address) { Objects.requireNonNull(address); if (role != ROLE_OUTPUT && role != ROLE_INPUT) { @@ -111,7 +111,7 @@ public final class AudioDeviceAddress implements Parcelable { mAddress = address; } - /*package*/ AudioDeviceAddress(int nativeType, @NonNull String address) { + /*package*/ AudioDevice(int nativeType, @NonNull String address) { mRole = (nativeType & AudioSystem.DEVICE_BIT_IN) != 0 ? ROLE_INPUT : ROLE_OUTPUT; mType = AudioDeviceInfo.convertInternalDeviceToDeviceType(nativeType); mAddress = address; @@ -157,7 +157,7 @@ public final class AudioDeviceAddress implements Parcelable { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AudioDeviceAddress that = (AudioDeviceAddress) o; + AudioDevice that = (AudioDevice) o; return ((mRole == that.mRole) && (mType == that.mType) && mAddress.equals(that.mAddress)); @@ -170,7 +170,7 @@ public final class AudioDeviceAddress implements Parcelable { @Override public String toString() { - return new String("AudioDeviceAddress:" + return new String("AudioDevice:" + " role:" + roleToString(mRole) + " type:" + (mRole == ROLE_OUTPUT ? AudioSystem.getOutputDeviceName( AudioDeviceInfo.convertDeviceTypeToInternalDevice(mType)) @@ -191,25 +191,25 @@ public final class AudioDeviceAddress implements Parcelable { dest.writeString(mAddress); } - private AudioDeviceAddress(@NonNull Parcel in) { + private AudioDevice(@NonNull Parcel in) { mRole = in.readInt(); mType = in.readInt(); mAddress = in.readString(); } - public static final @NonNull Parcelable.Creator<AudioDeviceAddress> CREATOR = - new Parcelable.Creator<AudioDeviceAddress>() { + public static final @NonNull Parcelable.Creator<AudioDevice> CREATOR = + new Parcelable.Creator<AudioDevice>() { /** - * Rebuilds an AudioDeviceAddress previously stored with writeToParcel(). - * @param p Parcel object to read the AudioDeviceAddress from - * @return a new AudioDeviceAddress created from the data in the parcel + * Rebuilds an AudioDevice previously stored with writeToParcel(). + * @param p Parcel object to read the AudioDevice from + * @return a new AudioDevice created from the data in the parcel */ - public AudioDeviceAddress createFromParcel(Parcel p) { - return new AudioDeviceAddress(p); + public AudioDevice createFromParcel(Parcel p) { + return new AudioDevice(p); } - public AudioDeviceAddress[] newArray(int size) { - return new AudioDeviceAddress[size]; + public AudioDevice[] newArray(int size) { + return new AudioDevice[size]; } }; } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 7b17f9f764fb..4a1088bfa877 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1596,7 +1596,7 @@ public class AudioManager { @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull AudioProductStrategy strategy, - @NonNull AudioDeviceAddress device) { + @NonNull AudioDevice device) { Objects.requireNonNull(strategy); Objects.requireNonNull(device); try { @@ -1611,7 +1611,7 @@ public class AudioManager { /** * @hide * Removes the preferred audio device previously set with - * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAddress)}. + * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDevice)}. * @param strategy the audio strategy whose routing will be affected * @return true if the operation was successful, false otherwise (invalid strategy, or no * device set for example) @@ -1632,14 +1632,14 @@ public class AudioManager { /** * @hide * Return the preferred device for an audio strategy, previously set with - * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAddress)} + * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDevice)} * @param strategy the strategy to query * @return the preferred device for that strategy, or null if none was ever set or if the * strategy is invalid */ @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - public @Nullable AudioDeviceAddress getPreferredDeviceForStrategy( + public @Nullable AudioDevice getPreferredDeviceForStrategy( @NonNull AudioProductStrategy strategy) { Objects.requireNonNull(strategy); try { @@ -4379,7 +4379,7 @@ public class AudioManager { */ @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - public @NonNull List<AudioDeviceAddress> getDevicesForAttributes( + public @NonNull List<AudioDevice> getDevicesForAttributes( @NonNull AudioAttributes attributes) { Objects.requireNonNull(attributes); final IAudioService service = getService(); diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 02cb8aafea0c..0a0f7f643ac9 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1085,18 +1085,18 @@ public class AudioSystem * @return an empty list if there was an issue with the request, a list of audio devices * otherwise (typically one device, except for duplicated paths). */ - public static @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes( + public static @NonNull ArrayList<AudioDevice> getDevicesForAttributes( @NonNull AudioAttributes attributes) { Objects.requireNonNull(attributes); - final AudioDeviceAddress[] devices = new AudioDeviceAddress[MAX_DEVICE_ROUTING]; + final AudioDevice[] devices = new AudioDevice[MAX_DEVICE_ROUTING]; final int res = getDevicesForAttributes(attributes, devices); - final ArrayList<AudioDeviceAddress> routeDevices = new ArrayList<>(); + final ArrayList<AudioDevice> routeDevices = new ArrayList<>(); if (res != SUCCESS) { Log.e(TAG, "error " + res + " in getDevicesForAttributes for " + attributes); return routeDevices; } - for (AudioDeviceAddress device : devices) { + for (AudioDevice device : devices) { if (device != null) { routeDevices.add(device); } @@ -1106,12 +1106,12 @@ public class AudioSystem /** * Maximum number of audio devices a track is ever routed to, determines the size of the - * array passed to {@link #getDevicesForAttributes(AudioAttributes, AudioDeviceAddress[])} + * array passed to {@link #getDevicesForAttributes(AudioAttributes, AudioDevice[])} */ private static final int MAX_DEVICE_ROUTING = 4; private static native int getDevicesForAttributes(@NonNull AudioAttributes aa, - @NonNull AudioDeviceAddress[] devices); + @NonNull AudioDevice[] devices); /** @hide returns true if master mono is enabled. */ public static native boolean getMasterMono(); @@ -1246,7 +1246,7 @@ public class AudioSystem * @return {@link #SUCCESS} if successfully set */ public static int setPreferredDeviceForStrategy( - int strategy, @NonNull AudioDeviceAddress device) { + int strategy, @NonNull AudioDevice device) { return setPreferredDeviceForStrategy(strategy, AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()), device.getAddress()); @@ -1277,7 +1277,7 @@ public class AudioSystem * and written to the array */ public static native int getPreferredDeviceForStrategy(int strategy, - AudioDeviceAddress[] device); + AudioDevice[] device); // Items shared with audio service diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 64c5c05b5621..0fbc0d2180ba 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -18,7 +18,7 @@ package android.media; import android.bluetooth.BluetoothDevice; import android.media.AudioAttributes; -import android.media.AudioDeviceAddress; +import android.media.AudioDevice; import android.media.AudioFocusInfo; import android.media.AudioPlaybackConfiguration; import android.media.AudioRecordingConfiguration; @@ -274,13 +274,13 @@ interface IAudioService { boolean isCallScreeningModeSupported(); - int setPreferredDeviceForStrategy(in int strategy, in AudioDeviceAddress device); + int setPreferredDeviceForStrategy(in int strategy, in AudioDevice device); int removePreferredDeviceForStrategy(in int strategy); - AudioDeviceAddress getPreferredDeviceForStrategy(in int strategy); + AudioDevice getPreferredDeviceForStrategy(in int strategy); - List<AudioDeviceAddress> getDevicesForAttributes(in AudioAttributes attributes); + List<AudioDevice> getDevicesForAttributes(in AudioAttributes attributes); int setAllowedCapturePolicy(in int capturePolicy); diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index d7b74df0a5e6..64186106d2c8 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -471,7 +471,8 @@ public class MediaRouter2 { synchronized (sRouterLock) { for (MediaRoute2Info route : routes) { mRoutes.put(route.getId(), route); - if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { + if (route.isSystemRoute() + || route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { addedRoutes.add(route); } } @@ -487,7 +488,8 @@ public class MediaRouter2 { synchronized (sRouterLock) { for (MediaRoute2Info route : routes) { mRoutes.remove(route.getId()); - if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { + if (route.isSystemRoute() + || route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { removedRoutes.add(route); } } @@ -503,7 +505,8 @@ public class MediaRouter2 { synchronized (sRouterLock) { for (MediaRoute2Info route : routes) { mRoutes.put(route.getId(), route); - if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { + if (route.isSystemRoute() + || route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) { changedRoutes.add(route); } } @@ -643,8 +646,8 @@ public class MediaRouter2 { private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes, RouteDiscoveryPreference discoveryRequest) { return routes.stream() - .filter( - route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures())) + .filter(route -> route.isSystemRoute() + || route.hasAnyFeatures(discoveryRequest.getPreferredFeatures())) .collect(Collectors.toList()); } diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java index c25a5333017b..6157ef4657ed 100644 --- a/media/java/android/media/audiofx/AudioEffect.java +++ b/media/java/android/media/audiofx/AudioEffect.java @@ -25,7 +25,7 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; -import android.media.AudioDeviceAddress; +import android.media.AudioDevice; import android.media.AudioDeviceInfo; import android.media.AudioSystem; import android.os.Build; @@ -476,12 +476,12 @@ public class AudioEffect { */ @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) - public AudioEffect(@NonNull UUID uuid, @NonNull AudioDeviceAddress device) { + public AudioEffect(@NonNull UUID uuid, @NonNull AudioDevice device) { this(EFFECT_TYPE_NULL, Objects.requireNonNull(uuid), 0, -2, Objects.requireNonNull(device)); } private AudioEffect(UUID type, UUID uuid, int priority, - int audioSession, @Nullable AudioDeviceAddress device) + int audioSession, @Nullable AudioDevice device) throws IllegalArgumentException, UnsupportedOperationException, RuntimeException { int[] id = new int[1]; diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java index 118f65c214e1..08953392ca18 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java +++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java @@ -113,7 +113,7 @@ public final class SoundTriggerDetector { * This capability may or may not be supported by the system, and support can be queried * by calling {@link SoundTriggerManager#getModuleProperties()} and checking * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for - * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_ECHO_CANCELLATION}. + * this flag is {@link SoundTrigger.ModuleProperties#AUDIO_CAPABILITY_ECHO_CANCELLATION}. * If this flag is passed without the audio capability supported, there will be no audio effect * applied. */ @@ -125,8 +125,9 @@ public final class SoundTriggerDetector { * This capability may or may not be supported by the system, and support can be queried * by calling {@link SoundTriggerManager#getModuleProperties()} and checking * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for - * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_NOISE_SUPPRESSION}. If this flag - * is passed without the audio capability supported, there will be no audio effect applied. + * this flag is {@link SoundTrigger.ModuleProperties#AUDIO_CAPABILITY_NOISE_SUPPRESSION}. + * If this flag is passed without the audio capability supported, there will be no audio effect + * applied. */ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8; @@ -296,10 +297,10 @@ public final class SoundTriggerDetector { int audioCapabilities = 0; if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) { - audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION; + audioCapabilities |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION; } if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) { - audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION; + audioCapabilities |= SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION; } int status; diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java index dd4dac223a86..6a8483c58276 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerManager.java +++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java @@ -173,8 +173,13 @@ public final class SoundTriggerManager { } /** - * Factory constructor to create a SoundModel instance for use with methods in this - * class. + * Factory constructor to a voice model to be used with {@link SoundTriggerManager} + * + * @param modelUuid Unique identifier associated with the model. + * @param vendorUuid Unique identifier associated the calling vendor. + * @param data Model's data. + * @param version Version identifier for the model. + * @return Voice model */ @NonNull public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid, @@ -186,8 +191,12 @@ public final class SoundTriggerManager { } /** - * Factory constructor to create a SoundModel instance for use with methods in this - * class. + * Factory constructor to a voice model to be used with {@link SoundTriggerManager} + * + * @param modelUuid Unique identifier associated with the model. + * @param vendorUuid Unique identifier associated the calling vendor. + * @param data Model's data. + * @return Voice model */ @NonNull public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid, @@ -195,20 +204,40 @@ public final class SoundTriggerManager { return create(modelUuid, vendorUuid, data, -1); } + /** + * Get the model's unique identifier + * + * @return UUID associated with the model + */ @NonNull public UUID getModelUuid() { return mGenericSoundModel.uuid; } + /** + * Get the model's vendor identifier + * + * @return UUID associated with the vendor of the model + */ @NonNull public UUID getVendorUuid() { return mGenericSoundModel.vendorUuid; } + /** + * Get the model's version + * + * @return Version associated with the model + */ public int getVersion() { return mGenericSoundModel.version; } + /** + * Get the underlying model data + * + * @return Backing data of the model + */ @Nullable public byte[] getModelData() { return mGenericSoundModel.data; diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 5e012447e9dd..3561f8393eea 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -74,6 +74,8 @@ public class Tuner implements AutoCloseable { private List<Integer> mFrontendIds; private Frontend mFrontend; private EventHandler mHandler; + @Nullable + private FrontendInfo mFrontendInfo; private List<Integer> mLnbIds; private Lnb mLnb; @@ -97,6 +99,7 @@ public class Tuner implements AutoCloseable { public Tuner(@NonNull Context context, @NonNull String tvInputSessionId, @TvInputService.PriorityHintUseCaseType int useCase, @Nullable OnResourceLostListener listener) { + nativeSetup(); mContext = context; } @@ -185,7 +188,7 @@ public class Tuner implements AutoCloseable { /** * Listener for resource lost. * - * <p>Resource is reclaimed and tuner instance is forced to close. + * <p>Insufficient resources are reclaimed by higher priority clients. */ public interface OnResourceLostListener { /** @@ -292,6 +295,7 @@ public class Tuner implements AutoCloseable { @Result public int tune(@NonNull FrontendSettings settings) { TunerUtils.checkTunerPermission(mContext); + mFrontendInfo = null; return nativeTune(settings.getType(), settings); } @@ -333,6 +337,7 @@ public class Tuner implements AutoCloseable { } mScanCallback = scanCallback; mScanCallbackExecutor = executor; + mFrontendInfo = null; return nativeScan(settings.getType(), settings, scanType); } @@ -468,7 +473,10 @@ public class Tuner implements AutoCloseable { if (mFrontend == null) { throw new IllegalStateException("frontend is not initialized"); } - return nativeGetFrontendInfo(mFrontend.mId); + if (mFrontendInfo == null) { + mFrontendInfo = nativeGetFrontendInfo(mFrontend.mId); + } + return mFrontendInfo; } /** diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 9b37f955fa17..71ba59c8a707 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -724,18 +724,21 @@ status_t JMediaCodec::getOutputFrame( } if (buffer->size() > 0) { - // asC2Buffer clears internal reference, so set the reference again. std::shared_ptr<C2Buffer> c2Buffer = buffer->asC2Buffer(); - buffer->copy(c2Buffer); if (c2Buffer) { + // asC2Buffer clears internal reference, so set the reference again. + buffer->copy(c2Buffer); switch (c2Buffer->data().type()) { case C2BufferData::LINEAR: { std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock}; context->mBuffer = c2Buffer; ScopedLocalRef<jobject> linearBlock{env, env->NewObject( gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)}; - env->SetLongField( - linearBlock.get(), gLinearBlockInfo.contextId, (jlong)context.release()); + env->CallVoidMethod( + linearBlock.get(), + gLinearBlockInfo.setInternalStateId, + (jlong)context.release(), + true); env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get()); break; } @@ -744,8 +747,11 @@ status_t JMediaCodec::getOutputFrame( context->mBuffer = c2Buffer; ScopedLocalRef<jobject> graphicBlock{env, env->NewObject( gGraphicBlockInfo.clazz, gGraphicBlockInfo.ctorId)}; - env->SetLongField( - graphicBlock.get(), gGraphicBlockInfo.contextId, (jlong)context.release()); + env->CallVoidMethod( + graphicBlock.get(), + gGraphicBlockInfo.setInternalStateId, + (jlong)context.release(), + true); env->SetObjectField(frame, gFields.outputFrameGraphicBlockID, graphicBlock.get()); break; } @@ -761,16 +767,22 @@ status_t JMediaCodec::getOutputFrame( context->mLegacyBuffer = buffer; ScopedLocalRef<jobject> linearBlock{env, env->NewObject( gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)}; - env->SetLongField( - linearBlock.get(), gLinearBlockInfo.contextId, (jlong)context.release()); + env->CallVoidMethod( + linearBlock.get(), + gLinearBlockInfo.setInternalStateId, + (jlong)context.release(), + true); env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get()); } else { std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock}; context->mLegacyBuffer = buffer; ScopedLocalRef<jobject> graphicBlock{env, env->NewObject( gGraphicBlockInfo.clazz, gGraphicBlockInfo.ctorId)}; - env->SetLongField( - graphicBlock.get(), gGraphicBlockInfo.contextId, (jlong)context.release()); + env->CallVoidMethod( + graphicBlock.get(), + gGraphicBlockInfo.setInternalStateId, + (jlong)context.release(), + true); env->SetObjectField(frame, gFields.outputFrameGraphicBlockID, graphicBlock.get()); } } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 81a85c9f08d6..08c3f988e85e 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -322,6 +322,179 @@ jobject JTuner::openFrontendById(int id) { (jint) jId); } +jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V"); + + jint typeCap = caps.analogCaps().typeCap; + jint sifStandardCap = caps.analogCaps().sifStandardCap; + return env->NewObject(clazz, capsInit, typeCap, sifStandardCap); +} + +jobject JTuner::getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/Atsc3FrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIIII)V"); + + jint bandwidthCap = caps.atsc3Caps().bandwidthCap; + jint modulationCap = caps.atsc3Caps().modulationCap; + jint timeInterleaveModeCap = caps.atsc3Caps().timeInterleaveModeCap; + jint codeRateCap = caps.atsc3Caps().codeRateCap; + jint fecCap = caps.atsc3Caps().fecCap; + jint demodOutputFormatCap = caps.atsc3Caps().demodOutputFormatCap; + + return env->NewObject(clazz, capsInit, bandwidthCap, modulationCap, timeInterleaveModeCap, + codeRateCap, fecCap, demodOutputFormatCap); +} + +jobject JTuner::getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AtscFrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(I)V"); + + jint modulationCap = caps.atscCaps().modulationCap; + + return env->NewObject(clazz, capsInit, modulationCap); +} + +jobject JTuner::getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbcFrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(III)V"); + + jint modulationCap = caps.dvbcCaps().modulationCap; + jint fecCap = caps.dvbcCaps().fecCap; + jint annexCap = caps.dvbcCaps().annexCap; + + return env->NewObject(clazz, capsInit, modulationCap, fecCap, annexCap); +} + +jobject JTuner::getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbsFrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IJI)V"); + + jint modulationCap = caps.dvbsCaps().modulationCap; + jlong innerfecCap = caps.dvbsCaps().innerfecCap; + jint standard = caps.dvbsCaps().standard; + + return env->NewObject(clazz, capsInit, modulationCap, innerfecCap, standard); +} + +jobject JTuner::getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbtFrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIIIIZZ)V"); + + jint transmissionModeCap = caps.dvbtCaps().transmissionModeCap; + jint bandwidthCap = caps.dvbtCaps().bandwidthCap; + jint constellationCap = caps.dvbtCaps().constellationCap; + jint coderateCap = caps.dvbtCaps().coderateCap; + jint hierarchyCap = caps.dvbtCaps().hierarchyCap; + jint guardIntervalCap = caps.dvbtCaps().guardIntervalCap; + jboolean isT2Supported = caps.dvbtCaps().isT2Supported; + jboolean isMisoSupported = caps.dvbtCaps().isMisoSupported; + + return env->NewObject(clazz, capsInit, transmissionModeCap, bandwidthCap, constellationCap, + coderateCap, hierarchyCap, guardIntervalCap, isT2Supported, isMisoSupported); +} + +jobject JTuner::getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V"); + + jint modulationCap = caps.isdbs3Caps().modulationCap; + jint coderateCap = caps.isdbs3Caps().coderateCap; + + return env->NewObject(clazz, capsInit, modulationCap, coderateCap); +} + +jobject JTuner::getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/IsdbsFrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V"); + + jint modulationCap = caps.isdbsCaps().modulationCap; + jint coderateCap = caps.isdbsCaps().coderateCap; + + return env->NewObject(clazz, capsInit, modulationCap, coderateCap); +} + +jobject JTuner::getIsdbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/IsdbtFrontendCapabilities"); + jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIII)V"); + + jint modeCap = caps.isdbtCaps().modeCap; + jint bandwidthCap = caps.isdbtCaps().bandwidthCap; + jint modulationCap = caps.isdbtCaps().modulationCap; + jint coderateCap = caps.isdbtCaps().coderateCap; + jint guardIntervalCap = caps.isdbtCaps().guardIntervalCap; + + return env->NewObject(clazz, capsInit, modeCap, bandwidthCap, modulationCap, coderateCap, + guardIntervalCap); +} + +jobject JTuner::getFrontendInfo(int id) { + FrontendInfo feInfo; + Result res; + mTuner->getFrontendInfo(id, [&](Result r, const FrontendInfo& info) { + feInfo = info; + res = r; + }); + if (res != Result::SUCCESS) { + return NULL; + } + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendInfo"); + jmethodID infoInit = env->GetMethodID(clazz, "<init>", + "(IIIIIIII[ILandroid/media/tv/tuner/frontend/FrontendCapabilities;)V"); + + jint type = (jint) feInfo.type; + jint minFrequency = feInfo.minFrequency; + jint maxFrequency = feInfo.maxFrequency; + jint minSymbolRate = feInfo.minSymbolRate; + jint maxSymbolRate = feInfo.maxSymbolRate; + jint acquireRange = feInfo.acquireRange; + jint exclusiveGroupId = feInfo.exclusiveGroupId; + jintArray statusCaps = env->NewIntArray(feInfo.statusCaps.size()); + env->SetIntArrayRegion( + statusCaps, 0, feInfo.statusCaps.size(), + reinterpret_cast<jint*>(&feInfo.statusCaps[0])); + FrontendInfo::FrontendCapabilities caps = feInfo.frontendCaps; + + jobject jcaps = NULL; + switch(feInfo.type) { + case FrontendType::ANALOG: + jcaps = getAnalogFrontendCaps(env, caps); + break; + case FrontendType::ATSC3: + jcaps = getAtsc3FrontendCaps(env, caps); + break; + case FrontendType::ATSC: + jcaps = getAtscFrontendCaps(env, caps); + break; + case FrontendType::DVBC: + jcaps = getDvbcFrontendCaps(env, caps); + break; + case FrontendType::DVBS: + jcaps = getDvbsFrontendCaps(env, caps); + break; + case FrontendType::DVBT: + jcaps = getDvbtFrontendCaps(env, caps); + break; + case FrontendType::ISDBS: + jcaps = getIsdbsFrontendCaps(env, caps); + break; + case FrontendType::ISDBS3: + jcaps = getIsdbs3FrontendCaps(env, caps); + break; + case FrontendType::ISDBT: + jcaps = getIsdbtFrontendCaps(env, caps); + break; + default: + break; + } + + return env->NewObject( + clazz, infoInit, (jint) id, type, minFrequency, maxFrequency, minSymbolRate, + maxSymbolRate, acquireRange, exclusiveGroupId, statusCaps, jcaps); +} + jobject JTuner::getLnbIds() { ALOGD("JTuner::getLnbIds()"); mTuner->getLnbIds([&](Result, const hidl_vec<FrontendId>& lnbIds) { @@ -1162,8 +1335,9 @@ static int android_media_tv_Tuner_disconnect_cicam(JNIEnv*, jobject) { return 0; } -static jobject android_media_tv_Tuner_get_frontend_info(JNIEnv*, jobject, jint) { - return NULL; +static jobject android_media_tv_Tuner_get_frontend_info(JNIEnv *env, jobject thiz, jint id) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->getFrontendInfo(id); } static jobject android_media_tv_Tuner_get_lnb_ids(JNIEnv *env, jobject thiz) { diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index 978d9c6e1059..cfe99b399979 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -39,6 +39,7 @@ using ::android::hardware::tv::tuner::V1_0::DemuxPid; using ::android::hardware::tv::tuner::V1_0::DvrType; using ::android::hardware::tv::tuner::V1_0::FrontendEventType; using ::android::hardware::tv::tuner::V1_0::FrontendId; +using ::android::hardware::tv::tuner::V1_0::FrontendInfo; using ::android::hardware::tv::tuner::V1_0::FrontendScanMessage; using ::android::hardware::tv::tuner::V1_0::FrontendScanMessageType; using ::android::hardware::tv::tuner::V1_0::FrontendScanType; @@ -132,6 +133,7 @@ struct JTuner : public RefBase { sp<ITuner> getTunerService(); jobject getFrontendIds(); jobject openFrontendById(int id); + jobject getFrontendInfo(int id); int tune(const FrontendSettings& settings); int stopTune(); int scan(const FrontendSettings& settings, FrontendScanType scanType); @@ -158,6 +160,15 @@ private: sp<ILnb> mLnb; sp<IDemux> mDemux; int mDemuxId; + static jobject getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getIsdbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); }; } // namespace android diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp index c1143ce9c3dc..2e4d2140809e 100644 --- a/native/graphics/jni/imagedecoder.cpp +++ b/native/graphics/jni/imagedecoder.cpp @@ -213,12 +213,12 @@ int32_t AImageDecoderHeaderInfo_getDataSpace(const AImageDecoderHeaderInfo* info return ANDROID_IMAGE_DECODER_BAD_PARAMETER; } - // Note: This recomputes the data space because it's possible the client has - // changed the output color space, so we cannot rely on it. Alternatively, + // Note: This recomputes the color type because it's possible the client has + // changed the output color type, so we cannot rely on it. Alternatively, // we could store the ADataSpace in the ImageDecoder. const ImageDecoder* imageDecoder = toDecoder(info); SkColorType colorType = imageDecoder->mCodec->computeOutputColorType(kN32_SkColorType); - sk_sp<SkColorSpace> colorSpace = imageDecoder->mCodec->computeOutputColorSpace(colorType); + sk_sp<SkColorSpace> colorSpace = imageDecoder->getDefaultColorSpace(); return uirenderer::ColorSpaceToADataSpace(colorSpace.get(), colorType); } diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 38cf5ab9afe6..617305cf6e5e 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -38,7 +38,6 @@ import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Path; import android.provider.DocumentsContract.Root; -import android.provider.MediaStore; import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; @@ -100,7 +99,6 @@ public class ExternalStorageProvider extends FileSystemProvider { private static final String ROOT_ID_PRIMARY_EMULATED = DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID; - private static final String ROOT_ID_HOME = "home"; private static final String GET_DOCUMENT_URI_CALL = "get_document_uri"; private static final String GET_MEDIA_URI_CALL = "get_media_uri"; @@ -156,7 +154,6 @@ public class ExternalStorageProvider extends FileSystemProvider { private void updateVolumesLocked() { mRoots.clear(); - VolumeInfo primaryVolume = null; final int userId = UserHandle.myUserId(); final List<VolumeInfo> volumes = mStorageManager.getVolumes(); for (VolumeInfo volume : volumes) { @@ -234,8 +231,6 @@ public class ExternalStorageProvider extends FileSystemProvider { } if (volume.isPrimary()) { - // save off the primary volume for subsequent "Home" dir initialization. - primaryVolume = volume; root.flags |= Root.FLAG_ADVANCED; } // Dunno when this would NOT be the case, but never hurts to be correct. @@ -259,37 +254,6 @@ public class ExternalStorageProvider extends FileSystemProvider { } } - // Finally, if primary storage is available we add the "Documents" directory. - // If I recall correctly the actual directory is created on demand - // by calling either getPathForUser, or getInternalPathForUser. - if (primaryVolume != null && primaryVolume.isVisible()) { - final RootInfo root = new RootInfo(); - root.rootId = ROOT_ID_HOME; - mRoots.put(root.rootId, root); - root.title = getContext().getString(R.string.root_documents); - - // Only report bytes on *volumes*...as a matter of policy. - root.reportAvailableBytes = false; - root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_SEARCH - | Root.FLAG_SUPPORTS_IS_CHILD; - - // Dunno when this would NOT be the case, but never hurts to be correct. - if (primaryVolume.isMountedWritable()) { - root.flags |= Root.FLAG_SUPPORTS_CREATE; - } - - // Create the "Documents" directory on disk (don't use the localized title). - root.visiblePath = new File( - primaryVolume.getPathForUser(userId), Environment.DIRECTORY_DOCUMENTS); - root.path = new File( - primaryVolume.getInternalPathForUser(userId), Environment.DIRECTORY_DOCUMENTS); - try { - root.docId = getDocIdForFile(root.path); - } catch (FileNotFoundException e) { - throw new IllegalStateException(e); - } - } - Log.d(TAG, "After updating volumes, found " + mRoots.size() + " active roots"); // Note this affects content://com.android.externalstorage.documents/root/39BD-07C5 diff --git a/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_blue.xml b/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_blue.xml new file mode 100644 index 000000000000..04de17474368 --- /dev/null +++ b/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_blue.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2020 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="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z" + android:fillColor="#4285F4"/> +</vector> diff --git a/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_grey.xml b/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_grey.xml new file mode 100644 index 000000000000..b4145f23f61a --- /dev/null +++ b/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_grey.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2020 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="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z" + android:fillColor="#757575"/> +</vector> diff --git a/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml b/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml new file mode 100644 index 000000000000..e6f8c01e22ac --- /dev/null +++ b/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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. + --> + +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/entity_header" + style="@style/EntityHeader" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:orientation="horizontal"> + + <LinearLayout + android:id="@+id/entity_header_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:gravity="center_horizontal" + android:orientation="horizontal"> + + <LinearLayout + android:id="@+id/entity_header_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:gravity="center_horizontal" + android:orientation="vertical"> + + <ImageView + android:id="@+id/entity_header_icon_personal" + android:layout_width="48dp" + android:layout_height="48dp" + android:scaleType="fitCenter" + android:antialias="true"/> + + <TextView + android:id="@+id/install_type" + style="@style/TextAppearance.EntityHeaderSummary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="2dp" + android:text="Personal"/> + </LinearLayout> + + <ImageView + android:id="@+id/entity_header_swap_horiz" + android:layout_width="24dp" + android:layout_height="24dp" + android:scaleType="fitCenter" + android:antialias="true" + android:src="@drawable/ic_swap_horiz_grey"/> + + <LinearLayout + android:id="@+id/entity_header_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:gravity="center_horizontal" + android:orientation="vertical"> + + <ImageView + android:id="@+id/entity_header_icon_work" + android:layout_width="48dp" + android:layout_height="48dp" + android:scaleType="fitCenter" + android:antialias="true"/> + <TextView + android:id="@+id/install_type" + style="@style/TextAppearance.EntityHeaderSummary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="2dp" + android:text="Work"/> + </LinearLayout> + </LinearLayout> + +</RelativeLayout> diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml new file mode 100644 index 000000000000..332d6c7bc0fa --- /dev/null +++ b/packages/SettingsLib/res/values/config.xml @@ -0,0 +1,24 @@ +<?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. + --> +<!-- These resources are around just to allow their values to be customized --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Threshold in micro watts below which a charger is rated as "slow"; 1A @ 5V --> + <integer name="config_chargingSlowlyThreshold">5000000</integer> + + <!-- Threshold in micro watts above which a charger is rated as "fast"; 1.5A @ 5V --> + <integer name="config_chargingFastThreshold">7500000</integer> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index dd21e5c07b13..4d96251043ce 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -970,7 +970,7 @@ <!-- Title for the accessibility preference to configure display color space correction. [CHAR LIMIT=NONE] --> <string name="accessibility_display_daltonizer_preference_title">Color correction</string> <!-- Subtitle for the accessibility preference to configure display color space correction. [CHAR LIMIT=NONE] --> - <string name="accessibility_display_daltonizer_preference_subtitle">Color correction helps people with color blindness to see more accurate colors</string> + <string name="accessibility_display_daltonizer_preference_subtitle">Color correction helps people with colorblindness see more accurate colors</string> <!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] --> <string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string> @@ -1034,8 +1034,10 @@ <string name="battery_info_status_unknown">Unknown</string> <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging from an unknown source. --> <string name="battery_info_status_charging">Charging</string> - <!-- [CHAR_LIMIT=20] Battery use screen with lower case. Battery status shown in chart label when charging from an unknown source. --> - <string name="battery_info_status_charging_lower">charging</string> + <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging speed is fast. --> + <string name="battery_info_status_charging_fast">Charging rapidly</string> + <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging speed is slow. --> + <string name="battery_info_status_charging_slow">Charging slowly</string> <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed --> <string name="battery_info_status_discharging">Not charging</string> <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed --> diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 213e3657254d..f4857932064f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -35,6 +35,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.UserIcons; import com.android.launcher3.icons.IconFactory; import com.android.settingslib.drawable.UserIconDrawable; +import com.android.settingslib.fuelgauge.BatteryStatus; import java.text.NumberFormat; @@ -122,7 +123,7 @@ public class Utils { public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) { final int iconSize = UserIconDrawable.getSizeForList(context); if (user.isManagedProfile()) { - Drawable drawable = UserIconDrawable.getManagedUserDrawable(context); + Drawable drawable = UserIconDrawable.getManagedUserDrawable(context); drawable.setBounds(0, 0, iconSize, iconSize); return drawable; } @@ -164,20 +165,43 @@ public class Utils { return (level * 100) / scale; } - public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) { - int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS, + /** + * Get battery status string + * + * @param context the context + * @param batteryChangedIntent battery broadcast intent received from {@link + * Intent.ACTION_BATTERY_CHANGED}. + * @return battery status string + */ + public static String getBatteryStatus(Context context, Intent batteryChangedIntent) { + final int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); - String statusString; - if (status == BatteryManager.BATTERY_STATUS_CHARGING) { - statusString = res.getString(R.string.battery_info_status_charging); - } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) { - statusString = res.getString(R.string.battery_info_status_discharging); - } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { - statusString = res.getString(R.string.battery_info_status_not_charging); - } else if (status == BatteryManager.BATTERY_STATUS_FULL) { + final Resources res = context.getResources(); + + String statusString = res.getString(R.string.battery_info_status_unknown); + final BatteryStatus batteryStatus = new BatteryStatus(batteryChangedIntent); + + if (batteryStatus.isCharged()) { statusString = res.getString(R.string.battery_info_status_full); } else { - statusString = res.getString(R.string.battery_info_status_unknown); + if (status == BatteryManager.BATTERY_STATUS_CHARGING) { + switch (batteryStatus.getChargingSpeed(context)) { + case BatteryStatus.CHARGING_FAST: + statusString = res.getString(R.string.battery_info_status_charging_fast); + break; + case BatteryStatus.CHARGING_SLOWLY: + statusString = res.getString(R.string.battery_info_status_charging_slow); + break; + default: + statusString = res.getString(R.string.battery_info_status_charging); + break; + } + + } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) { + statusString = res.getString(R.string.battery_info_status_discharging); + } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { + statusString = res.getString(R.string.battery_info_status_not_charging); + } } return statusString; @@ -211,7 +235,7 @@ public class Utils { /** * This method computes disabled color from normal color * - * @param context + * @param context the context * @param inputColor normal color. * @return disabled color. */ diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java new file mode 100644 index 000000000000..bc40903d88e4 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java @@ -0,0 +1,147 @@ +/* + * 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.settingslib.fuelgauge; + +import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN; +import static android.os.BatteryManager.BATTERY_STATUS_FULL; +import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; +import static android.os.BatteryManager.EXTRA_HEALTH; +import static android.os.BatteryManager.EXTRA_LEVEL; +import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT; +import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE; +import static android.os.BatteryManager.EXTRA_PLUGGED; +import static android.os.BatteryManager.EXTRA_STATUS; + +import android.content.Context; +import android.content.Intent; +import android.os.BatteryManager; + +import com.android.settingslib.R; + +/** + * Stores and computes some battery information. + */ +public class BatteryStatus { + private static final int LOW_BATTERY_THRESHOLD = 20; + private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000; + + public static final int CHARGING_UNKNOWN = -1; + public static final int CHARGING_SLOWLY = 0; + public static final int CHARGING_REGULAR = 1; + public static final int CHARGING_FAST = 2; + + public final int status; + public final int level; + public final int plugged; + public final int health; + public final int maxChargingWattage; + + public BatteryStatus(int status, int level, int plugged, int health, + int maxChargingWattage) { + this.status = status; + this.level = level; + this.plugged = plugged; + this.health = health; + this.maxChargingWattage = maxChargingWattage; + } + + public BatteryStatus(Intent batteryChangedIntent) { + status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN); + plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0); + level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0); + health = batteryChangedIntent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN); + + final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, + -1); + int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1); + + if (maxChargingMicroVolt <= 0) { + maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT; + } + if (maxChargingMicroAmp > 0) { + // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor + // to maintain precision equally on both factors. + maxChargingWattage = (maxChargingMicroAmp / 1000) + * (maxChargingMicroVolt / 1000); + } else { + maxChargingWattage = -1; + } + } + + /** + * Determine whether the device is plugged in (USB, power, or wireless). + * + * @return true if the device is plugged in. + */ + public boolean isPluggedIn() { + return plugged == BatteryManager.BATTERY_PLUGGED_AC + || plugged == BatteryManager.BATTERY_PLUGGED_USB + || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; + } + + /** + * Determine whether the device is plugged in (USB, power). + * + * @return true if the device is plugged in wired (as opposed to wireless) + */ + public boolean isPluggedInWired() { + return plugged == BatteryManager.BATTERY_PLUGGED_AC + || plugged == BatteryManager.BATTERY_PLUGGED_USB; + } + + /** + * Whether or not the device is charged. Note that some devices never return 100% for + * battery level, so this allows either battery level or status to determine if the + * battery is charged. + * + * @return true if the device is charged + */ + public boolean isCharged() { + return status == BATTERY_STATUS_FULL || level >= 100; + } + + /** + * Whether battery is low and needs to be charged. + * + * @return true if battery is low + */ + public boolean isBatteryLow() { + return level < LOW_BATTERY_THRESHOLD; + } + + /** + * Return current chargin speed is fast, slow or normal. + * + * @return the charing speed + */ + public final int getChargingSpeed(Context context) { + final int slowThreshold = context.getResources().getInteger( + R.integer.config_chargingSlowlyThreshold); + final int fastThreshold = context.getResources().getInteger( + R.integer.config_chargingFastThreshold); + return maxChargingWattage <= 0 ? CHARGING_UNKNOWN : + maxChargingWattage < slowThreshold ? CHARGING_SLOWLY : + maxChargingWattage > fastThreshold ? CHARGING_FAST : + CHARGING_REGULAR; + } + + @Override + public String toString() { + return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged + + ",health=" + health + ",maxChargingWattage=" + maxChargingWattage + "}"; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java index 3a53d29f7618..e551b69e024a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java @@ -19,7 +19,8 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.graphics.drawable.Drawable; -import android.util.Log; +import android.media.MediaRoute2Info; +import android.media.MediaRouter2Manager; import android.util.Pair; import com.android.settingslib.R; @@ -35,8 +36,9 @@ public class BluetoothMediaDevice extends MediaDevice { private CachedBluetoothDevice mCachedDevice; - BluetoothMediaDevice(Context context, CachedBluetoothDevice device) { - super(context, MediaDeviceType.TYPE_BLUETOOTH_DEVICE); + BluetoothMediaDevice(Context context, CachedBluetoothDevice device, + MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) { + super(context, MediaDeviceType.TYPE_BLUETOOTH_DEVICE, routerManager, info, packageName); mCachedDevice = device; initDeviceRecord(); } @@ -65,20 +67,6 @@ public class BluetoothMediaDevice extends MediaDevice { return MediaDeviceUtils.getId(mCachedDevice); } - @Override - public boolean connect() { - //TODO(b/117129183): add callback to notify LocalMediaManager connection state. - final boolean isConnected = mCachedDevice.setActive(); - setConnectedRecord(); - Log.d(TAG, "connect() device : " + getName() + ", is selected : " + isConnected); - return isConnected; - } - - @Override - public void disconnect() { - //TODO(b/117129183): disconnected last select device - } - /** * Get current CachedBluetoothDevice */ diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java index 3a807c9b9b77..d84788b2739f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java @@ -157,7 +157,7 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall private void addMediaDevice(CachedBluetoothDevice cachedDevice) { MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice)); if (mediaDevice == null) { - mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice); + mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, null, null, null); cachedDevice.registerCallback(mDeviceAttributeChangeCallback); mLastAddedDevice = mediaDevice; mMediaDevices.add(mediaDevice); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java index d287f95e504a..b9081f241a91 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java @@ -16,9 +16,12 @@ package com.android.settingslib.media; import android.content.Context; +import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.text.TextUtils; +import android.util.Log; import com.android.settingslib.R; import com.android.settingslib.bluetooth.BluetoothUtils; @@ -30,16 +33,9 @@ public class InfoMediaDevice extends MediaDevice { private static final String TAG = "InfoMediaDevice"; - private final MediaRoute2Info mRouteInfo; - private final MediaRouter2Manager mRouterManager; - private final String mPackageName; - InfoMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) { - super(context, MediaDeviceType.TYPE_CAST_DEVICE); - mRouterManager = routerManager; - mRouteInfo = info; - mPackageName = packageName; + super(context, MediaDeviceType.TYPE_CAST_DEVICE, routerManager, info, packageName); initDeviceRecord(); } @@ -67,13 +63,6 @@ public class InfoMediaDevice extends MediaDevice { } @Override - public boolean connect() { - setConnectedRecord(); - mRouterManager.selectRoute(mPackageName, mRouteInfo); - return true; - } - - @Override public void requestSetVolume(int volume) { mRouterManager.requestSetVolume(mRouteInfo, volume); } @@ -89,11 +78,30 @@ public class InfoMediaDevice extends MediaDevice { } @Override - public void disconnect() { - //TODO(b/144535188): disconnected last select device + public String getClientPackageName() { + return mRouteInfo.getClientPackageName(); } @Override + public String getClientAppLabel() { + final String packageName = mRouteInfo.getClientPackageName(); + if (TextUtils.isEmpty(packageName)) { + Log.d(TAG, "Client package name is empty"); + return mContext.getResources().getString(R.string.unknown); + } + try { + final PackageManager packageManager = mContext.getPackageManager(); + final String appLabel = packageManager.getApplicationLabel( + packageManager.getApplicationInfo(packageName, 0)).toString(); + if (!TextUtils.isEmpty(appLabel)) { + return appLabel; + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "unable to find " + packageName); + } + return mContext.getResources().getString(R.string.unknown); + } + public boolean isConnected() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index a4be46cf1f63..e91096760180 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -15,13 +15,23 @@ */ package com.android.settingslib.media; +import static android.media.MediaRoute2Info.DEVICE_TYPE_BLUETOOTH; +import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV; +import static android.media.MediaRoute2Info.DEVICE_TYPE_UNKNOWN; + import android.app.Notification; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.media.RoutingSessionInfo; import android.text.TextUtils; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; import java.util.List; import java.util.concurrent.Executor; @@ -44,11 +54,14 @@ public class InfoMediaManager extends MediaManager { String mPackageName; private MediaDevice mCurrentConnectedDevice; + private LocalBluetoothManager mBluetoothManager; - public InfoMediaManager(Context context, String packageName, Notification notification) { + public InfoMediaManager(Context context, String packageName, Notification notification, + LocalBluetoothManager localBluetoothManager) { super(context, notification); mRouterManager = MediaRouter2Manager.getInstance(context); + mBluetoothManager = localBluetoothManager; if (!TextUtils.isEmpty(packageName)) { mPackageName = packageName; } @@ -94,23 +107,73 @@ public class InfoMediaManager extends MediaManager { private void buildAllRoutes() { for (MediaRoute2Info route : mRouterManager.getAllRoutes()) { - final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, route, - mPackageName); - mMediaDevices.add(device); + addMediaDevice(route); } } private void buildAvailableRoutes() { for (MediaRoute2Info route : mRouterManager.getAvailableRoutes(mPackageName)) { - final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, route, - mPackageName); - if (TextUtils.equals(route.getClientPackageName(), mPackageName)) { - mCurrentConnectedDevice = device; - } - mMediaDevices.add(device); + addMediaDevice(route); } } + private void addMediaDevice(MediaRoute2Info route) { + final int deviceType = route.getDeviceType(); + MediaDevice mediaDevice = null; + switch (deviceType) { + case DEVICE_TYPE_UNKNOWN: + //TODO(b/148765806): use correct device type once api is ready. + final String defaultRoute = "DEFAULT_ROUTE"; + if (TextUtils.equals(defaultRoute, route.getOriginalId())) { + mediaDevice = + new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName); + } else { + mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route, + mPackageName); + if (!TextUtils.isEmpty(mPackageName) + && TextUtils.equals(route.getClientPackageName(), mPackageName)) { + mCurrentConnectedDevice = mediaDevice; + } + } + break; + case DEVICE_TYPE_REMOTE_TV: + break; + case DEVICE_TYPE_BLUETOOTH: + final BluetoothDevice device = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getOriginalId()); + final CachedBluetoothDevice cachedDevice = + mBluetoothManager.getCachedDeviceManager().findDevice(device); + mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager, + route, mPackageName); + break; + default: + Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType); + break; + + } + + if (mediaDevice != null) { + mMediaDevices.add(mediaDevice); + } + } + + /** + * Transfer MediaDevice for media without package name. + */ + public boolean connectDeviceWithoutPackageName(MediaDevice device) { + boolean isConnected = false; + final List<RoutingSessionInfo> infos = mRouterManager.getActiveSessions(); + if (infos.size() > 0) { + final RoutingSessionInfo info = infos.get(0); + final MediaRouter2Manager.RoutingController controller = + mRouterManager.getControllerForSession(info); + + controller.transferToRoute(device.mRouteInfo); + isConnected = true; + } + return isConnected; + } + class RouterManagerCallback extends MediaRouter2Manager.Callback { @Override @@ -124,5 +187,10 @@ public class InfoMediaManager extends MediaManager { refreshDevices(); } } + + @Override + public void onRoutesChanged(List<MediaRoute2Info> routes) { + refreshDevices(); + } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index 50196d2b2994..a1342ecdcfa7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -18,6 +18,7 @@ package com.android.settingslib.media; import android.app.Notification; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.text.TextUtils; import android.util.Log; import androidx.annotation.IntDef; @@ -57,7 +58,6 @@ public class LocalMediaManager implements BluetoothCallback { final MediaDeviceCallback mMediaDeviceCallback = new MediaDeviceCallback(); private Context mContext; - private BluetoothMediaManager mBluetoothMediaManager; private LocalBluetoothManager mLocalBluetoothManager; private InfoMediaManager mInfoMediaManager; private String mPackageName; @@ -97,18 +97,18 @@ public class LocalMediaManager implements BluetoothCallback { return; } - mBluetoothMediaManager = - new BluetoothMediaManager(context, mLocalBluetoothManager, notification); - mInfoMediaManager = new InfoMediaManager(context, packageName, notification); + mInfoMediaManager = + new InfoMediaManager(context, packageName, notification, mLocalBluetoothManager); } @VisibleForTesting LocalMediaManager(Context context, LocalBluetoothManager localBluetoothManager, - BluetoothMediaManager bluetoothMediaManager, InfoMediaManager infoMediaManager) { + InfoMediaManager infoMediaManager, String packageName) { mContext = context; mLocalBluetoothManager = localBluetoothManager; - mBluetoothMediaManager = bluetoothMediaManager; mInfoMediaManager = infoMediaManager; + mPackageName = packageName; + } /** @@ -135,7 +135,12 @@ public class LocalMediaManager implements BluetoothCallback { mCurrentConnectedDevice.disconnect(); } - final boolean isConnected = device.connect(); + boolean isConnected = false; + if (TextUtils.isEmpty(mPackageName)) { + isConnected = mInfoMediaManager.connectDeviceWithoutPackageName(device); + } else { + isConnected = device.connect(); + } if (isConnected) { mCurrentConnectedDevice = device; } @@ -159,29 +164,10 @@ public class LocalMediaManager implements BluetoothCallback { */ public void startScan() { mMediaDevices.clear(); - mBluetoothMediaManager.registerCallback(mMediaDeviceCallback); - mBluetoothMediaManager.startScan(); mInfoMediaManager.registerCallback(mMediaDeviceCallback); mInfoMediaManager.startScan(); } - private void addPhoneDeviceIfNecessary() { - // add phone device to list if there have any Bluetooth device and cast device. - if (mMediaDevices.size() > 0 && !mMediaDevices.contains(mPhoneDevice)) { - if (mPhoneDevice == null) { - mPhoneDevice = new PhoneMediaDevice(mContext, mLocalBluetoothManager); - } - mMediaDevices.add(mPhoneDevice); - } - } - - private void removePhoneMediaDeviceIfNecessary() { - // if PhoneMediaDevice is the last item in the list, remove it. - if (mMediaDevices.size() == 1 && mMediaDevices.contains(mPhoneDevice)) { - mMediaDevices.clear(); - } - } - void dispatchDeviceListUpdate() { synchronized (mCallbacks) { Collections.sort(mMediaDevices, COMPARATOR); @@ -203,8 +189,6 @@ public class LocalMediaManager implements BluetoothCallback { * Stop scan MediaDevice */ public void stopScan() { - mBluetoothMediaManager.unregisterCallback(mMediaDeviceCallback); - mBluetoothMediaManager.stopScan(); mInfoMediaManager.unregisterCallback(mMediaDeviceCallback); mInfoMediaManager.stopScan(); } @@ -227,6 +211,22 @@ public class LocalMediaManager implements BluetoothCallback { } /** + * Find the MediaDevice from all media devices by id. + * + * @param id the unique id of MediaDevice + * @return MediaDevice + */ + public MediaDevice getMediaDeviceById(String id) { + for (MediaDevice mediaDevice : mMediaDevices) { + if (mediaDevice.getId().equals(id)) { + return mediaDevice; + } + } + Log.i(TAG, "Unable to find device " + id); + return null; + } + + /** * Find the current connected MediaDevice. * * @return MediaDevice @@ -235,15 +235,34 @@ public class LocalMediaManager implements BluetoothCallback { return mCurrentConnectedDevice; } + /** + * Find the active MediaDevice. + * + * @param type the media device type. + * @return MediaDevice list + */ + public List<MediaDevice> getActiveMediaDevice(@MediaDevice.MediaDeviceType int type) { + final List<MediaDevice> devices = new ArrayList<>(); + for (MediaDevice device : mMediaDevices) { + if (type == device.mType && device.getClientPackageName() != null) { + devices.add(device); + } + } + return devices; + } + private MediaDevice updateCurrentConnectedDevice() { + MediaDevice phoneMediaDevice = null; for (MediaDevice device : mMediaDevices) { if (device instanceof BluetoothMediaDevice) { if (isConnected(((BluetoothMediaDevice) device).getCachedDevice())) { return device; } + } else if (device instanceof PhoneMediaDevice) { + phoneMediaDevice = device; } } - return mMediaDevices.contains(mPhoneDevice) ? mPhoneDevice : null; + return mMediaDevices.contains(phoneMediaDevice) ? phoneMediaDevice : null; } private boolean isConnected(CachedBluetoothDevice device) { @@ -256,38 +275,24 @@ public class LocalMediaManager implements BluetoothCallback { public void onDeviceAdded(MediaDevice device) { if (!mMediaDevices.contains(device)) { mMediaDevices.add(device); - addPhoneDeviceIfNecessary(); dispatchDeviceListUpdate(); } } @Override public void onDeviceListAdded(List<MediaDevice> devices) { - for (MediaDevice device : devices) { - if (getMediaDeviceById(mMediaDevices, device.getId()) == null) { - mMediaDevices.add(device); - } - } - addPhoneDeviceIfNecessary(); + mMediaDevices.clear(); + mMediaDevices.addAll(devices); final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice(); mCurrentConnectedDevice = infoMediaDevice != null ? infoMediaDevice : updateCurrentConnectedDevice(); - updatePhoneMediaDeviceSummary(); dispatchDeviceListUpdate(); } - private void updatePhoneMediaDeviceSummary() { - if (mPhoneDevice != null) { - ((PhoneMediaDevice) mPhoneDevice) - .updateSummary(mCurrentConnectedDevice == mPhoneDevice); - } - } - @Override public void onDeviceRemoved(MediaDevice device) { if (mMediaDevices.contains(device)) { mMediaDevices.remove(device); - removePhoneMediaDeviceIfNecessary(); dispatchDeviceListUpdate(); } } @@ -295,7 +300,6 @@ public class LocalMediaManager implements BluetoothCallback { @Override public void onDeviceListRemoved(List<MediaDevice> devices) { mMediaDevices.removeAll(devices); - removePhoneMediaDeviceIfNecessary(); dispatchDeviceListUpdate(); } @@ -308,7 +312,6 @@ public class LocalMediaManager implements BluetoothCallback { return; } mCurrentConnectedDevice = connectDevice; - updatePhoneMediaDeviceSummary(); dispatchDeviceAttributesChanged(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index 839d528406cd..580e086973d6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -17,9 +17,12 @@ package com.android.settingslib.media; import android.content.Context; import android.graphics.drawable.Drawable; +import android.media.MediaRoute2Info; +import android.media.MediaRouter2Manager; import android.text.TextUtils; import androidx.annotation.IntDef; +import androidx.annotation.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -40,14 +43,23 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { int TYPE_BLUETOOTH_DEVICE = 3; } + @VisibleForTesting + int mType; + private int mConnectedRecord; - protected Context mContext; - protected int mType; + protected final Context mContext; + protected final MediaRoute2Info mRouteInfo; + protected final MediaRouter2Manager mRouterManager; + protected final String mPackageName; - MediaDevice(Context context, @MediaDeviceType int type) { + MediaDevice(Context context, @MediaDeviceType int type, MediaRouter2Manager routerManager, + MediaRoute2Info info, String packageName) { mType = type; mContext = context; + mRouteInfo = info; + mRouterManager = routerManager; + mPackageName = packageName; } void initDeviceRecord() { @@ -83,13 +95,6 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { */ public abstract String getId(); - /** - * Transfer MediaDevice for media - * - * @return result of transfer media - */ - public abstract boolean connect(); - void setConnectedRecord() { mConnectedRecord++; ConnectionRecordManager.getInstance().setConnectionRecord(mContext, getId(), @@ -97,11 +102,6 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { } /** - * Stop transfer MediaDevice - */ - public abstract void disconnect(); - - /** * According the MediaDevice type to check whether we are connected to this MediaDevice. * * @return Whether it is connected. @@ -162,6 +162,23 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { } /** + * Transfer MediaDevice for media + * + * @return result of transfer media + */ + public boolean connect() { + setConnectedRecord(); + mRouterManager.selectRoute(mPackageName, mRouteInfo); + return true; + } + + /** + * Stop transfer MediaDevice + */ + public void disconnect() { + } + + /** * Rules: * 1. If there is one of the connected devices identified as a carkit, this carkit will * be always on the top of the device list. Rule 2 and Rule 3 can’t overrule this rule. diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java index 248b118f6ece..f341bf17edd3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java @@ -32,6 +32,16 @@ public class MediaOutputSliceConstants { public static final String KEY_REMOTE_MEDIA = "remote_media"; /** + * Key for the {@link android.media.session.MediaSession.Token}. + */ + public static final String KEY_MEDIA_SESSION_TOKEN = "key_media_session_token"; + + /** + * Key for the {@link android.media.RoutingSessionInfo#getId()} + */ + public static final String KEY_SESSION_INFO_ID = "key_session_info_id"; + + /** * Activity Action: Show a settings dialog containing {@link MediaDevice} to transfer media. */ public static final String ACTION_MEDIA_OUTPUT = diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index af91c3464194..166fbaa2a337 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -17,14 +17,11 @@ package com.android.settingslib.media; import android.content.Context; import android.graphics.drawable.Drawable; -import android.util.Log; +import android.media.MediaRoute2Info; +import android.media.MediaRouter2Manager; import com.android.settingslib.R; -import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.BluetoothUtils; -import com.android.settingslib.bluetooth.HearingAidProfile; -import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; /** * PhoneMediaDevice extends MediaDevice to represents Phone device. @@ -35,15 +32,12 @@ public class PhoneMediaDevice extends MediaDevice { public static final String ID = "phone_media_device_id_1"; - private LocalBluetoothProfileManager mProfileManager; - private LocalBluetoothManager mLocalBluetoothManager; private String mSummary = ""; - PhoneMediaDevice(Context context, LocalBluetoothManager localBluetoothManager) { - super(context, MediaDeviceType.TYPE_PHONE_DEVICE); + PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info, + String packageName) { + super(context, MediaDeviceType.TYPE_PHONE_DEVICE, routerManager, info, packageName); - mLocalBluetoothManager = localBluetoothManager; - mProfileManager = mLocalBluetoothManager.getProfileManager(); initDeviceRecord(); } @@ -69,32 +63,6 @@ public class PhoneMediaDevice extends MediaDevice { } @Override - public boolean connect() { - final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile(); - final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); - - // Some device may not have HearingAidProfile, consider all situation to set active device. - boolean isConnected = false; - if (hapProfile != null && a2dpProfile != null) { - isConnected = hapProfile.setActiveDevice(null) && a2dpProfile.setActiveDevice(null); - } else if (a2dpProfile != null) { - isConnected = a2dpProfile.setActiveDevice(null); - } else if (hapProfile != null) { - isConnected = hapProfile.setActiveDevice(null); - } - updateSummary(isConnected); - setConnectedRecord(); - - Log.d(TAG, "connect() device : " + getName() + ", is selected : " + isConnected); - return isConnected; - } - - @Override - public void disconnect() { - updateSummary(false); - } - - @Override public boolean isConnected() { return true; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java index 11829451f640..6307caf5e02b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java @@ -31,6 +31,7 @@ import android.content.Intent; import android.content.res.Resources; import android.location.LocationManager; import android.media.AudioManager; +import android.os.BatteryManager; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; @@ -122,12 +123,12 @@ public class UtilsTest { public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() { Resources resources = mock(Resources.class); when(resources.getInteger( - eq( - com.android - .internal - .R - .integer - .config_storageManagerDaystoRetainDefault))) + eq( + com.android + .internal + .R + .integer + .config_storageManagerDaystoRetainDefault))) .thenReturn(60); assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60); } @@ -147,7 +148,8 @@ public class UtilsTest { private static Map<String, Integer> map = new HashMap<>(); @Implementation - public static boolean putIntForUser(ContentResolver cr, String name, int value, int userHandle) { + public static boolean putIntForUser(ContentResolver cr, String name, int value, + int userHandle) { map.put(name, value); return true; } @@ -312,4 +314,33 @@ public class UtilsTest { assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo( ServiceState.STATE_OUT_OF_SERVICE); } + + @Test + public void getBatteryStatus_statusIsFull_returnFullString() { + final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100); + final Resources resources = mContext.getResources(); + + assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo( + resources.getString(R.string.battery_info_status_full)); + } + + @Test + public void getBatteryStatus_batteryLevelIs100_returnFullString() { + final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS, + BatteryManager.BATTERY_STATUS_FULL); + final Resources resources = mContext.getResources(); + + assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo( + resources.getString(R.string.battery_info_status_full)); + } + + @Test + public void getBatteryStatus_batteryLevel99_returnChargingString() { + final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS, + BatteryManager.BATTERY_STATUS_CHARGING); + final Resources resources = mContext.getResources(); + + assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo( + resources.getString(R.string.battery_info_status_charging)); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java index e0e2fd6860ff..a39bcb7f08f6 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java @@ -51,21 +51,7 @@ public class BluetoothMediaDeviceTest { when(mDevice.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(true); when(mDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true); - mBluetoothMediaDevice = new BluetoothMediaDevice(mContext, mDevice); - } - - @Test - public void connect_setActiveSuccess_isConnectedReturnTrue() { - when(mDevice.setActive()).thenReturn(true); - - assertThat(mBluetoothMediaDevice.connect()).isTrue(); - } - - @Test - public void connect_setActiveFail_isConnectedReturnFalse() { - when(mDevice.setActive()).thenReturn(false); - - assertThat(mBluetoothMediaDevice.connect()).isFalse(); + mBluetoothMediaDevice = new BluetoothMediaDevice(mContext, mDevice, null, null, null); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java index c9db0d13a7e7..04ceb2147c6e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java @@ -18,10 +18,12 @@ package com.android.settingslib.media; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageStats; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; @@ -34,11 +36,14 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowPackageManager; @RunWith(RobolectricTestRunner.class) public class InfoMediaDeviceTest { private static final String TEST_PACKAGE_NAME = "com.test.packagename"; + private static final String TEST_PACKAGE_NAME2 = "com.test.packagename2"; private static final String TEST_ID = "test_id"; private static final String TEST_NAME = "test_name"; @@ -50,11 +55,24 @@ public class InfoMediaDeviceTest { private Context mContext; private InfoMediaDevice mInfoMediaDevice; + private ShadowPackageManager mShadowPackageManager; + private ApplicationInfo mAppInfo; + private PackageInfo mPackageInfo; + private PackageStats mPackageStats; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; + mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); + mAppInfo = new ApplicationInfo(); + mAppInfo.flags = ApplicationInfo.FLAG_INSTALLED; + mAppInfo.packageName = TEST_PACKAGE_NAME; + mAppInfo.name = TEST_NAME; + mPackageInfo = new PackageInfo(); + mPackageInfo.packageName = TEST_PACKAGE_NAME; + mPackageInfo.applicationInfo = mAppInfo; + mPackageStats = new PackageStats(TEST_PACKAGE_NAME); mInfoMediaDevice = new InfoMediaDevice(mContext, mRouterManager, mRouteInfo, TEST_PACKAGE_NAME); @@ -90,9 +108,30 @@ public class InfoMediaDeviceTest { } @Test - public void connect_shouldSelectRoute() { - mInfoMediaDevice.connect(); + public void getClientPackageName_returnPackageName() { + when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + assertThat(mInfoMediaDevice.getClientPackageName()).isEqualTo(TEST_PACKAGE_NAME); + } + + @Test + public void getClientAppLabel_matchedPackageName_returnLabel() { + when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo( + mContext.getResources().getString(R.string.unknown)); + + mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); + + assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo(TEST_NAME); + } + + @Test + public void getClientAppLabel_noMatchedPackageName_returnDefault() { + mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); + when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME2); - verify(mRouterManager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo); + assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo( + mContext.getResources().getString(R.string.unknown)); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 3726fb24479a..05f5fa0afc9e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -25,6 +25,9 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.media.RoutingSessionInfo; + +import com.android.settingslib.bluetooth.LocalBluetoothManager; import org.junit.Before; import org.junit.Test; @@ -45,6 +48,8 @@ public class InfoMediaManagerTest { @Mock private MediaRouter2Manager mRouterManager; + @Mock + private LocalBluetoothManager mLocalBluetoothManager; private InfoMediaManager mInfoMediaManager; private Context mContext; @@ -54,7 +59,8 @@ public class InfoMediaManagerTest { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - mInfoMediaManager = new InfoMediaManager(mContext, TEST_PACKAGE_NAME, null); + mInfoMediaManager = + new InfoMediaManager(mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager); mInfoMediaManager.mRouterManager = mRouterManager; } @@ -142,4 +148,59 @@ public class InfoMediaManagerTest { assertThat(mInfoMediaManager.mMediaDevices).hasSize(0); } + + @Test + public void onRoutesChanged_getAvailableRoutes_shouldAddMediaDevice() { + final MediaRoute2Info info = mock(MediaRoute2Info.class); + when(info.getId()).thenReturn(TEST_ID); + when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + final List<MediaRoute2Info> routes = new ArrayList<>(); + routes.add(info); + when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(routes); + + final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID); + assertThat(mediaDevice).isNull(); + + mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes); + + final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); + assertThat(infoDevice.getId()).isEqualTo(TEST_ID); + assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice); + assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size()); + } + + @Test + public void onRoutesChanged_buildAllRoutes_shouldAddMediaDevice() { + final MediaRoute2Info info = mock(MediaRoute2Info.class); + when(info.getId()).thenReturn(TEST_ID); + when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + final List<MediaRoute2Info> routes = new ArrayList<>(); + routes.add(info); + when(mRouterManager.getAllRoutes()).thenReturn(routes); + + final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID); + assertThat(mediaDevice).isNull(); + + mInfoMediaManager.mPackageName = ""; + mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes); + + final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); + assertThat(infoDevice.getId()).isEqualTo(TEST_ID); + assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size()); + } + + @Test + public void connectDeviceWithoutPackageName_noSession_returnFalse() { + final MediaRoute2Info info = mock(MediaRoute2Info.class); + final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, info, + TEST_PACKAGE_NAME); + + final List<RoutingSessionInfo> infos = new ArrayList<>(); + + when(mRouterManager.getActiveSessions()).thenReturn(infos); + + assertThat(mInfoMediaManager.connectDeviceWithoutPackageName(device)).isFalse(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index c780a64c2fb4..3d67ba053a45 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -80,7 +80,7 @@ public class LocalMediaManagerTest { when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile); mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager, - mBluetoothMediaManager, mInfoMediaManager); + mInfoMediaManager, "com.test.packagename"); } @Test @@ -163,14 +163,14 @@ public class LocalMediaManagerTest { } @Test - public void onDeviceAdded_mediaDeviceAndPhoneDeviceNotExistInList_addBothDevice() { + public void onDeviceAdded_addDevice() { final MediaDevice device = mock(MediaDevice.class); assertThat(mLocalMediaManager.mMediaDevices).isEmpty(); mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onDeviceAdded(device); - assertThat(mLocalMediaManager.mMediaDevices).hasSize(2); + assertThat(mLocalMediaManager.mMediaDevices).hasSize(1); verify(mCallback).onDeviceListUpdate(any()); } @@ -206,7 +206,7 @@ public class LocalMediaManagerTest { } @Test - public void onDeviceListAdded_phoneDeviceNotExistInList_addPhoneDeviceAndDevicesList() { + public void onDeviceListAdded_addDevicesList() { final List<MediaDevice> devices = new ArrayList<>(); final MediaDevice device1 = mock(MediaDevice.class); final MediaDevice device2 = mock(MediaDevice.class); @@ -220,12 +220,12 @@ public class LocalMediaManagerTest { mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices); - assertThat(mLocalMediaManager.mMediaDevices).hasSize(3); + assertThat(mLocalMediaManager.mMediaDevices).hasSize(2); verify(mCallback).onDeviceListUpdate(any()); } @Test - public void onDeviceListAdded_phoneDeviceExistInList_addDeviceList() { + public void onDeviceListAdded_addDeviceList() { final List<MediaDevice> devices = new ArrayList<>(); final MediaDevice device1 = mock(MediaDevice.class); final MediaDevice device2 = mock(MediaDevice.class); @@ -245,12 +245,12 @@ public class LocalMediaManagerTest { mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices); - assertThat(mLocalMediaManager.mMediaDevices).hasSize(4); + assertThat(mLocalMediaManager.mMediaDevices).hasSize(2); verify(mCallback).onDeviceListUpdate(any()); } @Test - public void onDeviceRemoved_phoneDeviceIsLastDeviceAfterRemoveMediaDevice_removeBothDevice() { + public void onDeviceRemoved_removeDevice() { final MediaDevice device1 = mock(MediaDevice.class); mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class); mLocalMediaManager.mMediaDevices.add(device1); @@ -260,7 +260,7 @@ public class LocalMediaManagerTest { mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onDeviceRemoved(device1); - assertThat(mLocalMediaManager.mMediaDevices).isEmpty(); + assertThat(mLocalMediaManager.mMediaDevices).hasSize(1); verify(mCallback).onDeviceListUpdate(any()); } @@ -298,7 +298,7 @@ public class LocalMediaManagerTest { } @Test - public void onDeviceListRemoved_phoneDeviceIsLastDeviceAfterRemoveDeviceList_removeAll() { + public void onDeviceListRemoved_removeAll() { final List<MediaDevice> devices = new ArrayList<>(); final MediaDevice device1 = mock(MediaDevice.class); final MediaDevice device2 = mock(MediaDevice.class); @@ -311,7 +311,8 @@ public class LocalMediaManagerTest { assertThat(mLocalMediaManager.mMediaDevices).hasSize(3); mLocalMediaManager.registerCallback(mCallback); - mLocalMediaManager.mMediaDeviceCallback.onDeviceListRemoved(devices); + mLocalMediaManager.mMediaDeviceCallback + .onDeviceListRemoved(mLocalMediaManager.mMediaDevices); assertThat(mLocalMediaManager.mMediaDevices).isEmpty(); verify(mCallback).onDeviceListUpdate(any()); @@ -384,4 +385,38 @@ public class LocalMediaManagerTest { verify(mCallback).onDeviceAttributesChanged(); } + + @Test + public void getActiveMediaDevice_checkList() { + final List<MediaDevice> devices = new ArrayList<>(); + final MediaDevice device1 = mock(MediaDevice.class); + final MediaDevice device2 = mock(MediaDevice.class); + final MediaDevice device3 = mock(MediaDevice.class); + device1.mType = MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE; + device2.mType = MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE; + device3.mType = MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE; + when(device1.getClientPackageName()).thenReturn(TEST_DEVICE_ID_1); + when(device2.getClientPackageName()).thenReturn(TEST_DEVICE_ID_2); + when(device3.getClientPackageName()).thenReturn(TEST_DEVICE_ID_3); + when(device1.getId()).thenReturn(TEST_DEVICE_ID_1); + when(device2.getId()).thenReturn(TEST_DEVICE_ID_2); + when(device3.getId()).thenReturn(TEST_DEVICE_ID_3); + devices.add(device1); + devices.add(device2); + devices.add(device3); + mLocalMediaManager.registerCallback(mCallback); + mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices); + + List<MediaDevice> activeDevices = mLocalMediaManager.getActiveMediaDevice( + MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE); + assertThat(activeDevices).containsExactly(device1); + + activeDevices = mLocalMediaManager.getActiveMediaDevice( + MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE); + assertThat(activeDevices).containsExactly(device2); + + activeDevices = mLocalMediaManager.getActiveMediaDevice( + MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE); + assertThat(activeDevices).containsExactly(device3); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java index 02cb83e0281a..fb8b78b22055 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java @@ -17,6 +17,7 @@ package com.android.settingslib.media; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothClass; @@ -83,6 +84,14 @@ public class MediaDeviceTest { @Mock private MediaRoute2Info mRouteInfo3; @Mock + private MediaRoute2Info mBluetoothRouteInfo1; + @Mock + private MediaRoute2Info mBluetoothRouteInfo2; + @Mock + private MediaRoute2Info mBluetoothRouteInfo3; + @Mock + private MediaRoute2Info mPhoneRouteInfo; + @Mock private LocalBluetoothProfileManager mProfileManager; @Mock private HearingAidProfile mHapProfile; @@ -90,6 +99,8 @@ public class MediaDeviceTest { private A2dpProfile mA2dpProfile; @Mock private BluetoothDevice mDevice; + @Mock + private MediaRouter2Manager mMediaRouter2Manager; private BluetoothMediaDevice mBluetoothMediaDevice1; private BluetoothMediaDevice mBluetoothMediaDevice2; @@ -100,7 +111,6 @@ public class MediaDeviceTest { private InfoMediaDevice mInfoMediaDevice3; private List<MediaDevice> mMediaDevices = new ArrayList<>(); private PhoneMediaDevice mPhoneMediaDevice; - private MediaRouter2Manager mMediaRouter2Manager; @Before public void setUp() { @@ -133,17 +143,24 @@ public class MediaDeviceTest { when(mProfileManager.getHearingAidProfile()).thenReturn(mHapProfile); when(mA2dpProfile.getActiveDevice()).thenReturn(mDevice); - mBluetoothMediaDevice1 = new BluetoothMediaDevice(mContext, mCachedDevice1); - mBluetoothMediaDevice2 = new BluetoothMediaDevice(mContext, mCachedDevice2); - mBluetoothMediaDevice3 = new BluetoothMediaDevice(mContext, mCachedDevice3); - mMediaRouter2Manager = MediaRouter2Manager.getInstance(mContext); + mBluetoothMediaDevice1 = + new BluetoothMediaDevice(mContext, mCachedDevice1, mMediaRouter2Manager, + mBluetoothRouteInfo1, TEST_PACKAGE_NAME); + mBluetoothMediaDevice2 = + new BluetoothMediaDevice(mContext, mCachedDevice2, mMediaRouter2Manager, + mBluetoothRouteInfo2, TEST_PACKAGE_NAME); + mBluetoothMediaDevice3 = + new BluetoothMediaDevice(mContext, mCachedDevice3, mMediaRouter2Manager, + mBluetoothRouteInfo3, TEST_PACKAGE_NAME); mInfoMediaDevice1 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo1, TEST_PACKAGE_NAME); mInfoMediaDevice2 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo2, TEST_PACKAGE_NAME); mInfoMediaDevice3 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo3, TEST_PACKAGE_NAME); - mPhoneMediaDevice = new PhoneMediaDevice(mContext, mLocalBluetoothManager); + mPhoneMediaDevice = + new PhoneMediaDevice(mContext, mMediaRouter2Manager, mPhoneRouteInfo, + TEST_PACKAGE_NAME); } @Test @@ -370,4 +387,11 @@ public class MediaDeviceTest { assertThat(mMediaDevices.get(5)).isEqualTo(mBluetoothMediaDevice1); assertThat(mMediaDevices.get(6)).isEqualTo(mBluetoothMediaDevice2); } + + @Test + public void connect_shouldSelectRoute() { + mInfoMediaDevice1.connect(); + + verify(mMediaRouter2Manager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo1); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java index 0752dc03f397..db984fb8dc26 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java @@ -18,21 +18,13 @@ package com.android.settingslib.media; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.when; - -import android.bluetooth.BluetoothDevice; import android.content.Context; import com.android.settingslib.R; -import com.android.settingslib.bluetooth.A2dpProfile; -import com.android.settingslib.bluetooth.HearingAidProfile; -import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; @@ -40,17 +32,6 @@ import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class PhoneMediaDeviceTest { - @Mock - private LocalBluetoothProfileManager mLocalProfileManager; - @Mock - private LocalBluetoothManager mLocalBluetoothManager; - @Mock - private HearingAidProfile mHapProfile; - @Mock - private A2dpProfile mA2dpProfile; - @Mock - private BluetoothDevice mDevice; - private Context mContext; private PhoneMediaDevice mPhoneMediaDevice; @@ -59,68 +40,8 @@ public class PhoneMediaDeviceTest { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager); - when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); - when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile); - when(mA2dpProfile.getActiveDevice()).thenReturn(mDevice); - - mPhoneMediaDevice = new PhoneMediaDevice(mContext, mLocalBluetoothManager); - } - - @Test - public void connect_phoneDeviceSetActiveSuccess_isConnectedReturnTrue() { - when(mA2dpProfile.setActiveDevice(null)).thenReturn(true); - when(mHapProfile.setActiveDevice(null)).thenReturn(true); - - assertThat(mPhoneMediaDevice.connect()).isTrue(); - } - - @Test - public void connect_a2dpProfileSetActiveFail_isConnectedReturnFalse() { - when(mA2dpProfile.setActiveDevice(null)).thenReturn(false); - when(mHapProfile.setActiveDevice(null)).thenReturn(true); - - assertThat(mPhoneMediaDevice.connect()).isFalse(); - } - - @Test - public void connect_hearingAidProfileSetActiveFail_isConnectedReturnFalse() { - when(mA2dpProfile.setActiveDevice(null)).thenReturn(true); - when(mHapProfile.setActiveDevice(null)).thenReturn(false); - - assertThat(mPhoneMediaDevice.connect()).isFalse(); - } - - @Test - public void connect_hearingAidAndA2dpProfileSetActiveFail_isConnectedReturnFalse() { - when(mA2dpProfile.setActiveDevice(null)).thenReturn(false); - when(mHapProfile.setActiveDevice(null)).thenReturn(false); - - assertThat(mPhoneMediaDevice.connect()).isFalse(); - } - - @Test - public void connect_hearingAidProfileIsNullAndA2dpProfileNotNull_isConnectedReturnTrue() { - when(mLocalProfileManager.getHearingAidProfile()).thenReturn(null); - - when(mA2dpProfile.setActiveDevice(null)).thenReturn(true); - assertThat(mPhoneMediaDevice.connect()).isTrue(); - } - - @Test - public void connect_hearingAidProfileNotNullAndA2dpProfileIsNull_isConnectedReturnTrue() { - when(mLocalProfileManager.getA2dpProfile()).thenReturn(null); - - when(mHapProfile.setActiveDevice(null)).thenReturn(true); - assertThat(mPhoneMediaDevice.connect()).isTrue(); - } - - @Test - public void connect_hearingAidProfileAndA2dpProfileIsNull_isConnectedReturnFalse() { - when(mLocalProfileManager.getA2dpProfile()).thenReturn(null); - when(mLocalProfileManager.getHearingAidProfile()).thenReturn(null); - - assertThat(mPhoneMediaDevice.connect()).isFalse(); + mPhoneMediaDevice = + new PhoneMediaDevice(mContext, null, null, null); } @Test diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 1d679c7bcbdd..5946f2158ac8 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -308,6 +308,22 @@ android:excludeFromRecents="true" android:exported="false" /> + <!-- + The following is used as a no-op/null home activity when + no other MAIN/HOME activity is present (e.g., in CSI). + --> + <activity android:name=".NullHome" + android:excludeFromRecents="true" + android:label="" + android:screenOrientation="nosensor"> + <!-- The priority here is set to be lower than that for Settings --> + <intent-filter android:priority="-1100"> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.HOME" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + <receiver android:name=".BugreportRequestedReceiver" android:permission="android.permission.TRIGGER_SHELL_BUGREPORT"> diff --git a/packages/Shell/res/layout/null_home_finishing_boot.xml b/packages/Shell/res/layout/null_home_finishing_boot.xml new file mode 100644 index 000000000000..5f9563a5d25c --- /dev/null +++ b/packages/Shell/res/layout/null_home_finishing_boot.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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 + --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#80000000" + android:forceHasOverlappingRendering="false"> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_gravity="center" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="40sp" + android:textColor="?android:attr/textColorPrimary" + android:text="@*android:string/android_start_title"/> + <ProgressBar + style="@android:style/Widget.Material.ProgressBar.Horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="12.75dp" + android:colorControlActivated="?android:attr/textColorPrimary" + android:indeterminate="true"/> + </LinearLayout> +</FrameLayout> diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 48d405a4b91f..18145939c384 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -97,6 +97,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -193,9 +194,7 @@ public class BugreportProgressService extends Service { * <p> * Must be a path supported by its FileProvider. */ - // TODO: use the same variable for both dir - private static final String SCREENSHOT_DIR = "bugreports"; - private static final String BUGREPORT_DIR = "/bugreports"; + private static final String BUGREPORT_DIR = "bugreports"; private static final String NOTIFICATION_CHANNEL_ID = "bugreports"; @@ -230,7 +229,7 @@ public class BugreportProgressService extends Service { private final BugreportInfoDialog mInfoDialog = new BugreportInfoDialog(); - private File mScreenshotsDir; + private File mBugreportsDir; private BugreportManager mBugreportManager; @@ -263,11 +262,12 @@ public class BugreportProgressService extends Service { mScreenshotHandler = new ScreenshotHandler("BugreportProgressServiceScreenshotThread"); startSelfIntent = new Intent(this, this.getClass()); - mScreenshotsDir = new File(getFilesDir(), SCREENSHOT_DIR); - if (!mScreenshotsDir.exists()) { - Log.i(TAG, "Creating directory " + mScreenshotsDir + " to store temporary screenshots"); - if (!mScreenshotsDir.mkdir()) { - Log.w(TAG, "Could not create directory " + mScreenshotsDir); + mBugreportsDir = new File(getFilesDir(), BUGREPORT_DIR); + if (!mBugreportsDir.exists()) { + Log.i(TAG, "Creating directory " + mBugreportsDir + + " to store bugreports and screenshots"); + if (!mBugreportsDir.mkdir()) { + Log.w(TAG, "Could not create directory " + mBugreportsDir); } } final Configuration conf = mContext.getResources().getConfiguration(); @@ -372,7 +372,7 @@ public class BugreportProgressService extends Service { @Override public void onFinished() { mInfo.renameBugreportFile(); - mInfo.renameScreenshots(mScreenshotsDir); + mInfo.renameScreenshots(); synchronized (mLock) { sendBugreportFinishedBroadcastLocked(); } @@ -406,7 +406,7 @@ public class BugreportProgressService extends Service { mInfo.bugreportFile); } else { trackInfoWithIdLocked(); - cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE); + cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE, mBugreportsDir); final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED); intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath); intent.putExtra(EXTRA_SCREENSHOT, getScreenshotForIntent(mInfo)); @@ -418,7 +418,8 @@ public class BugreportProgressService extends Service { private static void sendRemoteBugreportFinishedBroadcast(Context context, String bugreportFileName, File bugreportFile) { - cleanupOldFiles(REMOTE_BUGREPORT_FILES_AMOUNT, REMOTE_MIN_KEEP_AGE); + cleanupOldFiles(REMOTE_BUGREPORT_FILES_AMOUNT, REMOTE_MIN_KEEP_AGE, + bugreportFile.getParentFile()); final Intent intent = new Intent(DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH); final Uri bugreportUri = getUri(context, bugreportFile); final String bugreportHash = generateFileHash(bugreportFileName); @@ -468,12 +469,12 @@ public class BugreportProgressService extends Service { return fileHash; } - static void cleanupOldFiles(final int minCount, final long minAge) { + static void cleanupOldFiles(final int minCount, final long minAge, File bugreportsDir) { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { - FileUtils.deleteOlderFiles(new File(BUGREPORT_DIR), minCount, minAge); + FileUtils.deleteOlderFiles(bugreportsDir, minCount, minAge); } catch (RuntimeException e) { Log.e(TAG, "RuntimeException deleting old files", e); } @@ -604,18 +605,25 @@ public class BugreportProgressService extends Service { String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()); BugreportInfo info = new BugreportInfo(mContext, baseName, name, - shareTitle, shareDescription, bugreportType); + shareTitle, shareDescription, bugreportType, mBugreportsDir); + ParcelFileDescriptor bugreportFd; + ParcelFileDescriptor screenshotFd; - ParcelFileDescriptor bugreportFd = info.createBugreportFd(); - if (bugreportFd == null) { - Log.e(TAG, "Bugreport parcel file descriptor is null."); - return; - } - ParcelFileDescriptor screenshotFd = info.createScreenshotFd(); - if (screenshotFd == null) { - Log.e(TAG, "Screenshot parcel file descriptor is null. Deleting bugreport file"); - FileUtils.closeQuietly(bugreportFd); - info.bugreportFile.delete(); + try { + bugreportFd = info.createAndGetBugreportFd(); + if (bugreportFd == null) { + Log.e(TAG, "Bugreport parcel file descriptor is null."); + return; + } + screenshotFd = info.createAndGetDefaultScreenshotFd(); + if (screenshotFd == null) { + Log.e(TAG, "Screenshot parcel file descriptor is null. Deleting bugreport file"); + FileUtils.closeQuietly(bugreportFd); + info.bugreportFile.delete(); + return; + } + } catch (IOException e) { + Log.e(TAG, "Error in generating bugreport files: ", e); return; } mBugreportManager = (BugreportManager) mContext.getSystemService( @@ -639,21 +647,24 @@ public class BugreportProgressService extends Service { } } - private static ParcelFileDescriptor createReadWriteFile(File file) { + private static ParcelFileDescriptor getFd(File file) { try { - file.createNewFile(); - file.setReadable(true, true); - file.setWritable(true, true); - - ParcelFileDescriptor fd = ParcelFileDescriptor.open(file, + return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND); - return fd; - } catch (IOException e) { + } catch (FileNotFoundException e) { Log.i(TAG, "Error in generating bugreports: ", e); } return null; } + private static void createReadWriteFile(File file) throws IOException { + if (!file.exists()) { + file.createNewFile(); + file.setReadable(true, true); + file.setWritable(true, true); + } + } + /** * Updates the system notification for a given bugreport. */ @@ -874,7 +885,7 @@ public class BugreportProgressService extends Service { return; } final String screenshotPath = - new File(mScreenshotsDir, info.getPathNextScreenshot()).getAbsolutePath(); + new File(mBugreportsDir, info.getPathNextScreenshot()).getAbsolutePath(); Message.obtain(mScreenshotHandler, MSG_SCREENSHOT_REQUEST, id, UNUSED_ARG2, screenshotPath) .sendToTarget(); @@ -921,7 +932,7 @@ public class BugreportProgressService extends Service { info.addScreenshot(screenshotFile); if (info.finished) { Log.d(TAG, "Screenshot finished after bugreport; updating share notification"); - info.renameScreenshots(mScreenshotsDir); + info.renameScreenshots(); sendBugreportNotification(info, mTakingScreenshot); } msg = mContext.getString(R.string.bugreport_screenshot_taken); @@ -1030,11 +1041,10 @@ public class BugreportProgressService extends Service { /** * Build {@link Intent} that can be used to share the given bugreport. */ - private static Intent buildSendIntent(Context context, BugreportInfo info, - File screenshotsDir) { + private static Intent buildSendIntent(Context context, BugreportInfo info) { // Rename files (if required) before sharing info.renameBugreportFile(); - info.renameScreenshots(screenshotsDir); + info.renameScreenshots(); // Files are kept on private storage, so turn into Uris that we can // grant temporary permissions for. final Uri bugreportUri; @@ -1120,7 +1130,7 @@ public class BugreportProgressService extends Service { addDetailsToZipFile(info); - final Intent sendIntent = buildSendIntent(mContext, info, mScreenshotsDir); + final Intent sendIntent = buildSendIntent(mContext, info); if (sendIntent == null) { Log.w(TAG, "Stopping progres on ID " + id + " because share intent could not be built"); synchronized (mLock) { @@ -1813,24 +1823,37 @@ public class BugreportProgressService extends Service { */ BugreportInfo(Context context, String baseName, String name, @Nullable String shareTitle, @Nullable String shareDescription, - @BugreportParams.BugreportMode int type) { + @BugreportParams.BugreportMode int type, File bugreportsDir) { this.context = context; this.name = this.initialName = name; this.shareTitle = shareTitle == null ? "" : shareTitle; this.shareDescription = shareDescription == null ? "" : shareDescription; this.type = type; this.baseName = baseName; + createBugreportFile(bugreportsDir); + createScreenshotFile(bugreportsDir); } - ParcelFileDescriptor createBugreportFd() { - bugreportFile = new File(BUGREPORT_DIR, getFileName(this, ".zip")); - return createReadWriteFile(bugreportFile); + void createBugreportFile(File bugreportsDir) { + bugreportFile = new File(bugreportsDir, getFileName(this, ".zip")); } - ParcelFileDescriptor createScreenshotFd() { - File screenshotFile = new File(BUGREPORT_DIR, getScreenshotName("default")); + void createScreenshotFile(File bugreportsDir) { + File screenshotFile = new File(bugreportsDir, getScreenshotName("default")); addScreenshot(screenshotFile); - return createReadWriteFile(screenshotFile); + } + + ParcelFileDescriptor createAndGetBugreportFd() throws IOException { + createReadWriteFile(bugreportFile); + return getFd(bugreportFile); + } + + ParcelFileDescriptor createAndGetDefaultScreenshotFd() throws IOException { + if (screenshotFiles.isEmpty()) { + return null; + } + createReadWriteFile(screenshotFiles.get(0)); + return getFd(screenshotFiles.get(0)); } /** @@ -1859,7 +1882,7 @@ public class BugreportProgressService extends Service { * Rename all screenshots files so that they contain the new {@code name} instead of the * {@code initialName} if user has changed it. */ - void renameScreenshots(File screenshotDir) { + void renameScreenshots() { if (TextUtils.isEmpty(name)) { return; } @@ -1869,7 +1892,7 @@ public class BugreportProgressService extends Service { final String newName = oldName.replaceFirst(initialName, name); final File newFile; if (!newName.equals(oldName)) { - final File renamedFile = new File(screenshotDir, newName); + final File renamedFile = new File(oldFile.getParentFile(), newName); Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile); newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile; } else { @@ -1889,7 +1912,8 @@ public class BugreportProgressService extends Service { * Rename bugreport file to include the name given by user via UI */ void renameBugreportFile() { - File newBugreportFile = new File(BUGREPORT_DIR, getFileName(this, ".zip")); + File newBugreportFile = new File(bugreportFile.getParentFile(), + getFileName(this, ".zip")); if (!newBugreportFile.getPath().equals(bugreportFile.getPath())) { if (bugreportFile.renameTo(newBugreportFile)) { bugreportFile = newBugreportFile; diff --git a/packages/Shell/src/com/android/shell/NullHome.java b/packages/Shell/src/com/android/shell/NullHome.java new file mode 100644 index 000000000000..bd975614a50a --- /dev/null +++ b/packages/Shell/src/com/android/shell/NullHome.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.shell; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; + +/** + * This covers the fallback case where no launcher is available. + * Usually Settings.apk has one fallback home activity. + * Settings.apk, however, is not part of CSI, which needs to be + * standalone (bootable and testable). + */ +public class NullHome extends Activity { + private static final String TAG = "NullHome"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.i(TAG, "onCreate"); + setContentView(R.layout.null_home_finishing_boot); + } + + protected void onDestroy() { + super.onDestroy(); + Log.i(TAG, "onDestroy"); + } +} diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml index bde6ed531353..8d9d6ee68c67 100644 --- a/packages/SystemUI/res-keyguard/values/config.xml +++ b/packages/SystemUI/res-keyguard/values/config.xml @@ -22,10 +22,4 @@ <!-- Allow the menu hard key to be disabled in LockScreen on some devices [DO NOT TRANSLATE] --> <bool name="config_disableMenuKeyInLockScreen">false</bool> - - <!-- Threshold in micro watts below which a charger is rated as "slow"; 1A @ 5V --> - <integer name="config_chargingSlowlyThreshold">5000000</integer> - - <!-- Threshold in micro watts above which a charger is rated as "fast"; 1.5A @ 5V --> - <integer name="config_chargingFastThreshold">7500000</integer> </resources> diff --git a/packages/SystemUI/res/layout/controls_icon.xml b/packages/SystemUI/res/layout/controls_icon.xml new file mode 100644 index 000000000000..cc46ced0a538 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_icon.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2020, 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. +*/ +--> + +<ImageView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="28dp" + android:layout_height="28dp" + android:scaleType="fitCenter" + android:layout_marginLeft="2dp" + android:layout_marginRight="2dp" /> diff --git a/packages/SystemUI/res/layout/controls_no_favorites.xml b/packages/SystemUI/res/layout/controls_no_favorites.xml index 096f1f49aaba..3e0699dff197 100644 --- a/packages/SystemUI/res/layout/controls_no_favorites.xml +++ b/packages/SystemUI/res/layout/controls_no_favorites.xml @@ -1,18 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2020, 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. +*/ +--> + <merge xmlns:android="http://schemas.android.com/apk/res/android"> - <TextView - android:id="@+id/controls_title" - android:text="@string/quick_controls_title" + <LinearLayout + android:id="@+id/controls_no_favorites_group" android:layout_width="match_parent" android:layout_height="wrap_content" - android:singleLine="true" - android:gravity="center" - android:textSize="25dp" + android:orientation="vertical" android:paddingTop="40dp" android:paddingBottom="40dp" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" - android:textColor="@*android:color/foreground_material_dark" - android:fontFamily="@*android:string/config_headlineFontFamily" - android:background="@drawable/control_no_favorites_background"/> + android:background="@drawable/control_no_favorites_background"> + + <LinearLayout + android:id="@+id/controls_icon_row" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:orientation="horizontal" + android:paddingBottom="8dp" /> + + <TextView + android:id="@+id/controls_title" + android:text="@string/quick_controls_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:layout_gravity="center" + android:textSize="25sp" + android:textColor="@*android:color/foreground_material_dark" + android:fontFamily="@*android:string/config_headlineFontFamily" /> + </LinearLayout> </merge> diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java index b2423b9bf252..85724a969fed 100644 --- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java +++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java @@ -27,7 +27,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; -import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.ViewGroup; @@ -47,7 +47,6 @@ public class AdminSecondaryLockScreenController { private Handler mHandler; private IKeyguardClient mClient; private KeyguardSecurityCallback mKeyguardCallback; - private SurfaceControl.Transaction mTransaction; private final ServiceConnection mConnection = new ServiceConnection() { @Override @@ -84,13 +83,13 @@ public class AdminSecondaryLockScreenController { } @Override - public void onSurfaceControlCreated(@Nullable SurfaceControl remoteSurfaceControl) { + public void onRemoteContentReady( + @Nullable SurfaceControlViewHost.SurfacePackage surfacePackage) { if (mHandler != null) { mHandler.removeCallbacksAndMessages(null); } - if (remoteSurfaceControl != null) { - mTransaction.reparent(remoteSurfaceControl, mView.getSurfaceControl()) - .apply(); + if (surfacePackage != null) { + mView.setChildSurfacePackage(surfacePackage); } else { dismiss(KeyguardUpdateMonitor.getCurrentUser()); } @@ -138,11 +137,10 @@ public class AdminSecondaryLockScreenController { public AdminSecondaryLockScreenController(Context context, ViewGroup parent, KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback, - Handler handler, SurfaceControl.Transaction transaction) { + Handler handler) { mContext = context; mHandler = handler; mParent = parent; - mTransaction = transaction; mUpdateMonitor = updateMonitor; mKeyguardCallback = callback; mView = new AdminSecurityView(mContext, mSurfaceHolderCallback); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 29c67ae1b4a6..ba8a1a945a77 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -39,7 +39,6 @@ import android.util.Slog; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.SurfaceControl; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; @@ -149,8 +148,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe mViewConfiguration = ViewConfiguration.get(context); mKeyguardStateController = Dependency.get(KeyguardStateController.class); mSecondaryLockScreenController = new AdminSecondaryLockScreenController(context, this, - mUpdateMonitor, mCallback, new Handler(Looper.myLooper()), - new SurfaceControl.Transaction()); + mUpdateMonitor, mCallback, new Handler(Looper.myLooper())); } public void setSecurityCallback(SecurityCallback callback) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 6a04583b70ba..e5f6d3c90eb7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -21,15 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.ACTION_USER_STOPPED; import static android.content.Intent.ACTION_USER_UNLOCKED; -import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN; -import static android.os.BatteryManager.BATTERY_STATUS_FULL; import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; -import static android.os.BatteryManager.EXTRA_HEALTH; -import static android.os.BatteryManager.EXTRA_LEVEL; -import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT; -import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE; -import static android.os.BatteryManager.EXTRA_PLUGGED; -import static android.os.BatteryManager.EXTRA_STATUS; import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; import static android.telephony.TelephonyManager.MODEM_COUNT_DUAL_MODEM; @@ -67,7 +59,6 @@ import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; import android.media.AudioManager; -import android.os.BatteryManager; import android.os.CancellationSignal; import android.os.Handler; import android.os.IRemoteCallback; @@ -94,6 +85,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import com.android.settingslib.WirelessUtils; +import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.DejankUtils; import com.android.systemui.DumpController; import com.android.systemui.Dumpable; @@ -1059,29 +1051,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab MSG_TIMEZONE_UPDATE, intent.getStringExtra("time-zone")); mHandler.sendMessage(msg); } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { - final int status = intent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN); - final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0); - final int level = intent.getIntExtra(EXTRA_LEVEL, 0); - final int health = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN); - final int maxChargingMicroAmp = intent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1); - int maxChargingMicroVolt = intent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1); - final int maxChargingMicroWatt; - - if (maxChargingMicroVolt <= 0) { - maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT; - } - if (maxChargingMicroAmp > 0) { - // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor - // to maintain precision equally on both factors. - maxChargingMicroWatt = (maxChargingMicroAmp / 1000) - * (maxChargingMicroVolt / 1000); - } else { - maxChargingMicroWatt = -1; - } final Message msg = mHandler.obtainMessage( - MSG_BATTERY_UPDATE, new BatteryStatus(status, level, plugged, health, - maxChargingMicroWatt)); + MSG_BATTERY_UPDATE, new BatteryStatus(intent)); mHandler.sendMessage(msg); } else if (Intent.ACTION_SIM_STATE_CHANGED.equals(action)) { SimData args = SimData.fromIntent(intent); @@ -1323,82 +1295,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - public static class BatteryStatus { - public static final int CHARGING_UNKNOWN = -1; - public static final int CHARGING_SLOWLY = 0; - public static final int CHARGING_REGULAR = 1; - public static final int CHARGING_FAST = 2; - - public final int status; - public final int level; - public final int plugged; - public final int health; - public final int maxChargingWattage; - - public BatteryStatus(int status, int level, int plugged, int health, - int maxChargingWattage) { - this.status = status; - this.level = level; - this.plugged = plugged; - this.health = health; - this.maxChargingWattage = maxChargingWattage; - } - - /** - * Determine whether the device is plugged in (USB, power, or wireless). - * - * @return true if the device is plugged in. - */ - public boolean isPluggedIn() { - return plugged == BatteryManager.BATTERY_PLUGGED_AC - || plugged == BatteryManager.BATTERY_PLUGGED_USB - || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; - } - - /** - * Determine whether the device is plugged in (USB, power). - * - * @return true if the device is plugged in wired (as opposed to wireless) - */ - public boolean isPluggedInWired() { - return plugged == BatteryManager.BATTERY_PLUGGED_AC - || plugged == BatteryManager.BATTERY_PLUGGED_USB; - } - - /** - * Whether or not the device is charged. Note that some devices never return 100% for - * battery level, so this allows either battery level or status to determine if the - * battery is charged. - * - * @return true if the device is charged - */ - public boolean isCharged() { - return status == BATTERY_STATUS_FULL || level >= 100; - } - - /** - * Whether battery is low and needs to be charged. - * - * @return true if battery is low - */ - public boolean isBatteryLow() { - return level < LOW_BATTERY_THRESHOLD; - } - - public final int getChargingSpeed(int slowThreshold, int fastThreshold) { - return maxChargingWattage <= 0 ? CHARGING_UNKNOWN : - maxChargingWattage < slowThreshold ? CHARGING_SLOWLY : - maxChargingWattage > fastThreshold ? CHARGING_FAST : - CHARGING_REGULAR; - } - - @Override - public String toString() { - return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged - + ",health=" + health + ",maxChargingWattage=" + maxChargingWattage + "}"; - } - } - public static class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker { private final Consumer<Integer> mStrongAuthRequiredChangedCallback; @@ -2640,9 +2536,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ private boolean refreshSimState(int subId, int slotId) { final TelephonyManager tele = - (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); int state = (tele != null) ? - tele.getSimState(slotId) : TelephonyManager.SIM_STATE_UNKNOWN; + tele.getSimState(slotId) : TelephonyManager.SIM_STATE_UNKNOWN; SimData data = mSimDatas.get(subId); final boolean changed; if (data == null) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 8e87b7ad45b9..49f72a925a0e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -23,6 +23,7 @@ import android.os.SystemClock; import android.telephony.TelephonyManager; import android.view.WindowManagerPolicyConstants; +import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.statusbar.KeyguardIndicationController; import java.util.TimeZone; @@ -42,7 +43,7 @@ public class KeyguardUpdateMonitorCallback { * * @param status current battery status */ - public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { } + public void onRefreshBatteryInfo(BatteryStatus status) { } /** * Called once per minute or when the time changes. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 5c65977c8929..8a492a83b3df 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -87,7 +87,7 @@ public class AuthContainerView extends LinearLayout @VisibleForTesting @Nullable AuthBiometricView mBiometricView; @VisibleForTesting @Nullable AuthCredentialView mCredentialView; - private final ImageView mBackgroundView; + @VisibleForTesting final ImageView mBackgroundView; @VisibleForTesting final ScrollView mBiometricScrollView; private final View mPanelView; @@ -333,6 +333,12 @@ public class AuthContainerView extends LinearLayout throw new IllegalStateException("Unknown credential type: " + credentialType); } + // The background is used for detecting taps / cancelling authentication. Since the + // credential view is full-screen and should not be canceled from background taps, + // disable it. + mBackgroundView.setOnClickListener(null); + mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + mCredentialView.setContainerView(this); mCredentialView.setEffectiveUserId(mEffectiveUserId); mCredentialView.setCredentialType(credentialType); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 2f1e4b41bd2d..05838abe184a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -16,7 +16,6 @@ package com.android.systemui.bubbles; -import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY; import static android.app.Notification.FLAG_BUBBLE; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; @@ -44,19 +43,13 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.UserIdInt; import android.app.ActivityManager.RunningTaskInfo; -import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.RemoteInput; import android.content.Context; -import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; -import android.content.pm.ShortcutManager; import android.content.res.Configuration; import android.graphics.Rect; -import android.net.Uri; -import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.service.notification.NotificationListenerService.RankingMap; @@ -75,25 +68,33 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.util.ScreenshotHelper; +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.DumpController; +import com.android.systemui.Dumpable; import com.android.systemui.R; +import com.android.systemui.bubbles.BubbleController.BubbleExpandListener; +import com.android.systemui.bubbles.BubbleController.BubbleStateChangeListener; +import com.android.systemui.bubbles.BubbleController.NotifCallback; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.PinnedStackListenerForwarder; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.WindowManagerWrapper; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationRemoveInterceptor; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; +import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.ZenModeController; import java.io.FileDescriptor; @@ -101,10 +102,8 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Singleton; @@ -116,7 +115,7 @@ import javax.inject.Singleton; * The controller manages addition, removal, and visible state of bubbles on screen. */ @Singleton -public class BubbleController implements ConfigurationController.ConfigurationListener { +public class BubbleController implements ConfigurationController.ConfigurationListener, Dumpable { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES; @@ -140,14 +139,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final Context mContext; private final NotificationEntryManager mNotificationEntryManager; + private final NotifPipeline mNotifPipeline; private final BubbleTaskStackListener mTaskStackListener; private BubbleStateChangeListener mStateChangeListener; private BubbleExpandListener mExpandListener; @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer; private final NotificationGroupManager mNotificationGroupManager; private final ShadeController mShadeController; - private final RemoteInputUriController mRemoteInputUriController; - private Handler mHandler = new Handler() {}; private BubbleData mBubbleData; @Nullable private BubbleStackView mStackView; @@ -171,7 +169,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final NotificationShadeWindowController mNotificationShadeWindowController; private final ZenModeController mZenModeController; private StatusBarStateListener mStatusBarStateListener; - private final ScreenshotHelper mScreenshotHelper; // Callback that updates BubbleOverflowActivity on data change. @Nullable private Runnable mOverflowCallback = null; @@ -217,16 +214,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** - * Listener for handling bubble screenshot events. - */ - public interface BubbleScreenshotListener { - /** - * Called to trigger taking a screenshot and sending the result to a bubble. - */ - void onBubbleScreenshot(Bubble bubble); - } - - /** * Listener to be notified when a bubbles' notification suppression state changes. */ public interface NotificationSuppressionChangedListener { @@ -243,16 +230,17 @@ public class BubbleController implements ConfigurationController.ConfigurationLi */ public interface NotifCallback { /** - * Called when the BubbleController wants to remove an entry that it was previously hiding - * from the shade. See {@link BubbleController#isBubbleNotificationSuppressedFromShade}. + * Called when a bubbled notification that was hidden from the shade is now being removed + * This can happen when an app cancels a bubbled notification or when the user dismisses a + * bubble. */ - void removeNotification(NotificationEntry entry); + void removeNotification(NotificationEntry entry, int reason); /** * Called when a bubbled notification has changed whether it should be * filtered from the shade. */ - void invalidateNotificationFilter(String reason); + void invalidateNotifications(String reason); /** * Called on a bubbled entry that has been removed when there are no longer @@ -301,11 +289,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi NotificationLockscreenUserManager notifUserManager, NotificationGroupManager groupManager, NotificationEntryManager entryManager, - RemoteInputUriController remoteInputUriController) { + NotifPipeline notifPipeline, + FeatureFlags featureFlags, + DumpController dumpController) { this(context, notificationShadeWindowController, statusBarStateController, shadeController, data, null /* synchronizer */, configurationController, interruptionStateProvider, zenModeController, notifUserManager, groupManager, entryManager, - remoteInputUriController); + notifPipeline, featureFlags, dumpController); } public BubbleController(Context context, @@ -320,13 +310,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi NotificationLockscreenUserManager notifUserManager, NotificationGroupManager groupManager, NotificationEntryManager entryManager, - RemoteInputUriController remoteInputUriController) { + NotifPipeline notifPipeline, + FeatureFlags featureFlags, + DumpController dumpController) { + dumpController.registerDumpable(TAG, this); mContext = context; mShadeController = shadeController; mNotificationInterruptionStateProvider = interruptionStateProvider; mNotifUserManager = notifUserManager; mZenModeController = zenModeController; - mRemoteInputUriController = remoteInputUriController; mZenModeController.addCallback(new ZenModeController.Callback() { @Override public void onZenChanged(int zen) { @@ -364,7 +356,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mNotificationEntryManager = entryManager; mNotificationGroupManager = groupManager; - setupNEM(); + mNotifPipeline = notifPipeline; + + if (!featureFlags.isNewNotifPipelineRenderingEnabled()) { + setupNEM(); + } else { + setupNotifPipeline(); + } mNotificationShadeWindowController = notificationShadeWindowController; mStatusBarStateListener = new StatusBarStateListener(); @@ -399,7 +397,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mUserCreatedBubbles = new HashSet<>(); mUserBlockedBubbles = new HashSet<>(); - mScreenshotHelper = new ScreenshotHelper(context); mBubbleIconFactory = new BubbleIconFactory(context); } @@ -424,6 +421,14 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } @Override + public void onEntryRemoved( + NotificationEntry entry, + @android.annotation.Nullable NotificationVisibility visibility, + boolean removedByUser) { + BubbleController.this.onEntryRemoved(entry); + } + + @Override public void onNotificationRankingUpdated(RankingMap rankingMap) { onRankingUpdated(rankingMap); } @@ -433,8 +438,29 @@ public class BubbleController implements ConfigurationController.ConfigurationLi new NotificationRemoveInterceptor() { @Override public boolean onNotificationRemoveRequested( - String key, NotificationEntry entry, int reason) { - return shouldInterceptDismissal(entry, reason); + String key, + NotificationEntry entry, + int dismissReason) { + final boolean isClearAll = dismissReason == REASON_CANCEL_ALL; + final boolean isUserDimiss = dismissReason == REASON_CANCEL + || dismissReason == REASON_CLICK; + final boolean isAppCancel = dismissReason == REASON_APP_CANCEL + || dismissReason == REASON_APP_CANCEL_ALL; + final boolean isSummaryCancel = + dismissReason == REASON_GROUP_SUMMARY_CANCELED; + + // Need to check for !appCancel here because the notification may have + // previously been dismissed & entry.isRowDismissed would still be true + boolean userRemovedNotif = + (entry != null && entry.isRowDismissed() && !isAppCancel) + || isClearAll || isUserDimiss || isSummaryCancel; + + if (userRemovedNotif || isUserCreatedBubble(key) + || isSummaryOfUserCreatedBubble(entry)) { + return handleDismissalInterception(entry); + } + + return false; } }); @@ -458,13 +484,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi addNotifCallback(new NotifCallback() { @Override - public void removeNotification(NotificationEntry entry) { + public void removeNotification(NotificationEntry entry, int reason) { mNotificationEntryManager.performRemoveNotification(entry.getSbn(), - UNDEFINED_DISMISS_REASON); + reason); } @Override - public void invalidateNotificationFilter(String reason) { + public void invalidateNotifications(String reason) { mNotificationEntryManager.updateNotifications(reason); } @@ -472,18 +498,28 @@ public class BubbleController implements ConfigurationController.ConfigurationLi public void maybeCancelSummary(NotificationEntry entry) { // Check if removed bubble has an associated suppressed group summary that needs // to be removed now. - final String groupKey = entry.getSbn().getGroup(); + final String groupKey = entry.getSbn().getGroupKey(); if (mBubbleData.isSummarySuppressed(groupKey)) { - mBubbleData.removeSuppressedSummary(entry.getSbn().getGroupKey()); + mBubbleData.removeSuppressedSummary(groupKey); final NotificationEntry summary = mNotificationEntryManager.getActiveNotificationUnfiltered( mBubbleData.getSummaryKey(groupKey)); - mNotificationEntryManager.performRemoveNotification(summary.getSbn(), - UNDEFINED_DISMISS_REASON); + if (summary != null) { + mNotificationEntryManager.performRemoveNotification(summary.getSbn(), + UNDEFINED_DISMISS_REASON); + } } - // Check if summary should be removed from NoManGroup + // Check if we still need to remove the summary from NoManGroup because the summary + // may not be in the mBubbleData.mSuppressedGroupKeys list and removed above. + // For example: + // 1. Bubbled notifications (group) is posted to shade and are visible bubbles + // 2. User expands bubbles so now their respective notifications in the shade are + // hidden, including the group summary + // 3. User removes all bubbles + // 4. We expect all the removed bubbles AND the summary (note: the summary was + // never added to the suppressedSummary list in BubbleData, so we add this check) NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(entry.getSbn()); if (summary != null) { @@ -500,6 +536,31 @@ public class BubbleController implements ConfigurationController.ConfigurationLi }); } + private void setupNotifPipeline() { + mNotifPipeline.addCollectionListener(new NotifCollectionListener() { + @Override + public void onEntryAdded(NotificationEntry entry) { + BubbleController.this.onEntryAdded(entry); + } + + @Override + public void onEntryUpdated(NotificationEntry entry) { + BubbleController.this.onEntryUpdated(entry); + } + + @Override + public void onRankingUpdate(RankingMap rankingMap) { + onRankingUpdated(rankingMap); + } + + @Override + public void onEntryRemoved(NotificationEntry entry, + @NotifCollection.CancellationReason int reason) { + BubbleController.this.onEntryRemoved(entry); + } + }); + } + /** * Sets whether to perform inflation on the same thread as the caller. This method should only * be used in tests, not in production. @@ -536,9 +597,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } - if (mBubbleScreenshotListener != null) { - mStackView.setBubbleScreenshotListener(mBubbleScreenshotListener); - } } } @@ -783,7 +841,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi Log.d(TAG, "onUserDemotedBubble: " + entry.getKey()); } entry.setFlagBubble(false); - removeBubble(entry.getKey(), DISMISS_BLOCKED); + removeBubble(entry, DISMISS_BLOCKED); mUserCreatedBubbles.remove(entry.getKey()); if (BubbleExperimentConfig.isPackageWhitelistedToAutoBubble( mContext, entry.getSbn().getPackageName())) { @@ -800,17 +858,29 @@ public class BubbleController implements ConfigurationController.ConfigurationLi return mUserCreatedBubbles.contains(key); } + boolean isSummaryOfUserCreatedBubble(NotificationEntry entry) { + if (isSummaryOfBubbles(entry)) { + List<Bubble> bubbleChildren = + mBubbleData.getBubblesInGroup(entry.getSbn().getGroupKey()); + for (int i = 0; i < bubbleChildren.size(); i++) { + // Check if any are user-created (i.e. experimental bubbles) + if (isUserCreatedBubble(bubbleChildren.get(i).getKey())) { + return true; + } + } + } + return false; + } + /** - * Removes the bubble associated with the {@param uri}. + * Removes the bubble with the given NotificationEntry. * <p> * Must be called from the main thread. */ @MainThread - void removeBubble(String key, int reason) { - // TEMP: refactor to change this to pass entry - Bubble bubble = mBubbleData.getBubbleWithKey(key); - if (bubble != null) { - mBubbleData.notificationEntryRemoved(bubble.getEntry(), reason); + void removeBubble(NotificationEntry entry, int reason) { + if (mBubbleData.hasBubbleWithKey(entry.getKey())) { + mBubbleData.notificationEntryRemoved(entry, reason); } } @@ -840,7 +910,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi && (canLaunchInActivityView(mContext, entry) || wasAdjusted); if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) { // It was previously a bubble but no longer a bubble -- lets remove it - removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE); + removeBubble(entry, DISMISS_NO_LONGER_BUBBLE); } else if (shouldBubble) { if (wasAdjusted && !previouslyUserCreated) { // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated @@ -850,6 +920,21 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } + private void onEntryRemoved(NotificationEntry entry) { + if (isSummaryOfBubbles(entry)) { + final String groupKey = entry.getSbn().getGroupKey(); + mBubbleData.removeSuppressedSummary(groupKey); + + // Remove any associated bubble children with the summary + final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + for (int i = 0; i < bubbleChildren.size(); i++) { + removeBubble(bubbleChildren.get(i).getEntry(), DISMISS_GROUP_CANCELLED); + } + } else { + removeBubble(entry, DISMISS_NOTIF_CANCEL); + } + } + private void onRankingUpdated(RankingMap rankingMap) { // Forward to BubbleData to block any bubbles which should no longer be shown mBubbleData.notificationRankingUpdated(rankingMap); @@ -877,7 +962,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi final Bubble bubble = removed.first; @DismissReason final int reason = removed.second; mStackView.removeBubble(bubble); - // If the bubble is removed for user switching, leave the notification in place. if (reason != DISMISS_USER_CHANGED) { if (!mBubbleData.hasBubbleWithKey(bubble.getKey()) @@ -885,7 +969,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // The bubble is now gone & the notification is hidden from the shade, so // time to actually remove it for (NotifCallback cb : mCallbacks) { - cb.removeNotification(bubble.getEntry()); + cb.removeNotification(bubble.getEntry(), REASON_CANCEL); } } else { // Update the flag for SysUI @@ -939,7 +1023,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } for (NotifCallback cb : mCallbacks) { - cb.invalidateNotificationFilter("BubbleData.Listener.applyUpdate"); + cb.invalidateNotifications("BubbleData.Listener.applyUpdate"); } updateStack(); @@ -961,124 +1045,85 @@ public class BubbleController implements ConfigurationController.ConfigurationLi }; /** - * We intercept notification entries cancelled by the user (i.e. dismissed) when there is an - * active bubble associated with it. We do this so that developers can still cancel it - * (and hence the bubbles associated with it). However, these intercepted notifications - * should then be hidden from the shade since the user has cancelled them, so we update - * {@link Bubble#showInShade}. - * - * The cancellation of summaries with children associated with bubbles are also handled in this - * method. User-cancelled summaries are tracked by {@link BubbleData#addSummaryToSuppress}. + * We intercept notification entries (including group summaries) dismissed by the user when + * there is an active bubble associated with it. We do this so that developers can still + * cancel it (and hence the bubbles associated with it). However, these intercepted + * notifications should then be hidden from the shade since the user has cancelled them, so we + * {@link Bubble#setSuppressNotification}. For the case of suppressed summaries, we also add + * {@link BubbleData#addSummaryToSuppress}. * * @return true if we want to intercept the dismissal of the entry, else false. */ - public boolean shouldInterceptDismissal(NotificationEntry entry, int dismissReason) { + public boolean handleDismissalInterception(NotificationEntry entry) { if (entry == null) { return false; } - String key = entry.getKey(); - String groupKey = entry != null ? entry.getSbn().getGroupKey() : null; - ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); - boolean inBubbleData = mBubbleData.hasBubbleWithKey(key); - boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey) - && mBubbleData.getSummaryKey(groupKey).equals(key)); - boolean isSummary = entry != null - && entry.getSbn().getNotification().isGroupSummary(); - boolean isSummaryOfBubbles = (isSuppressedSummary || isSummary) - && bubbleChildren != null && !bubbleChildren.isEmpty(); + final boolean interceptBubbleDismissal = mBubbleData.hasBubbleWithKey(entry.getKey()) + && entry.isBubble(); + final boolean interceptSummaryDismissal = isSummaryOfBubbles(entry); - if (!inBubbleData && !isSummaryOfBubbles) { + if (interceptSummaryDismissal) { + handleSummaryDismissalInterception(entry); + } else if (interceptBubbleDismissal) { + Bubble bubble = mBubbleData.getBubbleWithKey(entry.getKey()); + bubble.setSuppressNotification(true); + bubble.setShowDot(false /* show */, true /* animate */); + } else { return false; } - final boolean isClearAll = dismissReason == REASON_CANCEL_ALL; - final boolean isUserDimiss = dismissReason == REASON_CANCEL - || dismissReason == REASON_CLICK; - final boolean isAppCancel = dismissReason == REASON_APP_CANCEL - || dismissReason == REASON_APP_CANCEL_ALL; - final boolean isSummaryCancel = dismissReason == REASON_GROUP_SUMMARY_CANCELED; - - // Need to check for !appCancel here because the notification may have - // previously been dismissed & entry.isRowDismissed would still be true - boolean userRemovedNotif = (entry != null && entry.isRowDismissed() && !isAppCancel) - || isClearAll || isUserDimiss || isSummaryCancel; - if (isSummaryOfBubbles) { - return handleSummaryRemovalInterception(entry, userRemovedNotif); + // Update the shade + for (NotifCallback cb : mCallbacks) { + cb.invalidateNotifications("BubbleController.handleDismissalInterception"); } + return true; + } - // The bubble notification sticks around in the data as long as the bubble is - // not dismissed and the app hasn't cancelled the notification. - Bubble bubble = mBubbleData.getBubbleWithKey(key); - boolean bubbleExtended = entry != null && entry.isBubble() - && (userRemovedNotif || isUserCreatedBubble(bubble.getKey())); - if (bubbleExtended) { - bubble.setSuppressNotification(true); - bubble.setShowDot(false /* show */, true /* animate */); - for (NotifCallback cb : mCallbacks) { - cb.invalidateNotificationFilter("BubbleController" - + ".shouldInterceptDismissal"); - } - return true; - } else if (!userRemovedNotif && entry != null) { - // This wasn't a user removal so we should remove the bubble as well - mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL); + private boolean isSummaryOfBubbles(NotificationEntry entry) { + if (entry == null) { return false; } - return false; - } - private boolean handleSummaryRemovalInterception(NotificationEntry summary, - boolean userRemovedNotif) { - String groupKey = summary.getSbn().getGroupKey(); + String groupKey = entry.getSbn().getGroupKey(); ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey) + && mBubbleData.getSummaryKey(groupKey).equals(entry.getKey())); + boolean isSummary = entry.getSbn().getNotification().isGroupSummary(); + return (isSuppressedSummary || isSummary) + && bubbleChildren != null + && !bubbleChildren.isEmpty(); + } - if (userRemovedNotif) { - // If it's a user dismiss we mark the children to be hidden from the shade. - for (int i = 0; i < bubbleChildren.size(); i++) { - Bubble bubbleChild = bubbleChildren.get(i); - // As far as group manager is concerned, once a child is no longer shown - // in the shade, it is essentially removed. - mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry()); - bubbleChild.setSuppressNotification(true); - bubbleChild.setShowDot(false /* show */, true /* animate */); - } - // And since all children are removed, remove the summary. - mNotificationGroupManager.onEntryRemoved(summary); - - // If the summary was auto-generated we don't need to keep that notification around - // because apps can't cancel it; so we only intercept & suppress real summaries. - boolean isAutogroupSummary = (summary.getSbn().getNotification().flags - & FLAG_AUTOGROUP_SUMMARY) != 0; - if (!isAutogroupSummary) { - // TODO: (b/145659174) remove references to mSuppressedGroupKeys once fully migrated - mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(), - summary.getKey()); - // Tell shade to update for the suppression - mNotificationEntryManager.updateNotifications("BubbleController" - + ".handleSummaryRemovalInterception"); - } - return !isAutogroupSummary; - } else { - // If it's not a user dismiss it's a cancel. - for (int i = 0; i < bubbleChildren.size(); i++) { - // First check if any of these are user-created (i.e. experimental bubbles) - if (mUserCreatedBubbles.contains(bubbleChildren.get(i).getKey())) { - // Experimental bubble! Intercept the removal. - return true; + private void handleSummaryDismissalInterception(NotificationEntry summary) { + // current children in the row: + final List<NotificationEntry> children = summary.getChildren(); + if (children != null) { + for (int i = 0; i < children.size(); i++) { + NotificationEntry child = children.get(i); + if (mBubbleData.hasBubbleWithKey(child.getKey())) { + // Suppress the bubbled child + // As far as group manager is concerned, once a child is no longer shown + // in the shade, it is essentially removed. + Bubble bubbleChild = mBubbleData.getBubbleWithKey(child.getKey()); + mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry()); + bubbleChild.setSuppressNotification(true); + bubbleChild.setShowDot(false /* show */, true /* animate */); + } else { + // non-bubbled children can be removed + for (NotifCallback cb : mCallbacks) { + cb.removeNotification(child, REASON_GROUP_SUMMARY_CANCELED); + } } } - - // Not an experimental bubble, safe to remove. - mBubbleData.removeSuppressedSummary(groupKey); - // Remove any associated bubble children with the summary. - for (int i = 0; i < bubbleChildren.size(); i++) { - Bubble bubbleChild = bubbleChildren.get(i); - mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(), - DISMISS_GROUP_CANCELLED); - } - return false; } + + // And since all children are removed, remove the summary. + mNotificationGroupManager.onEntryRemoved(summary); + + // TODO: (b/145659174) remove references to mSuppressedGroupKeys once fully migrated + mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(), + summary.getKey()); } /** @@ -1267,68 +1312,4 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } } - - // TODO: Copied from RemoteInputView. Consolidate RemoteInput intent logic. - private Intent prepareRemoteInputFromData(String contentType, Uri data, - RemoteInput remoteInput, NotificationEntry entry) { - HashMap<String, Uri> results = new HashMap<>(); - results.put(contentType, data); - mRemoteInputUriController.grantInlineReplyUriPermission(entry.getSbn(), data); - Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - RemoteInput.addDataResultToIntent(remoteInput, fillInIntent, results); - - return fillInIntent; - } - - // TODO: Copied from RemoteInputView. Consolidate RemoteInput intent logic. - private void sendRemoteInput(Intent intent, NotificationEntry entry, - PendingIntent pendingIntent) { - // Tell ShortcutManager that this package has been "activated". ShortcutManager - // will reset the throttling for this package. - // Strictly speaking, the intent receiver may be different from the notification publisher, - // but that's an edge case, and also because we can't always know which package will receive - // an intent, so we just reset for the publisher. - mContext.getSystemService(ShortcutManager.class).onApplicationActive( - entry.getSbn().getPackageName(), - entry.getSbn().getUser().getIdentifier()); - - try { - pendingIntent.send(mContext, 0, intent); - } catch (PendingIntent.CanceledException e) { - Log.i(TAG, "Unable to send remote input result", e); - } - } - - private void sendScreenshotToBubble(Bubble bubble) { - mScreenshotHelper.takeScreenshot( - android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN, - true /* hasStatus */, - true /* hasNav */, - mHandler, - new Consumer<Uri>() { - @Override - public void accept(Uri uri) { - if (uri != null) { - NotificationEntry entry = bubble.getEntry(); - Pair<RemoteInput, Notification.Action> pair = entry.getSbn() - .getNotification().findRemoteInputActionPair(false); - if (pair != null) { - RemoteInput remoteInput = pair.first; - Notification.Action action = pair.second; - Intent dataIntent = prepareRemoteInputFromData("image/png", uri, - remoteInput, entry); - sendRemoteInput(dataIntent, entry, action.actionIntent); - mBubbleData.setSelectedBubble(bubble); - mBubbleData.setExpanded(true); - } else { - Log.w(TAG, "No RemoteInput found for notification: " - + entry.getSbn().getKey()); - } - } - } - }); - } - - private final BubbleScreenshotListener mBubbleScreenshotListener = - bubble -> sendScreenshotToBubble(bubble); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 50a50633f43c..0d5261dcb7f3 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -19,9 +19,9 @@ package com.android.systemui.bubbles; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.view.Display.INVALID_DISPLAY; - import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.ViewRootImpl.sNewInsetsMode; + import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; @@ -56,6 +56,7 @@ import com.android.systemui.R; import com.android.systemui.recents.TriangleShape; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.AlphaOptimizedButton; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * Container for the expanded bubble view, handles rendering the caret and settings icon. @@ -146,7 +147,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList // the bubble again so we'll just remove it. Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey() + ", " + e.getMessage() + "; removing bubble"); - mBubbleController.removeBubble(getBubbleKey(), + mBubbleController.removeBubble(getBubbleEntry(), BubbleController.DISMISS_INVALID_INTENT); } }); @@ -190,7 +191,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } if (mBubble != null && !mBubbleController.isUserCreatedBubble(mBubble.getKey())) { // Must post because this is called from a binder thread. - post(() -> mBubbleController.removeBubble(mBubble.getKey(), + post(() -> mBubbleController.removeBubble(mBubble.getEntry(), BubbleController.DISMISS_TASK_FINISHED)); } } @@ -279,6 +280,10 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList return mBubble != null ? mBubble.getKey() : "null"; } + private NotificationEntry getBubbleEntry() { + return mBubble != null ? mBubble.getEntry() : null; + } + void applyThemeAttrs() { final TypedArray ta = mContext.obtainStyledAttributes( new int[] { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java index 006de8406ce2..20b3386b450d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java @@ -73,9 +73,6 @@ public class BubbleExperimentConfig { private static final String WHITELISTED_AUTO_BUBBLE_APPS = "whitelisted_auto_bubble_apps"; - private static final String ALLOW_BUBBLE_MENU = "allow_bubble_screenshot_menu"; - private static final boolean ALLOW_BUBBLE_MENU_DEFAULT = false; - private static final String ALLOW_BUBBLE_OVERFLOW = "allow_bubble_overflow"; private static final boolean ALLOW_BUBBLE_OVERFLOW_DEFAULT = false; @@ -137,16 +134,6 @@ public class BubbleExperimentConfig { * When true, show a menu when a bubble is long-pressed, which will allow the user to take * actions on that bubble. */ - static boolean allowBubbleScreenshotMenu(Context context) { - return Settings.Secure.getInt(context.getContentResolver(), - ALLOW_BUBBLE_MENU, - ALLOW_BUBBLE_MENU_DEFAULT ? 1 : 0) != 0; - } - - /** - * When true, show a menu when a bubble is long-pressed, which will allow the user to take - * actions on that bubble. - */ static boolean allowBubbleOverflow(Context context) { return Settings.Secure.getInt(context.getContentResolver(), ALLOW_BUBBLE_OVERFLOW, diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java deleted file mode 100644 index bf8306561f1b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.bubbles; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import com.android.systemui.R; - -/** - * Menu which allows users to take actions on bubbles, ex. screenshots. - */ -public class BubbleMenuView extends FrameLayout { - private FrameLayout mMenu; - private boolean mShowing = false; - - /** Delay before taking a screenshot once the button is tapped to allow the menu time to hide.*/ - public static final long SCREENSHOT_DELAY = 200; - - public BubbleMenuView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public BubbleMenuView(Context context) { - super(context); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mMenu = findViewById(R.id.bubble_menu_view); - ImageView icon = findViewById(com.android.internal.R.id.icon); - icon.setImageDrawable(mContext.getDrawable(com.android.internal.R.drawable.ic_screenshot)); - } - - /** - * Get the bubble menu view. - */ - public View getMenuView() { - return mMenu; - } - - /** - * Checks whether the bubble menu is currently displayed. - */ - public boolean isShowing() { - return mShowing; - } - - /** - * Show the bubble menu at the specified position on the screen. - */ - public void show(float x, float y) { - mShowing = true; - this.setVisibility(VISIBLE); - mMenu.setTranslationX(x); - mMenu.setTranslationY(y); - } - - /** - * Hide the bubble menu. - */ - public void hide() { - mShowing = false; - this.setVisibility(GONE); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 20d19ece575c..bce172b89187 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -115,7 +115,6 @@ public class BubbleStackView extends FrameLayout { /** How long to wait, in milliseconds, before hiding the flyout. */ @VisibleForTesting static final int FLYOUT_HIDE_AFTER = 5000; - private BubbleController.BubbleScreenshotListener mBubbleScreenshotListener; /** * Interface to synchronize {@link View} state and the screen. @@ -169,7 +168,6 @@ public class BubbleStackView extends FrameLayout { private ExpandedAnimationController mExpandedAnimationController; private FrameLayout mExpandedViewContainer; - @Nullable private BubbleMenuView mBubbleMenuView; private BubbleFlyoutView mFlyout; /** Runnable that fades out the flyout and then sets it to GONE. */ @@ -516,9 +514,6 @@ public class BubbleStackView extends FrameLayout { mDesaturateAndDarkenPaint.setColorFilter(new ColorMatrixColorFilter(animatedMatrix)); mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint); }); - - mInflater.inflate(R.layout.bubble_menu_view, this); - mBubbleMenuView = findViewById(R.id.bubble_menu_container); } private void setUpOverflow() { @@ -738,13 +733,6 @@ public class BubbleStackView extends FrameLayout { } /** - * Sets the screenshot listener. - */ - public void setBubbleScreenshotListener(BubbleController.BubbleScreenshotListener listener) { - mBubbleScreenshotListener = listener; - } - - /** * Whether the stack of bubbles is expanded or not. */ public boolean isExpanded() { @@ -942,12 +930,6 @@ public class BubbleStackView extends FrameLayout { public View getTargetView(MotionEvent event) { float x = event.getRawX(); float y = event.getRawY(); - if (mBubbleMenuView.isShowing()) { - if (isIntersecting(mBubbleMenuView.getMenuView(), x, y)) { - return mBubbleMenuView; - } - return null; - } if (mIsExpanded) { if (isIntersecting(mBubbleContainer, x, y)) { if (BubbleExperimentConfig.allowBubbleOverflow(mContext) @@ -1168,7 +1150,6 @@ public class BubbleStackView extends FrameLayout { } return; } - hideBubbleMenu(); mStackAnimationController.cancelStackPositionAnimations(); mBubbleContainer.setActiveController(mStackAnimationController); hideFlyoutImmediate(); @@ -1570,10 +1551,6 @@ public class BubbleStackView extends FrameLayout { @Override public void getBoundsOnScreen(Rect outRect) { // If the bubble menu is open, the entire screen should capture touch events. - if (mBubbleMenuView.isShowing()) { - outRect.set(0, 0, getWidth(), getHeight()); - return; - } if (!mIsExpanded) { if (getBubbleCount() > 0) { mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect); @@ -1808,50 +1785,4 @@ public class BubbleStackView extends FrameLayout { } return bubbles; } - - /** - * Show the bubble menu, positioned relative to the stack. - */ - public void showBubbleMenu() { - PointF currentPos = mStackAnimationController.getStackPosition(); - mBubbleMenuView.setVisibility(View.INVISIBLE); - post(() -> { - float yPos = currentPos.y; - float xPos = currentPos.x; - if (mStackAnimationController.isStackOnLeftSide()) { - xPos += mBubbleSize; - } else { - xPos -= mBubbleMenuView.getMenuView().getWidth(); - } - - mBubbleMenuView.show(xPos, yPos); - }); - } - - /** - * Hide the bubble menu. - */ - public void hideBubbleMenu() { - mBubbleMenuView.hide(); - } - - /** - * Determines whether the bubble menu is currently showing. - */ - public boolean isShowingBubbleMenu() { - return mBubbleMenuView.isShowing(); - } - - /** - * Take a screenshot and send it to the specified bubble. - */ - public void sendScreenshotToBubble(Bubble bubble) { - hideBubbleMenu(); - // delay allows the bubble menu to disappear before the screenshot - // done here because we already have a Handler to delay with. - // TODO: Hide bubble + menu UI from screenshots entirely instead of just delaying. - postDelayed(() -> { - mBubbleScreenshotListener.onBubbleScreenshot(bubble); - }, BubbleMenuView.SCREENSHOT_DELAY); - } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index 5a9d44b6da2c..645696d0bcac 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -58,14 +58,12 @@ class BubbleTouchHandler implements View.OnTouchListener { private final PointF mViewPositionOnTouchDown = new PointF(); private final BubbleStackView mStack; private final BubbleData mBubbleData; - private final Context mContext; private BubbleController mController = Dependency.get(BubbleController.class); private boolean mMovedEnough; private int mTouchSlopSquared; private VelocityTracker mVelocityTracker; - private Runnable mShowBubbleMenuRunnable; /** View that was initially touched, when we received the first ACTION_DOWN event. */ private View mTouchedView; @@ -78,7 +76,6 @@ class BubbleTouchHandler implements View.OnTouchListener { mTouchSlopSquared = touchSlop * touchSlop; mBubbleData = bubbleData; mStack = stackView; - mContext = context; } @Override @@ -95,18 +92,10 @@ class BubbleTouchHandler implements View.OnTouchListener { // anything, collapse the stack. if (action == MotionEvent.ACTION_OUTSIDE || mTouchedView == null) { mBubbleData.setExpanded(false); - mStack.hideBubbleMenu(); resetForNextGesture(); return false; } - if (mTouchedView instanceof BubbleMenuView) { - mStack.hideBubbleMenu(); - resetForNextGesture(); - mStack.sendScreenshotToBubble(mBubbleData.getSelectedBubble()); - return false; - } - if (!(mTouchedView instanceof BadgedImageView) && !(mTouchedView instanceof BubbleStackView) && !(mTouchedView instanceof BubbleFlyoutView)) { @@ -116,7 +105,6 @@ class BubbleTouchHandler implements View.OnTouchListener { } // Not touching anything touchable, but we shouldn't collapse (e.g. touching edge // of expanded view). - mStack.hideBubbleMenu(); resetForNextGesture(); return false; } @@ -139,12 +127,6 @@ class BubbleTouchHandler implements View.OnTouchListener { if (isStack) { mViewPositionOnTouchDown.set(mStack.getStackPosition()); mStack.onDragStart(); - if (!mStack.isShowingBubbleMenu() && !mStack.isExpanded() - && BubbleExperimentConfig.allowBubbleScreenshotMenu(mContext)) { - mShowBubbleMenuRunnable = mStack::showBubbleMenu; - mStack.postDelayed(mShowBubbleMenuRunnable, - ViewConfiguration.getLongPressTimeout()); - } } else if (isFlyout) { mStack.onFlyoutDragStart(); } else { @@ -155,10 +137,6 @@ class BubbleTouchHandler implements View.OnTouchListener { break; case MotionEvent.ACTION_MOVE: - // block all further touch inputs once the menu is open - if (mStack.isShowingBubbleMenu()) { - return true; - } trackMovement(event); final float deltaX = rawX - mTouchDown.x; final float deltaY = rawY - mTouchDown.y; @@ -168,7 +146,6 @@ class BubbleTouchHandler implements View.OnTouchListener { } if (mMovedEnough) { - mStack.removeCallbacks(mShowBubbleMenuRunnable); if (isStack) { mStack.onDragged(viewX, viewY); } else if (isFlyout) { @@ -199,12 +176,6 @@ class BubbleTouchHandler implements View.OnTouchListener { break; case MotionEvent.ACTION_UP: - if (mStack.isShowingBubbleMenu()) { - resetForNextGesture(); - return true; - } else { - mStack.removeCallbacks(mShowBubbleMenuRunnable); - } trackMovement(event); mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000); final float velX = mVelocityTracker.getXVelocity(); @@ -227,9 +198,14 @@ class BubbleTouchHandler implements View.OnTouchListener { if (isStack) { mController.dismissStack(BubbleController.DISMISS_USER_GESTURE); } else { - mController.removeBubble( - individualBubbleKey, - BubbleController.DISMISS_USER_GESTURE); + final Bubble bubble = + mBubbleData.getBubbleWithKey(individualBubbleKey); + // bubble can be null if the user is in the middle of + // dismissing the bubble, but the app also sent a cancel + if (bubble != null) { + mController.removeBubble(bubble.getEntry(), + BubbleController.DISMISS_USER_GESTURE); + } } }); } else if (isFlyout) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index 88b19b58a453..78e0e8b81b44 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -39,7 +39,7 @@ import com.android.systemui.R const val MIN_LEVEL = 0 const val MAX_LEVEL = 10000 -private const val UPDATE_DELAY_IN_MILLIS = 2000L +private const val UPDATE_DELAY_IN_MILLIS = 3000L class ControlViewHolder( val layout: ViewGroup, diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index ed521e3be535..f029dfbe1bb2 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -22,6 +22,7 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection +import android.graphics.drawable.Drawable import android.os.IBinder import android.service.controls.Control import android.service.controls.TokenProvider @@ -29,19 +30,24 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageView import android.widget.LinearLayout import android.widget.Space -import android.widget.TextView +import com.android.settingslib.widget.CandidateInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.ControlInfo +import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.management.ControlsProviderSelectorActivity +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.R import com.android.systemui.util.concurrency.DelayableExecutor import dagger.Lazy +import java.text.Collator + import javax.inject.Inject import javax.inject.Singleton @@ -110,7 +116,9 @@ private data class ControlKey(val componentName: ComponentName, val controlId: S class ControlsUiControllerImpl @Inject constructor ( val controlsController: Lazy<ControlsController>, val context: Context, - @Main val uiExecutor: DelayableExecutor + @Main val uiExecutor: DelayableExecutor, + @Background val bgExecutor: DelayableExecutor, + val controlsListingController: Lazy<ControlsListingController> ) : ControlsUiController { private lateinit var controlInfos: List<ControlInfo> @@ -121,6 +129,22 @@ class ControlsUiControllerImpl @Inject constructor ( override val available: Boolean get() = controlsController.get().available + private val listingCallback = object : ControlsListingController.ControlsListingCallback { + override fun onServicesUpdated(candidates: List<CandidateInfo>) { + bgExecutor.execute { + val collator = Collator.getInstance(context.getResources() + .getConfiguration().locale) + val localeComparator = compareBy<CandidateInfo, CharSequence>(collator) { + it.loadLabel() + } + + val mList = candidates.toMutableList() + mList.sortWith(localeComparator) + loadInitialSetupViewIcons(mList.map { it.loadLabel() to it.loadIcon() }) + } + } + } + override fun show(parent: ViewGroup) { Log.d(TAG, "show()") @@ -153,8 +177,26 @@ class ControlsUiControllerImpl @Inject constructor ( val inflater = LayoutInflater.from(context) inflater.inflate(R.layout.controls_no_favorites, parent, true) - val textView = parent.requireViewById(R.id.controls_title) as TextView - textView.setOnClickListener(launchSelectorActivityListener(context)) + val viewGroup = parent.requireViewById(R.id.controls_no_favorites_group) as ViewGroup + viewGroup.setOnClickListener(launchSelectorActivityListener(context)) + + controlsListingController.get().addCallback(listingCallback) + } + + private fun loadInitialSetupViewIcons(icons: List<Pair<CharSequence, Drawable>>) { + uiExecutor.execute { + val viewGroup = parent.requireViewById(R.id.controls_icon_row) as ViewGroup + viewGroup.removeAllViews() + + val inflater = LayoutInflater.from(context) + icons.forEach { + val imageView = inflater.inflate(R.layout.controls_icon, viewGroup, false) + as ImageView + imageView.setContentDescription(it.first) + imageView.setImageDrawable(it.second) + viewGroup.addView(imageView) + } + } } private fun launchSelectorActivityListener(context: Context): (View) -> Unit { @@ -206,6 +248,7 @@ class ControlsUiControllerImpl @Inject constructor ( parent.removeAllViews() controlsById.clear() controlViewsById.clear() + controlsListingController.get().removeCallback(listingCallback) } override fun onRefreshState(componentName: ComponentName, controls: List<Control>) { @@ -231,9 +274,9 @@ class ControlsUiControllerImpl @Inject constructor ( } } - private fun createRow(inflater: LayoutInflater, parent: ViewGroup): ViewGroup { - val row = inflater.inflate(R.layout.controls_row, parent, false) as ViewGroup - parent.addView(row) + private fun createRow(inflater: LayoutInflater, listView: ViewGroup): ViewGroup { + val row = inflater.inflate(R.layout.controls_row, listView, false) as ViewGroup + listView.addView(row) return row } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 14eec59211bd..9da99c453022 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -151,7 +151,7 @@ public class KeyguardViewMediator extends SystemUI { private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000; private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000; - private static final boolean DEBUG = KeyguardConstants.DEBUG; + private static final boolean DEBUG = true; private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES; private final static String TAG = "KeyguardViewMediator"; diff --git a/packages/SystemUI/src/com/android/systemui/log/Event.java b/packages/SystemUI/src/com/android/systemui/log/Event.java deleted file mode 100644 index 7bc1abfbb0d8..000000000000 --- a/packages/SystemUI/src/com/android/systemui/log/Event.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.log; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Stores information about an event that occurred in SystemUI to be used for debugging and triage. - * Every event has a time stamp, log level and message. - * Events are stored in {@link SysuiLog} and can be printed in a dumpsys. - */ -public class Event { - public static final int UNINITIALIZED = -1; - - @IntDef({ERROR, WARN, INFO, DEBUG, VERBOSE}) - @Retention(RetentionPolicy.SOURCE) - public @interface Level {} - public static final int VERBOSE = 2; - public static final int DEBUG = 3; - public static final int INFO = 4; - public static final int WARN = 5; - public static final int ERROR = 6; - public static final @Level int DEFAULT_LOG_LEVEL = DEBUG; - - private long mTimestamp; - private @Level int mLogLevel = DEFAULT_LOG_LEVEL; - private String mMessage = ""; - - /** - * initialize an event with a message - */ - public Event init(String message) { - init(DEFAULT_LOG_LEVEL, message); - return this; - } - - /** - * initialize an event with a logLevel and message - */ - public Event init(@Level int logLevel, String message) { - mTimestamp = System.currentTimeMillis(); - mLogLevel = logLevel; - mMessage = message; - return this; - } - - public String getMessage() { - return mMessage; - } - - public long getTimestamp() { - return mTimestamp; - } - - public @Level int getLogLevel() { - return mLogLevel; - } - - /** - * Recycle this event - */ - void recycle() { - mTimestamp = -1; - mLogLevel = DEFAULT_LOG_LEVEL; - mMessage = ""; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/log/RichEvent.java b/packages/SystemUI/src/com/android/systemui/log/RichEvent.java deleted file mode 100644 index 470f2b0d1b98..000000000000 --- a/packages/SystemUI/src/com/android/systemui/log/RichEvent.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.log; - -/** - * Stores information about an event that occurred in SystemUI to be used for debugging and triage. - * Every rich event has a time stamp, event type, and log level, with the option to provide the - * reason this event was triggered. - * Events are stored in {@link SysuiLog} and can be printed in a dumpsys. - */ -public abstract class RichEvent extends Event { - private int mType; - - /** - * Initializes a rich event that includes an event type that matches with an index in the array - * getEventLabels(). - */ - public RichEvent init(@Event.Level int logLevel, int type, String reason) { - final int numEvents = getEventLabels().length; - if (type < 0 || type >= numEvents) { - throw new IllegalArgumentException("Unsupported event type. Events only supported" - + " from 0 to " + (numEvents - 1) + ", but given type=" + type); - } - mType = type; - super.init(logLevel, getEventLabels()[mType] + " " + reason); - return this; - } - - /** - * Returns an array of the event labels. The index represents the event type and the - * corresponding String stored at that index is the user-readable representation of that event. - * @return array of user readable events, where the index represents its event type constant - */ - public abstract String[] getEventLabels(); - - @Override - public void recycle() { - super.recycle(); - mType = -1; - } - - public int getType() { - return mType; - } - - /** - * Builder to build a RichEvent. - * @param <B> Log specific builder that is extending this builder - * @param <E> Type of event we'll be building - */ - public abstract static class Builder<B extends Builder<B, E>, E extends RichEvent> { - public static final int UNINITIALIZED = -1; - - public final SysuiLog mLog; - private B mBuilder = getBuilder(); - protected int mType; - protected String mReason; - protected @Level int mLogLevel; - - public Builder(SysuiLog sysuiLog) { - mLog = sysuiLog; - reset(); - } - - /** - * Reset this builder's parameters so it can be reused to build another RichEvent. - */ - public void reset() { - mType = UNINITIALIZED; - mReason = null; - mLogLevel = VERBOSE; - } - - /** - * Get the log-specific builder. - */ - public abstract B getBuilder(); - - /** - * Build the log-specific event given an event to populate. - */ - public abstract E build(E e); - - /** - * Optional - set the log level. Defaults to DEBUG. - */ - public B setLogLevel(@Level int logLevel) { - mLogLevel = logLevel; - return mBuilder; - } - - /** - * Required - set the event type. These events must correspond with the events from - * getEventLabels(). - */ - public B setType(int type) { - mType = type; - return mBuilder; - } - - /** - * Optional - set the reason why this event was triggered. - */ - public B setReason(String reason) { - mReason = reason; - return mBuilder; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java b/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java deleted file mode 100644 index 9ee3e6765e4a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.log; - -import android.os.Build; -import android.os.SystemProperties; -import android.util.Log; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.DumpController; -import com.android.systemui.Dumpable; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.ArrayDeque; -import java.util.Locale; - -/** - * Thread-safe logger in SystemUI which prints logs to logcat and stores logs to be - * printed by the DumpController. This is an alternative to printing directly - * to avoid logs being deleted by chatty. The number of logs retained is varied based on - * whether the build is {@link Build.IS_DEBUGGABLE}. - * - * To manually view the logs via adb: - * adb shell dumpsys activity service com.android.systemui/.SystemUIService \ - * dependency DumpController <SysuiLogId> - * - * Logs can be disabled by setting the following SystemProperty and then restarting the device: - * adb shell setprop persist.sysui.log.enabled.<id> true/false && adb reboot - * - * @param <E> Type of event we'll be logging - */ -public class SysuiLog<E extends Event> implements Dumpable { - public static final SimpleDateFormat DATE_FORMAT = - new SimpleDateFormat("MM-dd HH:mm:ss.S", Locale.US); - - protected final Object mDataLock = new Object(); - private final String mId; - private final int mMaxLogs; - protected boolean mEnabled; - protected boolean mLogToLogcatEnabled; - - @VisibleForTesting protected ArrayDeque<E> mTimeline; - - /** - * Creates a SysuiLog - * @param dumpController where to register this logger's dumpsys - * @param id user-readable tag for this logger - * @param maxDebugLogs maximum number of logs to retain when {@link sDebuggable} is true - * @param maxLogs maximum number of logs to retain when {@link sDebuggable} is false - */ - public SysuiLog(DumpController dumpController, String id, int maxDebugLogs, int maxLogs) { - this(dumpController, id, sDebuggable ? maxDebugLogs : maxLogs, - SystemProperties.getBoolean(SYSPROP_ENABLED_PREFIX + id, DEFAULT_ENABLED), - SystemProperties.getBoolean(SYSPROP_LOGCAT_ENABLED_PREFIX + id, - DEFAULT_LOGCAT_ENABLED)); - } - - @VisibleForTesting - protected SysuiLog(DumpController dumpController, String id, int maxLogs, boolean enabled, - boolean logcatEnabled) { - mId = id; - mMaxLogs = maxLogs; - mEnabled = enabled; - mLogToLogcatEnabled = logcatEnabled; - mTimeline = mEnabled ? new ArrayDeque<>(mMaxLogs) : null; - dumpController.registerDumpable(mId, this); - } - - /** - * Logs an event to the timeline which can be printed by the dumpsys. - * May also log to logcat if enabled. - * @return the last event that was discarded from the Timeline (can be recycled) - */ - public E log(E event) { - if (!mEnabled) { - return null; - } - - E recycledEvent = null; - synchronized (mDataLock) { - if (mTimeline.size() >= mMaxLogs) { - recycledEvent = mTimeline.removeFirst(); - } - - mTimeline.add(event); - } - - if (mLogToLogcatEnabled) { - final String strEvent = eventToString(event); - switch (event.getLogLevel()) { - case Event.VERBOSE: - Log.v(mId, strEvent); - break; - case Event.DEBUG: - Log.d(mId, strEvent); - break; - case Event.ERROR: - Log.e(mId, strEvent); - break; - case Event.INFO: - Log.i(mId, strEvent); - break; - case Event.WARN: - Log.w(mId, strEvent); - break; - } - } - - if (recycledEvent != null) { - recycledEvent.recycle(); - } - - return recycledEvent; - } - - /** - * @return user-readable string of the given event with timestamp - */ - private String eventToTimestampedString(Event event) { - StringBuilder sb = new StringBuilder(); - sb.append(SysuiLog.DATE_FORMAT.format(event.getTimestamp())); - sb.append(" "); - sb.append(event.getMessage()); - return sb.toString(); - } - - /** - * @return user-readable string of the given event without a timestamp - */ - public String eventToString(Event event) { - return event.getMessage(); - } - - @GuardedBy("mDataLock") - private void dumpTimelineLocked(PrintWriter pw) { - pw.println("\tTimeline:"); - - for (Event event : mTimeline) { - pw.println("\t" + eventToTimestampedString(event)); - } - } - - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println(mId + ":"); - - if (mEnabled) { - synchronized (mDataLock) { - dumpTimelineLocked(pw); - } - } else { - pw.print(" - Logging disabled."); - } - } - - private static boolean sDebuggable = Build.IS_DEBUGGABLE; - private static final String SYSPROP_ENABLED_PREFIX = "persist.sysui.log.enabled."; - private static final String SYSPROP_LOGCAT_ENABLED_PREFIX = "persist.sysui.log.enabled.logcat."; - private static final boolean DEFAULT_ENABLED = sDebuggable; - private static final boolean DEFAULT_LOGCAT_ENABLED = false; - private static final int DEFAULT_MAX_DEBUG_LOGS = 100; - private static final int DEFAULT_MAX_LOGS = 50; -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 411980b399bd..ae6162219afa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -83,7 +83,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mTile = new Tile(); updateDefaultTileAndIcon(); mServiceManager = host.getTileServices().getTileWrapper(this); - if (mServiceManager.isBooleanTile()) { + if (mServiceManager.isToggleableTile()) { // Replace states with BooleanState resetStates(); } @@ -252,7 +252,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener @Override public State newTileState() { - if (mServiceManager != null && mServiceManager.isBooleanTile()) { + if (mServiceManager != null && mServiceManager.isToggleableTile()) { return new BooleanState(); } return new State(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index ad79cadcc12d..17b0251837e7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -141,16 +141,16 @@ public class TileLifecycleManager extends BroadcastReceiver implements /** * Determines whether the associated TileService is a Boolean Tile. * - * @return true if {@link TileService#META_DATA_BOOLEAN_TILE} is set to {@code true} for this + * @return true if {@link TileService#META_DATA_TOGGLEABLE_TILE} is set to {@code true} for this * tile - * @see TileService#META_DATA_BOOLEAN_TILE + * @see TileService#META_DATA_TOGGLEABLE_TILE */ - public boolean isBooleanTile() { + public boolean isToggleableTile() { try { ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA); return info.metaData != null - && info.metaData.getBoolean(TileService.META_DATA_BOOLEAN_TILE, false); + && info.metaData.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false); } catch (PackageManager.NameNotFoundException e) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java index 1902d655abc3..cfa8fb6373a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java @@ -124,8 +124,8 @@ public class TileServiceManager { return mStateManager.isActiveTile(); } - public boolean isBooleanTile() { - return mStateManager.isBooleanTile(); + public boolean isToggleableTile() { + return mStateManager.isToggleableTile(); } public void setShowingDialog(boolean dialog) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 7ad07c266cc3..7d3d4061014b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -20,7 +20,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.ColorStateList; -import android.content.res.Resources; import android.graphics.Color; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; @@ -46,6 +45,7 @@ import com.android.internal.widget.ViewClippingUtil; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.Utils; +import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -97,8 +97,6 @@ public class KeyguardIndicationController implements StateListener, private final LockPatternUtils mLockPatternUtils; private final DockManager mDockManager; - private final int mSlowThreshold; - private final int mFastThreshold; private final LockIcon mLockIcon; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); @@ -178,10 +176,6 @@ public class KeyguardIndicationController implements StateListener, mWakeLock = new SettableWakeLock(wakeLock, TAG); mLockPatternUtils = lockPatternUtils; - Resources res = context.getResources(); - mSlowThreshold = res.getInteger(R.integer.config_chargingSlowlyThreshold); - mFastThreshold = res.getInteger(R.integer.config_chargingFastThreshold); - mUserManager = context.getSystemService(UserManager.class); mBatteryInfo = iBatteryStats; @@ -484,12 +478,12 @@ public class KeyguardIndicationController implements StateListener, int chargingId; if (mPowerPluggedInWired) { switch (mChargingSpeed) { - case KeyguardUpdateMonitor.BatteryStatus.CHARGING_FAST: + case BatteryStatus.CHARGING_FAST: chargingId = hasChargingTime ? R.string.keyguard_indication_charging_time_fast : R.string.keyguard_plugged_in_charging_fast; break; - case KeyguardUpdateMonitor.BatteryStatus.CHARGING_SLOWLY: + case BatteryStatus.CHARGING_SLOWLY: chargingId = hasChargingTime ? R.string.keyguard_indication_charging_time_slowly : R.string.keyguard_plugged_in_charging_slowly; @@ -620,7 +614,7 @@ public class KeyguardIndicationController implements StateListener, public static final int HIDE_DELAY_MS = 5000; @Override - public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { + public void onRefreshBatteryInfo(BatteryStatus status) { boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING || status.status == BatteryManager.BATTERY_STATUS_FULL; boolean wasPluggedIn = mPowerPluggedIn; @@ -628,7 +622,7 @@ public class KeyguardIndicationController implements StateListener, mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull; mPowerCharged = status.isCharged(); mChargingWattage = status.maxChargingWattage; - mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold); + mChargingSpeed = status.getChargingSpeed(mContext); mBatteryLevel = status.level; try { mChargingTimeRemaining = mPowerPluggedIn 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 61915ad92d87..916da6eca0c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -47,8 +47,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationRankin import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.logging.NotifEvent; -import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -129,6 +127,8 @@ public class NotificationEntryManager implements private final Map<NotificationEntry, NotificationLifetimeExtender> mRetainedNotifications = new ArrayMap<>(); + private final NotificationEntryManagerLogger mLogger; + // Lazily retrieved dependencies private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy; private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy; @@ -143,7 +143,6 @@ public class NotificationEntryManager implements private NotificationPresenter mPresenter; private RankingMap mLatestRankingMap; - private NotifLog mNotifLog; @VisibleForTesting final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders @@ -184,7 +183,7 @@ public class NotificationEntryManager implements @Inject public NotificationEntryManager( - NotifLog notifLog, + NotificationEntryManagerLogger logger, NotificationGroupManager groupManager, NotificationRankingManager rankingManager, KeyguardEnvironment keyguardEnvironment, @@ -193,7 +192,7 @@ public class NotificationEntryManager implements Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy, LeakDetector leakDetector, ForegroundServiceDismissalFeatureController fgsFeatureController) { - mNotifLog = notifLog; + mLogger = logger; mGroupManager = groupManager; mRankingManager = rankingManager; mKeyguardEnvironment = keyguardEnvironment; @@ -291,13 +290,12 @@ public class NotificationEntryManager implements NotificationEntry entry = mPendingNotifications.get(key); entry.abortTask(); mPendingNotifications.remove(key); - mNotifLog.log(NotifEvent.INFLATION_ABORTED, entry, "PendingNotification aborted" - + " reason=" + reason); + mLogger.logInflationAborted(key, "pending", reason); } NotificationEntry addedEntry = getActiveNotificationUnfiltered(key); if (addedEntry != null) { addedEntry.abortTask(); - mNotifLog.log(NotifEvent.INFLATION_ABORTED, addedEntry.getKey() + " " + reason); + mLogger.logInflationAborted(key, "active", reason); } } @@ -328,9 +326,9 @@ public class NotificationEntryManager implements // the list, otherwise we might get leaks. if (!entry.isRowRemoved()) { boolean isNew = getActiveNotificationUnfiltered(entry.getKey()) == null; + mLogger.logNotifInflated(entry.getKey(), isNew); if (isNew) { for (NotificationEntryListener listener : mNotificationEntryListeners) { - mNotifLog.log(NotifEvent.INFLATED, entry); listener.onEntryInflated(entry); } addActiveNotification(entry); @@ -340,7 +338,6 @@ public class NotificationEntryManager implements } } else { for (NotificationEntryListener listener : mNotificationEntryListeners) { - mNotifLog.log(NotifEvent.INFLATED, entry); listener.onEntryReinflated(entry); } } @@ -422,7 +419,7 @@ public class NotificationEntryManager implements for (NotificationRemoveInterceptor interceptor : mRemoveInterceptors) { if (interceptor.onNotificationRemoveRequested(key, entry, reason)) { // Remove intercepted; log and skip - mNotifLog.log(NotifEvent.REMOVE_INTERCEPTED); + mLogger.logRemovalIntercepted(key); return; } } @@ -437,10 +434,7 @@ public class NotificationEntryManager implements if (extender.shouldExtendLifetimeForPendingNotification(pendingEntry)) { extendLifetime(pendingEntry, extender); lifetimeExtended = true; - mNotifLog.log( - NotifEvent.LIFETIME_EXTENDED, - pendingEntry.getSbn(), - "pendingEntry extendedBy=" + extender.toString()); + mLogger.logLifetimeExtended(key, extender.getClass().getName(), "pending"); } } } @@ -460,10 +454,7 @@ public class NotificationEntryManager implements mLatestRankingMap = ranking; extendLifetime(entry, extender); lifetimeExtended = true; - mNotifLog.log( - NotifEvent.LIFETIME_EXTENDED, - entry.getSbn(), - "entry extendedBy=" + extender.toString()); + mLogger.logLifetimeExtended(key, extender.getClass().getName(), "active"); break; } } @@ -486,8 +477,7 @@ public class NotificationEntryManager implements mLeakDetector.trackGarbage(entry); removedByUser |= entryDismissed; - mNotifLog.log(NotifEvent.NOTIF_REMOVED, entry.getSbn(), - "removedByUser=" + removedByUser); + mLogger.logNotifRemoved(entry.getKey(), removedByUser); for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onEntryRemoved(entry, visibility, removedByUser); } @@ -576,7 +566,7 @@ public class NotificationEntryManager implements abortExistingInflation(key, "addNotification"); mPendingNotifications.put(key, entry); - mNotifLog.log(NotifEvent.NOTIF_ADDED, entry); + mLogger.logNotifAdded(entry.getKey()); for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPendingEntryAdded(entry); } @@ -613,7 +603,7 @@ public class NotificationEntryManager implements entry.setSbn(notification); mGroupManager.onEntryUpdated(entry, oldSbn); - mNotifLog.log(NotifEvent.NOTIF_UPDATED, entry); + mLogger.logNotifUpdated(entry.getKey()); for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPreEntryUpdated(entry); } @@ -808,7 +798,7 @@ public class NotificationEntryManager implements //TODO: Get rid of this in favor of NotificationUpdateHandler#updateNotificationRanking /** * @param rankingMap the {@link RankingMap} to apply to the current notification list - * @param reason the reason for calling this method, for {@link NotifLog} + * @param reason the reason for calling this method, which will be logged */ public void updateRanking(RankingMap rankingMap, String reason) { updateRankingAndSort(rankingMap, reason); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt new file mode 100644 index 000000000000..4382ab50390a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 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 com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +/** Logger for [NotificationEntryManager]. */ +class NotificationEntryManagerLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logNotifAdded(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "NOTIF ADDED $str1" + }) + } + + fun logNotifUpdated(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "NOTIF UPDATED $str1" + }) + } + + fun logInflationAborted(key: String, status: String, reason: String) { + buffer.log(TAG, DEBUG, { + str1 = key + str2 = status + str3 = reason + }, { + "NOTIF INFLATION ABORTED $str1 notifStatus=$str2 reason=$str3" + }) + } + + fun logNotifInflated(key: String, isNew: Boolean) { + buffer.log(TAG, DEBUG, { + str1 = key + bool1 = isNew + }, { + "NOTIF INFLATED $str1 isNew=$bool1}" + }) + } + + fun logRemovalIntercepted(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "NOTIF REMOVE INTERCEPTED for $str1" + }) + } + + fun logLifetimeExtended(key: String, extenderName: String, status: String) { + buffer.log(TAG, INFO, { + str1 = key + str2 = extenderName + str3 = status + }, { + "NOTIF LIFETIME EXTENDED $str1 extender=$str2 status=$str3" + }) + } + + fun logNotifRemoved(key: String, removedByUser: Boolean) { + buffer.log(TAG, INFO, { + str1 = key + bool1 = removedByUser + }, { + "NOTIF REMOVED $str1 removedByUser=$bool1" + }) + } + + fun logFilterAndSort(reason: String) { + buffer.log(TAG, INFO, { + str1 = reason + }, { + "FILTER AND SORT reason=$str1" + }) + } +} + +private const val TAG = "NotificationEntryMgr"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java index 7fe229c26f3a..3fa1954a7fcc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java @@ -123,6 +123,16 @@ public class ListDumper { .append(" "); } + if (!notifEntry.mDismissInterceptors.isEmpty()) { + String[] interceptorsNames = new String[notifEntry.mDismissInterceptors.size()]; + for (int i = 0; i < interceptorsNames.length; i++) { + interceptorsNames[i] = notifEntry.mDismissInterceptors.get(i).getName(); + } + rksb.append("dismissInterceptors=") + .append(Arrays.toString(interceptorsNames)) + .append(" "); + } + if (notifEntry.mExcludingFilter != null) { rksb.append("filter=") .append(notifEntry.mExcludingFilter) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 3b2fe9441c32..38d8d979a4da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -63,6 +63,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Co import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.util.Assert; @@ -116,6 +117,7 @@ public class NotifCollection implements Dumpable { @Nullable private CollectionReadyForBuildListener mBuildListener; private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>(); private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>(); + private final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>(); private boolean mAttached = false; private boolean mAmDispatchingToOtherCode; @@ -176,10 +178,21 @@ public class NotifCollection implements Dumpable { extender.setCallback(this::onEndLifetimeExtension); } + /** @see NotifPipeline#addNotificationDismissInterceptor(NotifDismissInterceptor) */ + void addNotificationDismissInterceptor(NotifDismissInterceptor interceptor) { + Assert.isMainThread(); + checkForReentrantCall(); + if (mDismissInterceptors.contains(interceptor)) { + throw new IllegalArgumentException("Interceptor " + interceptor + " already added."); + } + mDismissInterceptors.add(interceptor); + interceptor.setCallback(this::onEndDismissInterception); + } + /** * Dismiss a notification on behalf of the user. */ - void dismissNotification(NotificationEntry entry, @NonNull DismissedByUserStats stats) { + public void dismissNotification(NotificationEntry entry, @NonNull DismissedByUserStats stats) { Assert.isMainThread(); requireNonNull(stats); checkForReentrantCall(); @@ -192,6 +205,12 @@ public class NotifCollection implements Dumpable { return; } + updateDismissInterceptors(entry); + if (isDismissIntercepted(entry)) { + mLogger.logNotifDismissedIntercepted(entry.getKey()); + return; + } + // Optimistically mark the notification as dismissed -- we'll wait for the signal from // system server before removing it from our notification set. entry.setDismissState(DISMISSED); @@ -236,7 +255,6 @@ public class NotifCollection implements Dumpable { for (NotificationEntry canceledEntry : canceledEntries) { tryRemoveNotification(canceledEntry); } - rebuildList(); } @@ -307,11 +325,11 @@ public class NotifCollection implements Dumpable { // Update to an existing entry mLogger.logNotifUpdated(sbn.getKey()); + // Notification is updated so it is essentially re-added and thus alive again, so we + // can reset its state. cancelLocalDismissal(entry); - - // Notification is updated so it is essentially re-added and thus alive again. Don't - // need to keep its lifetime extended. cancelLifetimeExtension(entry); + cancelDismissInterception(entry); entry.mCancellationReason = REASON_NOT_CANCELED; entry.setSbn(sbn); @@ -348,6 +366,7 @@ public class NotifCollection implements Dumpable { if (!isLifetimeExtended(entry)) { mNotificationSet.remove(entry.getKey()); + cancelDismissInterception(entry); dispatchOnEntryRemoved(entry, entry.mCancellationReason); dispatchOnEntryCleanUp(entry); return true; @@ -436,6 +455,17 @@ public class NotifCollection implements Dumpable { mAmDispatchingToOtherCode = false; } + private void updateDismissInterceptors(@NonNull NotificationEntry entry) { + entry.mDismissInterceptors.clear(); + mAmDispatchingToOtherCode = true; + for (NotifDismissInterceptor interceptor : mDismissInterceptors) { + if (interceptor.shouldInterceptDismissal(entry)) { + entry.mDismissInterceptors.add(interceptor); + } + } + mAmDispatchingToOtherCode = false; + } + private void cancelLocalDismissal(NotificationEntry entry) { if (isDismissedByUser(entry)) { entry.setDismissState(NOT_DISMISSED); @@ -450,6 +480,42 @@ public class NotifCollection implements Dumpable { } } + private void onEndDismissInterception( + NotifDismissInterceptor interceptor, + NotificationEntry entry, + @NonNull DismissedByUserStats stats) { + Assert.isMainThread(); + if (!mAttached) { + return; + } + checkForReentrantCall(); + + if (!entry.mDismissInterceptors.remove(interceptor)) { + throw new IllegalStateException( + String.format( + "Cannot end dismiss interceptor for interceptor \"%s\" (%s)", + interceptor.getName(), + interceptor)); + } + + if (!isDismissIntercepted(entry)) { + dismissNotification(entry, stats); + } + } + + private void cancelDismissInterception(NotificationEntry entry) { + mAmDispatchingToOtherCode = true; + for (NotifDismissInterceptor interceptor : entry.mDismissInterceptors) { + interceptor.cancelDismissInterception(entry); + } + mAmDispatchingToOtherCode = false; + entry.mDismissInterceptors.clear(); + } + + private boolean isDismissIntercepted(NotificationEntry entry) { + return entry.mDismissInterceptors.size() > 0; + } + private void checkForReentrantCall() { if (mAmDispatchingToOtherCode) { throw new IllegalStateException("Reentrant call detected"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java index 5767ad93014e..d4d2369ba822 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java @@ -25,6 +25,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import java.util.Collection; @@ -97,13 +98,21 @@ public class NotifPipeline implements CommonNotifCollection { /** * Registers a lifetime extender. Lifetime extenders can cause notifications that have been - * dismissed or retracted to be temporarily retained in the collection. + * dismissed or retracted by system server to be temporarily retained in the collection. */ public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) { mNotifCollection.addNotificationLifetimeExtender(extender); } /** + * Registers a dismiss interceptor. Dismiss interceptors can cause notifications that have been + * dismissed by the user to be retained (won't send a dismissal to system server). + */ + public void addNotificationDismissInterceptor(NotifDismissInterceptor interceptor) { + mNotifCollection.addNotificationDismissInterceptor(interceptor); + } + + /** * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters * are called on each notification in the order that they were registered. If any filter * returns true, the notification is removed from the pipeline (and no other filters are diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 5dbf47e29407..41c1b7b5fae8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -66,6 +66,7 @@ import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGuts; @@ -104,6 +105,9 @@ public final class NotificationEntry extends ListEntry { /** List of lifetime extenders that are extending the lifetime of this notification. */ final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>(); + /** List of dismiss interceptors that are intercepting the dismissal of this notification. */ + final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>(); + /** If this notification was filtered out, then the filter that did the filtering. */ @Nullable NotifFilter mExcludingFilter; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt index 1eeeab3e93cb..2981252f148c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt @@ -22,11 +22,10 @@ import android.service.notification.NotificationListenerService.Ranking import android.service.notification.NotificationListenerService.RankingMap import android.service.notification.StatusBarNotification import com.android.systemui.statusbar.NotificationMediaManager +import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider -import com.android.systemui.statusbar.notification.logging.NotifEvent -import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE @@ -53,7 +52,7 @@ open class NotificationRankingManager @Inject constructor( private val groupManager: NotificationGroupManager, private val headsUpManager: HeadsUpManager, private val notifFilter: NotificationFilter, - private val notifLog: NotifLog, + private val logger: NotificationEntryManagerLogger, sectionsFeatureManager: NotificationSectionsFeatureManager, private val peopleNotificationIdentifier: PeopleNotificationIdentifier, private val highPriorityProvider: HighPriorityProvider @@ -134,7 +133,7 @@ open class NotificationRankingManager @Inject constructor( entries: Sequence<NotificationEntry>, reason: String ): Sequence<NotificationEntry> { - notifLog.log(NotifEvent.FILTER_AND_SORT, reason) + logger.logFilterAndSort(reason) return entries.filter { !notifFilter.shouldFilterOut(it) } .sortedWith(rankingComparator) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java new file mode 100644 index 000000000000..116c70c4f1cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2020 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.collection.coordinator; + +import static android.service.notification.NotificationStats.DISMISSAL_OTHER; +import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_UNKNOWN; + +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.bubbles.BubbleController; +import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; + +import java.util.HashSet; +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Coordinates hiding, intercepting (the dismissal), and deletion of bubbled notifications. + * + * The typical "start state" for a bubbled notification is when a bubble-able notification is + * posted. It is visible as a bubble AND as a notification in the shade. From here, we can get + * into a few hidden-from-shade states described below: + * + * Start State -> Hidden from shade + * User expands the bubble so we hide its notification from the shade. + * OR + * User dismisses a group summary with a bubbled child. All bubbled children are now hidden from + * the shade. And the group summary's dismissal is intercepted + hidden from the shade (see below). + * + * Start State -> Dismissal intercepted + hidden from shade + * User dismisses the notification from the shade. We now hide the notification from the shade + * and intercept its dismissal (the removal signal is never sent to system server). We + * keep the notification alive in system server so that {@link BubbleController} can still + * respond to app-cancellations (ie: remove the bubble if the app cancels the notification). + * + */ +@Singleton +public class BubbleCoordinator implements Coordinator { + private static final String TAG = "BubbleCoordinator"; + + private final BubbleController mBubbleController; + private final NotifCollection mNotifCollection; + private final Set<String> mInterceptedDismissalEntries = new HashSet<>(); + private NotifPipeline mNotifPipeline; + private NotifDismissInterceptor.OnEndDismissInterception mOnEndDismissInterception; + + @Inject + public BubbleCoordinator( + BubbleController bubbleController, + NotifCollection notifCollection) { + mBubbleController = bubbleController; + mNotifCollection = notifCollection; + } + + @Override + public void attach(NotifPipeline pipeline) { + mNotifPipeline = pipeline; + mNotifPipeline.addNotificationDismissInterceptor(mDismissInterceptor); + mNotifPipeline.addPreRenderFilter(mNotifFilter); + mBubbleController.addNotifCallback(mNotifCallback); + } + + private final NotifFilter mNotifFilter = new NotifFilter(TAG) { + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + return mBubbleController.isBubbleNotificationSuppressedFromShade(entry); + } + }; + + private final NotifDismissInterceptor mDismissInterceptor = new NotifDismissInterceptor() { + @Override + public String getName() { + return TAG; + } + + @Override + public void setCallback(OnEndDismissInterception callback) { + mOnEndDismissInterception = callback; + } + + @Override + public boolean shouldInterceptDismissal(NotificationEntry entry) { + // TODO: b/149041810 add support for intercepting app-cancelled bubble notifications + // for experimental bubbles + if (mBubbleController.handleDismissalInterception(entry)) { + mInterceptedDismissalEntries.add(entry.getKey()); + return true; + } else { + mInterceptedDismissalEntries.remove(entry.getKey()); + return false; + } + } + + @Override + public void cancelDismissInterception(NotificationEntry entry) { + mInterceptedDismissalEntries.remove(entry.getKey()); + } + }; + + private final BubbleController.NotifCallback mNotifCallback = + new BubbleController.NotifCallback() { + @Override + public void removeNotification(NotificationEntry entry, int reason) { + if (isInterceptingDismissal(entry)) { + mInterceptedDismissalEntries.remove(entry.getKey()); + mOnEndDismissInterception.onEndDismissInterception(mDismissInterceptor, entry, + createDismissedByUserStats(entry)); + } else if (mNotifPipeline.getActiveNotifs().contains(entry)) { + // Bubbles are hiding the notifications from the shade, but the bubble was + // deleted; therefore, the notification should be cancelled as if it were a user + // dismissal (this won't re-enter handleInterceptDimissal because Bubbles + // will have already marked it as no longer a bubble) + mNotifCollection.dismissNotification(entry, createDismissedByUserStats(entry)); + } + } + + @Override + public void invalidateNotifications(String reason) { + mNotifFilter.invalidateList(); + } + + @Override + public void maybeCancelSummary(NotificationEntry entry) { + // no-op + } + }; + + private boolean isInterceptingDismissal(NotificationEntry entry) { + return mInterceptedDismissalEntries.contains(entry.getKey()); + } + + private DismissedByUserStats createDismissedByUserStats(NotificationEntry entry) { + return new DismissedByUserStats( + DISMISSAL_OTHER, + DISMISS_SENTIMENT_UNKNOWN, + NotificationVisibility.obtain(entry.getKey(), + entry.getRanking().getRank(), + mNotifPipeline.getActiveNotifs().size(), + true, // was visible as a bubble + NotificationLogger.getNotificationLocation(entry)) + ); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java index 0a1e09f4c99d..7a9547c573bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java @@ -53,6 +53,7 @@ public class NotifCoordinators implements Dumpable { RankingCoordinator rankingCoordinator, ForegroundCoordinator foregroundCoordinator, DeviceProvisionedCoordinator deviceProvisionedCoordinator, + BubbleCoordinator bubbleCoordinator, PreparationCoordinator preparationCoordinator) { dumpController.registerDumpable(TAG, this); @@ -61,6 +62,7 @@ public class NotifCoordinators implements Dumpable { mCoordinators.add(rankingCoordinator); mCoordinators.add(foregroundCoordinator); mCoordinators.add(deviceProvisionedCoordinator); + mCoordinators.add(bubbleCoordinator); if (featureFlags.isNewNotifPipelineRenderingEnabled()) { mCoordinators.add(preparationCoordinator); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 41314b86695a..1e5946a85cfa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -22,8 +22,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.logging.NotifEvent; -import com.android.systemui.statusbar.notification.logging.NotifLog; import java.util.ArrayList; import java.util.List; @@ -42,13 +40,15 @@ import javax.inject.Singleton; public class PreparationCoordinator implements Coordinator { private static final String TAG = "PreparationCoordinator"; - private final NotifLog mNotifLog; + private final PreparationCoordinatorLogger mLogger; private final NotifInflater mNotifInflater; private final List<NotificationEntry> mPendingNotifications = new ArrayList<>(); @Inject - public PreparationCoordinator(NotifLog notifLog, NotifInflaterImpl notifInflater) { - mNotifLog = notifLog; + public PreparationCoordinator( + PreparationCoordinatorLogger logger, + NotifInflaterImpl notifInflater) { + mLogger = logger; mNotifInflater = notifInflater; mNotifInflater.setInflationCallback(mInflationCallback); } @@ -106,7 +106,7 @@ public class PreparationCoordinator implements Coordinator { new NotifInflater.InflationCallback() { @Override public void onInflationFinished(NotificationEntry entry) { - mNotifLog.log(NotifEvent.INFLATED, entry); + mLogger.logNotifInflated(entry.getKey()); mPendingNotifications.remove(entry); mNotifInflatingFilter.invalidateList(); } @@ -123,7 +123,7 @@ public class PreparationCoordinator implements Coordinator { } private void abortInflation(NotificationEntry entry, String reason) { - mNotifLog.log(NotifEvent.INFLATION_ABORTED, reason); + mLogger.logInflationAborted(entry.getKey(), reason); entry.abortTask(); mPendingNotifications.remove(entry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt new file mode 100644 index 000000000000..75e7bc9b79a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 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.collection.coordinator + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +class PreparationCoordinatorLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logNotifInflated(key: String) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = key + }, { + "NOTIF INFLATED $str1" + }) + } + + fun logInflationAborted(key: String, reason: String) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = key + str2 = reason + }, { + "NOTIF INFLATION ABORTED $str1 reason=$str2" + }) + } +} + +private const val TAG = "PreparationCoordinator"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 59d82a1bc5cf..ecf62db4680b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -295,6 +295,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { } //TODO: Replace this API with RowContentBindParams directly row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry)); + params.rebindAllContentViews(); mRowContentBindStage.requestRebind(entry, en -> { row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight); row.setUsesIncreasedHeadsUpHeight(useIncreasedHeadsUp); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index 14e15031056f..dc7a50d621a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -69,6 +69,14 @@ class NotifCollectionLogger @Inject constructor( }) } + fun logNotifDismissedIntercepted(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "DISMISS INTERCEPTED $str1" + }) + } + fun logRankingMissing(key: String, rankingMap: RankingMap) { buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" }) buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifDismissInterceptor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifDismissInterceptor.java new file mode 100644 index 000000000000..3354ad1daf20 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifDismissInterceptor.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 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.collection.notifcollection; + +import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +/** + * A way for coordinators to temporarily intercept a user-dismissed notification before a message + * is sent to system server to officially remove this notification. + * See {@link NotifCollection#addNotificationDismissInterceptor(NotifDismissInterceptor)}. + */ +public interface NotifDismissInterceptor { + /** Name to associate with this interceptor (for the purposes of debugging) */ + String getName(); + + /** + * Called on the interceptor immediately after it has been registered. The interceptor should + * hang on to this callback and execute it whenever it no longer needs to intercept the + * dismissal of the notification. + */ + void setCallback(OnEndDismissInterception callback); + + /** + * Called by the NotifCollection whenever a notification has been dismissed (by the user). + * If the interceptor returns true, it is considered to be intercepting the notification. + * Intercepted notifications will not be sent to system server for removal until it is no + * longer being intercepted. However, the notification can still be cancelled by the app. + * This method is called on all interceptors even if earlier ones return true. + */ + boolean shouldInterceptDismissal(NotificationEntry entry); + + + /** + * Called by the NotifCollection to inform a DismissInterceptor that its interception of a notif + * is no longer valid (usually because the notif has been removed by means other than the + * user dismissing the notification from the shade, or the notification has been updated). The + * interceptor should clean up any references it has to the notif in question. + */ + void cancelDismissInterception(NotificationEntry entry); + + /** + * Callback for notifying the NotifCollection that it no longer is intercepting the dismissal. + * If the end of this dismiss interception triggers a dismiss (ie: no other + * NotifDismissInterceptors are intercepting the entry), NotifCollection will use stats + * in the message sent to system server for the notification's dismissal. + */ + interface OnEndDismissInterception { + void onEndDismissInterception( + NotifDismissInterceptor interceptor, + NotificationEntry entry, + DismissedByUserStats stats); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java deleted file mode 100644 index 9adceb78c249..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.logging; - -import android.annotation.IntDef; -import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; - -import com.android.systemui.log.RichEvent; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; -import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * An event related to notifications. {@link NotifLog} stores and prints these events for debugging - * and triaging purposes. We do not store a copy of the status bar notification nor ranking - * here to mitigate memory usage. - */ -public class NotifEvent extends RichEvent { - /** - * Initializes a rich event that includes an event type that matches with an index in the array - * getEventLabels(). - */ - public NotifEvent init(@EventType int type, StatusBarNotification sbn, - NotificationListenerService.Ranking ranking, String reason) { - StringBuilder extraInfo = new StringBuilder(reason); - if (sbn != null) { - extraInfo.append(" " + sbn.getKey()); - } - - if (ranking != null) { - extraInfo.append(" Ranking="); - extraInfo.append(ranking.getRank()); - } - super.init(INFO, type, extraInfo.toString()); - return this; - } - - /** - * Event labels for ListBuilderEvents - * Index corresponds to an # in {@link EventType} - */ - @Override - public String[] getEventLabels() { - assert (TOTAL_EVENT_LABELS - == (TOTAL_NEM_EVENT_TYPES - + TOTAL_LIST_BUILDER_EVENT_TYPES - + TOTAL_COALESCER_EVENT_TYPES)); - return EVENT_LABELS; - } - - /** - * @return if this event occurred in {@link ShadeListBuilder} - */ - static boolean isListBuilderEvent(@EventType int type) { - return isBetweenInclusive(type, 0, TOTAL_LIST_BUILDER_EVENT_TYPES); - } - - /** - * @return if this event occurred in {@link NotificationEntryManager} - */ - static boolean isNemEvent(@EventType int type) { - return isBetweenInclusive(type, TOTAL_LIST_BUILDER_EVENT_TYPES, - TOTAL_LIST_BUILDER_EVENT_TYPES + TOTAL_NEM_EVENT_TYPES); - } - - private static boolean isBetweenInclusive(int x, int a, int b) { - return x >= a && x <= b; - } - - @IntDef({ - // NotifListBuilder events: - WARN, - ON_BUILD_LIST, - START_BUILD_LIST, - DISPATCH_FINAL_LIST, - LIST_BUILD_COMPLETE, - PRE_GROUP_FILTER_INVALIDATED, - PROMOTER_INVALIDATED, - SECTION_INVALIDATED, - COMPARATOR_INVALIDATED, - PARENT_CHANGED, - FILTER_CHANGED, - PROMOTER_CHANGED, - PRE_RENDER_FILTER_INVALIDATED, - - // NotificationEntryManager events: - NOTIF_ADDED, - NOTIF_REMOVED, - NOTIF_UPDATED, - FILTER, - SORT, - FILTER_AND_SORT, - NOTIF_VISIBILITY_CHANGED, - LIFETIME_EXTENDED, - REMOVE_INTERCEPTED, - INFLATION_ABORTED, - INFLATED, - - // GroupCoalescer - COALESCED_EVENT, - EARLY_BATCH_EMIT, - EMIT_EVENT_BATCH - }) - @Retention(RetentionPolicy.SOURCE) - public @interface EventType {} - - private static final String[] EVENT_LABELS = - new String[]{ - // NotifListBuilder labels: - "Warning", - "OnBuildList", - "StartBuildList", - "DispatchFinalList", - "ListBuildComplete", - "FilterInvalidated", - "PromoterInvalidated", - "SectionInvalidated", - "ComparatorInvalidated", - "ParentChanged", - "FilterChanged", - "PromoterChanged", - "FinalFilterInvalidated", - "SectionerChanged", - - // NEM event labels: - "NotifAdded", - "NotifRemoved", - "NotifUpdated", - "Filter", - "Sort", - "FilterAndSort", - "NotifVisibilityChanged", - "LifetimeExtended", - "RemoveIntercepted", - "InflationAborted", - "Inflated", - - // GroupCoalescer labels: - "CoalescedEvent", - "EarlyBatchEmit", - "EmitEventBatch", - "BatchMaxTimeout" - }; - - private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length; - - /** - * Events related to {@link ShadeListBuilder} - */ - public static final int WARN = 0; - public static final int ON_BUILD_LIST = 1; - public static final int START_BUILD_LIST = 2; - public static final int DISPATCH_FINAL_LIST = 3; - public static final int LIST_BUILD_COMPLETE = 4; - public static final int PRE_GROUP_FILTER_INVALIDATED = 5; - public static final int PROMOTER_INVALIDATED = 6; - public static final int SECTION_INVALIDATED = 7; - public static final int COMPARATOR_INVALIDATED = 8; - public static final int PARENT_CHANGED = 9; - public static final int FILTER_CHANGED = 10; - public static final int PROMOTER_CHANGED = 11; - public static final int PRE_RENDER_FILTER_INVALIDATED = 12; - public static final int SECTION_CHANGED = 13; - private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 14; - - /** - * Events related to {@link NotificationEntryManager} - */ - private static final int NEM_EVENT_START_INDEX = TOTAL_LIST_BUILDER_EVENT_TYPES; - public static final int NOTIF_ADDED = NEM_EVENT_START_INDEX; - public static final int NOTIF_REMOVED = NEM_EVENT_START_INDEX + 1; - public static final int NOTIF_UPDATED = NEM_EVENT_START_INDEX + 2; - public static final int FILTER = NEM_EVENT_START_INDEX + 3; - public static final int SORT = NEM_EVENT_START_INDEX + 4; - public static final int FILTER_AND_SORT = NEM_EVENT_START_INDEX + 5; - public static final int NOTIF_VISIBILITY_CHANGED = NEM_EVENT_START_INDEX + 6; - public static final int LIFETIME_EXTENDED = NEM_EVENT_START_INDEX + 7; - // unable to remove notif - removal intercepted by {@link NotificationRemoveInterceptor} - public static final int REMOVE_INTERCEPTED = NEM_EVENT_START_INDEX + 8; - public static final int INFLATION_ABORTED = NEM_EVENT_START_INDEX + 9; - public static final int INFLATED = NEM_EVENT_START_INDEX + 10; - private static final int TOTAL_NEM_EVENT_TYPES = 11; - - /** - * Events related to {@link GroupCoalescer} - */ - private static final int COALESCER_EVENT_START_INDEX = NEM_EVENT_START_INDEX - + TOTAL_NEM_EVENT_TYPES; - public static final int COALESCED_EVENT = COALESCER_EVENT_START_INDEX; - public static final int EARLY_BATCH_EMIT = COALESCER_EVENT_START_INDEX + 1; - public static final int EMIT_EVENT_BATCH = COALESCER_EVENT_START_INDEX + 2; - public static final int BATCH_MAX_TIMEOUT = COALESCER_EVENT_START_INDEX + 3; - private static final int TOTAL_COALESCER_EVENT_TYPES = 3; -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java deleted file mode 100644 index 299d628d0fd2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.logging; - -import android.os.SystemProperties; -import android.service.notification.NotificationListenerService.Ranking; -import android.service.notification.StatusBarNotification; - -import com.android.systemui.DumpController; -import com.android.systemui.log.SysuiLog; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; - -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * Logs systemui notification events for debugging and triaging purposes. Logs are dumped in - * bugreports or on demand: - * adb shell dumpsys activity service com.android.systemui/.SystemUIService \ - * dependency DumpController NotifLog - */ -@Singleton -public class NotifLog extends SysuiLog<NotifEvent> { - private static final String TAG = "NotifLog"; - private static final boolean SHOW_NEM_LOGS = - SystemProperties.getBoolean("persist.sysui.log.notif.nem", true); - private static final boolean SHOW_LIST_BUILDER_LOGS = - SystemProperties.getBoolean("persist.sysui.log.notif.listbuilder", true); - - private static final int MAX_DOZE_DEBUG_LOGS = 400; - private static final int MAX_DOZE_LOGS = 50; - - private NotifEvent mRecycledEvent; - - @Inject - public NotifLog(DumpController dumpController) { - super(dumpController, TAG, MAX_DOZE_DEBUG_LOGS, MAX_DOZE_LOGS); - } - - /** - * Logs a {@link NotifEvent} with a notification, ranking and message. - * Uses the last recycled event if available. - * @return true if successfully logged, else false - */ - public void log(@NotifEvent.EventType int eventType, - StatusBarNotification sbn, Ranking ranking, String msg) { - if (!mEnabled - || (NotifEvent.isListBuilderEvent(eventType) && !SHOW_LIST_BUILDER_LOGS) - || (NotifEvent.isNemEvent(eventType) && !SHOW_NEM_LOGS)) { - return; - } - - if (mRecycledEvent != null) { - mRecycledEvent = log(mRecycledEvent.init(eventType, sbn, ranking, msg)); - } else { - mRecycledEvent = log(new NotifEvent().init(eventType, sbn, ranking, msg)); - } - } - - /** - * Logs a {@link NotifEvent} with no extra information aside from the event type - */ - public void log(@NotifEvent.EventType int eventType) { - log(eventType, null, null, ""); - } - - /** - * Logs a {@link NotifEvent} with a message - */ - public void log(@NotifEvent.EventType int eventType, String msg) { - log(eventType, null, null, msg); - } - - /** - * Logs a {@link NotifEvent} with a entry - */ - public void log(@NotifEvent.EventType int eventType, NotificationEntry entry) { - log(eventType, entry.getSbn(), entry.getRanking(), ""); - } - - /** - * Logs a {@link NotifEvent} with a NotificationEntry and message - */ - public void log(@NotifEvent.EventType int eventType, NotificationEntry entry, String msg) { - log(eventType, entry.getSbn(), entry.getRanking(), msg); - } - - /** - * Logs a {@link NotifEvent} with a notification and message - */ - public void log(@NotifEvent.EventType int eventType, StatusBarNotification sbn, String msg) { - log(eventType, sbn, null, msg); - } - - /** - * Logs a {@link NotifEvent} with a ranking and message - */ - public void log(@NotifEvent.EventType int eventType, Ranking ranking, String msg) { - log(eventType, null, ranking, msg); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java index 8280a63dedd9..5170d0b85b17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java @@ -123,6 +123,14 @@ public final class RowContentBindParams { } /** + * Request that all content views be rebound. This may happen if, for example, the underlying + * layout has changed. + */ + public void rebindAllContentViews() { + mDirtyContentViews = mContentViews; + } + + /** * Clears all dirty content views so that they no longer need to be rebound. */ void clearDirtyContentViews() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index db692c8a8c89..44a320419309 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -422,9 +422,7 @@ public class EdgeBackGestureHandler implements DisplayListener, } private void updateDisplaySize() { - mContext.getSystemService(DisplayManager.class) - .getDisplay(mDisplayId) - .getRealSize(mDisplaySize); + mContext.getDisplay().getRealSize(mDisplaySize); if (mEdgeBackPlugin != null) { mEdgeBackPlugin.setDisplaySize(mDisplaySize); } 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 11f70796748c..c68d9942419b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -2466,10 +2466,6 @@ public class StatusBar extends SystemUI implements DemoMode, pw.println(" mHeadsUpManager: null"); } - if (mBubbleController != null) { - mBubbleController.dump(fd, pw, args); - } - if (mLightBarController != null) { mLightBarController.dump(fd, pw, args); } diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt index cfd77be9303d..f4157f21e158 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt @@ -901,6 +901,23 @@ class PhysicsAnimator<T> private constructor (val target: T) { verboseLogging = debug } + /** + * Estimates the end value of a fling that starts at the given value using the provided + * start velocity and fling configuration. + * + * This is only an estimate. Fling animations use a timing-based physics simulation that is + * non-deterministic, so this exact value may not be reached. + */ + @JvmStatic + fun estimateFlingEndValue( + startValue: Float, + startVelocity: Float, + flingConfig: FlingConfig + ): Float { + val distance = startVelocity / (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER) + return Math.min(flingConfig.max, Math.max(flingConfig.min, startValue + distance)) + } + @JvmStatic fun getReadablePropertyName(property: FloatPropertyCompat<*>): String { return when (property) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java index 1954b3936376..0e9a245d5be6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java @@ -39,7 +39,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.ViewUtils; -import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.SurfaceView; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -54,7 +54,6 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.Spy; @RunWithLooper @RunWith(AndroidTestingRunner.class) @@ -77,8 +76,8 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { private KeyguardSecurityCallback mKeyguardCallback; @Mock private KeyguardUpdateMonitor mUpdateMonitor; - @Spy - private StubTransaction mTransaction; + @Mock + private SurfaceControlViewHost.SurfacePackage mSurfacePackage; @Before public void setUp() { @@ -97,21 +96,20 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient); mTestController = new AdminSecondaryLockScreenController( - mContext, mParent, mUpdateMonitor, mKeyguardCallback, mHandler, mTransaction); + mContext, mParent, mUpdateMonitor, mKeyguardCallback, mHandler); } @Test public void testShow() throws Exception { doAnswer(invocation -> { IKeyguardCallback callback = (IKeyguardCallback) invocation.getArguments()[1]; - callback.onSurfaceControlCreated(new SurfaceControl()); + callback.onRemoteContentReady(mSurfacePackage); return null; }).when(mKeyguardClient).onSurfaceReady(any(), any(IKeyguardCallback.class)); mTestController.show(mServiceIntent); verifySurfaceReady(); - verify(mTransaction).reparent(any(), any()); assertThat(mContext.isBound(mComponentName)).isTrue(); } @@ -133,7 +131,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { // Show the view first, then hide. doAnswer(invocation -> { IKeyguardCallback callback = (IKeyguardCallback) invocation.getArguments()[1]; - callback.onSurfaceControlCreated(new SurfaceControl()); + callback.onRemoteContentReady(mSurfacePackage); return null; }).when(mKeyguardClient).onSurfaceReady(any(), any(IKeyguardCallback.class)); @@ -189,19 +187,4 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID); assertThat(mContext.isBound(mComponentName)).isFalse(); } - - /** - * Stubbed {@link SurfaceControl.Transaction} class that can be used when unit testing to - * avoid calls to native code. - */ - private class StubTransaction extends SurfaceControl.Transaction { - @Override - public void apply() { - } - - @Override - public SurfaceControl.Transaction reparent(SurfaceControl sc, SurfaceControl newParent) { - return this; - } - } } 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 486aac894d9b..c6c7b87da544 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java @@ -178,6 +178,20 @@ public class AuthContainerViewTest extends SysuiTestCase { } @Test + public void testCredentialUI_disablesClickingOnBackground() { + // In the credential view, clicking on the background (to cancel authentication) is not + // valid. Thus, the listener should be null, and it should not be in the accessibility + // hierarchy. + initializeContainer(Authenticators.DEVICE_CREDENTIAL); + + mAuthContainer.onAttachedToWindowInternal(); + + verify(mAuthContainer.mBackgroundView).setOnClickListener(eq(null)); + verify(mAuthContainer.mBackgroundView).setImportantForAccessibility( + eq(View.IMPORTANT_FOR_ACCESSIBILITY_NO)); + } + + @Test public void testLayoutParams_hasSecureWindowFlag() { final IBinder windowToken = mock(IBinder.class); final WindowManager.LayoutParams layoutParams = diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index d7f0f50d66db..c3b55e2ec168 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -20,8 +20,7 @@ import static android.app.Notification.FLAG_BUBBLE; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; - -import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON; +import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; import static com.google.common.truth.Truth.assertThat; @@ -34,6 +33,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -55,10 +55,12 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; +import com.android.systemui.DumpController; import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoveInterceptor; @@ -68,6 +70,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -81,7 +84,6 @@ import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.InjectionInflationController; @@ -93,6 +95,13 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + +/** + * Tests the NotificationEntryManager setup with BubbleController. + * The {@link NotifPipeline} setup with BubbleController is tested in + * {@link NewNotifPipelineBubbleControllerTest}. + */ @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @@ -152,9 +161,13 @@ public class BubbleControllerTest extends SysuiTestCase { @Mock private ShadeController mShadeController; @Mock - private RemoteInputUriController mRemoteInputUriController; - @Mock private NotificationRowComponent mNotificationRowComponent; + @Mock + private NotifPipeline mNotifPipeline; + @Mock + private FeatureFlags mFeatureFlagsOldPipeline; + @Mock + private DumpController mDumpController; private SuperStatusBarViewFactory mSuperStatusBarViewFactory; private BubbleData mBubbleData; @@ -216,6 +229,7 @@ public class BubbleControllerTest extends SysuiTestCase { mock(HeadsUpManager.class), mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class)); mBubbleData = new BubbleData(mContext); + when(mFeatureFlagsOldPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(false); mBubbleController = new TestableBubbleController(mContext, mNotificationShadeWindowController, mStatusBarStateController, @@ -227,7 +241,9 @@ public class BubbleControllerTest extends SysuiTestCase { mLockscreenUserManager, mNotificationGroupManager, mNotificationEntryManager, - mRemoteInputUriController); + mNotifPipeline, + mFeatureFlagsOldPipeline, + mDumpController); mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); mBubbleController.setExpandListener(mBubbleExpandListener); @@ -265,7 +281,7 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleStateChangeListener).onHasBubblesChanged(true); mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); assertFalse(mNotificationShadeWindowController.getBubblesShowing()); assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); verify(mNotificationEntryManager, times(2)).updateNotifications(anyString()); @@ -286,12 +302,12 @@ public class BubbleControllerTest extends SysuiTestCase { // Now remove the bubble mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); // Since the notif is dismissed, once the bubble is removed, performRemoveNotification gets // called to really remove the notif verify(mNotificationEntryManager, times(1)).performRemoveNotification( - mRow.getEntry().getSbn(), UNDEFINED_DISMISS_REASON); + eq(mRow.getEntry().getSbn()), anyInt()); assertFalse(mBubbleController.hasBubbles()); } @@ -471,7 +487,7 @@ public class BubbleControllerTest extends SysuiTestCase { mRow2.getEntry())); // Dismiss currently expanded - mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(), + mBubbleController.removeBubble(stackView.getExpandedBubble().getEntry(), BubbleController.DISMISS_USER_GESTURE); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); @@ -480,7 +496,7 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); // Dismiss that one - mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(), + mBubbleController.removeBubble(stackView.getExpandedBubble().getEntry(), BubbleController.DISMISS_USER_GESTURE); // Make sure state changes and collapse happens @@ -608,7 +624,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mRow.getEntry()); - mBubbleController.removeBubble(mRow.getEntry().getKey(), BubbleController.DISMISS_AGED); + mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED); verify(mDeleteIntent, never()).send(); } @@ -616,7 +632,7 @@ public class BubbleControllerTest extends SysuiTestCase { public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mRow.getEntry()); mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); verify(mDeleteIntent, times(1)).send(); } @@ -653,11 +669,22 @@ public class BubbleControllerTest extends SysuiTestCase { // Cancels always remove so no need to intercept assertFalse(intercepted); + } + + @Test + public void testRemoveBubble_entryListenerRemove() { + mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + + // Removes the notification + mEntryListener.onEntryRemoved(mRow.getEntry(), null, false); assertFalse(mBubbleController.hasBubbles()); } @Test - public void removeBubble_fails_clearAll() { + public void removeBubble_clearAllIntercepted() { mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); @@ -673,14 +700,10 @@ public class BubbleControllerTest extends SysuiTestCase { // Should update show in shade state assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); - - verify(mNotificationEntryManager, never()).performRemoveNotification( - any(), anyInt()); - assertTrue(mBubbleController.hasBubbles()); } @Test - public void removeBubble_fails_userDismissNotif() { + public void removeBubble_userDismissNotifIntercepted() { mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); @@ -696,10 +719,6 @@ public class BubbleControllerTest extends SysuiTestCase { // Should update show in shade state assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); - - verify(mNotificationEntryManager, never()).performRemoveNotification( - any(), anyInt()); - assertTrue(mBubbleController.hasBubbles()); } @Test @@ -713,7 +732,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Dismiss the bubble mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); assertFalse(mBubbleController.hasBubbles()); // Dismiss the notification @@ -771,6 +790,74 @@ public class BubbleControllerTest extends SysuiTestCase { mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); } + @Test + public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade() throws Exception { + // GIVEN a group summary with a bubble child + ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); + ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); + groupSummary.addChildNotification(groupedBubble); + assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey())); + + // WHEN the summary is dismissed + mBubbleController.handleDismissalInterception(groupSummary.getEntry()); + + // THEN the summary and bubbled child are suppressed from the shade + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + groupedBubble.getEntry())); + assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey())); + } + + @Test + public void testAppRemovesSummary_removesAllBubbleChildren() throws Exception { + // GIVEN a group summary with a bubble child + ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); + ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); + groupSummary.addChildNotification(groupedBubble); + assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey())); + + // GIVEN the summary is dismissed + mBubbleController.handleDismissalInterception(groupSummary.getEntry()); + + // WHEN the summary is cancelled by the app + mEntryListener.onEntryRemoved(groupSummary.getEntry(), null, true); + + // THEN the summary and its children are removed from bubble data + assertFalse(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey())); + assertFalse(mBubbleData.isSummarySuppressed( + groupSummary.getEntry().getSbn().getGroupKey())); + } + + @Test + public void testSummaryDismissal_marksBubblesHiddenFromShadeAndDismissesNonBubbledChildren() + throws Exception { + // GIVEN a group summary with two (non-bubble) children and one bubble child + ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2); + ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); + groupSummary.addChildNotification(groupedBubble); + + // WHEN the summary is dismissed + mBubbleController.handleDismissalInterception(groupSummary.getEntry()); + + // THEN only the NON-bubble children are dismissed + List<ExpandableNotificationRow> childrenRows = groupSummary.getNotificationChildren(); + verify(mNotificationEntryManager, times(1)).performRemoveNotification( + childrenRows.get(0).getEntry().getSbn(), REASON_GROUP_SUMMARY_CANCELED); + verify(mNotificationEntryManager, times(1)).performRemoveNotification( + childrenRows.get(1).getEntry().getSbn(), REASON_GROUP_SUMMARY_CANCELED); + verify(mNotificationEntryManager, never()).performRemoveNotification( + eq(groupedBubble.getEntry().getSbn()), anyInt()); + + // THEN the bubble child is suppressed from the shade + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + groupedBubble.getEntry())); + + // THEN the summary is removed from GroupManager + verify(mNotificationGroupManager, times(1)).onEntryRemoved(groupSummary.getEntry()); + } + static class TestableBubbleController extends BubbleController { // Let's assume surfaces can be synchronized immediately. TestableBubbleController(Context context, @@ -784,12 +871,14 @@ public class BubbleControllerTest extends SysuiTestCase { NotificationLockscreenUserManager lockscreenUserManager, NotificationGroupManager groupManager, NotificationEntryManager entryManager, - RemoteInputUriController remoteInputUriController) { + NotifPipeline notifPipeline, + FeatureFlags featureFlags, + DumpController dumpController) { super(context, notificationShadeWindowController, statusBarStateController, shadeController, data, Runnable::run, configurationController, interruptionStateProvider, zenModeController, lockscreenUserManager, groupManager, entryManager, - remoteInputUriController); + notifPipeline, featureFlags, dumpController); setInflateSynchronously(true); } } @@ -806,7 +895,7 @@ public class BubbleControllerTest extends SysuiTestCase { } /** - * Sets the bubble metadata flags for this entry. These flags are normally set by + * Sets the bubble metadata flags for this entry. These ]flags are normally set by * NotificationManagerService when the notification is sent, however, these tests do not * go through that path so we set them explicitly when testing. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java new file mode 100644 index 000000000000..72405fc519fa --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -0,0 +1,842 @@ +/* + * Copyright (C) 2020 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.bubbles; + +import static android.app.Notification.FLAG_BUBBLE; +import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.IActivityManager; +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Context; +import android.content.res.Resources; +import android.hardware.face.FaceManager; +import android.service.notification.ZenModeConfig; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.WindowManager; + +import androidx.test.filters.SmallTest; + +import com.android.internal.colorextraction.ColorExtractor; +import com.android.systemui.DumpController; +import com.android.systemui.SystemUIFactory; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.statusbar.SuperStatusBarViewFactory; +import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationFilter; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; +import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.phone.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.util.InjectionInflationController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +/** + * Tests the NotifPipeline setup with BubbleController. + * The NotificationEntryManager setup with BubbleController is tested in + * {@link BubbleControllerTest}. + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { + @Mock + private NotificationEntryManager mNotificationEntryManager; + @Mock + private NotificationGroupManager mNotificationGroupManager; + @Mock + private BubbleController.NotifCallback mNotifCallback; + @Mock + private WindowManager mWindowManager; + @Mock + private IActivityManager mActivityManager; + @Mock + private DozeParameters mDozeParameters; + @Mock + private ConfigurationController mConfigurationController; + @Mock + private ZenModeController mZenModeController; + @Mock + private ZenModeConfig mZenModeConfig; + @Mock + private FaceManager mFaceManager; + @Mock + private NotificationLockscreenUserManager mLockscreenUserManager; + @Mock + private SysuiStatusBarStateController mStatusBarStateController; + @Mock + private KeyguardBypassController mKeyguardBypassController; + + @Captor + private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor; + + private TestableBubbleController mBubbleController; + private NotificationShadeWindowController mNotificationShadeWindowController; + private NotifCollectionListener mEntryListener; + + private NotificationTestHelper mNotificationTestHelper; + private ExpandableNotificationRow mRow; + private ExpandableNotificationRow mRow2; + private ExpandableNotificationRow mNonBubbleNotifRow; + + @Mock + private BubbleController.BubbleStateChangeListener mBubbleStateChangeListener; + @Mock + private BubbleController.BubbleExpandListener mBubbleExpandListener; + @Mock + private PendingIntent mDeleteIntent; + @Mock + private SysuiColorExtractor mColorExtractor; + @Mock + ColorExtractor.GradientColors mGradientColors; + @Mock + private Resources mResources; + @Mock + private ShadeController mShadeController; + @Mock + private NotificationRowComponent mNotificationRowComponent; + @Mock + private NotifPipeline mNotifPipeline; + @Mock + private FeatureFlags mFeatureFlagsNewPipeline; + @Mock + private DumpController mDumpController; + + private SuperStatusBarViewFactory mSuperStatusBarViewFactory; + private BubbleData mBubbleData; + + private TestableLooper mTestableLooper; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mTestableLooper = TestableLooper.get(this); + + mContext.addMockSystemService(FaceManager.class, mFaceManager); + when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); + + mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext, + new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()), + new NotificationRowComponent.Builder() { + @Override + public NotificationRowComponent.Builder activatableNotificationView( + ActivatableNotificationView view) { + return this; + } + + @Override + public NotificationRowComponent build() { + return mNotificationRowComponent; + } + }); + + // Bubbles get added to status bar window view + mNotificationShadeWindowController = new NotificationShadeWindowController(mContext, + mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, + mConfigurationController, mKeyguardBypassController, mColorExtractor, + mSuperStatusBarViewFactory); + mNotificationShadeWindowController.attach(); + + // Need notifications for bubbles + mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); + mRow = mNotificationTestHelper.createBubble(mDeleteIntent); + mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent); + mNonBubbleNotifRow = mNotificationTestHelper.createRow(); + + mZenModeConfig.suppressedVisualEffects = 0; + when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); + + TestableNotificationInterruptionStateProvider interruptionStateProvider = + new TestableNotificationInterruptionStateProvider(mContext, + mock(NotificationFilter.class), + mock(StatusBarStateController.class), + mock(BatteryController.class)); + interruptionStateProvider.setUpWithPresenter( + mock(NotificationPresenter.class), + mock(HeadsUpManager.class), + mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class)); + mBubbleData = new BubbleData(mContext); + when(mFeatureFlagsNewPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(true); + mBubbleController = new TestableBubbleController(mContext, + mNotificationShadeWindowController, + mStatusBarStateController, + mShadeController, + mBubbleData, + mConfigurationController, + interruptionStateProvider, + mZenModeController, + mLockscreenUserManager, + mNotificationGroupManager, + mNotificationEntryManager, + mNotifPipeline, + mFeatureFlagsNewPipeline, + mDumpController); + mBubbleController.addNotifCallback(mNotifCallback); + mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); + mBubbleController.setExpandListener(mBubbleExpandListener); + + // Get a reference to the BubbleController's entry listener + verify(mNotifPipeline, atLeastOnce()) + .addCollectionListener(mNotifListenerCaptor.capture()); + mEntryListener = mNotifListenerCaptor.getValue(); + } + + @Test + public void testAddBubble() { + mBubbleController.updateBubble(mRow.getEntry()); + assertTrue(mBubbleController.hasBubbles()); + + verify(mBubbleStateChangeListener).onHasBubblesChanged(true); + } + + @Test + public void testHasBubbles() { + assertFalse(mBubbleController.hasBubbles()); + mBubbleController.updateBubble(mRow.getEntry()); + assertTrue(mBubbleController.hasBubbles()); + } + + @Test + public void testRemoveBubble() { + mBubbleController.updateBubble(mRow.getEntry()); + assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); + assertTrue(mBubbleController.hasBubbles()); + verify(mNotifCallback, times(1)).invalidateNotifications(anyString()); + verify(mBubbleStateChangeListener).onHasBubblesChanged(true); + + mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); + assertFalse(mNotificationShadeWindowController.getBubblesShowing()); + assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); + verify(mNotifCallback, times(2)).invalidateNotifications(anyString()); + verify(mBubbleStateChangeListener).onHasBubblesChanged(false); + } + + @Test + public void testRemoveBubble_withDismissedNotif() { + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry())); + + // Make it look like dismissed notif + mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).setSuppressNotification(true); + + // Now remove the bubble + mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); + + // Since the notif is dismissed, once the bubble is removed, removeNotification gets + // called to really remove the notif + verify(mNotifCallback, times(1)).removeNotification(eq(mRow.getEntry()), anyInt()); + assertFalse(mBubbleController.hasBubbles()); + } + + @Test + public void testDismissStack() { + mBubbleController.updateBubble(mRow.getEntry()); + verify(mNotifCallback, times(1)).invalidateNotifications(anyString()); + assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); + mBubbleController.updateBubble(mRow2.getEntry()); + verify(mNotifCallback, times(2)).invalidateNotifications(anyString()); + assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey())); + assertTrue(mBubbleController.hasBubbles()); + + mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE); + assertFalse(mNotificationShadeWindowController.getBubblesShowing()); + verify(mNotifCallback, times(3)).invalidateNotifications(anyString()); + assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); + assertNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey())); + } + + @Test + public void testExpandCollapseStack() { + assertFalse(mBubbleController.isStackExpanded()); + + // Mark it as a bubble and add it explicitly + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + // We should have bubbles & their notifs should not be suppressed + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + assertFalse(mNotificationShadeWindowController.getBubbleExpanded()); + + // Expand the stack + BubbleStackView stackView = mBubbleController.getStackView(); + mBubbleController.expandStack(); + assertTrue(mBubbleController.isStackExpanded()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + assertTrue(mNotificationShadeWindowController.getBubbleExpanded()); + + // Make sure the notif is suppressed + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry())); + + // Collapse + mBubbleController.collapseStack(); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey()); + assertFalse(mBubbleController.isStackExpanded()); + assertFalse(mNotificationShadeWindowController.getBubbleExpanded()); + } + + @Test + public void testCollapseAfterChangingExpandedBubble() { + // Mark it as a bubble and add it explicitly + mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow2.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + mBubbleController.updateBubble(mRow2.getEntry()); + + // We should have bubbles & their notifs should not be suppressed + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow2.getEntry())); + + // Expand + BubbleStackView stackView = mBubbleController.getStackView(); + mBubbleController.expandStack(); + assertTrue(mBubbleController.isStackExpanded()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); + + // Last added is the one that is expanded + assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry())); + + // Switch which bubble is expanded + mBubbleController.selectBubble(mRow.getEntry().getKey()); + mBubbleData.setExpanded(true); + assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry()); + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + // collapse for previous bubble + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); + // expand for selected bubble + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + + // Collapse + mBubbleController.collapseStack(); + assertFalse(mBubbleController.isStackExpanded()); + } + + @Test + public void testExpansionRemovesShowInShadeAndDot() { + // Mark it as a bubble and add it explicitly + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + // We should have bubbles & their notifs should not be suppressed + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry())); + + mTestableLooper.processAllMessages(); + assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + + // Expand + mBubbleController.expandStack(); + assertTrue(mBubbleController.isStackExpanded()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + + // Notif is suppressed after expansion + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + // Notif shouldn't show dot after expansion + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + } + + @Test + public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() { + // Mark it as a bubble and add it explicitly + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + // We should have bubbles & their notifs should not be suppressed + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + mTestableLooper.processAllMessages(); + assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + + // Expand + mBubbleController.expandStack(); + assertTrue(mBubbleController.isStackExpanded()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + + // Notif is suppressed after expansion + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + // Notif shouldn't show dot after expansion + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + + // Send update + mEntryListener.onEntryUpdated(mRow.getEntry()); + + // Nothing should have changed + // Notif is suppressed after expansion + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + // Notif shouldn't show dot after expansion + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + } + + @Test + public void testRemoveLastExpandedCollapses() { + // Mark it as a bubble and add it explicitly + mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow2.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + mBubbleController.updateBubble(mRow2.getEntry()); + verify(mBubbleStateChangeListener).onHasBubblesChanged(true); + + // Expand + BubbleStackView stackView = mBubbleController.getStackView(); + mBubbleController.expandStack(); + + assertTrue(mBubbleController.isStackExpanded()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); + + // Last added is the one that is expanded + assertEquals(mRow2.getEntry(), stackView.getExpandedBubble().getEntry()); + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow2.getEntry())); + + // Dismiss currently expanded + mBubbleController.removeBubble(stackView.getExpandedBubble().getEntry(), + BubbleController.DISMISS_USER_GESTURE); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); + + // Make sure first bubble is selected + assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + + // Dismiss that one + mBubbleController.removeBubble(stackView.getExpandedBubble().getEntry(), + BubbleController.DISMISS_USER_GESTURE); + + // Make sure state changes and collapse happens + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey()); + verify(mBubbleStateChangeListener).onHasBubblesChanged(false); + assertFalse(mBubbleController.hasBubbles()); + } + + @Test + public void testAutoExpand_fails_noFlag() { + assertFalse(mBubbleController.isStackExpanded()); + setMetadataFlags(mRow.getEntry(), + Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */); + + // Add the auto expand bubble + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + // Expansion shouldn't change + verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */, + mRow.getEntry().getKey()); + assertFalse(mBubbleController.isStackExpanded()); + + // # of bubbles should change + verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */); + } + + @Test + public void testAutoExpand_succeeds_withFlag() { + setMetadataFlags(mRow.getEntry(), + Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */); + + // Add the auto expand bubble + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + // Expansion should change + verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */, + mRow.getEntry().getKey()); + assertTrue(mBubbleController.isStackExpanded()); + + // # of bubbles should change + verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */); + } + + @Test + public void testSuppressNotif_onInitialNotif() { + setMetadataFlags(mRow.getEntry(), + Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */); + + // Add the suppress notif bubble + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + // Notif should be suppressed because we were foreground + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + // Dot + flyout is hidden because notif is suppressed + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showFlyout()); + + // # of bubbles should change + verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */); + } + + @Test + public void testSuppressNotif_onUpdateNotif() { + mBubbleController.updateBubble(mRow.getEntry()); + + // Should not be suppressed + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + // Should show dot + assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + + // Update to suppress notif + setMetadataFlags(mRow.getEntry(), + Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */); + mBubbleController.updateBubble(mRow.getEntry()); + + // Notif should be suppressed + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + // Dot + flyout is hidden because notif is suppressed + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showFlyout()); + + // # of bubbles should change + verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */); + } + + @Test + public void testMarkNewNotificationAsShowInShade() { + mEntryListener.onEntryAdded(mRow.getEntry()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + mTestableLooper.processAllMessages(); + assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + } + + @Test + public void testAddNotif_notBubble() { + mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry()); + mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry()); + + verify(mBubbleStateChangeListener, never()).onHasBubblesChanged(anyBoolean()); + assertThat(mBubbleController.hasBubbles()).isFalse(); + } + + @Test + public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException { + mBubbleController.updateBubble(mRow.getEntry()); + mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED); + verify(mDeleteIntent, never()).send(); + } + + @Test + public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException { + mBubbleController.updateBubble(mRow.getEntry()); + mBubbleController.removeBubble( + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); + verify(mDeleteIntent, times(1)).send(); + } + + @Test + public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException { + mBubbleController.updateBubble(mRow.getEntry()); + mBubbleController.updateBubble(mRow2.getEntry()); + mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE); + verify(mDeleteIntent, times(2)).send(); + } + + @Test + public void testRemoveBubble_noLongerBubbleAfterUpdate() + throws PendingIntent.CanceledException { + mBubbleController.updateBubble(mRow.getEntry()); + assertTrue(mBubbleController.hasBubbles()); + + mRow.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE; + mEntryListener.onEntryUpdated(mRow.getEntry()); + + assertFalse(mBubbleController.hasBubbles()); + verify(mDeleteIntent, never()).send(); + } + + @Test + public void testRemoveBubble_entryListenerRemove() { + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + + // Removes the notification + mEntryListener.onEntryRemoved(mRow.getEntry(), 0); + assertFalse(mBubbleController.hasBubbles()); + } + + @Test + public void removeBubble_intercepted() { + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + boolean intercepted = mBubbleController.handleDismissalInterception(mRow.getEntry()); + + // Intercept! + assertTrue(intercepted); + // Should update show in shade state + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry())); + } + + @Test + public void removeBubble_succeeds_userDismissBubble_userDimissNotif() { + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + // Dismiss the bubble + mBubbleController.removeBubble( + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); + assertFalse(mBubbleController.hasBubbles()); + + // Dismiss the notification + boolean intercepted = mBubbleController.handleDismissalInterception(mRow.getEntry()); + + // It's no longer a bubble so we shouldn't intercept + assertFalse(intercepted); + } + + @Test + public void testNotifyShadeSuppressionChange_notificationDismiss() { + BubbleController.NotificationSuppressionChangedListener listener = + mock(BubbleController.NotificationSuppressionChangedListener.class); + mBubbleData.setSuppressionChangedListener(listener); + + mEntryListener.onEntryAdded(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + mBubbleController.handleDismissalInterception(mRow.getEntry()); + + // Should update show in shade state + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + // Should notify delegate that shade state changed + verify(listener).onBubbleNotificationSuppressionChange( + mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); + } + + @Test + public void testNotifyShadeSuppressionChange_bubbleExpanded() { + BubbleController.NotificationSuppressionChangedListener listener = + mock(BubbleController.NotificationSuppressionChangedListener.class); + mBubbleData.setSuppressionChangedListener(listener); + + mEntryListener.onEntryAdded(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + mBubbleData.setExpanded(true); + + // Once a bubble is expanded the notif is suppressed + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + // Should notify delegate that shade state changed + verify(listener).onBubbleNotificationSuppressionChange( + mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); + } + + @Test + public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade() throws Exception { + // GIVEN a group summary with a bubble child + ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); + ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + mEntryListener.onEntryAdded(groupedBubble.getEntry()); + groupSummary.addChildNotification(groupedBubble); + assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey())); + + // WHEN the summary is dismissed + mBubbleController.handleDismissalInterception(groupSummary.getEntry()); + + // THEN the summary and bubbled child are suppressed from the shade + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + groupedBubble.getEntry())); + assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey())); + } + + @Test + public void testAppRemovesSummary_removesAllBubbleChildren() throws Exception { + // GIVEN a group summary with a bubble child + ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); + ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + mEntryListener.onEntryAdded(groupedBubble.getEntry()); + groupSummary.addChildNotification(groupedBubble); + assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey())); + + // GIVEN the summary is dismissed + mBubbleController.handleDismissalInterception(groupSummary.getEntry()); + + // WHEN the summary is cancelled by the app + mEntryListener.onEntryRemoved(groupSummary.getEntry(), 0); + + // THEN the summary and its children are removed from bubble data + assertFalse(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey())); + assertFalse(mBubbleData.isSummarySuppressed( + groupSummary.getEntry().getSbn().getGroupKey())); + } + + @Test + public void testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren() + throws Exception { + // GIVEN a group summary with two (non-bubble) children and one bubble child + ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2); + ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + mEntryListener.onEntryAdded(groupedBubble.getEntry()); + groupSummary.addChildNotification(groupedBubble); + + // WHEN the summary is dismissed + mBubbleController.handleDismissalInterception(groupSummary.getEntry()); + + // THEN only the NON-bubble children are dismissed + List<ExpandableNotificationRow> childrenRows = groupSummary.getNotificationChildren(); + verify(mNotifCallback, times(1)).removeNotification( + childrenRows.get(0).getEntry(), REASON_GROUP_SUMMARY_CANCELED); + verify(mNotifCallback, times(1)).removeNotification( + childrenRows.get(1).getEntry(), REASON_GROUP_SUMMARY_CANCELED); + verify(mNotifCallback, never()).removeNotification(eq(groupedBubble.getEntry()), anyInt()); + + // THEN the bubble child still exists as a bubble and is suppressed from the shade + assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey())); + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + groupedBubble.getEntry())); + + // THEN the summary is also suppressed from the shade + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + groupSummary.getEntry())); + } + + static class TestableBubbleController extends BubbleController { + // Let's assume surfaces can be synchronized immediately. + TestableBubbleController(Context context, + NotificationShadeWindowController notificationShadeWindowController, + StatusBarStateController statusBarStateController, + ShadeController shadeController, + BubbleData data, + ConfigurationController configurationController, + NotificationInterruptionStateProvider interruptionStateProvider, + ZenModeController zenModeController, + NotificationLockscreenUserManager lockscreenUserManager, + NotificationGroupManager groupManager, + NotificationEntryManager entryManager, + NotifPipeline notifPipeline, + FeatureFlags featureFlags, + DumpController dumpController) { + super(context, + notificationShadeWindowController, statusBarStateController, shadeController, + data, Runnable::run, configurationController, interruptionStateProvider, + zenModeController, lockscreenUserManager, groupManager, entryManager, + notifPipeline, featureFlags, dumpController); + setInflateSynchronously(true); + } + } + + static class TestableNotificationInterruptionStateProvider extends + NotificationInterruptionStateProvider { + + TestableNotificationInterruptionStateProvider(Context context, + NotificationFilter filter, StatusBarStateController controller, + BatteryController batteryController) { + super(context, filter, controller, batteryController); + mUseHeadsUp = true; + } + } + + /** + * Sets the bubble metadata flags for this entry. These flags are normally set by + * NotificationManagerService when the notification is sent, however, these tests do not + * go through that path so we set them explicitly when testing. + */ + private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) { + Notification.BubbleMetadata bubbleMetadata = + entry.getSbn().getNotification().getBubbleMetadata(); + int flags = bubbleMetadata.getFlags(); + if (enableFlag) { + flags |= flag; + } else { + flags &= ~flag; + } + bubbleMetadata.setFlags(flags); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java deleted file mode 100644 index 4a90bb91ca37..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.log; - -import static junit.framework.Assert.assertEquals; - -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; - -import junit.framework.Assert; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class RichEventTest extends SysuiTestCase { - - private static final int TOTAL_EVENT_TYPES = 1; - - @Test - public void testCreateRichEvent_invalidType() { - try { - // indexing for events starts at 0, so TOTAL_EVENT_TYPES is an invalid type - new TestableRichEvent(Event.DEBUG, TOTAL_EVENT_TYPES, "msg"); - } catch (IllegalArgumentException e) { - // expected - return; - } - - Assert.fail("Expected an invalidArgumentException since the event type was invalid."); - } - - @Test - public void testCreateRichEvent() { - final int eventType = 0; - RichEvent e = new TestableRichEvent(Event.DEBUG, eventType, "msg"); - assertEquals(e.getType(), eventType); - } - - class TestableRichEvent extends RichEvent { - TestableRichEvent(int logLevel, int type, String reason) { - init(logLevel, type, reason); - } - - @Override - public String[] getEventLabels() { - return new String[]{"ACTION_NAME"}; - } - } - -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java deleted file mode 100644 index e7b317e882ef..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.log; - -import static junit.framework.Assert.assertEquals; - -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.DumpController; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class SysuiLogTest extends SysuiTestCase { - private static final String TEST_ID = "TestLogger"; - private static final String TEST_MSG = "msg"; - private static final int MAX_LOGS = 5; - - @Mock - private DumpController mDumpController; - private SysuiLog<Event> mSysuiLog; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testLogDisabled_noLogsWritten() { - mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, false); - assertEquals(null, mSysuiLog.mTimeline); - - mSysuiLog.log(createEvent(TEST_MSG)); - assertEquals(null, mSysuiLog.mTimeline); - } - - @Test - public void testLogEnabled_logWritten() { - mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true); - assertEquals(0, mSysuiLog.mTimeline.size()); - - mSysuiLog.log(createEvent(TEST_MSG)); - assertEquals(1, mSysuiLog.mTimeline.size()); - } - - @Test - public void testMaxLogs() { - mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true); - assertEquals(mSysuiLog.mTimeline.size(), 0); - - for (int i = 0; i < MAX_LOGS + 1; i++) { - mSysuiLog.log(createEvent(TEST_MSG + i)); - } - - assertEquals(MAX_LOGS, mSysuiLog.mTimeline.size()); - - // check the first message (msg0) was replaced with msg1: - assertEquals(TEST_MSG + "1", mSysuiLog.mTimeline.getFirst().getMessage()); - } - - @Test - public void testRecycleLogs() { - // GIVEN a SysuiLog with one log - mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true); - Event e = createEvent(TEST_MSG); // msg - mSysuiLog.log(e); // Logs: [msg] - - Event recycledEvent = null; - // WHEN we add MAX_LOGS after the first log - for (int i = 0; i < MAX_LOGS; i++) { - recycledEvent = mSysuiLog.log(createEvent(TEST_MSG + i)); - } - // Logs: [msg1, msg2, msg3, msg4] - - // THEN we see the recycledEvent is e - assertEquals(e, recycledEvent); - } - - private Event createEvent(String msg) { - return new Event().init(msg); - } - - public class TestSysuiLog extends SysuiLog<Event> { - protected TestSysuiLog(DumpController dumpController, String id, int maxLogs, - boolean enabled) { - super(dumpController, id, maxLogs, enabled, false); - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt index 4becd522ebd6..9fe2569177d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -88,8 +88,8 @@ class CustomTileTest : SysuiTestCase() { } @Test - fun testBooleanTileHasBooleanState() { - `when`(mTileServiceManager.isBooleanTile).thenReturn(true) + fun testToggleableTileHasBooleanState() { + `when`(mTileServiceManager.isToggleableTile).thenReturn(true) customTile = CustomTile.create(mTileHost, TILE_SPEC) assertTrue(customTile.state is QSTile.BooleanState) @@ -104,7 +104,7 @@ class CustomTileTest : SysuiTestCase() { @Test fun testValueUpdatedInBooleanTile() { - `when`(mTileServiceManager.isBooleanTile).thenReturn(true) + `when`(mTileServiceManager.isToggleableTile).thenReturn(true) customTile = CustomTile.create(mTileHost, TILE_SPEC) customTile.qsTile.icon = mock(Icon::class.java) `when`(customTile.qsTile.icon.loadDrawable(any(Context::class.java))) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index 9e5e582bf5e7..42fd288d94ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -105,7 +105,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { defaultServiceInfo = new ServiceInfo(); defaultServiceInfo.metaData = new Bundle(); defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_ACTIVE_TILE, true); - defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_BOOLEAN_TILE, true); + defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_TOGGLEABLE_TILE, true); } when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt(), anyInt())) .thenReturn(defaultServiceInfo); @@ -244,7 +244,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { } @Test - public void testBooleanTile() throws Exception { - assertTrue(mStateManager.isBooleanTile()); + public void testToggleableTile() throws Exception { + assertTrue(mStateManager.isToggleableTile()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 0a3bc6def160..1d4b4be9e683 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -53,8 +53,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.app.IBatteryStats; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.KeyguardUpdateMonitor.BatteryStatus; import com.android.settingslib.Utils; +import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; 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 b51581f544f5..07f6936ece07 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 @@ -79,7 +79,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; -import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; @@ -139,7 +138,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Mock private NotificationRemoteInputManager mRemoteInputManager; @Mock private DeviceProvisionedController mDeviceProvisionedController; @Mock private RowInflaterTask mAsyncInflationTask; - @Mock private NotifLog mNotifLog; + @Mock private NotificationEntryManagerLogger mLogger; @Mock private FeatureFlags mFeatureFlags; @Mock private LeakDetector mLeakDetector; @Mock private ActivatableNotificationViewController mActivatableNotificationViewController; @@ -234,14 +233,14 @@ public class NotificationEntryManagerTest extends SysuiTestCase { when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false); when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false); mEntryManager = new TestableNotificationEntryManager( - mNotifLog, + mLogger, mGroupManager, new NotificationRankingManager( () -> mock(NotificationMediaManager.class), mGroupManager, mHeadsUpManager, mock(NotificationFilter.class), - mNotifLog, + mLogger, mock(NotificationSectionsFeatureManager.class), mock(PeopleNotificationIdentifier.class), mock(HighPriorityProvider.class)), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt index a9f9db67ff0b..0e730e5c3ffb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt @@ -22,7 +22,6 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationRankingManager import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder -import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.HeadsUpManagerPhone import com.android.systemui.statusbar.phone.NotificationGroupManager @@ -34,7 +33,7 @@ import java.util.concurrent.CountDownLatch * Enable some test capabilities for NEM without making everything public on the base class */ class TestableNotificationEntryManager( - log: NotifLog, + logger: NotificationEntryManagerLogger, gm: NotificationGroupManager, rm: NotificationRankingManager, ke: KeyguardEnvironment, @@ -43,7 +42,7 @@ class TestableNotificationEntryManager( notificationRemoteInputManagerLazy: dagger.Lazy<NotificationRemoteInputManager>, leakDetector: LeakDetector, fgsFeatureController: ForegroundServiceDismissalFeatureController -) : NotificationEntryManager(log, gm, rm, ke, ff, rb, +) : NotificationEntryManager(logger, gm, rm, ke, ff, rb, notificationRemoteInputManagerLazy, leakDetector, fgsFeatureController) { public var countDownLatch: CountDownLatch = CountDownLatch(1) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 7c94ed20e95a..abc0f3ee8a52 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -34,6 +34,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -47,6 +48,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.Nullable; import android.os.RemoteException; import android.service.notification.NotificationListenerService.Ranking; +import android.service.notification.NotificationStats; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArrayMap; @@ -69,6 +71,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Co import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.util.Assert; @@ -98,11 +101,19 @@ public class NotifCollectionTest extends SysuiTestCase { @Spy private RecordingCollectionListener mCollectionListener; @Mock private CollectionReadyForBuildListener mBuildListener; @Mock private FeatureFlags mFeatureFlags; + @Mock private DismissedByUserStats mDismissedByUserStats; @Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1"); @Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2"); @Spy private RecordingLifetimeExtender mExtender3 = new RecordingLifetimeExtender("Extender3"); + @Spy private RecordingDismissInterceptor mInterceptor1 = new RecordingDismissInterceptor( + "Interceptor1"); + @Spy private RecordingDismissInterceptor mInterceptor2 = new RecordingDismissInterceptor( + "Interceptor2"); + @Spy private RecordingDismissInterceptor mInterceptor3 = new RecordingDismissInterceptor( + "Interceptor3"); + @Captor private ArgumentCaptor<BatchableNotificationHandler> mListenerCaptor; @Captor private ArgumentCaptor<NotificationEntry> mEntryCaptor; @Captor private ArgumentCaptor<Collection<NotificationEntry>> mBuildListCaptor; @@ -441,6 +452,169 @@ public class NotifCollectionTest extends SysuiTestCase { assertEquals(NOT_DISMISSED, entry3.getDismissState()); } + @Test + public void testDismissInterceptorsAreCalled() throws RemoteException { + // GIVEN a collection with notifications with multiple dismiss interceptors + mInterceptor1.shouldInterceptDismissal = true; + mInterceptor2.shouldInterceptDismissal = true; + mInterceptor3.shouldInterceptDismissal = false; + mCollection.addNotificationDismissInterceptor(mInterceptor1); + mCollection.addNotificationDismissInterceptor(mInterceptor2); + mCollection.addNotificationDismissInterceptor(mInterceptor3); + + NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + // WHEN a notification is manually dismissed + DismissedByUserStats stats = new DismissedByUserStats( + NotificationStats.DISMISSAL_SHADE, + NotificationStats.DISMISS_SENTIMENT_NEUTRAL, + NotificationVisibility.obtain(entry.getKey(), 7, 2, true)); + mCollection.dismissNotification(entry, stats); + + // THEN all interceptors get checked + verify(mInterceptor1).shouldInterceptDismissal(entry); + verify(mInterceptor2).shouldInterceptDismissal(entry); + verify(mInterceptor3).shouldInterceptDismissal(entry); + assertEquals(List.of(mInterceptor1, mInterceptor2), entry.mDismissInterceptors); + + // THEN we never send the dismissal to system server + verify(mStatusBarService, never()).onNotificationClear( + notif.sbn.getPackageName(), + notif.sbn.getTag(), + 47, + notif.sbn.getUser().getIdentifier(), + notif.sbn.getKey(), + stats.dismissalSurface, + stats.dismissalSentiment, + stats.notificationVisibility); + } + + @Test + public void testDismissInterceptorsCanceledWhenNotifIsUpdated() throws RemoteException { + // GIVEN a few lifetime extenders and a couple notifications + mCollection.addNotificationDismissInterceptor(mInterceptor1); + mCollection.addNotificationDismissInterceptor(mInterceptor2); + + mInterceptor1.shouldInterceptDismissal = true; + mInterceptor2.shouldInterceptDismissal = true; + + NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + // WHEN a notification is manually dismissed and intercepted + DismissedByUserStats stats = new DismissedByUserStats( + NotificationStats.DISMISSAL_SHADE, + NotificationStats.DISMISS_SENTIMENT_NEUTRAL, + NotificationVisibility.obtain(entry.getKey(), 7, 2, true)); + mCollection.dismissNotification(entry, stats); + assertEquals(List.of(mInterceptor1, mInterceptor2), entry.mDismissInterceptors); + clearInvocations(mInterceptor1, mInterceptor2); + + // WHEN the notification is reposted + mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); + + // THEN all of the active dismissal interceptors are canceled + verify(mInterceptor1).cancelDismissInterception(entry); + verify(mInterceptor2).cancelDismissInterception(entry); + assertEquals(List.of(), entry.mDismissInterceptors); + + // THEN the notification is never sent to system server to dismiss + verify(mStatusBarService, never()).onNotificationClear( + eq(notif.sbn.getPackageName()), + eq(notif.sbn.getTag()), + eq(47), + eq(notif.sbn.getUser().getIdentifier()), + eq(notif.sbn.getKey()), + anyInt(), + anyInt(), + anyObject()); + } + + @Test + public void testEndingAllDismissInterceptorsSendsDismiss() throws RemoteException { + // GIVEN a collection with notifications a dismiss interceptor + mInterceptor1.shouldInterceptDismissal = true; + mCollection.addNotificationDismissInterceptor(mInterceptor1); + + NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + // GIVEN a notification is manually dismissed + DismissedByUserStats stats = new DismissedByUserStats( + NotificationStats.DISMISSAL_SHADE, + NotificationStats.DISMISS_SENTIMENT_NEUTRAL, + NotificationVisibility.obtain(entry.getKey(), 7, 2, true)); + mCollection.dismissNotification(entry, stats); + + // WHEN all interceptors end their interception dismissal + mInterceptor1.shouldInterceptDismissal = false; + mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry, + mDismissedByUserStats); + + // THEN we send the dismissal to system server + verify(mStatusBarService, times(1)).onNotificationClear( + eq(notif.sbn.getPackageName()), + eq(notif.sbn.getTag()), + eq(47), + eq(notif.sbn.getUser().getIdentifier()), + eq(notif.sbn.getKey()), + anyInt(), + anyInt(), + anyObject()); + } + + @Test + public void testEndDismissInterceptionUpdatesDismissInterceptors() throws RemoteException { + // GIVEN a collection with notifications with multiple dismiss interceptors + mInterceptor1.shouldInterceptDismissal = true; + mInterceptor2.shouldInterceptDismissal = true; + mInterceptor3.shouldInterceptDismissal = false; + mCollection.addNotificationDismissInterceptor(mInterceptor1); + mCollection.addNotificationDismissInterceptor(mInterceptor2); + mCollection.addNotificationDismissInterceptor(mInterceptor3); + + NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + // GIVEN a notification is manually dismissed + DismissedByUserStats stats = new DismissedByUserStats( + NotificationStats.DISMISSAL_SHADE, + NotificationStats.DISMISS_SENTIMENT_NEUTRAL, + NotificationVisibility.obtain(entry.getKey(), 7, 2, true)); + mCollection.dismissNotification(entry, stats); + + // WHEN an interceptor ends its interception + mInterceptor1.shouldInterceptDismissal = false; + mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry, + mDismissedByUserStats); + + // THEN all interceptors get checked + verify(mInterceptor1).shouldInterceptDismissal(entry); + verify(mInterceptor2).shouldInterceptDismissal(entry); + verify(mInterceptor3).shouldInterceptDismissal(entry); + + // THEN mInterceptor2 is the only dismiss interceptor + assertEquals(List.of(mInterceptor2), entry.mDismissInterceptors); + } + + + @Test(expected = IllegalStateException.class) + public void testEndingDismissalOfNonInterceptedThrows() throws RemoteException { + // GIVEN a collection with notifications with a dismiss interceptor that hasn't been called + mInterceptor1.shouldInterceptDismissal = false; + mCollection.addNotificationDismissInterceptor(mInterceptor1); + + NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + // WHEN we try to end the dismissal of an interceptor that didn't intercept the notif + mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry, + mDismissedByUserStats); + + // THEN an exception is thrown + } + @Test(expected = IllegalStateException.class) public void testDismissingNonExistentNotificationThrows() { // GIVEN a collection that originally had three notifs, but where one was dismissed @@ -894,6 +1068,36 @@ public class NotifCollectionTest extends SysuiTestCase { } } + private static class RecordingDismissInterceptor implements NotifDismissInterceptor { + private final String mName; + + public @Nullable OnEndDismissInterception onEndInterceptionCallback; + public boolean shouldInterceptDismissal = false; + + private RecordingDismissInterceptor(String name) { + mName = name; + } + + @Override + public String getName() { + return mName; + } + + @Override + public void setCallback(OnEndDismissInterception callback) { + this.onEndInterceptionCallback = callback; + } + + @Override + public boolean shouldInterceptDismissal(NotificationEntry entry) { + return shouldInterceptDismissal; + } + + @Override + public void cancelDismissInterception(NotificationEntry entry) { + } + } + private static final String TEST_PACKAGE = "com.android.test.collection"; private static final String TEST_PACKAGE2 = "com.android.test.collection2"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt index 7ab4846ea066..c6b496dd8215 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt @@ -27,10 +27,10 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking import com.android.systemui.statusbar.NotificationMediaManager +import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider -import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT @@ -62,7 +62,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { mock(NotificationGroupManager::class.java), mock(HeadsUpManager::class.java), mock(NotificationFilter::class.java), - mock(NotifLog::class.java), + mock(NotificationEntryManagerLogger::class.java), mock(NotificationSectionsFeatureManager::class.java), personNotificationIdentifier, HighPriorityProvider(personNotificationIdentifier) @@ -189,7 +189,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { groupManager: NotificationGroupManager, headsUpManager: HeadsUpManager, filter: NotificationFilter, - notifLog: NotifLog, + logger: NotificationEntryManagerLogger, sectionsFeatureManager: NotificationSectionsFeatureManager, peopleNotificationIdentifier: PeopleNotificationIdentifier, highPriorityProvider: HighPriorityProvider @@ -198,7 +198,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { groupManager, headsUpManager, filter, - notifLog, + logger, sectionsFeatureManager, peopleNotificationIdentifier, highPriorityProvider diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 3d9832de417a..9b2e0c375e87 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -199,9 +199,17 @@ public class NotificationTestHelper { /** * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. */ + public ExpandableNotificationRow createBubbleInGroup() + throws Exception { + return createBubble(makeBubbleMetadata(null), PKG, true); + } + + /** + * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. + */ public ExpandableNotificationRow createBubble() throws Exception { - return createBubble(makeBubbleMetadata(null), PKG); + return createBubble(makeBubbleMetadata(null), PKG, false); } /** @@ -211,7 +219,7 @@ public class NotificationTestHelper { */ public ExpandableNotificationRow createBubble(@Nullable PendingIntent deleteIntent) throws Exception { - return createBubble(makeBubbleMetadata(deleteIntent), PKG); + return createBubble(makeBubbleMetadata(deleteIntent), PKG, false); } /** @@ -221,8 +229,14 @@ public class NotificationTestHelper { */ public ExpandableNotificationRow createBubble(BubbleMetadata bubbleMetadata, String pkg) throws Exception { + return createBubble(bubbleMetadata, pkg, false); + } + + private ExpandableNotificationRow createBubble(BubbleMetadata bubbleMetadata, String pkg, + boolean inGroup) + throws Exception { Notification n = createNotification(false /* isGroupSummary */, - null /* groupKey */, bubbleMetadata); + inGroup ? GROUP_KEY : null /* groupKey */, bubbleMetadata); n.flags |= FLAG_BUBBLE; ExpandableNotificationRow row = generateRow(n, pkg, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java index 66aa5e18d0c9..775f722b13f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -67,7 +67,7 @@ public class RowContentBindStageTest extends SysuiTestCase { } @Test - public void testSetShouldContentViewsBeBound_bindsContent() { + public void testRequireContentViews() { // WHEN inflation flags are set and pipeline is invalidated. final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); @@ -85,7 +85,7 @@ public class RowContentBindStageTest extends SysuiTestCase { } @Test - public void testSetShouldContentViewsBeBound_unbindsContent() { + public void testFreeContentViews() { // GIVEN a view with all content bound. RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); params.requireContentViews(FLAG_CONTENT_VIEW_ALL); @@ -100,6 +100,28 @@ public class RowContentBindStageTest extends SysuiTestCase { } @Test + public void testRebindAllContentViews() { + // GIVEN a view with content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; + params.requireContentViews(flags); + params.clearDirtyContentViews(); + + // WHEN we request rebind and stage executed. + params.rebindAllContentViews(); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder binds inflation flags. + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(flags), + any(), + anyBoolean(), + any()); + } + + @Test public void testSetUseLowPriority() { // GIVEN a view with all content bound. RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); 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 70d76f0c3a52..b16e52ce7bd4 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 @@ -65,6 +65,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.TestableNotificationEntryManager; @@ -74,7 +75,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; -import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.FooterView; @@ -163,14 +163,14 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor .forClass(UserChangedListener.class); mEntryManager = new TestableNotificationEntryManager( - mock(NotifLog.class), + mock(NotificationEntryManagerLogger.class), mock(NotificationGroupManager.class), new NotificationRankingManager( () -> mock(NotificationMediaManager.class), mGroupManager, mHeadsUpManager, mock(NotificationFilter.class), - mock(NotifLog.class), + mock(NotificationEntryManagerLogger.class), mock(NotificationSectionsFeatureManager.class), mock(PeopleNotificationIdentifier.class), mock(HighPriorityProvider.class) diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp index e0adb34dad6c..8c4f733fb62a 100644 --- a/packages/Tethering/common/TetheringLib/Android.bp +++ b/packages/Tethering/common/TetheringLib/Android.bp @@ -59,16 +59,33 @@ java_library { ], hostdex: true, // for hiddenapi check - visibility: [ - "//frameworks/base/packages/Tethering:__subpackages__", - //TODO(b/147200698) remove below lines when the platform is built with stubs - "//frameworks/base", - "//frameworks/base/services", - "//frameworks/base/services/core", - ], + visibility: ["//frameworks/base/packages/Tethering:__subpackages__"], apex_available: ["com.android.tethering"], } +droidstubs { + name: "framework-tethering-stubs-sources", + defaults: ["framework-module-stubs-defaults-module_libs_api"], + srcs: [ + "src/android/net/TetheredClient.java", + "src/android/net/TetheringManager.java", + "src/android/net/TetheringConstants.java", + ], + libs: [ + "tethering-aidl-interfaces-java", + "framework-all", + ], + sdk_version: "core_platform", +} + +java_library { + name: "framework-tethering-stubs", + srcs: [":framework-tethering-stubs-sources"], + libs: ["framework-all"], + static_libs: ["tethering-aidl-interfaces-java"], + sdk_version: "core_platform", +} + filegroup { name: "framework-tethering-srcs", srcs: [ diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java index 651468846ca8..ca5ef09d6ad9 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java @@ -16,6 +16,8 @@ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -34,6 +36,7 @@ import java.util.Objects; * @hide */ @SystemApi +@SystemApi(client = MODULE_LIBRARIES) @TestApi public final class TetheredClient implements Parcelable { @NonNull diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java index 00cf98e0fc2d..df87ac994d42 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java @@ -16,6 +16,9 @@ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.SystemApi; import android.os.ResultReceiver; /** @@ -28,39 +31,30 @@ import android.os.ResultReceiver; * symbols from framework-tethering even when they are in a non-hidden class. * @hide */ +@SystemApi(client = MODULE_LIBRARIES) public class TetheringConstants { /** * Extra used for communicating with the TetherService. Includes the type of tethering to * enable if any. - * - * {@hide} */ public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; /** * Extra used for communicating with the TetherService. Includes the type of tethering for * which to cancel provisioning. - * - * {@hide} */ public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType"; /** * Extra used for communicating with the TetherService. True to schedule a recheck of tether * provisioning. - * - * {@hide} */ public static final String EXTRA_SET_ALARM = "extraSetAlarm"; /** * Tells the TetherService to run a provision check now. - * - * {@hide} */ public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; /** * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver} * which will receive provisioning results. Can be left empty. - * - * {@hide} */ public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback"; } diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index 53a358f29a97..6a9f010449c4 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -15,6 +15,8 @@ */ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -50,6 +52,7 @@ import java.util.function.Supplier; * @hide */ @SystemApi +@SystemApi(client = MODULE_LIBRARIES) @TestApi public class TetheringManager { private static final String TAG = TetheringManager.class.getSimpleName(); @@ -177,6 +180,7 @@ public class TetheringManager { * service is not connected. * {@hide} */ + @SystemApi(client = MODULE_LIBRARIES) public TetheringManager(@NonNull final Context context, @NonNull Supplier<IBinder> connectorSupplier) { mContext = context; @@ -395,6 +399,7 @@ public class TetheringManager { * {@hide} */ @Deprecated + @SystemApi(client = MODULE_LIBRARIES) public int tether(@NonNull final String iface) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "tether caller:" + callerPkg); @@ -418,6 +423,7 @@ public class TetheringManager { * {@hide} */ @Deprecated + @SystemApi(client = MODULE_LIBRARIES) public int untether(@NonNull final String iface) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "untether caller:" + callerPkg); @@ -444,6 +450,7 @@ public class TetheringManager { * {@hide} */ @Deprecated + @SystemApi(client = MODULE_LIBRARIES) public int setUsbTethering(final boolean enable) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "setUsbTethering caller:" + callerPkg); @@ -702,6 +709,7 @@ public class TetheringManager { * {@hide} */ // TODO: improve the usage of ResultReceiver, b/145096122 + @SystemApi(client = MODULE_LIBRARIES) public void requestLatestTetheringEntitlementResult(final int type, @NonNull final ResultReceiver receiver, final boolean showEntitlementUi) { final String callerPkg = mContext.getOpPackageName(); @@ -982,6 +990,7 @@ public class TetheringManager { * interface * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public int getLastTetherError(@NonNull final String iface) { mCallback.waitForStarted(); if (mTetherStatesParcel == null) return TETHER_ERROR_NO_ERROR; @@ -1004,6 +1013,7 @@ public class TetheringManager { * what interfaces are considered tetherable usb interfaces. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetherableUsbRegexs() { mCallback.waitForStarted(); return mTetheringConfiguration.tetherableUsbRegexs; @@ -1018,6 +1028,7 @@ public class TetheringManager { * what interfaces are considered tetherable wifi interfaces. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetherableWifiRegexs() { mCallback.waitForStarted(); return mTetheringConfiguration.tetherableWifiRegexs; @@ -1032,6 +1043,7 @@ public class TetheringManager { * what interfaces are considered tetherable bluetooth interfaces. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetherableBluetoothRegexs() { mCallback.waitForStarted(); return mTetheringConfiguration.tetherableBluetoothRegexs; @@ -1044,6 +1056,7 @@ public class TetheringManager { * @return an array of 0 or more Strings of tetherable interface names. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetherableIfaces() { mCallback.waitForStarted(); if (mTetherStatesParcel == null) return new String[0]; @@ -1057,6 +1070,7 @@ public class TetheringManager { * @return an array of 0 or more String of currently tethered interface names. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetheredIfaces() { mCallback.waitForStarted(); if (mTetherStatesParcel == null) return new String[0]; @@ -1076,6 +1090,7 @@ public class TetheringManager { * which failed to tether. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetheringErroredIfaces() { mCallback.waitForStarted(); if (mTetherStatesParcel == null) return new String[0]; @@ -1103,6 +1118,7 @@ public class TetheringManager { * @return a boolean - {@code true} indicating Tethering is supported. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public boolean isTetheringSupported() { final String callerPkg = mContext.getOpPackageName(); diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java index 39c402be84a3..64c16e41dbf5 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -1944,7 +1944,8 @@ public class Tethering { parcel.tetheringSupported = mDeps.isTetheringSupported(); parcel.upstreamNetwork = mTetherUpstream; parcel.config = mConfig.toStableParcelable(); - parcel.states = mTetherStatesParcel; + parcel.states = + mTetherStatesParcel != null ? mTetherStatesParcel : emptyTetherStatesParcel(); try { callback.onCallbackStarted(parcel); } catch (RemoteException e) { @@ -1953,6 +1954,17 @@ public class Tethering { }); } + private TetherStatesParcel emptyTetherStatesParcel() { + final TetherStatesParcel parcel = new TetherStatesParcel(); + parcel.availableList = new String[0]; + parcel.tetheredList = new String[0]; + parcel.localOnlyList = new String[0]; + parcel.erroredIfaceList = new String[0]; + parcel.lastErrorList = new int[0]; + + return parcel; + } + /** Unregister tethering event callback */ void unregisterTetheringEventCallback(ITetheringEventCallback callback) { mHandler.post(() -> { diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp index 13174c5bb57a..c6905ec8efce 100644 --- a/packages/Tethering/tests/unit/Android.bp +++ b/packages/Tethering/tests/unit/Android.bp @@ -34,7 +34,15 @@ android_test { "TetheringApiCurrentLib", "testables", ], + // TODO(b/147200698) change sdk_version to module-current and + // remove framework-minus-apex, ext, and framework-res + sdk_version: "core_platform", libs: [ + "framework-minus-apex", + "ext", + "framework-res", + "framework-wifi-stubs", + "framework-telephony-stubs", "android.test.runner", "android.test.base", "android.test.mock", diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index 4710287f33f3..6d49e20e5753 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -127,6 +127,7 @@ import com.android.internal.util.StateMachine; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; import com.android.networkstack.tethering.R; +import com.android.testutils.MiscAssertsKt; import org.junit.After; import org.junit.Before; @@ -1220,6 +1221,16 @@ public class TetheringTest { } } + private void assertTetherStatesNotNullButEmpty(final TetherStatesParcel parcel) { + assertFalse(parcel == null); + assertEquals(0, parcel.availableList.length); + assertEquals(0, parcel.tetheredList.length); + assertEquals(0, parcel.localOnlyList.length); + assertEquals(0, parcel.erroredIfaceList.length); + assertEquals(0, parcel.lastErrorList.length); + MiscAssertsKt.assertFieldCountEquals(5, TetherStatesParcel.class); + } + @Test public void testRegisterTetheringEventCallback() throws Exception { TestTetheringEventCallback callback = new TestTetheringEventCallback(); @@ -1232,7 +1243,7 @@ public class TetheringTest { callback.expectConfigurationChanged( mTethering.getTetheringConfiguration().toStableParcelable()); TetherStatesParcel tetherState = callback.pollTetherStatesChanged(); - assertEquals(tetherState, null); + assertTetherStatesNotNullButEmpty(tetherState); // 2. Enable wifi tethering. UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState(); when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState); diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk index fc7709c4a51e..dcdb80b497d0 100644 --- a/packages/overlays/Android.mk +++ b/packages/overlays/Android.mk @@ -28,6 +28,7 @@ LOCAL_REQUIRED_MODULES := \ DisplayCutoutEmulationDoubleOverlay \ DisplayCutoutEmulationHoleOverlay \ DisplayCutoutEmulationTallOverlay \ + DisplayCutoutEmulationWaterfallOverlay \ FontNotoSerifSourceOverlay \ IconPackCircularAndroidOverlay \ IconPackCircularLauncherOverlay \ diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.mk new file mode 100644 index 000000000000..b6b6dd1c25bc --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_RRO_THEME := DisplayCutoutEmulationWaterfall + + +LOCAL_PRODUCT_MODULE := true + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := DisplayCutoutEmulationWaterfallOverlay +LOCAL_SDK_VERSION := current + +include $(BUILD_RRO_PACKAGE) diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/AndroidManifest.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..2d5bb14f4dd3 --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/AndroidManifest.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2020 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.internal.display.cutout.emulation.waterfall" + android:versionCode="1" + android:versionName="1.0"> + <overlay android:targetPackage="android" + android:category="com.android.internal.display_cutout_emulation" + android:priority="1"/> + + <application android:label="@string/display_cutout_emulation_overlay" android:hasCode="false"/> +</manifest> diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml new file mode 100644 index 000000000000..df2f3d19626e --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml @@ -0,0 +1,22 @@ +<!-- + ~ Copyright (C) 2020 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> + <!-- Can't link to other dimensions here, but this should be status_bar_height_landscape --> + <dimen name="quick_qs_offset_height">48dp</dimen> + <!-- Total height of QQS in landscape; quick_qs_offset_height + 128 --> + <dimen name="quick_qs_total_height">176dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml new file mode 100644 index 000000000000..6f692c8021c0 --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml @@ -0,0 +1,35 @@ +<!-- + ~ Copyright (C) 2020 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"> + + <!-- Height of the status bar in portrait. The height should be + Max((status bar content height + waterfall top size), top cutout size) --> + <dimen name="status_bar_height_portrait">28dp</dimen> + <!-- Max((28 + 20), 0) = 48 --> + <dimen name="status_bar_height_landscape">48dp</dimen> + <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) --> + <dimen name="quick_qs_offset_height">28dp</dimen> + <!-- Total height of QQS (quick_qs_offset_height + 128) --> + <dimen name="quick_qs_total_height">156dp</dimen> + + <dimen name="waterfall_display_left_edge_size">20dp</dimen> + <dimen name="waterfall_display_top_edge_size">0dp</dimen> + <dimen name="waterfall_display_right_edge_size">20dp</dimen> + <dimen name="waterfall_display_bottom_edge_size">0dp</dimen> +</resources> + + diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/strings.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/strings.xml new file mode 100644 index 000000000000..ed073d0e244e --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/strings.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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="display_cutout_emulation_overlay">Waterfall cutout</string> + +</resources>
\ No newline at end of file diff --git a/services/Android.bp b/services/Android.bp index 28c8aeea4e53..32394f4fe5d9 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -75,7 +75,7 @@ java_library { libs: [ "android.hidl.manager-V1.0-java", - "framework-tethering" + "framework-tethering-stubs", ], plugins: [ diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index a5877ccbde7c..565ee63d89ab 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -16,6 +16,8 @@ package com.android.server.accessibility; +import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID; +import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER; import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS; @@ -36,10 +38,11 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; -import android.graphics.Bitmap; +import android.graphics.GraphicBuffer; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; @@ -50,6 +53,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -71,6 +75,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.compat.IPlatformCompat; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; +import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection; import com.android.server.wm.ActivityTaskManagerInternal; @@ -106,6 +111,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ private final PowerManager mPowerManager; private final IPlatformCompat mIPlatformCompat; + private final Handler mMainHandler; + // Handler for scheduling method invocations on the main thread. public final InvocationHandler mInvocationHandler; @@ -238,6 +245,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mSecurityPolicy = securityPolicy; mSystemActionPerformer = systemActionPerfomer; mSystemSupport = systemSupport; + mMainHandler = mainHandler; mInvocationHandler = new InvocationHandler(mainHandler.getLooper()); mA11yWindowManager = a11yWindowManager; mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); @@ -959,52 +967,72 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled); } - @Nullable @Override - public Bitmap takeScreenshot(int displayId) { + public void takeScreenshot(int displayId, RemoteCallback callback) { synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { - return null; + sendScreenshotResult(true, null, callback); + return; } if (!mSecurityPolicy.canTakeScreenshotLocked(this)) { - return null; + sendScreenshotResult(true, null, callback); + throw new SecurityException("Services don't have the capability of taking" + + " the screenshot."); } } if (!mSecurityPolicy.checkAccessibilityAccess(this)) { - return null; + sendScreenshotResult(true, null, callback); + return; } final Display display = DisplayManagerGlobal.getInstance() .getRealDisplay(displayId); if (display == null) { - return null; + sendScreenshotResult(true, null, callback); + return; } - final Point displaySize = new Point(); - display.getRealSize(displaySize); - final int rotation = display.getRotation(); - Bitmap screenShot = null; + sendScreenshotResult(false, display, callback); + } + private void sendScreenshotResult(boolean noResult, Display display, RemoteCallback callback) { + final boolean noScreenshot = noResult; final long identity = Binder.clearCallingIdentity(); try { - final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y); - // TODO (b/145893483): calling new API with the display as a parameter - // when surface control supported. - screenShot = SurfaceControl.screenshot(crop, displaySize.x, displaySize.y, - rotation); - if (screenShot != null) { - // Optimization for telling the bitmap that all of the pixels are known to be - // opaque (false). This is meant as a drawing hint, as in some cases a bitmap - // that is known to be opaque can take a faster drawing case than one that may - // have non-opaque per-pixel alpha values. - screenShot.setHasAlpha(false); - } + mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { + if (noScreenshot) { + callback.sendResult(null); + return; + } + final Point displaySize = new Point(); + // TODO (b/145893483): calling new API with the display as a parameter + // when surface control supported. + final IBinder token = SurfaceControl.getInternalDisplayToken(); + final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y); + final int rotation = display.getRotation(); + display.getRealSize(displaySize); + + final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer = + SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, crop, + displaySize.x, displaySize.y, false, + rotation); + final GraphicBuffer graphicBuffer = screenshotBuffer.getGraphicBuffer(); + final HardwareBuffer hardwareBuffer = + HardwareBuffer.createFromGraphicBuffer(graphicBuffer); + final int colorSpaceId = screenshotBuffer.getColorSpace().getId(); + + // Send back the result. + final Bundle payload = new Bundle(); + payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, + hardwareBuffer); + payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID, colorSpaceId); + callback.sendResult(payload); + }, null).recycleOnUse()); } finally { Binder.restoreCallingIdentity(identity); } - return screenShot; } @Override diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 25911a7ed0ea..edb4445151d5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -16,8 +16,6 @@ package com.android.server.accessibility; -import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT; - import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.Manifest; @@ -27,20 +25,16 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; -import android.graphics.Bitmap; import android.os.Binder; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Process; -import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; import android.view.Display; -import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -393,15 +387,4 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } } } - - @Override - public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) { - mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { - final Bitmap screenshot = super.takeScreenshot(displayId); - // Send back the result. - final Bundle payload = new Bundle(); - payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT, screenshot); - callback.sendResult(payload); - }, null).recycleOnUse()); - } } diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 5d9af26a8339..d1c3a02c6761 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -328,6 +328,6 @@ class UiAutomationManager { public void onFingerprintGesture(int gesture) {} @Override - public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {} + public void takeScreenshot(int displayId, RemoteCallback callback) {} } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 1cb9313d9bf9..4474f608f955 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -82,6 +82,7 @@ import com.android.server.LocalServices; import com.android.server.autofill.AutofillManagerService.AutofillCompatState; import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks; import com.android.server.autofill.ui.AutoFillUI; +import com.android.server.contentcapture.ContentCaptureManagerInternal; import com.android.server.infra.AbstractPerUserSystemService; import com.android.server.inputmethod.InputMethodManagerInternal; @@ -180,6 +181,8 @@ final class AutofillManagerServiceImpl private final InputMethodManagerInternal mInputMethodManagerInternal; + private final ContentCaptureManagerInternal mContentCaptureManagerInternal; + AutofillManagerServiceImpl(AutofillManagerService master, Object lock, LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui, AutofillCompatState autofillCompatState, @@ -192,10 +195,22 @@ final class AutofillManagerServiceImpl mFieldClassificationStrategy = new FieldClassificationStrategy(getContext(), userId); mAutofillCompatState = autofillCompatState; mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class); + mContentCaptureManagerInternal = LocalServices.getService( + ContentCaptureManagerInternal.class); updateLocked(disabled); } + boolean sendActivityAssistDataToContentCapture(@NonNull IBinder activityToken, + @NonNull Bundle data) { + if (mContentCaptureManagerInternal != null) { + mContentCaptureManagerInternal.sendActivityAssistData(getUserId(), activityToken, data); + return true; + } + + return false; + } + @GuardedBy("mLock") void onBackKeyPressed() { final RemoteAugmentedAutofillService remoteService = diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index 880c40158114..d93ac68bde1e 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -166,8 +166,8 @@ final class RemoteAugmentedAutofillService public void onSuccess(@Nullable Dataset[] inlineSuggestionsData) { mCallbacks.resetLastResponse(); maybeRequestShowInlineSuggestions(sessionId, - inlineSuggestionsData, focusedId, - inlineSuggestionsCallback, client, + inlineSuggestionsRequest, inlineSuggestionsData, + focusedId, inlineSuggestionsCallback, client, onErrorCallback); requestAutofill.complete(null); } @@ -231,10 +231,12 @@ final class RemoteAugmentedAutofillService } private void maybeRequestShowInlineSuggestions(int sessionId, - @Nullable Dataset[] inlineSuggestionsData, @NonNull AutofillId focusedId, + @Nullable InlineSuggestionsRequest request, @Nullable Dataset[] inlineSuggestionsData, + @NonNull AutofillId focusedId, @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback, @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback) { - if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null) { + if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null + || request == null) { return; } mCallbacks.setLastResponse(sessionId); @@ -242,7 +244,7 @@ final class RemoteAugmentedAutofillService try { inlineSuggestionsCallback.onInlineSuggestionsResponse( InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse( - inlineSuggestionsData, focusedId, mContext, + request, inlineSuggestionsData, focusedId, mContext, dataset -> { mCallbacks.logAugmentedAutofillSelected(sessionId, dataset.getId()); diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 7e5123c82054..a25d7353edcb 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -314,6 +314,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private final InputMethodManagerInternal mInputMethodManagerInternal; @Nullable + @GuardedBy("mLock") private InlineSuggestionsRequestCallbackImpl mInlineSuggestionsRequestCallback; /** @@ -423,6 +424,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState suggestionsRequest); } + if (mActivityToken != null) { + mService.sendActivityAssistDataToContentCapture(mActivityToken, resultData); + } mRemoteFillService.onFillRequest(request); } @@ -2674,11 +2678,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (response.supportsInlineSuggestions()) { - if (requestShowInlineSuggestions(response)) { - //TODO(b/137800469): Fix it to log showed only when IME asks for inflation, rather - // than here where framework sends back the response. - mService.logDatasetShown(id, mClientState); - return; + synchronized (mLock) { + if (requestShowInlineSuggestions(response, mInlineSuggestionsRequestCallback)) { + //TODO(b/137800469): Fix it to log showed only when IME asks for inflation, + // rather than here where framework sends back the response. + mService.logDatasetShown(id, mClientState); + return; + } } } @@ -2716,30 +2722,32 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Returns whether we made a request to show inline suggestions. */ - private boolean requestShowInlineSuggestions(FillResponse response) { - final IInlineSuggestionsResponseCallback inlineContentCallback = - mInlineSuggestionsRequestCallback != null - ? mInlineSuggestionsRequestCallback.getResponseCallback() : null; - if (inlineContentCallback == null) { - Log.w(TAG, "Session input method callback is not set yet"); - return false; - } - + private boolean requestShowInlineSuggestions(@NonNull FillResponse response, + @Nullable InlineSuggestionsRequestCallbackImpl callback) { final List<Dataset> datasets = response.getDatasets(); if (datasets == null) { Log.w(TAG, "response returned null datasets"); return false; } + if (callback == null || callback.getRequest() == null + || callback.getResponseCallback() == null) { + Log.w(TAG, "Session input method callback is not set yet"); + return false; + } + + final InlineSuggestionsRequest request = callback.getRequest(); InlineSuggestionsResponse inlineSuggestionsResponse = - InlineSuggestionFactory.createInlineSuggestionsResponse(response.getRequestId(), - datasets.toArray(new Dataset[]{}), mCurrentViewId, mContext, this, () -> { - synchronized (mLock) { - requestHideFillUi(mCurrentViewId); - } - }); - try { - inlineContentCallback.onInlineSuggestionsResponse(inlineSuggestionsResponse); + InlineSuggestionFactory.createInlineSuggestionsResponse(request, + response.getRequestId(), + datasets.toArray(new Dataset[]{}), response.getInlineActions(), + mCurrentViewId, mContext, this, () -> { + synchronized (mLock) { + requestHideFillUi(mCurrentViewId); + } + }); + try { + callback.getResponseCallback().onInlineSuggestionsResponse(inlineSuggestionsResponse); } catch (RemoteException e) { Log.w(TAG, "onFillReady() remote error calling onInlineSuggestionsResponse()"); return false; diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java index 38a5b5b1cdaa..95a4a191a52e 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java @@ -28,16 +28,20 @@ import android.util.Slog; import android.view.SurfaceControl; import android.view.View; import android.view.autofill.AutofillId; +import android.view.inline.InlinePresentationSpec; import android.view.inputmethod.InlineSuggestion; import android.view.inputmethod.InlineSuggestionInfo; +import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InlineSuggestionsResponse; +import android.widget.Toast; -import com.android.internal.util.function.QuadFunction; import com.android.internal.view.inline.IInlineContentCallback; import com.android.internal.view.inline.IInlineContentProvider; import com.android.server.UiThread; import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; public final class InlineSuggestionFactory { private static final String TAG = "InlineSuggestionFactory"; @@ -57,46 +61,47 @@ public final class InlineSuggestionFactory { * augmented autofill service. */ public static InlineSuggestionsResponse createAugmentedInlineSuggestionsResponse( + @NonNull InlineSuggestionsRequest request, @NonNull Dataset[] datasets, @NonNull AutofillId autofillId, @NonNull Context context, @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback, @NonNull Runnable onErrorCallback) { - return createInlineSuggestionsResponseInternal(datasets, autofillId, - context, onErrorCallback, (dataset, inlinePresentation, inlineSuggestionUi, - filedIndex) -> createAugmentedInlineSuggestion(dataset, - inlinePresentation, inlineSuggestionUi, inlineSuggestionUiCallback)); + if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called"); + return createInlineSuggestionsResponseInternal(/* isAugmented= */ true, request, + datasets, /* inlineActions= */ null, autofillId, context, onErrorCallback, + (dataset, filedIndex) -> (v -> inlineSuggestionUiCallback.autofill(dataset))); } /** * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by the * autofill service. */ - public static InlineSuggestionsResponse createInlineSuggestionsResponse(int requestId, + public static InlineSuggestionsResponse createInlineSuggestionsResponse( + @NonNull InlineSuggestionsRequest request, int requestId, @NonNull Dataset[] datasets, + @Nullable List<InlinePresentation> inlineActions, @NonNull AutofillId autofillId, @NonNull Context context, @NonNull AutoFillUI.AutoFillUiCallback client, @NonNull Runnable onErrorCallback) { - return createInlineSuggestionsResponseInternal(datasets, autofillId, - context, onErrorCallback, (dataset, inlinePresentation, inlineSuggestionUi, - filedIndex) -> createInlineSuggestion(requestId, dataset, filedIndex, - inlinePresentation, inlineSuggestionUi, client)); + if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called"); + return createInlineSuggestionsResponseInternal(/* isAugmented= */ false, request, datasets, + inlineActions, autofillId, context, onErrorCallback, + (dataset, filedIndex) -> (v -> client.fill(requestId, filedIndex, dataset))); } private static InlineSuggestionsResponse createInlineSuggestionsResponseInternal( - @NonNull Dataset[] datasets, - @NonNull AutofillId autofillId, - @NonNull Context context, + boolean isAugmented, @NonNull InlineSuggestionsRequest request, + @NonNull Dataset[] datasets, @Nullable List<InlinePresentation> inlineActions, + @NonNull AutofillId autofillId, @NonNull Context context, @NonNull Runnable onErrorCallback, - @NonNull QuadFunction<Dataset, InlinePresentation, InlineSuggestionUi, - Integer, InlineSuggestion> suggestionFactory) { - if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called"); - + @NonNull BiFunction<Dataset, Integer, View.OnClickListener> onClickListenerFactory) { final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>(); final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context, onErrorCallback); - for (Dataset dataset : datasets) { + for (int i = 0; i < datasets.length; i++) { + final Dataset dataset = datasets[i]; final int fieldIndex = dataset.getFieldIds().indexOf(autofillId); if (fieldIndex < 0) { Slog.w(TAG, "AutofillId=" + autofillId + " not found in dataset"); @@ -108,50 +113,82 @@ public final class InlineSuggestionFactory { Slog.w(TAG, "InlinePresentation not found in dataset"); return null; } - InlineSuggestion inlineSuggestion = suggestionFactory.apply(dataset, - inlinePresentation, inlineSuggestionUi, fieldIndex); + InlineSuggestion inlineSuggestion = createInlineSuggestion(isAugmented, dataset, + fieldIndex, mergedInlinePresentation(request, i, inlinePresentation), + inlineSuggestionUi, onClickListenerFactory); inlineSuggestions.add(inlineSuggestion); } + if (inlineActions != null) { + for (InlinePresentation inlinePresentation : inlineActions) { + final InlineSuggestion inlineAction = createInlineAction(isAugmented, context, + mergedInlinePresentation(request, 0, inlinePresentation), + inlineSuggestionUi); + inlineSuggestions.add(inlineAction); + } + } return new InlineSuggestionsResponse(inlineSuggestions); } - private static InlineSuggestion createAugmentedInlineSuggestion(@NonNull Dataset dataset, + private static InlineSuggestion createInlineAction(boolean isAugmented, + @NonNull Context context, @NonNull InlinePresentation inlinePresentation, - @NonNull InlineSuggestionUi inlineSuggestionUi, - @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback) { + @NonNull InlineSuggestionUi inlineSuggestionUi) { // TODO(b/146453195): fill in the autofill hint properly. final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo( inlinePresentation.getInlinePresentationSpec(), - InlineSuggestionInfo.SOURCE_PLATFORM, new String[]{""}, - InlineSuggestionInfo.TYPE_SUGGESTION); - final View.OnClickListener onClickListener = v -> - inlineSuggestionUiCallback.autofill(dataset); - final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo, + isAugmented ? InlineSuggestionInfo.SOURCE_PLATFORM + : InlineSuggestionInfo.SOURCE_AUTOFILL, new String[]{""}, + InlineSuggestionInfo.TYPE_ACTION); + final View.OnClickListener onClickListener = v -> { + // TODO(b/148567875): Launch the intent provided through the slice. This + // should be part of the UI renderer therefore will be moved to the support + // library. + Toast.makeText(context, "icon clicked", Toast.LENGTH_SHORT).show(); + }; + return new InlineSuggestion(inlineSuggestionInfo, createInlineContentProvider(inlinePresentation, inlineSuggestionUi, onClickListener)); - return inlineSuggestion; } - private static InlineSuggestion createInlineSuggestion(int requestId, + private static InlineSuggestion createInlineSuggestion(boolean isAugmented, @NonNull Dataset dataset, - int fieldIndex, - @NonNull InlinePresentation inlinePresentation, + int fieldIndex, @NonNull InlinePresentation inlinePresentation, @NonNull InlineSuggestionUi inlineSuggestionUi, - @NonNull AutoFillUI.AutoFillUiCallback client) { + @NonNull BiFunction<Dataset, Integer, View.OnClickListener> onClickListenerFactory) { // TODO(b/146453195): fill in the autofill hint properly. final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo( inlinePresentation.getInlinePresentationSpec(), - InlineSuggestionInfo.SOURCE_AUTOFILL, new String[]{""}, + isAugmented ? InlineSuggestionInfo.SOURCE_PLATFORM + : InlineSuggestionInfo.SOURCE_AUTOFILL, new String[]{""}, InlineSuggestionInfo.TYPE_SUGGESTION); - final View.OnClickListener onClickListener = v -> { - client.fill(requestId, fieldIndex, dataset); - }; + final View.OnClickListener onClickListener = onClickListenerFactory.apply(dataset, + fieldIndex); final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo, createInlineContentProvider(inlinePresentation, inlineSuggestionUi, onClickListener)); return inlineSuggestion; } + /** + * Returns an {@link InlinePresentation} with the style spec from the request/host, and + * everything else from the provided {@code inlinePresentation}. + */ + private static InlinePresentation mergedInlinePresentation( + @NonNull InlineSuggestionsRequest request, + int index, @NonNull InlinePresentation inlinePresentation) { + final List<InlinePresentationSpec> specs = request.getPresentationSpecs(); + if (specs.isEmpty()) { + return inlinePresentation; + } + InlinePresentationSpec specFromHost = specs.get(Math.min(specs.size() - 1, index)); + InlinePresentationSpec mergedInlinePresentation = new InlinePresentationSpec.Builder( + inlinePresentation.getInlinePresentationSpec().getMinSize(), + inlinePresentation.getInlinePresentationSpec().getMaxSize()).setStyle( + specFromHost.getStyle()).build(); + return new InlinePresentation(inlinePresentation.getSlice(), mergedInlinePresentation, + inlinePresentation.isPinned()); + } + private static IInlineContentProvider.Stub createInlineContentProvider( @NonNull InlinePresentation inlinePresentation, @NonNull InlineSuggestionUi inlineSuggestionUi, diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 583c5b593b88..32bca35009a8 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -426,18 +426,26 @@ final class ContentCapturePerUserService public boolean sendActivityAssistDataLocked(@NonNull IBinder activityToken, @NonNull Bundle data) { final int id = getSessionId(activityToken); + final Bundle assistData = data.getBundle(ASSIST_KEY_DATA); + final AssistStructure assistStructure = data.getParcelable(ASSIST_KEY_STRUCTURE); + final AssistContent assistContent = data.getParcelable(ASSIST_KEY_CONTENT); + final SnapshotData snapshotData = new SnapshotData(assistData, + assistStructure, assistContent); if (id != NO_SESSION_ID) { final ContentCaptureServerSession session = mSessions.get(id); - final Bundle assistData = data.getBundle(ASSIST_KEY_DATA); - final AssistStructure assistStructure = data.getParcelable(ASSIST_KEY_STRUCTURE); - final AssistContent assistContent = data.getParcelable(ASSIST_KEY_CONTENT); - final SnapshotData snapshotData = new SnapshotData(assistData, - assistStructure, assistContent); session.sendActivitySnapshotLocked(snapshotData); return true; - } else { - Slog.e(TAG, "Failed to notify activity assist data for activity: " + activityToken); } + + // We want to send an activity snapshot regardless of whether a content capture session is + // present or not since a content capture session is not required for this functionality + if (mRemoteService != null) { + mRemoteService.onActivitySnapshotRequest(NO_SESSION_ID, snapshotData); + Slog.d(TAG, "Notified activity assist data for activity: " + + activityToken + " without a session Id"); + return true; + } + return false; } diff --git a/services/core/Android.bp b/services/core/Android.bp index f33237f490e4..6fc6084de181 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -98,7 +98,7 @@ java_library_static { "android.hardware.tv.cec-V1.0-java", "android.hardware.vibrator-java", "app-compat-annotations", - "framework-tethering", + "framework-tethering-stubs", ], required: [ diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 60f420e17e3b..e17c1f8f8276 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -24,7 +24,7 @@ import android.bluetooth.BluetoothProfile; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.media.AudioDeviceAddress; +import android.media.AudioDevice; import android.media.AudioManager; import android.media.AudioRoutesInfo; import android.media.AudioSystem; @@ -402,7 +402,7 @@ import java.io.PrintWriter; } /*package*/ int setPreferredDeviceForStrategySync(int strategy, - @NonNull AudioDeviceAddress device) { + @NonNull AudioDevice device) { return mDeviceInventory.setPreferredDeviceForStrategySync(strategy, device); } @@ -543,7 +543,7 @@ import java.io.PrintWriter; sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj); } - /*package*/ void postSaveSetPreferredDeviceForStrategy(int strategy, AudioDeviceAddress device) + /*package*/ void postSaveSetPreferredDeviceForStrategy(int strategy, AudioDevice device) { sendILMsgNoDelay(MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY, SENDMSG_QUEUE, strategy, device); } @@ -904,7 +904,7 @@ import java.io.PrintWriter; } break; case MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY: { final int strategy = msg.arg1; - final AudioDeviceAddress device = (AudioDeviceAddress) msg.obj; + final AudioDevice device = (AudioDevice) msg.obj; mDeviceInventory.onSaveSetPreferredDevice(strategy, device); } break; case MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY: { diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 75d9dd817487..1f998c377c7b 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -23,7 +23,7 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; import android.content.Intent; -import android.media.AudioDeviceAddress; +import android.media.AudioDevice; import android.media.AudioDevicePort; import android.media.AudioFormat; import android.media.AudioManager; @@ -75,7 +75,7 @@ public class AudioDeviceInventory { private final ArrayMap<Integer, String> mApmConnectedDevices = new ArrayMap<>(); // List of preferred devices for strategies - private final ArrayMap<Integer, AudioDeviceAddress> mPreferredDevices = new ArrayMap<>(); + private final ArrayMap<Integer, AudioDevice> mPreferredDevices = new ArrayMap<>(); // the wrapper for AudioSystem static methods, allows us to spy AudioSystem private final @NonNull AudioSystemAdapter mAudioSystem; @@ -468,7 +468,7 @@ public class AudioDeviceInventory { } } - /*package*/ void onSaveSetPreferredDevice(int strategy, @NonNull AudioDeviceAddress device) { + /*package*/ void onSaveSetPreferredDevice(int strategy, @NonNull AudioDevice device) { mPreferredDevices.put(strategy, device); } @@ -480,7 +480,7 @@ public class AudioDeviceInventory { // /*package*/ int setPreferredDeviceForStrategySync(int strategy, - @NonNull AudioDeviceAddress device) { + @NonNull AudioDevice device) { final long identity = Binder.clearCallingIdentity(); final int status = mAudioSystem.setPreferredDeviceForStrategy(strategy, device); Binder.restoreCallingIdentity(identity); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 82a2f01d4857..342ce22066b6 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -65,7 +65,7 @@ import android.hardware.usb.UsbManager; import android.hidl.manager.V1_0.IServiceManager; import android.media.AudioAttributes; import android.media.AudioAttributes.AttributeSystemUsage; -import android.media.AudioDeviceAddress; +import android.media.AudioDevice; import android.media.AudioDeviceInfo; import android.media.AudioFocusInfo; import android.media.AudioFocusRequest; @@ -1712,7 +1712,7 @@ public class AudioService extends IAudioService.Stub // IPC methods /////////////////////////////////////////////////////////////////////////// /** @see AudioManager#setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceInfo) */ - public int setPreferredDeviceForStrategy(int strategy, AudioDeviceAddress device) { + public int setPreferredDeviceForStrategy(int strategy, AudioDevice device) { if (device == null) { return AudioSystem.ERROR; } @@ -1721,7 +1721,7 @@ public class AudioService extends IAudioService.Stub "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s", Binder.getCallingUid(), Binder.getCallingPid(), strategy, device.toString()); sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG)); - if (device.getRole() == AudioDeviceAddress.ROLE_INPUT) { + if (device.getRole() == AudioDevice.ROLE_INPUT) { Log.e(TAG, "Unsupported input routing in " + logString); return AudioSystem.ERROR; } @@ -1749,9 +1749,9 @@ public class AudioService extends IAudioService.Stub } /** @see AudioManager#getPreferredDeviceForStrategy(AudioProductStrategy) */ - public AudioDeviceAddress getPreferredDeviceForStrategy(int strategy) { + public AudioDevice getPreferredDeviceForStrategy(int strategy) { enforceModifyAudioRoutingPermission(); - AudioDeviceAddress[] devices = new AudioDeviceAddress[1]; + AudioDevice[] devices = new AudioDevice[1]; final long identity = Binder.clearCallingIdentity(); final int status = AudioSystem.getPreferredDeviceForStrategy(strategy, devices); Binder.restoreCallingIdentity(identity); @@ -1765,7 +1765,7 @@ public class AudioService extends IAudioService.Stub } /** @see AudioManager#getDevicesForAttributes(AudioAttributes) */ - public @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes( + public @NonNull ArrayList<AudioDevice> getDevicesForAttributes( @NonNull AudioAttributes attributes) { Objects.requireNonNull(attributes); enforceModifyAudioRoutingPermission(); diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 9d06b4257b62..a3086c050af1 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -17,7 +17,7 @@ package com.android.server.audio; import android.annotation.NonNull; -import android.media.AudioDeviceAddress; +import android.media.AudioDevice; import android.media.AudioSystem; import android.util.Log; @@ -86,12 +86,12 @@ public class AudioSystemAdapter { } /** - * Same as {@link AudioSystem#setPreferredDeviceForStrategy(int, AudioDeviceAddress)} + * Same as {@link AudioSystem#setPreferredDeviceForStrategy(int, AudioDevice)} * @param strategy * @param device * @return */ - public int setPreferredDeviceForStrategy(int strategy, @NonNull AudioDeviceAddress device) { + public int setPreferredDeviceForStrategy(int strategy, @NonNull AudioDevice device) { return AudioSystem.setPreferredDeviceForStrategy(strategy, device); } @@ -138,7 +138,7 @@ public class AudioSystemAdapter { } @Override - public int setPreferredDeviceForStrategy(int strategy, @NonNull AudioDeviceAddress device) { + public int setPreferredDeviceForStrategy(int strategy, @NonNull AudioDevice device) { return AudioSystem.AUDIO_STATUS_OK; } diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 0d88388742d2..0f549842a763 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -99,6 +99,33 @@ public class AuthService extends SystemService { public String[] getConfiguration(Context context) { return context.getResources().getStringArray(R.array.config_biometric_sensors); } + + /** + * Allows us to mock FingerprintService for testing + */ + @VisibleForTesting + public IFingerprintService getFingerprintService() { + return IFingerprintService.Stub.asInterface( + ServiceManager.getService(Context.FINGERPRINT_SERVICE)); + } + + /** + * Allows us to mock FaceService for testing + */ + @VisibleForTesting + public IFaceService getFaceService() { + return IFaceService.Stub.asInterface( + ServiceManager.getService(Context.FACE_SERVICE)); + } + + /** + * Allows us to mock IrisService for testing + */ + @VisibleForTesting + public IIrisService getIrisService() { + return IIrisService.Stub.asInterface( + ServiceManager.getService(Context.IRIS_SERVICE)); + } } private final class AuthServiceImpl extends IAuthService.Stub { @@ -178,7 +205,6 @@ public class AuthService extends SystemService { mInjector = injector; mImpl = new AuthServiceImpl(); - final PackageManager pm = context.getPackageManager(); } private void registerAuthenticator(SensorConfig config) throws RemoteException { @@ -191,18 +217,36 @@ public class AuthService extends SystemService { switch (config.mModality) { case TYPE_FINGERPRINT: - authenticator = new FingerprintAuthenticator(IFingerprintService.Stub.asInterface( - ServiceManager.getService(Context.FINGERPRINT_SERVICE))); + final IFingerprintService fingerprintService = mInjector.getFingerprintService(); + if (fingerprintService == null) { + Slog.e(TAG, "Attempting to register with null FingerprintService. Please check" + + " your device configuration."); + return; + } + + authenticator = new FingerprintAuthenticator(fingerprintService); break; case TYPE_FACE: - authenticator = new FaceAuthenticator(IFaceService.Stub.asInterface( - ServiceManager.getService(Context.FACE_SERVICE))); + final IFaceService faceService = mInjector.getFaceService(); + if (faceService == null) { + Slog.e(TAG, "Attempting to register with null FaceService. Please check your" + + " device configuration."); + return; + } + + authenticator = new FaceAuthenticator(faceService); break; case TYPE_IRIS: - authenticator = new IrisAuthenticator(IIrisService.Stub.asInterface( - ServiceManager.getService(Context.IRIS_SERVICE))); + final IIrisService irisService = mInjector.getIrisService(); + if (irisService == null) { + Slog.e(TAG, "Attempting to register with null IrisService. Please check your" + + " device configuration."); + return; + } + + authenticator = new IrisAuthenticator(irisService); break; default: diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java index 2992f1e47dc7..a7e1a2876f81 100644 --- a/services/core/java/com/android/server/display/WifiDisplayController.java +++ b/services/core/java/com/android/server/display/WifiDisplayController.java @@ -291,7 +291,7 @@ final class WifiDisplayController implements DumpUtils.Dump { mWfdEnabling = true; WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo(); - wfdInfo.setWfdEnabled(true); + wfdInfo.setEnabled(true); wfdInfo.setDeviceType(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE); wfdInfo.setSessionAvailable(true); wfdInfo.setControlPort(DEFAULT_CONTROL_PORT); @@ -323,7 +323,7 @@ final class WifiDisplayController implements DumpUtils.Dump { // WFD should be disabled. if (mWfdEnabled || mWfdEnabling) { WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo(); - wfdInfo.setWfdEnabled(false); + wfdInfo.setEnabled(false); mWifiP2pManager.setWfdInfo(mWifiP2pChannel, wfdInfo, new ActionListener() { @Override public void onSuccess() { @@ -1044,7 +1044,7 @@ final class WifiDisplayController implements DumpUtils.Dump { private static boolean isWifiDisplay(WifiP2pDevice device) { WifiP2pWfdInfo wfdInfo = device.getWfdInfo(); return wfdInfo != null - && wfdInfo.isWfdEnabled() + && wfdInfo.isEnabled() && isPrimarySinkDeviceType(wfdInfo.getDeviceType()); } diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index 9754b6d4db02..0450647b9403 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -39,13 +39,20 @@ import android.content.integrity.Rule; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.content.pm.PackageParser; +import android.content.pm.PackageUserState; import android.content.pm.ParceledListSlice; import android.content.pm.Signature; +import android.content.pm.SigningInfo; +import android.content.pm.parsing.ApkParseUtils; +import android.content.pm.parsing.PackageInfoUtils; +import android.content.pm.parsing.ParsedPackage; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; +import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; @@ -67,6 +74,7 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -121,11 +129,11 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { IntegrityFileManager.getInstance(), handlerThread.getThreadHandler(), Settings.Global.getInt( - context.getContentResolver(), - Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, - 0) - == 1 - ); + context.getContentResolver(), + Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, + 0) + == 1 + ); } @VisibleForTesting @@ -260,16 +268,25 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { return; } - String appCert = getCertificateFingerprint(packageInfo); + List<String> appCertificates = getCertificateFingerprint(packageInfo); + List<String> installerCertificates = + getInstallerCertificateFingerprint(installerPackageName); + + // TODO (b/148373316): Figure out what field contains which fields are populated for + // rotated and the multiple signers. Until then, return the first certificate. + String appCert = appCertificates.isEmpty() ? "" : appCertificates.get(0); + String installerCert = + installerCertificates.isEmpty() ? "" : installerCertificates.get(0); + + Slog.w(TAG, appCertificates.toString()); AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder(); builder.setPackageName(getPackageNameNormalized(packageName)); - builder.setAppCertificate(appCert == null ? "" : appCert); + builder.setAppCertificate(appCert); builder.setVersionCode(intent.getLongExtra(EXTRA_LONG_VERSION_CODE, -1)); builder.setInstallerName(getPackageNameNormalized(installerPackageName)); - builder.setInstallerCertificate( - getInstallerCertificateFingerprint(installerPackageName)); + builder.setInstallerCertificate(installerCert); builder.setIsPreInstalled(isSystemApp(packageName)); AppInstallMetadata appInstallMetadata = builder.build(); @@ -320,7 +337,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { * Verify the UID and return the installer package name. * * @return the package name of the installer, or null if it cannot be determined or it is - * installed via adb. + * installed via adb. */ @Nullable private String getInstallerPackageName(Intent intent) { @@ -399,23 +416,27 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { } } - private String getCertificateFingerprint(@NonNull PackageInfo packageInfo) { - return getFingerprint(getSignature(packageInfo)); - } - - private String getInstallerCertificateFingerprint(String installer) { + private List<String> getInstallerCertificateFingerprint(String installer) { if (installer.equals(ADB_INSTALLER) || installer.equals(UNKNOWN_INSTALLER)) { - return INSTALLER_CERT_NOT_APPLICABLE; + return Collections.emptyList(); } try { PackageInfo installerInfo = mContext.getPackageManager() - .getPackageInfo(installer, PackageManager.GET_SIGNATURES); + .getPackageInfo(installer, PackageManager.GET_SIGNING_CERTIFICATES); return getCertificateFingerprint(installerInfo); } catch (PackageManager.NameNotFoundException e) { Slog.i(TAG, "Installer package " + installer + " not found."); - return ""; + return Collections.emptyList(); + } + } + + private List<String> getCertificateFingerprint(@NonNull PackageInfo packageInfo) { + ArrayList<String> certificateFingerprints = new ArrayList(); + for (Signature signature : getSignatures(packageInfo)) { + certificateFingerprints.add(getFingerprint(signature)); } + return certificateFingerprints; } /** Get the allowed installers and their associated certificate hashes from <meta-data> tag. */ @@ -445,12 +466,15 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { return packageCertMap; } - private static Signature getSignature(@NonNull PackageInfo packageInfo) { - if (packageInfo.signatures == null || packageInfo.signatures.length < 1) { + private static Signature[] getSignatures(@NonNull PackageInfo packageInfo) { + SigningInfo signingInfo = packageInfo.signingInfo; + + if (signingInfo == null || signingInfo.getApkContentsSigners().length < 1) { throw new IllegalArgumentException("Package signature not found in " + packageInfo); } - // Only the first element is guaranteed to be present. - return packageInfo.signatures[0]; + + // We are only interested in evaluating the active signatures. + return signingInfo.getApkContentsSigners(); } private static String getFingerprint(Signature cert) { @@ -489,20 +513,14 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { if (installationPath == null) { throw new IllegalArgumentException("Installation path is null, package not found"); } - PackageInfo packageInfo; + + PackageParser parser = new PackageParser(); try { - // The installation path will be a directory for a multi-apk install on L+ - if (installationPath.isDirectory()) { - packageInfo = getMultiApkInfo(installationPath); - } else { - packageInfo = - mContext.getPackageManager() - .getPackageArchiveInfo( - installationPath.getPath(), - PackageManager.GET_SIGNATURES - | PackageManager.GET_META_DATA); - } - return packageInfo; + ParsedPackage pkg = parser.parseParsedPackage(installationPath, 0, false); + int flags = PackageManager.GET_SIGNING_CERTIFICATES | PackageManager.GET_META_DATA; + ApkParseUtils.collectCertificates(pkg, false); + return PackageInfoUtils.generate(pkg, null, flags, 0, 0, null, new PackageUserState(), + UserHandle.getCallingUserId()); } catch (Exception e) { throw new IllegalArgumentException("Exception reading " + dataUri, e); } @@ -515,7 +533,8 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { mContext.getPackageManager() .getPackageArchiveInfo( baseFile.getAbsolutePath(), - PackageManager.GET_SIGNATURES | PackageManager.GET_META_DATA); + PackageManager.GET_SIGNING_CERTIFICATES + | PackageManager.GET_META_DATA); if (basePackageInfo == null) { for (File apkFile : multiApkDirectory.listFiles()) { diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java index 669f1ac6750c..647430399bc9 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java +++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java @@ -27,6 +27,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.media.AudioManager; import android.media.MediaRoute2Info; import android.text.TextUtils; import android.util.Slog; @@ -55,6 +56,7 @@ class BluetoothRouteProvider { private final Context mContext; private final BluetoothAdapter mBluetoothAdapter; private final BluetoothRoutesUpdatedListener mListener; + private final AudioManager mAudioManager; private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>(); private final IntentFilter mIntentFilter = new IntentFilter(); private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver(); @@ -82,6 +84,7 @@ class BluetoothRouteProvider { mContext = context; mBluetoothAdapter = btAdapter; mListener = listener; + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); buildBluetoothRoutes(); mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP); @@ -181,12 +184,15 @@ class BluetoothRouteProvider { private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) { BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo(); newBtRoute.btDevice = device; + // Current / Max volume will be set when connected. + // TODO: Is there any BT device which has fixed volume? newBtRoute.route = new MediaRoute2Info.Builder(device.getAddress(), device.getName()) .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO) .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED) .setDescription(mContext.getResources().getText( R.string.bluetooth_a2dp_audio_route_name).toString()) .setDeviceType(MediaRoute2Info.DEVICE_TYPE_BLUETOOTH) + .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .build(); newBtRoute.connectedProfiles = new SparseBooleanArray(); return newBtRoute; @@ -203,10 +209,20 @@ class BluetoothRouteProvider { Slog.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null"); return; } - if (btRoute.route.getConnectionState() != state) { - btRoute.route = new MediaRoute2Info.Builder(btRoute.route) - .setConnectionState(state).build(); + if (btRoute.route.getConnectionState() == state) { + return; + } + + // Update volume when the connection state is changed. + MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.route) + .setConnectionState(state); + + if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) { + int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + int currentVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); + builder.setVolumeMax(maxVolume).setVolume(currentVolume); } + btRoute.route = builder.build(); } interface BluetoothRoutesUpdatedListener { diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 888f7ce74ba4..b5dcea8bb51e 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -20,9 +20,11 @@ import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO; import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO; import android.annotation.NonNull; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.media.AudioManager; import android.media.AudioRoutesInfo; import android.media.IAudioRoutesObserver; @@ -107,6 +109,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } }); initializeSessionInfo(); + + mContext.registerReceiver(new VolumeChangeReceiver(), + new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION)); } @Override @@ -278,4 +283,44 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } mCallback.onSessionUpdated(this, sessionInfo); } + + private class VolumeChangeReceiver extends BroadcastReceiver { + // This will be called in the main thread. + @Override + public void onReceive(Context context, Intent intent) { + if (!intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) { + return; + } + + final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); + if (streamType != AudioManager.STREAM_MUSIC) { + return; + } + + final int newVolume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); + final int oldVolume = intent.getIntExtra( + AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0); + + if (newVolume != oldVolume) { + String activeBtDeviceAddress = mBtRouteProvider.getActiveDeviceAddress(); + if (!TextUtils.isEmpty(activeBtDeviceAddress)) { + for (int i = mBluetoothRoutes.size() - 1; i >= 0; i--) { + MediaRoute2Info route = mBluetoothRoutes.get(i); + if (TextUtils.equals(activeBtDeviceAddress, route.getId())) { + mBluetoothRoutes.set(i, + new MediaRoute2Info.Builder(route) + .setVolume(newVolume) + .build()); + break; + } + } + } else { + mDefaultRoute = new MediaRoute2Info.Builder(mDefaultRoute) + .setVolume(newVolume) + .build(); + } + publishRoutes(); + } + } + } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f07113591fa5..46dc21bbeaad 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -9256,7 +9256,7 @@ public class NotificationManagerService extends SystemService { } BackgroundThread.getHandler().post(() -> { - if (hasCompanionDevice(serviceInfo)) { + if (serviceInfo.isSystem || hasCompanionDevice(serviceInfo)) { notifyNotificationChannelChanged( serviceInfo, pkg, user, channel, modificationType); } @@ -9276,7 +9276,7 @@ public class NotificationManagerService extends SystemService { } BackgroundThread.getHandler().post(() -> { - if (hasCompanionDevice(serviceInfo)) { + if (serviceInfo.isSystem || hasCompanionDevice(serviceInfo)) { notifyNotificationChannelGroupChanged( serviceInfo, pkg, user, group, modificationType); } diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 42bc464e943f..a440c62a5f3c 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -36,6 +36,7 @@ import android.content.pm.parsing.AndroidPackage; import android.os.Environment; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.Trace; import android.sysprop.ApexProperties; import android.util.ArrayMap; import android.util.ArraySet; @@ -46,6 +47,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.utils.TimingsTraceAndSlog; import com.google.android.collect.Lists; @@ -375,8 +377,11 @@ public abstract class ApexManager { @Override public List<ActiveApexInfo> getActiveApexInfos() { + final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing", + Trace.TRACE_TAG_APEX_MANAGER); synchronized (mLock) { if (mActiveApexInfosCache == null) { + t.traceBegin("getActiveApexInfos_noCache"); try { mActiveApexInfosCache = new ArraySet<>(); final ApexInfo[] activePackages = mApexService.getActivePackages(); @@ -387,6 +392,7 @@ public abstract class ApexManager { } catch (RemoteException e) { Slog.e(TAG, "Unable to retrieve packages from apexservice", e); } + t.traceEnd(); } if (mActiveApexInfosCache != null) { return new ArrayList<>(mActiveApexInfosCache); diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java index e550bae7484b..482fc4944691 100644 --- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java +++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java @@ -344,18 +344,10 @@ final class PackageAbiHelperImpl implements PackageAbiHelper { int abi64 = PackageManager.NO_NATIVE_LIBRARIES; if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { if (extractLibs) { - if (onIncremental) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, - "incrementalNativeBinaries"); - abi32 = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg, - handle, nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS, - useIsaSpecificSubdirs); - } else { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries"); - abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, - nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS, - useIsaSpecificSubdirs); - } + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries"); + abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, + nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS, + useIsaSpecificSubdirs, onIncremental); } else { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi"); abi32 = NativeLibraryHelper.findSupportedAbi( @@ -375,18 +367,10 @@ final class PackageAbiHelperImpl implements PackageAbiHelper { if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { if (extractLibs) { - if (onIncremental) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, - "incrementalNativeBinaries"); - abi64 = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg, - handle, nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS, - useIsaSpecificSubdirs); - } else { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries"); - abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, - nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS, - useIsaSpecificSubdirs); - } + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries"); + abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, + nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS, + useIsaSpecificSubdirs, onIncremental); } else { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi"); abi64 = NativeLibraryHelper.findSupportedAbi( @@ -437,15 +421,9 @@ final class PackageAbiHelperImpl implements PackageAbiHelper { final int copyRet; if (extractLibs) { - if (onIncremental) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "incrementalNativeBinaries"); - copyRet = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg, - handle, nativeLibraryRoot, abiList, useIsaSpecificSubdirs); - } else { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries"); - copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, - nativeLibraryRoot, abiList, useIsaSpecificSubdirs); - } + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries"); + copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, + nativeLibraryRoot, abiList, useIsaSpecificSubdirs, onIncremental); } else { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi"); copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 9116c4032ad5..33ef2d43d720 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -129,6 +129,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements /** Upper bound on number of historical sessions for a UID */ private static final long MAX_HISTORICAL_SESSIONS = 1048576; + /** + * Allow verification-skipping if it's a development app installed through ADB with + * disable verification flag specified. + */ + private static final int ADB_DEV_MODE = PackageManager.INSTALL_FROM_ADB + | PackageManager.INSTALL_ALLOW_TEST; + private final Context mContext; private final PackageManagerService mPm; private final ApexManager mApexManager; @@ -531,8 +538,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements params.installFlags &= ~PackageManager.INSTALL_REQUEST_DOWNGRADE; } - if (callingUid != Process.SYSTEM_UID) { - // Only system_server can use INSTALL_DISABLE_VERIFICATION. + if (callingUid != Process.SYSTEM_UID + && (params.installFlags & ADB_DEV_MODE) != ADB_DEV_MODE) { + // Only system_server or tools under specific conditions (test app installed + // through ADB, and verification disabled flag specified) can disable verification. params.installFlags &= ~PackageManager.INSTALL_DISABLE_VERIFICATION; } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 0cf8b424b84d..944280d6db88 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1681,10 +1681,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mInternalProgress = 0.5f; computeProgressLocked(true); - // Unpack native libraries for non-incremental installation - if (!isIncrementalInstallation()) { - extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs()); - } + extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs()); } // We've reached point of no return; call into PMS to install the stage. @@ -2260,7 +2257,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir); } - private static void extractNativeLibraries(File packageDir, String abiOverride, boolean inherit) + private void extractNativeLibraries(File packageDir, String abiOverride, boolean inherit) throws PackageManagerException { final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME); if (!inherit) { @@ -2272,7 +2269,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { try { handle = NativeLibraryHelper.Handle.create(packageDir); final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir, - abiOverride); + abiOverride, isIncrementalInstallation()); if (res != PackageManager.INSTALL_SUCCEEDED) { throw new PackageManagerException(res, "Failed to extract native libraries, res=" + res); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index c85859072d89..88c048cea267 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3627,7 +3627,7 @@ public class PackageManagerService extends IPackageManager.Stub try { handle = NativeLibraryHelper.Handle.create(dstCodePath); ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot, - null /*abiOverride*/); + null /*abiOverride*/, false /*isIncremental*/); } catch (IOException e) { logCriticalInfo(Log.ERROR, "Failed to extract native libraries" + "; pkg: " + packageName); @@ -13350,42 +13350,53 @@ public class PackageManagerService extends IPackageManager.Stub * * @return true if verification should be performed */ - private boolean isVerificationEnabled(int userId, int installFlags, int installerUid) { + private boolean isVerificationEnabled( + PackageInfoLite pkgInfoLite, int userId, int installFlags, int installerUid) { if (!DEFAULT_VERIFY_ENABLE) { return false; } - if ((installFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0) { - return false; - } - // Check if installing from ADB if ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0) { if (isUserRestricted(userId, UserManager.ENSURE_VERIFY_APPS)) { return true; } - // Check if the developer does not want package verification for ADB installs + // Check if the developer wants to skip verification for ADB installs + if ((installFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0) { + synchronized (mLock) { + if (mSettings.mPackages.get(pkgInfoLite.packageName) == null) { + // Always verify fresh install + return true; + } + } + // Only skip when apk is debuggable + return !pkgInfoLite.debuggable; + } return Global.getInt(mContext.getContentResolver(), Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0; - } else { - // only when not installed from ADB, skip verification for instant apps when - // the installer and verifier are the same. - if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { - if (mInstantAppInstallerActivity != null - && mInstantAppInstallerActivity.packageName.equals( - mRequiredVerifierPackage)) { - try { - mInjector.getAppOpsManager() - .checkPackage(installerUid, mRequiredVerifierPackage); - if (DEBUG_VERIFY) { - Slog.i(TAG, "disable verification for instant app"); - } - return false; - } catch (SecurityException ignore) { } - } + } + + if ((installFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0) { + return false; + } + + // only when not installed from ADB, skip verification for instant apps when + // the installer and verifier are the same. + if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { + if (mInstantAppInstallerActivity != null + && mInstantAppInstallerActivity.packageName.equals( + mRequiredVerifierPackage)) { + try { + mInjector.getAppOpsManager() + .checkPackage(installerUid, mRequiredVerifierPackage); + if (DEBUG_VERIFY) { + Slog.i(TAG, "disable verification for instant app"); + } + return false; + } catch (SecurityException ignore) { } } - return true; } + return true; } /** @@ -14549,7 +14560,7 @@ public class PackageManagerService extends IPackageManager.Stub verificationInfo == null ? -1 : verificationInfo.installerUid; if (!origin.existing && requiredUid != -1 && isVerificationEnabled( - verifierUser.getIdentifier(), installFlags, installerUid)) { + pkgLite, verifierUser.getIdentifier(), installFlags, installerUid)) { final Intent verification = new Intent( Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); verification.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); @@ -14959,12 +14970,13 @@ public class PackageManagerService extends IPackageManager.Stub return ret; } + final boolean isIncremental = isIncrementalPath(codeFile.getAbsolutePath()); final File libraryRoot = new File(codeFile, LIB_DIR_NAME); NativeLibraryHelper.Handle handle = null; try { handle = NativeLibraryHelper.Handle.create(codeFile); ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot, - abiOverride); + abiOverride, isIncremental); } catch (IOException e) { Slog.e(TAG, "Copying native libraries failed", e); ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 71a5545c4131..9395c972f04a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -794,6 +794,7 @@ public class PackageManagerServiceUtils { ret.verifiers = pkg.verifiers; ret.recommendedInstallLocation = recommendedInstallLocation; ret.multiArch = pkg.multiArch; + ret.debuggable = pkg.debuggable; return ret; } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index bb69680fb9f9..cb9404397f3d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -2757,6 +2757,9 @@ class PackageManagerShellCommand extends ShellCommand { case "--no-wait": params.mWaitForStagedSessionReady = false; break; + case "--skip-verification": + sessionParams.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION; + break; default: throw new IllegalArgumentException("Unknown option " + opt); } diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 89354537526c..614cc3fc2f3a 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -57,6 +57,7 @@ import android.os.UserHandle; import android.os.UserManagerInternal; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; +import android.text.TextUtils; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; @@ -67,6 +68,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageHelper; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; +import com.android.server.rollback.WatchdogRollbackLogger; import java.io.File; import java.io.IOException; @@ -99,6 +101,10 @@ public class StagingManager { @GuardedBy("mStagedSessions") private final SparseIntArray mSessionRollbackIds = new SparseIntArray(); + @GuardedBy("mFailedPackageNames") + private final List<String> mFailedPackageNames = new ArrayList<>(); + private String mNativeFailureReason; + StagingManager(PackageInstallerService pi, Context context) { mPi = pi; mContext = context; @@ -441,6 +447,22 @@ public class StagingManager { } } + /** + * Prepares for the logging of apexd reverts by storing the native failure reason if necessary, + * and adding the package name of the session which apexd reverted to the list of reverted + * session package names. + * Logging needs to wait until the ACTION_BOOT_COMPLETED broadcast is sent. + */ + private void prepareForLoggingApexdRevert(@NonNull PackageInstallerSession session, + @NonNull String nativeFailureReason) { + synchronized (mFailedPackageNames) { + mNativeFailureReason = nativeFailureReason; + if (session.getPackageName() != null) { + mFailedPackageNames.add(session.getPackageName()); + } + } + } + private void resumeSession(@NonNull PackageInstallerSession session) { Slog.d(TAG, "Resuming session " + session.sessionId); @@ -450,6 +472,12 @@ public class StagingManager { // Check with apexservice whether the apex packages have been activated. apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId); + // Prepare for logging a native crash during boot, if one occurred. + if (apexSessionInfo != null && !TextUtils.isEmpty( + apexSessionInfo.crashingNativeProcess)) { + prepareForLoggingApexdRevert(session, apexSessionInfo.crashingNativeProcess); + } + if (apexSessionInfo != null && apexSessionInfo.isVerified) { // Session has been previously submitted to apexd, but didn't complete all the // pre-reboot verification, perhaps because the device rebooted in the meantime. @@ -955,12 +983,23 @@ public class StagingManager { } } + private void logFailedApexSessionsIfNecessary() { + synchronized (mFailedPackageNames) { + if (!mFailedPackageNames.isEmpty()) { + WatchdogRollbackLogger.logApexdRevert(mContext, + mFailedPackageNames, mNativeFailureReason); + } + } + } + void systemReady() { // Register the receiver of boot completed intent for staging manager. mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context ctx, Intent intent) { mPreRebootVerificationHandler.readyToStart(); + BackgroundThread.getExecutor().execute( + () -> logFailedApexSessionsIfNecessary()); ctx.unregisterReceiver(this); } }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 0cb8f49540be..1c02161c5b96 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1901,7 +1901,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public boolean hasBaseUserRestriction(String restrictionKey, @UserIdInt int userId) { - checkManageUsersPermission("hasBaseUserRestriction"); + checkManageOrCreateUsersPermission("hasBaseUserRestriction"); if (!UserRestrictionsUtils.isValidRestriction(restrictionKey)) { return false; } diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index 4d7af9cc0d44..b5da1c2ec58a 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -182,6 +182,15 @@ class Rollback { private int mNumPackageSessionsWithSuccess; /** + * A temp flag to facilitate merging of the 2 rollback collections managed by + * RollbackManagerServiceImpl. True if this rollback is in the process of enabling and was + * originally managed by RollbackManagerServiceImpl#mNewRollbacks. + * TODO: remove this flag when merge is completed. + */ + @GuardedBy("mLock") + private boolean mIsNewRollback = false; + + /** * Constructs a new, empty Rollback instance. * * @param rollbackId the id of the rollback. @@ -829,6 +838,18 @@ class Rollback { } } + void setIsNewRollback(boolean newRollback) { + synchronized (mLock) { + mIsNewRollback = newRollback; + } + } + + boolean isNewRollback() { + synchronized (mLock) { + return mIsNewRollback; + } + } + static String rollbackStateToString(@RollbackState int state) { switch (state) { case Rollback.ROLLBACK_STATE_ENABLING: return "enabling"; diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 8bd9533727d6..1421258c12f6 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -19,6 +19,7 @@ package com.android.server.rollback; import android.Manifest; import android.annotation.AnyThread; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.AppOpsManager; @@ -50,7 +51,6 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; -import android.util.ArraySet; import android.util.IntArray; import android.util.Log; import android.util.LongArrayQueue; @@ -121,10 +121,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { @GuardedBy("mLock") private final SparseBooleanArray mAllocatedRollbackIds = new SparseBooleanArray(); - // Rollbacks we are in the process of enabling. - @GuardedBy("mLock") - private final Set<Rollback> mNewRollbacks = new ArraySet<>(); - // The list of all rollbacks, including available and committed rollbacks. @GuardedBy("mLock") private final List<Rollback> mRollbacks; @@ -240,17 +236,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { Slog.v(TAG, "broadcast=ACTION_CANCEL_ENABLE_ROLLBACK token=" + token); } synchronized (mLock) { - Rollback found = null; - for (Rollback newRollback : mNewRollbacks) { - if (newRollback.hasToken(token)) { - found = newRollback; + for (int i = 0; i < mRollbacks.size(); ++i) { + Rollback rollback = mRollbacks.get(i); + if (rollback.hasToken(token) && rollback.isEnabling()) { + mRollbacks.remove(i); + rollback.delete(mAppDataRollbackHelper); break; } } - if (found != null) { - mNewRollbacks.remove(found); - found.delete(mAppDataRollbackHelper); - } } } } @@ -442,15 +435,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { rollback.delete(mAppDataRollbackHelper); } } - Iterator<Rollback> iter2 = mNewRollbacks.iterator(); - while (iter2.hasNext()) { - Rollback newRollback = iter2.next(); - if (newRollback.includesPackage(packageName)) { - iter2.remove(); - newRollback.delete(mAppDataRollbackHelper); - } - - } } } @@ -810,7 +794,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { newRollback = getNewRollbackForPackageSessionLocked(packageSession.getSessionId()); if (newRollback == null) { newRollback = createNewRollbackLocked(parentSession); - mNewRollbacks.add(newRollback); + mRollbacks.add(newRollback); + newRollback.setIsNewRollback(true); } } newRollback.addToken(token); @@ -818,34 +803,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return enableRollbackForPackageSession(newRollback, packageSession); } - @WorkerThread - private void removeRollbackForPackageSessionId(int sessionId) { - if (LOCAL_LOGV) { - Slog.v(TAG, "removeRollbackForPackageSessionId=" + sessionId); - } - - synchronized (mLock) { - Rollback newRollback = getNewRollbackForPackageSessionLocked(sessionId); - if (newRollback != null) { - Slog.w(TAG, "Delete new rollback id=" + newRollback.info.getRollbackId() - + " for session id=" + sessionId); - mNewRollbacks.remove(newRollback); - newRollback.delete(mAppDataRollbackHelper); - } - Iterator<Rollback> iter = mRollbacks.iterator(); - while (iter.hasNext()) { - Rollback rollback = iter.next(); - if (rollback.getStagedSessionId() == sessionId) { - Slog.w(TAG, "Delete rollback id=" + rollback.info.getRollbackId() - + " for session id=" + sessionId); - iter.remove(); - rollback.delete(mAppDataRollbackHelper); - break; - } - } - } - } - /** * Do code and userdata backups to enable rollback of the given session. * In case of multiPackage sessions, <code>session</code> should be one of @@ -966,15 +923,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { + " users=" + Arrays.toString(userIds)); } synchronized (mLock) { - // staged installs for (int i = 0; i < mRollbacks.size(); i++) { Rollback rollback = mRollbacks.get(i); rollback.snapshotUserData(packageName, userIds, mAppDataRollbackHelper); } - // non-staged installs - for (Rollback rollback : mNewRollbacks) { - rollback.snapshotUserData(packageName, userIds, mAppDataRollbackHelper); - } } } @@ -1200,7 +1152,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { synchronized (mLock) { newRollback = getNewRollbackForPackageSessionLocked(sessionId); if (newRollback != null && newRollback.notifySessionWithSuccess()) { - mNewRollbacks.remove(newRollback); + mRollbacks.remove(newRollback); + newRollback.setIsNewRollback(false); } else { // Not all child sessions finished with success. // Don't enable the rollback yet. @@ -1215,7 +1168,15 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } } } else { - removeRollbackForPackageSessionId(sessionId); + synchronized (mLock) { + Rollback rollback = getRollbackForSessionLocked(sessionId); + if (rollback != null && rollback.isEnabling()) { + Slog.w(TAG, "Delete rollback id=" + rollback.info.getRollbackId() + + " for failed session id=" + sessionId); + mRollbacks.remove(rollback); + rollback.delete(mAppDataRollbackHelper); + } + } } } } @@ -1376,6 +1337,25 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } /** + * Returns the Rollback associated with the given session if parent or child session id matches. + * Returns null if not found. + */ + @WorkerThread + @GuardedBy("mLock") + @Nullable + private Rollback getRollbackForSessionLocked(int sessionId) { + // We expect mRollbacks to be a very small list; linear search should be plenty fast. + for (int i = 0; i < mRollbacks.size(); ++i) { + Rollback rollback = mRollbacks.get(i); + if (rollback.getStagedSessionId() == sessionId + || rollback.containsSessionId(sessionId)) { + return rollback; + } + } + return null; + } + + /** * Returns the NewRollback associated with the given package session. * Returns null if no NewRollback is found for the given package * session. @@ -1383,11 +1363,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { @WorkerThread @GuardedBy("mLock") Rollback getNewRollbackForPackageSessionLocked(int packageSessionId) { - // We expect mNewRollbacks to be a very small list; linear search + // We expect mRollbacks to be a very small list; linear search // should be plenty fast. - for (Rollback newRollback: mNewRollbacks) { - if (newRollback.containsSessionId(packageSessionId)) { - return newRollback; + for (Rollback rollback: mRollbacks) { + if (rollback.isNewRollback() && rollback.containsSessionId(packageSessionId)) { + return rollback; } } return null; diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java index 46ec2f8258ca..f3f14a95eac6 100644 --- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java +++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java @@ -20,7 +20,12 @@ import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCU import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING; import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK; import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH; +import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT; import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN; +import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED; +import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE; +import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE; +import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS; import android.annotation.NonNull; import android.annotation.Nullable; @@ -36,7 +41,6 @@ import android.util.Slog; import android.util.StatsLog; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.FrameworkStatsLog; import com.android.server.PackageWatchdog; import java.util.ArrayList; @@ -58,8 +62,8 @@ public final class WatchdogRollbackLogger { private static String getLoggingParentName(Context context, @NonNull String packageName) { PackageManager packageManager = context.getPackageManager(); try { - ApplicationInfo ai = packageManager.getApplicationInfo(packageName, - PackageManager.GET_META_DATA); + int flags = PackageManager.MATCH_APEX | PackageManager.GET_META_DATA; + ApplicationInfo ai = packageManager.getPackageInfo(packageName, flags).applicationInfo; if (ai.metaData == null) { return null; } @@ -95,6 +99,22 @@ public final class WatchdogRollbackLogger { return loggingParent; } + + /** + * Gets the set of parent packages for a given set of failed package names. In the case that + * multiple sessions have failed, we want to log failure for each of the parent packages. + * Even if multiple failed packages have the same parent, we only log the parent package once. + */ + private static Set<VersionedPackage> getLogPackages(Context context, + @NonNull List<String> failedPackageNames) { + Set<VersionedPackage> parentPackages = new ArraySet<>(); + for (String failedPackageName: failedPackageNames) { + parentPackages.add(getLogPackage(context, new VersionedPackage(failedPackageName, 0))); + } + return parentPackages; + } + + static void logRollbackStatusOnBoot(Context context, int rollbackId, List<RollbackInfo> recentlyCommittedRollbacks) { PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller(); @@ -142,19 +162,36 @@ public final class WatchdogRollbackLogger { for (VersionedPackage oldLoggingPackage : oldLoggingPackages) { if (sessionInfo.isStagedSessionApplied()) { logEvent(oldLoggingPackage, - FrameworkStatsLog - .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS, + WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS, WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, ""); } else if (sessionInfo.isStagedSessionFailed()) { logEvent(oldLoggingPackage, - FrameworkStatsLog - .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE, + WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE, WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, ""); } } } /** + * Logs that one or more apexd reverts have occurred, along with the crashing native process + * that caused apexd to revert during boot. + * + * @param context the context to use when determining the log packages + * @param failedPackageNames a list of names of packages which were reverted + * @param failingNativeProcess the crashing native process which caused a revert + */ + public static void logApexdRevert(Context context, @NonNull List<String> failedPackageNames, + @NonNull String failingNativeProcess) { + Set<VersionedPackage> logPackages = getLogPackages(context, failedPackageNames); + for (VersionedPackage logPackage: logPackages) { + logEvent(logPackage, + WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS, + WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT, + failingNativeProcess); + } + } + + /** * Log a Watchdog rollback event to statsd. * * @param logPackage the package to associate the rollback with. @@ -196,14 +233,13 @@ public final class WatchdogRollbackLogger { private static String rollbackTypeToString(int type) { switch (type) { - case FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE: + case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE: return "ROLLBACK_INITIATE"; - case FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS: + case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS: return "ROLLBACK_SUCCESS"; - case FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE: + case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE: return "ROLLBACK_FAILURE"; - case FrameworkStatsLog - .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED: + case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED: return "ROLLBACK_BOOT_TRIGGERED"; default: return "UNKNOWN"; @@ -212,16 +248,16 @@ public final class WatchdogRollbackLogger { private static String rollbackReasonToString(int reason) { switch (reason) { - case FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH: + case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH: return "REASON_NATIVE_CRASH"; - case FrameworkStatsLog - .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK: + case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK: return "REASON_EXPLICIT_HEALTH_CHECK"; - case FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH: + case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH: return "REASON_APP_CRASH"; - case FrameworkStatsLog - .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING: + case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING: return "REASON_APP_NOT_RESPONDING"; + case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT: + return "REASON_NATIVE_CRASH_DURING_BOOT"; default: return "UNKNOWN"; } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 0418afaf033a..798665972a33 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -240,6 +240,10 @@ class InsetsSourceProvider { target = target.getWindow().getImeControlTarget(); } + if (mWin != null && mWin.getSurfaceControl() == null) { + // if window doesn't have a surface, set it null and return. + setWindow(null, null, null); + } if (mWin == null) { mControlTarget = target; return; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 87c91ef6b96c..b1db9d7889dd 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -79,11 +79,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; -import static com.android.server.wm.TaskProto.DISPLAYED_BOUNDS; -import static com.android.server.wm.TaskProto.FILLS_PARENT; -import static com.android.server.wm.TaskProto.SURFACE_HEIGHT; -import static com.android.server.wm.TaskProto.SURFACE_WIDTH; -import static com.android.server.wm.TaskProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; @@ -125,7 +120,6 @@ import android.provider.Settings; import android.service.voice.IVoiceInteractionSession; import android.util.DisplayMetrics; import android.util.Slog; -import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; import android.view.ITaskOrganizer; import android.view.RemoteAnimationTarget; @@ -3195,12 +3189,16 @@ class Task extends WindowContainer<WindowContainer> { info.lastActiveTime = lastActiveTime; info.taskDescription = new ActivityManager.TaskDescription(getTaskDescription()); info.supportsSplitScreenMultiWindow = supportsSplitScreenWindowingMode(); - info.resizeMode = mResizeMode; info.configuration.setTo(getConfiguration()); info.token = mRemoteToken; // Get's the first non-undefined activity type among this and children. Can't use // configuration.windowConfiguration because that would only be this level. info.topActivityType = getActivityType(); + + //TODO (AM refactor): Just use local once updateEffectiveIntent is run during all child + // order changes. + final Task top = getTopMostTask(); + info.resizeMode = top != null ? top.mResizeMode : mResizeMode; } /** diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 096541f57ba5..0a0530c92a16 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -23,6 +23,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; +import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; +import static com.android.server.wm.WindowContainer.POSITION_TOP; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; @@ -47,6 +49,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.WeakHashMap; @@ -375,6 +378,45 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub } } + @Override + public List<RunningTaskInfo> getChildTasks(IWindowContainer parent) { + enforceStackPermission("getChildTasks()"); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + if (parent == null) { + throw new IllegalArgumentException("Can't get children of null parent"); + } + final WindowContainer container = WindowContainer.fromBinder(parent.asBinder()); + if (container == null) { + Slog.e(TAG, "Can't get children of " + parent + " because it is not valid."); + return null; + } + // For now, only support returning children of persistent root tasks (of which the + // only current implementation is TaskTile). + if (!(container instanceof TaskTile)) { + Slog.w(TAG, "Can only get children of root tasks created via createRootTask"); + return null; + } + ArrayList<RunningTaskInfo> out = new ArrayList<>(); + // Tiles aren't real parents, so we need to go through stacks on the display to + // ensure correct ordering. + final DisplayContent dc = container.getDisplayContent(); + for (int i = dc.getStackCount() - 1; i >= 0; --i) { + final ActivityStack as = dc.getStackAt(i); + if (as.getTile() == container) { + final RunningTaskInfo info = new RunningTaskInfo(); + as.fillTaskInfo(info); + out.add(info); + } + } + return out; + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + private int sanitizeAndApplyChange(WindowContainer container, WindowContainerTransaction.Change change) { if (!(container instanceof Task)) { @@ -405,6 +447,54 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub return effects; } + private int sanitizeAndApplyHierarchyOp(WindowContainer container, + WindowContainerTransaction.HierarchyOp hop) { + if (!(container instanceof Task)) { + throw new IllegalArgumentException("Invalid container in hierarchy op"); + } + if (hop.isReparent()) { + // special case for tiles since they are "virtual" parents + if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) { + ActivityStack as = (ActivityStack) container; + TaskTile newParent = hop.getNewParent() == null ? null + : (TaskTile) WindowContainer.fromBinder(hop.getNewParent()); + if (as.getTile() != newParent) { + if (as.getTile() != null) { + as.getTile().removeChild(as); + } + if (newParent != null) { + if (!as.affectedBySplitScreenResize()) { + return 0; + } + newParent.addChild(as, POSITION_TOP); + } + } + if (hop.getToTop()) { + as.getDisplay().positionStackAtTop(as, false /* includingParents */); + } else { + as.getDisplay().positionStackAtBottom(as); + } + } else if (container instanceof Task) { + throw new RuntimeException("Reparenting leaf Tasks is not supported now."); + } + } else { + // Ugh, of course ActivityStack has its own special reorder logic... + if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) { + ActivityStack as = (ActivityStack) container; + if (hop.getToTop()) { + as.getDisplay().positionStackAtTop(as, false /* includingParents */); + } else { + as.getDisplay().positionStackAtBottom(as); + } + } else { + container.getParent().positionChildAt( + hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM, + container, false /* includingParents */); + } + } + return TRANSACT_EFFECTS_LIFECYCLE; + } + private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask, int windowMask, Configuration config) { if ((container instanceof ActivityStack) @@ -470,8 +560,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub while (entries.hasNext()) { final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next(); - final WindowContainer wc = WindowContainer.RemoteToken.fromBinder( - entry.getKey()).getContainer(); + final WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); int containerEffect = applyWindowContainerChange(wc, entry.getValue()); effects |= containerEffect; @@ -484,6 +573,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub mBLASTSyncEngine.addToSyncSet(syncId, wc); } } + // Hierarchy changes + final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps(); + for (int i = 0, n = hops.size(); i < n; ++i) { + final WindowContainerTransaction.HierarchyOp hop = hops.get(i); + final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); + effects |= sanitizeAndApplyHierarchyOp(wc, hop); + } if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) { // Already calls ensureActivityConfig mService.mRootWindowContainer.ensureActivitiesVisible( diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 9acb660967cb..504aa2dc7b4c 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2296,6 +2296,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return mRemoteToken; } + static WindowContainer fromBinder(IBinder binder) { + return RemoteToken.fromBinder(binder).getContainer(); + } + static class RemoteToken extends IWindowContainer.Stub { final WeakReference<WindowContainer> mWeakRef; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index ec6b6c43215b..633098566b1b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5687,6 +5687,12 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void setForceShowSystemBars(boolean show) { + boolean isAutomotive = mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_AUTOMOTIVE); + if (!isAutomotive) { + throw new UnsupportedOperationException("Force showing system bars is only supported" + + "for Automotive use cases."); + } if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Caller does not hold permission " diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 87b04b2ae68f..c7d00ec5daab 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -178,7 +178,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio private long mLastActivityFinishTime; // Last configuration that was reported to the process. - private final Configuration mLastReportedConfiguration; + private final Configuration mLastReportedConfiguration = new Configuration(); // Configuration that is waiting to be dispatched to the process. private Configuration mPendingConfiguration; private final Configuration mNewOverrideConfig = new Configuration(); @@ -192,7 +192,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio /** Whether our process is currently running a {@link IRemoteAnimationRunner} */ private boolean mRunningRemoteAnimation; - public WindowProcessController(ActivityTaskManagerService atm, ApplicationInfo info, + public WindowProcessController(@NonNull ActivityTaskManagerService atm, ApplicationInfo info, String name, int uid, int userId, Object owner, WindowProcessListener listener) { mInfo = info; mName = name; @@ -201,11 +201,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio mOwner = owner; mListener = listener; mAtm = atm; - mLastReportedConfiguration = new Configuration(); mDisplayId = INVALID_DISPLAY; - if (atm != null) { - onConfigurationChanged(atm.getGlobalConfiguration()); - } + onConfigurationChanged(atm.getGlobalConfiguration()); } public void setPid(int pid) { @@ -220,6 +217,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio public void setThread(IApplicationThread thread) { synchronized (mAtm.mGlobalLockWithoutBoost) { mThread = thread; + // In general this is called from attaching application, so the last configuration + // has been sent to client by {@link android.app.IApplicationThread#bindApplication}. + // If this process is system server, it is fine because system is booting and a new + // configuration will update when display is ready. + setLastReportedConfiguration(getConfiguration()); } } @@ -1060,7 +1062,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio mNewOverrideConfig.setTo(mergedOverrideConfig); mNewOverrideConfig.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED); super.onRequestedOverrideConfigurationChanged(mNewOverrideConfig); - updateConfiguration(); } private void updateConfiguration() { diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 390068e1fa75..812bc438246f 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -109,6 +109,7 @@ cc_defaults { "libinputservice", "libprotobuf-cpp-lite", "libprotoutil", + "libstatshidl", "libstatspull", "libstatssocket", "libstatslog", @@ -155,6 +156,7 @@ cc_defaults { "android.hardware.vr@1.0", "android.frameworks.schedulerservice@1.0", "android.frameworks.sensorservice@1.0", + "android.frameworks.stats@1.0", "android.system.suspend@1.0", "service.incremental", "suspend_control_aidl_interface-cpp", diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp index 67254b811ee0..279ea4b9a790 100644 --- a/services/core/jni/com_android_server_SystemServer.cpp +++ b/services/core/jni/com_android_server_SystemServer.cpp @@ -29,6 +29,7 @@ #include <schedulerservice/SchedulingPolicyService.h> #include <sensorservice/SensorService.h> #include <sensorservicehidl/SensorManager.h> +#include <stats/StatsHal.h> #include <bionic/malloc.h> #include <bionic/reserved_signals.h> @@ -59,6 +60,8 @@ static void android_server_SystemServer_startHidlServices(JNIEnv* env, jobject / using ::android::frameworks::schedulerservice::V1_0::implementation::SchedulingPolicyService; using ::android::frameworks::sensorservice::V1_0::ISensorManager; using ::android::frameworks::sensorservice::V1_0::implementation::SensorManager; + using ::android::frameworks::stats::V1_0::IStats; + using ::android::frameworks::stats::V1_0::implementation::StatsHal; using ::android::hardware::configureRpcThreadpool; status_t err; @@ -75,6 +78,10 @@ static void android_server_SystemServer_startHidlServices(JNIEnv* env, jobject / sp<ISchedulingPolicyService> schedulingService = new SchedulingPolicyService(); err = schedulingService->registerAsService(); ALOGE_IF(err != OK, "Cannot register %s: %d", ISchedulingPolicyService::descriptor, err); + + sp<IStats> statsHal = new StatsHal(); + err = statsHal->registerAsService(); + ALOGE_IF(err != OK, "Cannot register %s: %d", IStats::descriptor, err); } static void android_server_SystemServer_initZygoteChildHeapProfiling(JNIEnv* /* env */, diff --git a/services/people/java/com/android/server/people/data/CallLogQueryHelper.java b/services/people/java/com/android/server/people/data/CallLogQueryHelper.java index d825b6b2bd8f..45e0aac24ca7 100644 --- a/services/people/java/com/android/server/people/data/CallLogQueryHelper.java +++ b/services/people/java/com/android/server/people/data/CallLogQueryHelper.java @@ -107,7 +107,7 @@ class CallLogQueryHelper { } @Event.EventType int eventType = CALL_TYPE_TO_EVENT_TYPE.get(callType); Event event = new Event.Builder(date, eventType) - .setCallDetails(new Event.CallDetails(durationSeconds)) + .setDurationSeconds((int) durationSeconds) .build(); mEventConsumer.accept(phoneNumber, event); return true; diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java index bb97533b3222..b60ed3e8783f 100644 --- a/services/people/java/com/android/server/people/data/ConversationInfo.java +++ b/services/people/java/com/android/server/people/data/ConversationInfo.java @@ -35,7 +35,7 @@ import java.util.Objects; */ public class ConversationInfo { - private static final int FLAG_VIP = 1; + private static final int FLAG_IMPORTANT = 1; private static final int FLAG_NOTIFICATION_SILENCED = 1 << 1; @@ -50,7 +50,7 @@ public class ConversationInfo { private static final int FLAG_DEMOTED = 1 << 6; @IntDef(flag = true, prefix = {"FLAG_"}, value = { - FLAG_VIP, + FLAG_IMPORTANT, FLAG_NOTIFICATION_SILENCED, FLAG_BUBBLED, FLAG_PERSON_IMPORTANT, @@ -129,9 +129,9 @@ public class ConversationInfo { return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED); } - /** Whether this conversation is marked as VIP by the user. */ - public boolean isVip() { - return hasConversationFlags(FLAG_VIP); + /** Whether this conversation is marked as important by the user. */ + public boolean isImportant() { + return hasConversationFlags(FLAG_IMPORTANT); } /** Whether the notifications for this conversation should be silenced. */ @@ -208,8 +208,8 @@ public class ConversationInfo { sb.append("]"); sb.append(", conversationFlags=0x").append(Integer.toHexString(mConversationFlags)); sb.append(" ["); - if (isVip()) { - sb.append("Vip"); + if (isImportant()) { + sb.append("Imp"); } if (isNotificationSilenced()) { sb.append("Sil"); @@ -221,7 +221,7 @@ public class ConversationInfo { sb.append("Dem"); } if (isPersonImportant()) { - sb.append("Imp"); + sb.append("PIm"); } if (isPersonBot()) { sb.append("Bot"); @@ -318,8 +318,8 @@ public class ConversationInfo { return this; } - Builder setVip(boolean value) { - return setConversationFlag(FLAG_VIP, value); + Builder setImportant(boolean value) { + return setConversationFlag(FLAG_IMPORTANT, value); } Builder setNotificationSilenced(boolean value) { diff --git a/services/people/java/com/android/server/people/data/ConversationStore.java b/services/people/java/com/android/server/people/data/ConversationStore.java index f17e1b91cb5d..364992181f75 100644 --- a/services/people/java/com/android/server/people/data/ConversationStore.java +++ b/services/people/java/com/android/server/people/data/ConversationStore.java @@ -40,6 +40,9 @@ class ConversationStore { // Phone Number -> Shortcut ID private final Map<String, String> mPhoneNumberToShortcutIdMap = new ArrayMap<>(); + // Notification Channel ID -> Shortcut ID + private final Map<String, String> mNotifChannelIdToShortcutIdMap = new ArrayMap<>(); + void addOrUpdate(@NonNull ConversationInfo conversationInfo) { mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo); @@ -57,6 +60,11 @@ class ConversationStore { if (phoneNumber != null) { mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId()); } + + String notifChannelId = conversationInfo.getNotificationChannelId(); + if (notifChannelId != null) { + mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId()); + } } void deleteConversation(@NonNull String shortcutId) { @@ -79,6 +87,11 @@ class ConversationStore { if (phoneNumber != null) { mPhoneNumberToShortcutIdMap.remove(phoneNumber); } + + String notifChannelId = conversationInfo.getNotificationChannelId(); + if (notifChannelId != null) { + mNotifChannelIdToShortcutIdMap.remove(notifChannelId); + } } void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { @@ -106,4 +119,9 @@ class ConversationStore { ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) { return getConversation(mPhoneNumberToShortcutIdMap.get(phoneNumber)); } + + @Nullable + ConversationInfo getConversationByNotificationChannelId(@NonNull String notifChannelId) { + return getConversation(mNotifChannelIdToShortcutIdMap.get(notifChannelId)); + } } diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index 43e773870124..7a3ed5348d30 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -21,11 +21,11 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.app.Person; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; -import android.app.usage.UsageEvents; -import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -69,6 +69,7 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Function; /** * A class manages the lifecycle of the conversations and associated data, and exposes the methods @@ -96,7 +97,6 @@ public class DataManager { private final ContentObserver mMmsSmsContentObserver; private ShortcutServiceInternal mShortcutServiceInternal; - private UsageStatsManagerInternal mUsageStatsManagerInternal; private ShortcutManager mShortcutManager; private UserManager mUserManager; @@ -118,7 +118,6 @@ public class DataManager { /** Initialization. Called when the system services are up running. */ public void initialize() { mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class); - mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class); mShortcutManager = mContext.getSystemService(ShortcutManager.class); mUserManager = mContext.getSystemService(UserManager.class); @@ -160,8 +159,8 @@ public class DataManager { mNotificationListeners.put(userId, notificationListener); try { notificationListener.registerAsSystemService(mContext, - new ComponentName(PLATFORM_PACKAGE_NAME, getClass().getSimpleName()), - UserHandle.myUserId()); + new ComponentName(PLATFORM_PACKAGE_NAME, getClass().getCanonicalName()), + userId); } catch (RemoteException e) { // Should never occur for local calls. } @@ -386,36 +385,6 @@ public class DataManager { } @VisibleForTesting - @WorkerThread - void queryUsageStatsService(@UserIdInt int userId, long currentTime, long lastQueryTime) { - UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser( - userId, lastQueryTime, currentTime, false, false); - if (usageEvents == null) { - return; - } - while (usageEvents.hasNextEvent()) { - UsageEvents.Event e = new UsageEvents.Event(); - usageEvents.getNextEvent(e); - - String packageName = e.getPackageName(); - PackageData packageData = getPackage(packageName, userId); - if (packageData == null) { - continue; - } - if (e.getEventType() == UsageEvents.Event.SHORTCUT_INVOCATION) { - String shortcutId = e.getShortcutId(); - if (packageData.getConversationStore().getConversation(shortcutId) != null) { - EventHistoryImpl eventHistory = - packageData.getEventStore().getOrCreateShortcutEventHistory( - shortcutId); - eventHistory.addEvent( - new Event(e.getTimeStamp(), Event.TYPE_SHORTCUT_INVOCATION)); - } - } - } - } - - @VisibleForTesting ContentObserver getContactsContentObserverForTesting(@UserIdInt int userId) { return mContactsContentObservers.get(userId); } @@ -604,6 +573,44 @@ public class DataManager { long currentTime = System.currentTimeMillis(); eventHistory.addEvent(new Event(currentTime, Event.TYPE_NOTIFICATION_OPENED)); } + + @Override + public void onNotificationChannelModified(String pkg, UserHandle user, + NotificationChannel channel, int modificationType) { + PackageData packageData = getPackage(pkg, user.getIdentifier()); + String shortcutId = channel.getConversationId(); + if (packageData == null || shortcutId == null) { + return; + } + ConversationStore conversationStore = packageData.getConversationStore(); + ConversationInfo conversationInfo = conversationStore.getConversation(shortcutId); + if (conversationInfo == null) { + return; + } + ConversationInfo.Builder builder = new ConversationInfo.Builder(conversationInfo); + switch (modificationType) { + case NOTIFICATION_CHANNEL_OR_GROUP_ADDED: + case NOTIFICATION_CHANNEL_OR_GROUP_UPDATED: + builder.setNotificationChannelId(channel.getId()); + builder.setImportant(channel.isImportantConversation()); + builder.setDemoted(channel.isDemoted()); + builder.setNotificationSilenced( + channel.getImportance() <= NotificationManager.IMPORTANCE_LOW); + builder.setBubbled(channel.canBubble()); + break; + case NOTIFICATION_CHANNEL_OR_GROUP_DELETED: + // If the notification channel is deleted, revert all the notification settings + // to the default value. + builder.setNotificationChannelId(null); + builder.setImportant(false); + builder.setDemoted(false); + builder.setNotificationSilenced(false); + builder.setBubbled(false); + break; + } + conversationStore.addOrUpdate(builder.build()); + // TODO: Cache the shortcut when a conversation's notification setting is changed. + } } /** @@ -612,19 +619,20 @@ public class DataManager { */ private class UsageStatsQueryRunnable implements Runnable { - private final int mUserId; - private long mLastQueryTime; + private final UsageStatsQueryHelper mUsageStatsQueryHelper; + private long mLastEventTimestamp; private UsageStatsQueryRunnable(int userId) { - mUserId = userId; - mLastQueryTime = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS; + mUsageStatsQueryHelper = mInjector.createUsageStatsQueryHelper(userId, + (packageName) -> getPackage(packageName, userId)); + mLastEventTimestamp = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS; } @Override public void run() { - long currentTime = System.currentTimeMillis(); - queryUsageStatsService(mUserId, currentTime, mLastQueryTime); - mLastQueryTime = currentTime; + if (mUsageStatsQueryHelper.querySince(mLastEventTimestamp)) { + mLastEventTimestamp = mUsageStatsQueryHelper.getLastEventTimestamp(); + } } } @@ -680,6 +688,11 @@ public class DataManager { return new SmsQueryHelper(context, eventConsumer); } + UsageStatsQueryHelper createUsageStatsQueryHelper(@UserIdInt int userId, + Function<String, PackageData> packageDataGetter) { + return new UsageStatsQueryHelper(userId, packageDataGetter); + } + int getCallingUserId() { return Binder.getCallingUserHandle().getIdentifier(); } diff --git a/services/people/java/com/android/server/people/data/Event.java b/services/people/java/com/android/server/people/data/Event.java index c2364a295e30..81411c00db51 100644 --- a/services/people/java/com/android/server/people/data/Event.java +++ b/services/people/java/com/android/server/people/data/Event.java @@ -18,14 +18,12 @@ package com.android.server.people.data; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.text.format.DateFormat; import android.util.ArraySet; -import com.android.internal.util.Preconditions; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; import java.util.Set; /** An event representing the interaction with a specific conversation or app. */ @@ -55,6 +53,8 @@ public class Event { public static final int TYPE_CALL_MISSED = 12; + public static final int TYPE_IN_APP_CONVERSATION = 13; + @IntDef(prefix = { "TYPE_" }, value = { TYPE_SHORTCUT_INVOCATION, TYPE_NOTIFICATION_POSTED, @@ -68,6 +68,7 @@ public class Event { TYPE_CALL_OUTGOING, TYPE_CALL_INCOMING, TYPE_CALL_MISSED, + TYPE_IN_APP_CONVERSATION, }) @Retention(RetentionPolicy.SOURCE) public @interface EventType {} @@ -95,6 +96,7 @@ public class Event { CALL_EVENT_TYPES.add(TYPE_CALL_MISSED); ALL_EVENT_TYPES.add(TYPE_SHORTCUT_INVOCATION); + ALL_EVENT_TYPES.add(TYPE_IN_APP_CONVERSATION); ALL_EVENT_TYPES.addAll(NOTIFICATION_EVENT_TYPES); ALL_EVENT_TYPES.addAll(SHARE_EVENT_TYPES); ALL_EVENT_TYPES.addAll(SMS_EVENT_TYPES); @@ -105,18 +107,18 @@ public class Event { private final int mType; - private final CallDetails mCallDetails; + private final int mDurationSeconds; Event(long timestamp, @EventType int type) { mTimestamp = timestamp; mType = type; - mCallDetails = null; + mDurationSeconds = 0; } private Event(@NonNull Builder builder) { mTimestamp = builder.mTimestamp; mType = builder.mType; - mCallDetails = builder.mCallDetails; + mDurationSeconds = builder.mDurationSeconds; } public long getTimestamp() { @@ -128,12 +130,35 @@ public class Event { } /** - * Gets the {@link CallDetails} of the event. It is only available if the event type is one of - * {@code CALL_EVENT_TYPES}, otherwise, it's always {@code null}. + * Gets the duration of the event in seconds. It is only available for these events: + * <ul> + * <li>{@link #TYPE_CALL_INCOMING} + * <li>{@link #TYPE_CALL_OUTGOING} + * <li>{@link #TYPE_IN_APP_CONVERSATION} + * </ul> + * <p>For the other event types, it always returns {@code 0}. */ - @Nullable - public CallDetails getCallDetails() { - return mCallDetails; + public int getDurationSeconds() { + return mDurationSeconds; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Event)) { + return false; + } + Event other = (Event) obj; + return mTimestamp == other.mTimestamp + && mType == other.mType + && mDurationSeconds == other.mDurationSeconds; + } + + @Override + public int hashCode() { + return Objects.hash(mTimestamp, mType, mDurationSeconds); } @Override @@ -142,32 +167,13 @@ public class Event { sb.append("Event {"); sb.append("timestamp=").append(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimestamp)); sb.append(", type=").append(mType); - if (mCallDetails != null) { - sb.append(", callDetails=").append(mCallDetails); + if (mDurationSeconds > 0) { + sb.append(", durationSeconds=").append(mDurationSeconds); } sb.append("}"); return sb.toString(); } - /** Type-specific details of a call event. */ - public static class CallDetails { - - private final long mDurationSeconds; - - CallDetails(long durationSeconds) { - mDurationSeconds = durationSeconds; - } - - public long getDurationSeconds() { - return mDurationSeconds; - } - - @Override - public String toString() { - return "CallDetails {durationSeconds=" + mDurationSeconds + "}"; - } - } - /** Builder class for {@link Event} objects. */ static class Builder { @@ -175,16 +181,15 @@ public class Event { private final int mType; - private CallDetails mCallDetails; + private int mDurationSeconds; Builder(long timestamp, @EventType int type) { mTimestamp = timestamp; mType = type; } - Builder setCallDetails(CallDetails callDetails) { - Preconditions.checkArgument(CALL_EVENT_TYPES.contains(mType)); - mCallDetails = callDetails; + Builder setDurationSeconds(int durationSeconds) { + mDurationSeconds = durationSeconds; return this; } diff --git a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java new file mode 100644 index 000000000000..4e37f47149b4 --- /dev/null +++ b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2020 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.people.data; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.app.usage.UsageEvents; +import android.app.usage.UsageStatsManagerInternal; +import android.content.ComponentName; +import android.content.LocusId; +import android.text.format.DateUtils; +import android.util.ArrayMap; + +import com.android.server.LocalServices; + +import java.util.Map; +import java.util.function.Function; + +/** A helper class that queries {@link UsageStatsManagerInternal}. */ +class UsageStatsQueryHelper { + + private final UsageStatsManagerInternal mUsageStatsManagerInternal; + private final int mUserId; + private final Function<String, PackageData> mPackageDataGetter; + // Activity name -> Conversation start event (LOCUS_ID_SET) + private final Map<ComponentName, UsageEvents.Event> mConvoStartEvents = new ArrayMap<>(); + private long mLastEventTimestamp; + + /** + * @param userId The user whose events are to be queried. + * @param packageDataGetter The function to get {@link PackageData} with a package name. + */ + UsageStatsQueryHelper(@UserIdInt int userId, + Function<String, PackageData> packageDataGetter) { + mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class); + mUserId = userId; + mPackageDataGetter = packageDataGetter; + } + + /** + * Queries {@link UsageStatsManagerInternal} for the recent events occurred since {@code + * sinceTime} and adds the derived {@link Event}s into the corresponding package's event store, + * + * @return true if the query runs successfully and at least one event is found. + */ + boolean querySince(long sinceTime) { + UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser( + mUserId, sinceTime, System.currentTimeMillis(), false, false); + if (usageEvents == null) { + return false; + } + boolean hasEvents = false; + while (usageEvents.hasNextEvent()) { + UsageEvents.Event e = new UsageEvents.Event(); + usageEvents.getNextEvent(e); + + hasEvents = true; + mLastEventTimestamp = Math.max(mLastEventTimestamp, e.getTimeStamp()); + String packageName = e.getPackageName(); + PackageData packageData = mPackageDataGetter.apply(packageName); + if (packageData == null) { + continue; + } + switch (e.getEventType()) { + case UsageEvents.Event.SHORTCUT_INVOCATION: + addEventByShortcutId(packageData, e.getShortcutId(), + new Event(e.getTimeStamp(), Event.TYPE_SHORTCUT_INVOCATION)); + break; + case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + addEventByNotificationChannelId(packageData, e.getNotificationChannelId(), + new Event(e.getTimeStamp(), Event.TYPE_NOTIFICATION_POSTED)); + break; + case UsageEvents.Event.LOCUS_ID_SET: + onInAppConversationEnded(packageData, e); + LocusId locusId = e.getLocusId() != null ? new LocusId(e.getLocusId()) : null; + if (locusId != null) { + if (packageData.getConversationStore().getConversationByLocusId(locusId) + != null) { + ComponentName activityName = + new ComponentName(packageName, e.getClassName()); + mConvoStartEvents.put(activityName, e); + } + } + break; + case UsageEvents.Event.ACTIVITY_PAUSED: + case UsageEvents.Event.ACTIVITY_STOPPED: + case UsageEvents.Event.ACTIVITY_DESTROYED: + onInAppConversationEnded(packageData, e); + break; + } + } + return hasEvents; + } + + long getLastEventTimestamp() { + return mLastEventTimestamp; + } + + private void onInAppConversationEnded(@NonNull PackageData packageData, + @NonNull UsageEvents.Event endEvent) { + ComponentName activityName = + new ComponentName(endEvent.getPackageName(), endEvent.getClassName()); + UsageEvents.Event startEvent = mConvoStartEvents.remove(activityName); + if (startEvent == null || startEvent.getTimeStamp() >= endEvent.getTimeStamp()) { + return; + } + long durationMillis = endEvent.getTimeStamp() - startEvent.getTimeStamp(); + Event event = new Event.Builder(startEvent.getTimeStamp(), Event.TYPE_IN_APP_CONVERSATION) + .setDurationSeconds((int) (durationMillis / DateUtils.SECOND_IN_MILLIS)) + .build(); + addEventByLocusId(packageData, new LocusId(startEvent.getLocusId()), event); + } + + private void addEventByShortcutId(PackageData packageData, String shortcutId, Event event) { + if (packageData.getConversationStore().getConversation(shortcutId) == null) { + return; + } + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory( + shortcutId); + eventHistory.addEvent(event); + } + + private void addEventByLocusId(PackageData packageData, LocusId locusId, Event event) { + if (packageData.getConversationStore().getConversationByLocusId(locusId) == null) { + return; + } + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateLocusEventHistory( + locusId); + eventHistory.addEvent(event); + } + + private void addEventByNotificationChannelId(PackageData packageData, + String notificationChannelId, Event event) { + ConversationInfo conversationInfo = + packageData.getConversationStore().getConversationByNotificationChannelId( + notificationChannelId); + if (conversationInfo == null) { + return; + } + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory( + conversationInfo.getShortcutId()); + eventHistory.addEvent(event); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java index 8d2a152dba83..6083ce34a3bd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java @@ -174,12 +174,12 @@ public class ApplicationExitInfoTest { final int app1ConnectiongGroup = 10; final int app1UidUser2 = 1010123; final int app1PidUser2 = 12347; - final int app1Pss1 = 34567; - final int app1Rss1 = 45678; - final int app1Pss2 = 34568; - final int app1Rss2 = 45679; - final int app1Pss3 = 34569; - final int app1Rss3 = 45680; + final long app1Pss1 = 34567; + final long app1Rss1 = 45678; + final long app1Pss2 = 34568; + final long app1Rss2 = 45679; + final long app1Pss3 = 34569; + final long app1Rss3 = 45680; final String app1ProcessName = "com.android.test.stub1:process"; final String app1PackageName = "com.android.test.stub1"; @@ -344,8 +344,8 @@ public class ApplicationExitInfoTest { // Case 4: Create a process from another package with kill from lmkd final int app2UidUser2 = 1010234; final int app2PidUser2 = 12348; - final int app2Pss1 = 54321; - final int app2Rss1 = 65432; + final long app2Pss1 = 54321; + final long app2Rss1 = 65432; final String app2ProcessName = "com.android.test.stub2:process"; final String app2PackageName = "com.android.test.stub2"; @@ -402,8 +402,8 @@ public class ApplicationExitInfoTest { final int app3UidUser2 = 1010345; final int app3PidUser2 = 12349; final int app3ConnectiongGroup = 4; - final int app3Pss1 = 54320; - final int app3Rss1 = 65430; + final long app3Pss1 = 54320; + final long app3Rss1 = 65430; final String app3ProcessName = "com.android.test.stub3:process"; final String app3PackageName = "com.android.test.stub3"; final String app3Description = "native crash"; @@ -529,8 +529,8 @@ public class ApplicationExitInfoTest { final int app3Uid = 10345; final int app3IsolatedUid = 99001; // it's an isolated process final int app3Pid = 12350; - final int app3Pss2 = 23232; - final int app3Rss2 = 32323; + final long app3Pss2 = 23232; + final long app3Rss2 = 32323; final String app3Description2 = "force close"; sleep(1); @@ -618,8 +618,8 @@ public class ApplicationExitInfoTest { sleep(1); final int app1IsolatedUidUser2 = 1099002; // isolated uid - final int app1Pss4 = 34343; - final int app1Rss4 = 43434; + final long app1Pss4 = 34343; + final long app1Rss4 = 43434; final long now8 = System.currentTimeMillis(); sigNum = OsConstants.SIGKILL; doReturn(new Pair<Long, Object>(now8, makeSignalStatus(sigNum))) @@ -673,8 +673,8 @@ public class ApplicationExitInfoTest { sleep(1); final int app1Pid2User2 = 56565; final int app1IsolatedUid2User2 = 1099003; // isolated uid - final int app1Pss5 = 34344; - final int app1Rss5 = 43435; + final long app1Pss5 = 34344; + final long app1Rss5 = 43435; final long now9 = System.currentTimeMillis(); sigNum = OsConstants.SIGKILL; doReturn(new Pair<Long, Object>(now9, makeSignalStatus(sigNum))) @@ -831,7 +831,7 @@ public class ApplicationExitInfoTest { } private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid, - int connectionGroup, int procState, int pss, int rss, + int connectionGroup, int procState, long pss, long rss, String processName, String packageName) { ApplicationInfo ai = new ApplicationInfo(); ai.packageName = packageName; @@ -847,8 +847,8 @@ public class ApplicationExitInfoTest { app.connectionGroup = connectionGroup; app.setProcState = procState; app.lastMemInfo = spy(new Debug.MemoryInfo()); - doReturn(pss).when(app.lastMemInfo).getTotalPss(); - doReturn(rss).when(app.lastMemInfo).getTotalRss(); + doReturn((int) pss).when(app.lastMemInfo).getTotalPss(); + doReturn((int) rss).when(app.lastMemInfo).getTotalRss(); return app; } @@ -856,7 +856,7 @@ public class ApplicationExitInfoTest { Long timestamp, Integer pid, Integer uid, Integer packageUid, Integer definingUid, String processName, Integer connectionGroup, Integer reason, Integer subReason, Integer status, - Integer pss, Integer rss, Integer importance, String description) { + Long pss, Long rss, Integer importance, String description) { assertNotNull(info); if (timestamp != null) { @@ -892,10 +892,10 @@ public class ApplicationExitInfoTest { assertEquals(status.intValue(), info.getStatus()); } if (pss != null) { - assertEquals(pss.intValue(), info.getPss()); + assertEquals(pss.longValue(), info.getPss()); } if (rss != null) { - assertEquals(rss.intValue(), info.getRss()); + assertEquals(rss.longValue(), info.getRss()); } if (importance != null) { assertEquals(importance.intValue(), info.getImportance()); diff --git a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java index 16dde4203e91..25964c592872 100644 --- a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java @@ -43,6 +43,7 @@ import com.android.server.blob.BlobStoreManagerService.Injector; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -209,6 +210,7 @@ public class BlobStoreManagerServiceTest { verify(file3, never()).delete(); } + @Ignore @Test public void testHandleIdleMaintenance_deleteStaleSessions() throws Exception { // Setup sessions 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 cf10559c8198..dfe950ea93d6 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -47,6 +47,7 @@ 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.assertNull; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -703,14 +704,20 @@ public class AbstractAccessibilityServiceConnectionTest { @Test public void takeScreenshot_returnNull() { - // no canTakeScreenshot, should return null. - when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false); - assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue())); - // no checkAccessibilityAccess, should return null. when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true); when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false); - assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue())); + mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY, new RemoteCallback((result) -> { + assertNull(result); + })); + } + + @Test (expected = SecurityException.class) + public void takeScreenshot_throwSecurityException() { + // no canTakeScreenshot, should throw security exception. + when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false); + mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY, new RemoteCallback((result) -> { + })); } private void updateServiceInfo(AccessibilityServiceInfo serviceInfo, int eventType, @@ -852,8 +859,5 @@ public class AbstractAccessibilityServiceConnectionTest { @Override public void onFingerprintGesture(int gesture) {} - - @Override - public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {} } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java index d38c80cdabe0..6aa928794244 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java @@ -24,6 +24,7 @@ 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.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,6 +33,9 @@ import android.content.pm.PackageManager; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; +import android.hardware.face.IFaceService; +import android.hardware.fingerprint.IFingerprintService; +import android.hardware.iris.IIrisService; import android.os.Binder; import android.os.Bundle; @@ -61,6 +65,12 @@ public class AuthServiceTest { AuthService.Injector mInjector; @Mock IBiometricService mBiometricService; + @Mock + IFingerprintService mFingerprintService; + @Mock + IIrisService mIrisService; + @Mock + IFaceService mFaceService; @Before public void setUp() { @@ -76,10 +86,28 @@ public class AuthServiceTest { when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mInjector.getBiometricService()).thenReturn(mBiometricService); when(mInjector.getConfiguration(any())).thenReturn(config); - when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) - .thenReturn(true); - when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)).thenReturn(true); - when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true); + when(mInjector.getFingerprintService()).thenReturn(mFingerprintService); + when(mInjector.getFaceService()).thenReturn(mFaceService); + when(mInjector.getIrisService()).thenReturn(mIrisService); + } + + @Test + public void testRegisterNullService_doesNotRegister() throws Exception { + + // Config contains Fingerprint, Iris, Face, but services are all null + + when(mInjector.getFingerprintService()).thenReturn(null); + when(mInjector.getFaceService()).thenReturn(null); + when(mInjector.getIrisService()).thenReturn(null); + + mAuthService = new AuthService(mContext, mInjector); + mAuthService.onStart(); + + verify(mBiometricService, never()).registerAuthenticator( + anyInt(), + anyInt(), + anyInt(), + any()); } diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java index 4a7636a179b1..c9ec87427722 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -423,7 +423,8 @@ public class AppIntegrityManagerServiceImplTest { PackageInfo packageInfo = mRealContext .getPackageManager() - .getPackageInfo(TEST_FRAMEWORK_PACKAGE, PackageManager.GET_SIGNATURES); + .getPackageInfo(TEST_FRAMEWORK_PACKAGE, + PackageManager.GET_SIGNING_CERTIFICATES); doReturn(packageInfo).when(mSpyPackageManager).getPackageInfo(eq(INSTALLER), anyInt()); doReturn(1).when(mSpyPackageManager).getPackageUid(eq(INSTALLER), anyInt()); return makeVerificationIntent(INSTALLER); diff --git a/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java index 7a16d171b475..a54501029712 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java @@ -92,7 +92,7 @@ public final class CallLogQueryHelperTest { assertEquals(1, events.size()); assertEquals(Event.TYPE_CALL_INCOMING, events.get(0).getType()); assertEquals(100L, events.get(0).getTimestamp()); - assertEquals(30L, events.get(0).getCallDetails().getDurationSeconds()); + assertEquals(30L, events.get(0).getDurationSeconds()); } @Test @@ -108,7 +108,7 @@ public final class CallLogQueryHelperTest { assertEquals(1, events.size()); assertEquals(Event.TYPE_CALL_OUTGOING, events.get(0).getType()); assertEquals(100L, events.get(0).getTimestamp()); - assertEquals(40L, events.get(0).getCallDetails().getDurationSeconds()); + assertEquals(40L, events.get(0).getDurationSeconds()); } @Test @@ -124,7 +124,7 @@ public final class CallLogQueryHelperTest { assertEquals(1, events.size()); assertEquals(Event.TYPE_CALL_MISSED, events.get(0).getType()); assertEquals(100L, events.get(0).getTimestamp()); - assertEquals(0L, events.get(0).getCallDetails().getDurationSeconds()); + assertEquals(0L, events.get(0).getDurationSeconds()); } @Test @@ -145,7 +145,7 @@ public final class CallLogQueryHelperTest { assertEquals(100L, events.get(0).getTimestamp()); assertEquals(Event.TYPE_CALL_OUTGOING, events.get(1).getType()); assertEquals(110L, events.get(1).getTimestamp()); - assertEquals(40L, events.get(1).getCallDetails().getDurationSeconds()); + assertEquals(40L, events.get(1).getDurationSeconds()); } private class EventConsumer implements BiConsumer<String, Event> { diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java index 05a9a80e262c..c0e7927a8d72 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java @@ -47,7 +47,7 @@ public final class ConversationInfoTest { .setContactPhoneNumber(PHONE_NUMBER) .setNotificationChannelId(NOTIFICATION_CHANNEL_ID) .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) - .setVip(true) + .setImportant(true) .setNotificationSilenced(true) .setBubbled(true) .setDemoted(true) @@ -62,7 +62,7 @@ public final class ConversationInfoTest { assertEquals(PHONE_NUMBER, conversationInfo.getContactPhoneNumber()); assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId()); assertTrue(conversationInfo.isShortcutLongLived()); - assertTrue(conversationInfo.isVip()); + assertTrue(conversationInfo.isImportant()); assertTrue(conversationInfo.isNotificationSilenced()); assertTrue(conversationInfo.isBubbled()); assertTrue(conversationInfo.isDemoted()); @@ -83,7 +83,7 @@ public final class ConversationInfoTest { assertNull(conversationInfo.getContactPhoneNumber()); assertNull(conversationInfo.getNotificationChannelId()); assertFalse(conversationInfo.isShortcutLongLived()); - assertFalse(conversationInfo.isVip()); + assertFalse(conversationInfo.isImportant()); assertFalse(conversationInfo.isNotificationSilenced()); assertFalse(conversationInfo.isBubbled()); assertFalse(conversationInfo.isDemoted()); @@ -101,7 +101,7 @@ public final class ConversationInfoTest { .setContactPhoneNumber(PHONE_NUMBER) .setNotificationChannelId(NOTIFICATION_CHANNEL_ID) .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) - .setVip(true) + .setImportant(true) .setNotificationSilenced(true) .setBubbled(true) .setPersonImportant(true) @@ -110,7 +110,7 @@ public final class ConversationInfoTest { .build(); ConversationInfo destination = new ConversationInfo.Builder(source) - .setVip(false) + .setImportant(false) .setContactStarred(false) .build(); @@ -120,7 +120,7 @@ public final class ConversationInfoTest { assertEquals(PHONE_NUMBER, destination.getContactPhoneNumber()); assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId()); assertTrue(destination.isShortcutLongLived()); - assertFalse(destination.isVip()); + assertFalse(destination.isImportant()); assertTrue(destination.isNotificationSilenced()); assertTrue(destination.isBubbled()); assertTrue(destination.isPersonImportant()); diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java index a40c6ab90197..331ad5972fc1 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java @@ -37,6 +37,7 @@ import java.util.Set; public final class ConversationStoreTest { private static final String SHORTCUT_ID = "abc"; + private static final String NOTIFICATION_CHANNEL_ID = "test : abc"; private static final LocusId LOCUS_ID = new LocusId("def"); private static final Uri CONTACT_URI = Uri.parse("tel:+1234567890"); private static final String PHONE_NUMBER = "+1234567890"; @@ -59,16 +60,19 @@ public final class ConversationStoreTest { @Test public void testUpdateConversation() { - ConversationInfo original = - buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER); + ConversationInfo original = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, null); mConversationStore.addOrUpdate(original); assertEquals(LOCUS_ID, mConversationStore.getConversation(SHORTCUT_ID).getLocusId()); + assertNull(mConversationStore.getConversation(SHORTCUT_ID).getNotificationChannelId()); LocusId newLocusId = new LocusId("ghi"); ConversationInfo update = buildConversationInfo( - SHORTCUT_ID, newLocusId, CONTACT_URI, PHONE_NUMBER); + SHORTCUT_ID, newLocusId, CONTACT_URI, PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); mConversationStore.addOrUpdate(update); - assertEquals(newLocusId, mConversationStore.getConversation(SHORTCUT_ID).getLocusId()); + ConversationInfo updated = mConversationStore.getConversation(SHORTCUT_ID); + assertEquals(newLocusId, updated.getLocusId()); + assertEquals(NOTIFICATION_CHANNEL_ID, updated.getNotificationChannelId()); } @Test @@ -97,8 +101,8 @@ public final class ConversationStoreTest { @Test public void testGetConversationByLocusId() { - ConversationInfo in = - buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER); + ConversationInfo in = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); mConversationStore.addOrUpdate(in); ConversationInfo out = mConversationStore.getConversationByLocusId(LOCUS_ID); assertNotNull(out); @@ -110,8 +114,8 @@ public final class ConversationStoreTest { @Test public void testGetConversationByContactUri() { - ConversationInfo in = - buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER); + ConversationInfo in = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); mConversationStore.addOrUpdate(in); ConversationInfo out = mConversationStore.getConversationByContactUri(CONTACT_URI); assertNotNull(out); @@ -123,8 +127,8 @@ public final class ConversationStoreTest { @Test public void testGetConversationByPhoneNumber() { - ConversationInfo in = - buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER); + ConversationInfo in = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); mConversationStore.addOrUpdate(in); ConversationInfo out = mConversationStore.getConversationByPhoneNumber(PHONE_NUMBER); assertNotNull(out); @@ -134,19 +138,36 @@ public final class ConversationStoreTest { assertNull(mConversationStore.getConversationByPhoneNumber(PHONE_NUMBER)); } + @Test + public void testGetConversationByNotificationChannelId() { + ConversationInfo in = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); + mConversationStore.addOrUpdate(in); + ConversationInfo out = mConversationStore.getConversationByNotificationChannelId( + NOTIFICATION_CHANNEL_ID); + assertNotNull(out); + assertEquals(SHORTCUT_ID, out.getShortcutId()); + + mConversationStore.deleteConversation(SHORTCUT_ID); + assertNull( + mConversationStore.getConversationByNotificationChannelId(NOTIFICATION_CHANNEL_ID)); + } + private static ConversationInfo buildConversationInfo(String shortcutId) { - return buildConversationInfo(shortcutId, null, null, null); + return buildConversationInfo(shortcutId, null, null, null, null); } private static ConversationInfo buildConversationInfo( - String shortcutId, LocusId locusId, Uri contactUri, String phoneNumber) { + String shortcutId, LocusId locusId, Uri contactUri, String phoneNumber, + String notificationChannelId) { return new ConversationInfo.Builder() .setShortcutId(shortcutId) .setLocusId(locusId) .setContactUri(contactUri) .setContactPhoneNumber(phoneNumber) + .setNotificationChannelId(notificationChannelId) .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) - .setVip(true) + .setImportant(true) .setBubbled(true) .build(); } diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 9d2091a8578e..498d8886eec3 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -16,15 +16,17 @@ package com.android.server.people.data; -import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -36,11 +38,12 @@ import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.app.Person; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; import android.app.prediction.AppTargetId; -import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.content.ContentResolver; import android.content.Context; @@ -92,6 +95,7 @@ public final class DataManagerTest { private static final String TEST_SHORTCUT_ID = "sc"; private static final String CONTACT_URI = "content://com.android.contacts/contacts/lookup/123"; private static final String PHONE_NUMBER = "+1234567890"; + private static final String NOTIFICATION_CHANNEL_ID = "test : sc"; private static final long MILLIS_PER_MINUTE = 1000L * 60L; @Mock private Context mContext; @@ -107,6 +111,7 @@ public final class DataManagerTest { @Mock private StatusBarNotification mStatusBarNotification; @Mock private Notification mNotification; + private NotificationChannel mNotificationChannel; private DataManager mDataManager; private int mCallingUserId; private TestInjector mInjector; @@ -156,6 +161,10 @@ public final class DataManagerTest { when(mStatusBarNotification.getUser()).thenReturn(UserHandle.of(USER_ID_PRIMARY)); when(mNotification.getShortcutId()).thenReturn(TEST_SHORTCUT_ID); + mNotificationChannel = new NotificationChannel( + NOTIFICATION_CHANNEL_ID, "test channel", NotificationManager.IMPORTANCE_DEFAULT); + mNotificationChannel.setConversationId("test", TEST_SHORTCUT_ID); + mCallingUserId = USER_ID_PRIMARY; mInjector = new TestInjector(); @@ -288,9 +297,8 @@ public final class DataManagerTest { } @Test - public void testNotificationListener() { + public void testNotificationOpened() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); - mDataManager.onUserUnlocked(USER_ID_SECONDARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); @@ -312,32 +320,80 @@ public final class DataManagerTest { } @Test - public void testQueryUsageStatsService() { - UsageEvents.Event e = new UsageEvents.Event(SHORTCUT_INVOCATION, - System.currentTimeMillis()); - e.mPackage = TEST_PKG_NAME; - e.mShortcutId = TEST_SHORTCUT_ID; - List<UsageEvents.Event> events = new ArrayList<>(); - events.add(e); - UsageEvents usageEvents = new UsageEvents(events, new String[]{}); - when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(), - anyBoolean(), anyBoolean())).thenReturn(usageEvents); + public void testNotificationChannelCreated() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onUserUnlocked(USER_ID_SECONDARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY), + mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_ADDED); + + ConversationInfo conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY) + .getConversationStore() + .getConversation(TEST_SHORTCUT_ID); + assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId()); + assertFalse(conversationInfo.isImportant()); + assertFalse(conversationInfo.isNotificationSilenced()); + assertFalse(conversationInfo.isDemoted()); + } + + @Test + public void testNotificationChannelModified() { + mNotificationChannel.setImportantConversation(true); mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onUserUnlocked(USER_ID_SECONDARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); mDataManager.onShortcutAddedOrUpdated(shortcut); - mDataManager.queryUsageStatsService(USER_ID_PRIMARY, 0L, Long.MAX_VALUE); + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY), + mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + + ConversationInfo conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY) + .getConversationStore() + .getConversation(TEST_SHORTCUT_ID); + assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId()); + assertTrue(conversationInfo.isImportant()); + assertFalse(conversationInfo.isNotificationSilenced()); + assertFalse(conversationInfo.isDemoted()); + } - List<Range<Long>> activeShortcutInvocationTimeSlots = new ArrayList<>(); - mDataManager.forAllPackages(packageData -> - activeShortcutInvocationTimeSlots.addAll( - packageData.getEventHistory(TEST_SHORTCUT_ID) - .getEventIndex(Event.TYPE_SHORTCUT_INVOCATION) - .getActiveTimeSlots())); - assertEquals(1, activeShortcutInvocationTimeSlots.size()); + @Test + public void testNotificationChannelDeleted() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onUserUnlocked(USER_ID_SECONDARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY), + mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_ADDED); + ConversationInfo conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY) + .getConversationStore() + .getConversation(TEST_SHORTCUT_ID); + assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId()); + + listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY), + mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_DELETED); + conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY) + .getConversationStore() + .getConversation(TEST_SHORTCUT_ID); + assertNull(conversationInfo.getNotificationChannelId()); + assertFalse(conversationInfo.isImportant()); + assertFalse(conversationInfo.isNotificationSilenced()); + assertFalse(conversationInfo.isDemoted()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java new file mode 100644 index 000000000000..e4248a04878d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2020 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.people.data; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.usage.UsageEvents; +import android.app.usage.UsageStatsManagerInternal; +import android.content.LocusId; + +import com.android.server.LocalServices; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +@RunWith(JUnit4.class) +public final class UsageStatsQueryHelperTest { + + private static final int USER_ID_PRIMARY = 0; + private static final String PKG_NAME = "pkg"; + private static final String ACTIVITY_NAME = "TestActivity"; + private static final String SHORTCUT_ID = "abc"; + private static final String NOTIFICATION_CHANNEL_ID = "test : abc"; + private static final LocusId LOCUS_ID_1 = new LocusId("locus_1"); + private static final LocusId LOCUS_ID_2 = new LocusId("locus_2"); + + @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; + + private TestPackageData mPackageData; + private UsageStatsQueryHelper mHelper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal); + + mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false); + mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder() + .setShortcutId(SHORTCUT_ID) + .setNotificationChannelId(NOTIFICATION_CHANNEL_ID) + .setLocusId(LOCUS_ID_1) + .build(); + + mHelper = new UsageStatsQueryHelper(USER_ID_PRIMARY, pkg -> mPackageData); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(UsageStatsManagerInternal.class); + } + + @Test + public void testQueryNoEvents() { + assertFalse(mHelper.querySince(50L)); + } + + @Test + public void testQueryShortcutInvocationEvent() { + addUsageEvents(createShortcutInvocationEvent(100L)); + + assertTrue(mHelper.querySince(50L)); + assertEquals(100L, mHelper.getLastEventTimestamp()); + Event expectedEvent = new Event(100L, Event.TYPE_SHORTCUT_INVOCATION); + List<Event> events = mPackageData.mEventStore.mShortcutEventHistory.queryEvents( + Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(1, events.size()); + assertEquals(expectedEvent, events.get(0)); + } + + @Test + public void testQueryNotificationInterruptionEvent() { + addUsageEvents(createNotificationInterruptionEvent(100L)); + + assertTrue(mHelper.querySince(50L)); + assertEquals(100L, mHelper.getLastEventTimestamp()); + Event expectedEvent = new Event(100L, Event.TYPE_NOTIFICATION_POSTED); + List<Event> events = mPackageData.mEventStore.mShortcutEventHistory.queryEvents( + Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(1, events.size()); + assertEquals(expectedEvent, events.get(0)); + } + + @Test + public void testInAppConversationSwitch() { + addUsageEvents( + createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()), + createLocusIdSetEvent(110_000L, LOCUS_ID_2.getId())); + + assertTrue(mHelper.querySince(50_000L)); + assertEquals(110_000L, mHelper.getLastEventTimestamp()); + List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents( + Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(1, events.size()); + assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0)); + } + + @Test + public void testInAppConversationExplicitlyEnd() { + addUsageEvents( + createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()), + createLocusIdSetEvent(110_000L, null)); + + assertTrue(mHelper.querySince(50_000L)); + assertEquals(110_000L, mHelper.getLastEventTimestamp()); + List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents( + Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(1, events.size()); + assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0)); + } + + @Test + public void testInAppConversationImplicitlyEnd() { + addUsageEvents( + createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()), + createActivityStoppedEvent(110_000L)); + + assertTrue(mHelper.querySince(50_000L)); + assertEquals(110_000L, mHelper.getLastEventTimestamp()); + List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents( + Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(1, events.size()); + assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0)); + } + + @Test + public void testMultipleInAppConversations() { + addUsageEvents( + createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()), + createLocusIdSetEvent(110_000L, LOCUS_ID_2.getId()), + createLocusIdSetEvent(130_000L, LOCUS_ID_1.getId()), + createActivityStoppedEvent(160_000L)); + + assertTrue(mHelper.querySince(50_000L)); + assertEquals(160_000L, mHelper.getLastEventTimestamp()); + List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents( + Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(3, events.size()); + assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0)); + assertEquals(createInAppConversationEvent(110_000L, 20), events.get(1)); + assertEquals(createInAppConversationEvent(130_000L, 30), events.get(2)); + } + + private void addUsageEvents(UsageEvents.Event ... events) { + UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{}); + when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(), + anyBoolean(), anyBoolean())).thenReturn(usageEvents); + } + + private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService(clazz, mock); + } + + private static UsageEvents.Event createShortcutInvocationEvent(long timestamp) { + UsageEvents.Event e = createUsageEvent(UsageEvents.Event.SHORTCUT_INVOCATION, timestamp); + e.mShortcutId = SHORTCUT_ID; + return e; + } + + private static UsageEvents.Event createNotificationInterruptionEvent(long timestamp) { + UsageEvents.Event e = createUsageEvent(UsageEvents.Event.NOTIFICATION_INTERRUPTION, + timestamp); + e.mNotificationChannelId = NOTIFICATION_CHANNEL_ID; + return e; + } + + private static UsageEvents.Event createLocusIdSetEvent(long timestamp, String locusId) { + UsageEvents.Event e = createUsageEvent(UsageEvents.Event.LOCUS_ID_SET, timestamp); + e.mClass = ACTIVITY_NAME; + e.mLocusId = locusId; + return e; + } + + private static UsageEvents.Event createActivityStoppedEvent(long timestamp) { + UsageEvents.Event e = createUsageEvent(UsageEvents.Event.ACTIVITY_STOPPED, timestamp); + e.mClass = ACTIVITY_NAME; + return e; + } + + private static UsageEvents.Event createUsageEvent(int eventType, long timestamp) { + UsageEvents.Event e = new UsageEvents.Event(eventType, timestamp); + e.mPackage = PKG_NAME; + return e; + } + + private static Event createInAppConversationEvent(long timestamp, int durationSeconds) { + return new Event.Builder(timestamp, Event.TYPE_IN_APP_CONVERSATION) + .setDurationSeconds(durationSeconds) + .build(); + } + + private static class TestConversationStore extends ConversationStore { + + private ConversationInfo mConversationInfo; + + @Override + @Nullable + ConversationInfo getConversation(@Nullable String shortcutId) { + return mConversationInfo; + } + } + + private static class TestPackageData extends PackageData { + + private final TestConversationStore mConversationStore = new TestConversationStore(); + private final TestEventStore mEventStore = new TestEventStore(); + + TestPackageData(@NonNull String packageName, @UserIdInt int userId, + @NonNull Predicate<String> isDefaultDialerPredicate, + @NonNull Predicate<String> isDefaultSmsAppPredicate) { + super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate); + } + + @Override + @NonNull + ConversationStore getConversationStore() { + return mConversationStore; + } + + @Override + @NonNull + EventStore getEventStore() { + return mEventStore; + } + } + + private static class TestEventStore extends EventStore { + + private final EventHistoryImpl mShortcutEventHistory = new TestEventHistoryImpl(); + private final EventHistoryImpl mLocusEventHistory = new TestEventHistoryImpl(); + + @Override + @NonNull + EventHistoryImpl getOrCreateShortcutEventHistory(String shortcutId) { + return mShortcutEventHistory; + } + + @Override + @NonNull + EventHistoryImpl getOrCreateLocusEventHistory(LocusId locusId) { + return mLocusEventHistory; + } + } + + private static class TestEventHistoryImpl extends EventHistoryImpl { + + private final List<Event> mEvents = new ArrayList<>(); + + @Override + @NonNull + public List<Event> queryEvents(Set<Integer> eventTypes, long startTime, long endTime) { + return mEvents; + } + + @Override + void addEvent(Event event) { + mEvents.add(event); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java b/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java index ba493d4f9646..d1c9643859e3 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java @@ -20,7 +20,11 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; @@ -36,6 +40,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.List; + @RunWith(JUnit4.class) public class WatchdogRollbackLoggerTest { @@ -46,6 +52,11 @@ public class WatchdogRollbackLoggerTest { private PackageInfo mPackageInfo; private static final String LOGGING_PARENT_KEY = "android.content.pm.LOGGING_PARENT"; + private static final String LOGGING_PARENT_VALUE = "logging.parent"; + private static final int PACKAGE_INFO_FLAGS = PackageManager.MATCH_APEX + | PackageManager.GET_META_DATA; + private static final List<String> sFailingPackages = + List.of("package1", "package2", "package3"); @Before public void setUp() { @@ -64,10 +75,12 @@ public class WatchdogRollbackLoggerTest { */ @Test public void testLogPackageHasNoMetadata() throws Exception { - when(mMockPm.getApplicationInfo(anyString(), anyInt())).thenReturn(mApplicationInfo); + when(mMockPm.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo); VersionedPackage logPackage = WatchdogRollbackLogger.getLogPackage(mMockContext, sTestPackageV1); assertThat(logPackage).isNull(); + verify(mMockPm, times(1)).getPackageInfo( + sTestPackageV1.getPackageName(), PACKAGE_INFO_FLAGS); } /** @@ -76,12 +89,16 @@ public class WatchdogRollbackLoggerTest { */ @Test public void testLogPackageParentKeyIsNull() throws Exception { - when(mMockPm.getApplicationInfo(anyString(), anyInt())).thenReturn(mApplicationInfo); + when(mMockPm.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo); Bundle bundle = new Bundle(); bundle.putString(LOGGING_PARENT_KEY, null); + mApplicationInfo.metaData = bundle; + mPackageInfo.applicationInfo = mApplicationInfo; VersionedPackage logPackage = WatchdogRollbackLogger.getLogPackage(mMockContext, sTestPackageV1); assertThat(logPackage).isNull(); + verify(mMockPm, times(1)).getPackageInfo( + sTestPackageV1.getPackageName(), PACKAGE_INFO_FLAGS); } /** @@ -90,15 +107,18 @@ public class WatchdogRollbackLoggerTest { @Test public void testLogPackageHasParentKey() throws Exception { Bundle bundle = new Bundle(); - bundle.putString(LOGGING_PARENT_KEY, "logging.parent"); + bundle.putString(LOGGING_PARENT_KEY, LOGGING_PARENT_VALUE); mApplicationInfo.metaData = bundle; + mPackageInfo.applicationInfo = mApplicationInfo; mPackageInfo.setLongVersionCode(12345L); - when(mMockPm.getApplicationInfo(anyString(), anyInt())).thenReturn(mApplicationInfo); when(mMockPm.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo); VersionedPackage logPackage = WatchdogRollbackLogger.getLogPackage(mMockContext, sTestPackageV1); - VersionedPackage expectedLogPackage = new VersionedPackage("logging.parent", 12345); + VersionedPackage expectedLogPackage = new VersionedPackage(LOGGING_PARENT_VALUE, 12345); assertThat(logPackage).isEqualTo(expectedLogPackage); + verify(mMockPm, times(1)).getPackageInfo( + sTestPackageV1.getPackageName(), PACKAGE_INFO_FLAGS); + } /** @@ -107,12 +127,64 @@ public class WatchdogRollbackLoggerTest { @Test public void testLogPackageNameNotFound() throws Exception { Bundle bundle = new Bundle(); - bundle.putString("android.content.pm.LOGGING_PARENT", "logging.parent"); + bundle.putString(LOGGING_PARENT_KEY, LOGGING_PARENT_VALUE); mApplicationInfo.metaData = bundle; - when(mMockPm.getPackageInfo(anyString(), anyInt())).thenThrow( + mPackageInfo.applicationInfo = mApplicationInfo; + when(mMockPm.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo); + when(mMockPm.getPackageInfo(same(LOGGING_PARENT_VALUE), anyInt())).thenThrow( new PackageManager.NameNotFoundException()); VersionedPackage logPackage = WatchdogRollbackLogger.getLogPackage(mMockContext, sTestPackageV1); assertThat(logPackage).isNull(); + verify(mMockPm, times(1)).getPackageInfo( + sTestPackageV1.getPackageName(), PACKAGE_INFO_FLAGS); + } + + /** + * Ensures that we make the correct Package Manager calls in the case that the failing packages + * are correctly configured with parent packages. + */ + @Test + public void testApexdLoggingCallsWithParents() throws Exception { + for (String failingPackage: sFailingPackages) { + PackageInfo packageInfo = new PackageInfo(); + ApplicationInfo applicationInfo = new ApplicationInfo(); + Bundle bundle = new Bundle(); + bundle.putString(LOGGING_PARENT_KEY, getParent(failingPackage)); + applicationInfo.metaData = bundle; + packageInfo.applicationInfo = applicationInfo; + when(mMockPm.getPackageInfo(same(failingPackage), anyInt())).thenReturn(packageInfo); + } + + when(mMockPm.getPackageInfo(anyString(), eq(0))).thenReturn(mPackageInfo); + WatchdogRollbackLogger.logApexdRevert(mMockContext, sFailingPackages, "test_process"); + for (String failingPackage: sFailingPackages) { + verify(mMockPm, times(1)).getPackageInfo(failingPackage, PACKAGE_INFO_FLAGS); + verify(mMockPm, times(1)).getPackageInfo(getParent(failingPackage), 0); + } + } + + /** + * Ensures that we don't make any calls to parent packages in the case that packages are not + * correctly configured with parent packages. + */ + @Test + public void testApexdLoggingCallsWithNoParents() throws Exception { + for (String failingPackage: sFailingPackages) { + PackageInfo packageInfo = new PackageInfo(); + packageInfo.applicationInfo = new ApplicationInfo(); + when(mMockPm.getPackageInfo(same(failingPackage), anyInt())).thenReturn(packageInfo); + } + when(mMockPm.getPackageInfo(anyString(), eq(0))).thenReturn(mPackageInfo); + + WatchdogRollbackLogger.logApexdRevert(mMockContext, sFailingPackages, "test_process"); + verify(mMockPm, times(sFailingPackages.size())).getPackageInfo(anyString(), anyInt()); + for (String failingPackage: sFailingPackages) { + verify(mMockPm, times(1)).getPackageInfo(failingPackage, PACKAGE_INFO_FLAGS); + } + } + + private String getParent(String packageName) { + return packageName + "-parent"; } } diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 8c4544249b5c..123bb079dbbb 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -37,6 +37,7 @@ <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.REORDER_TASKS" /> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> + <uses-permission android:name="android.permission.STATUS_BAR" /> <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) --> <application android:debuggable="true" diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java index 7172a1b14244..f7aa3cc9e52c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -29,10 +29,10 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -45,8 +45,10 @@ import android.app.ActivityManager.StackInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.util.ArrayMap; import android.view.Display; import android.view.ITaskOrganizer; import android.view.IWindowContainer; @@ -357,6 +359,78 @@ public class TaskOrganizerTests extends WindowTestsBase { assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType); } + @Test + public void testHierarchyTransaction() { + final ArrayMap<IBinder, RunningTaskInfo> lastReportedTiles = new ArrayMap<>(); + ITaskOrganizer listener = new ITaskOrganizer.Stub() { + @Override + public void taskAppeared(RunningTaskInfo taskInfo) { } + + @Override + public void taskVanished(IWindowContainer container) { } + + @Override + public void transactionReady(int id, SurfaceControl.Transaction t) { } + + @Override + public void onTaskInfoChanged(RunningTaskInfo info) { + lastReportedTiles.put(info.token.asBinder(), info); + } + }; + mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer( + listener, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + + final ActivityStack stack = createTaskStackOnDisplay( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); + final ActivityStack stack2 = createTaskStackOnDisplay( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent); + + lastReportedTiles.clear(); + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */); + wct.reparent(stack2.mRemoteToken, info2.token, true /* onTop */); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(wct, + null /* organizer */); + assertFalse(lastReportedTiles.isEmpty()); + assertEquals(ACTIVITY_TYPE_STANDARD, + lastReportedTiles.get(info1.token.asBinder()).topActivityType); + assertEquals(ACTIVITY_TYPE_HOME, + lastReportedTiles.get(info2.token.asBinder()).topActivityType); + + lastReportedTiles.clear(); + wct = new WindowContainerTransaction(); + wct.reparent(stack2.mRemoteToken, info1.token, false /* onTop */); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(wct, + null /* organizer */); + assertFalse(lastReportedTiles.isEmpty()); + // Standard should still be on top of tile 1, so no change there + assertFalse(lastReportedTiles.containsKey(info1.token.asBinder())); + // But tile 2 has no children, so should become undefined + assertEquals(ACTIVITY_TYPE_UNDEFINED, + lastReportedTiles.get(info2.token.asBinder()).topActivityType); + + // Check the getChildren call + List<RunningTaskInfo> children = + mWm.mAtmService.mTaskOrganizerController.getChildTasks(info1.token); + assertEquals(2, children.size()); + children = mWm.mAtmService.mTaskOrganizerController.getChildTasks(info2.token); + assertEquals(0, children.size()); + + lastReportedTiles.clear(); + wct = new WindowContainerTransaction(); + wct.reorder(stack2.mRemoteToken, true /* onTop */); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(wct, + null /* organizer */); + // Home should now be on top. No change occurs in second tile, so not reported + assertEquals(1, lastReportedTiles.size()); + assertEquals(ACTIVITY_TYPE_HOME, + lastReportedTiles.get(info1.token.asBinder()).topActivityType); + } + private List<TaskTile> getTaskTiles(DisplayContent dc) { ArrayList<TaskTile> out = new ArrayList<>(); for (int i = dc.getStackCount() - 1; i >= 0; --i) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java new file mode 100644 index 000000000000..35723abb4310 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 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.wm; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import android.content.pm.PackageManager; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class WindowManagerServiceTests extends WindowTestsBase { + @Rule + public ExpectedException mExpectedException = ExpectedException.none(); + + @Test + public void testForceShowSystemBarsThrowsExceptionForNonAutomotive() { + if (!isAutomotive()) { + mExpectedException.expect(UnsupportedOperationException.class); + + mWm.setForceShowSystemBars(true); + } + } + + @Test + public void testForceShowSystemBarsDoesNotThrowExceptionForAutomotiveWithStatusBarPermission() { + if (isAutomotive()) { + mExpectedException.none(); + + mWm.setForceShowSystemBars(true); + } + } + + private boolean isAutomotive() { + return getInstrumentation().getTargetContext().getPackageManager().hasSystemFeature( + PackageManager.FEATURE_AUTOMOTIVE); + } +} diff --git a/telephony/common/com/google/android/mms/pdu/CharacterSets.java b/telephony/common/com/google/android/mms/pdu/CharacterSets.java index 5172b7b67f88..a3894d6d2d34 100644 --- a/telephony/common/com/google/android/mms/pdu/CharacterSets.java +++ b/telephony/common/com/google/android/mms/pdu/CharacterSets.java @@ -48,6 +48,51 @@ public class CharacterSets { public static final int UTF_16 = 0x03F7; /** + * Extend charsets. + * + * From http://www.iana.org/assignments/character-sets/ + */ + public static final int BIG5_HKSCS = 0x0835; //2101 + public static final int BOCU_1 = 0x03FC; //1020 + public static final int CESU_8 = 0x03F8; //1016 + public static final int CP864 = 0x0803; //2051 + public static final int EUC_JP = 0x12; //18 + public static final int EUC_KR = 0x26; //38 + public static final int GB18030 = 0x72; //114 + public static final int GBK = 0x71; //113 + public static final int HZ_GB_2312 = 0x0825; //2085 + public static final int GB_2312 = 0x07E9; //2025 + public static final int ISO_2022_CN = 0x68; //104 + public static final int ISO_2022_CN_EXT = 0x69; //105 + public static final int ISO_2022_JP = 0x27; //39 + public static final int ISO_2022_KR = 0x25; //37 + public static final int ISO_8859_10 = 0x0D; //13 + public static final int ISO_8859_13 = 0x6D; //109 + public static final int ISO_8859_14 = 0x6E; //110 + public static final int ISO_8859_15 = 0x6F; //111 + public static final int ISO_8859_16 = 0x70; //112 + public static final int KOI8_R = 0x0824; //2084 + public static final int KOI8_U = 0x0828; //2088 + public static final int MACINTOSH = 0x07EB; //2027 + public static final int SCSU = 0x03F3; //1011 + public static final int TIS_620 = 0x08D3; //2259 + public static final int UTF_16BE = 0x03F5; //1013 + public static final int UTF_16LE = 0x03F6; //1014 + public static final int UTF_32 = 0x03F9; //1017 + public static final int UTF_32BE = 0x03FA; //1018 + public static final int UTF_32LE = 0x03FB; //1019 + public static final int UTF_7 = 0x03F4; //1012 + public static final int WINDOWS_1250 = 0x08CA; //2250 + public static final int WINDOWS_1251 = 0x08CB; //2251 + public static final int WINDOWS_1252 = 0x08CC; //2252 + public static final int WINDOWS_1253 = 0x08CD; //2253 + public static final int WINDOWS_1254 = 0x08CE; //2254 + public static final int WINDOWS_1255 = 0x08CF; //2255 + public static final int WINDOWS_1256 = 0x08D0; //2256 + public static final int WINDOWS_1257 = 0x08D1; //2257 + public static final int WINDOWS_1258 = 0x08D2; //2258 + + /** * If the encoding of given data is unsupported, use UTF_8 to decode it. */ public static final int DEFAULT_CHARSET = UTF_8; @@ -72,6 +117,45 @@ public class CharacterSets { BIG5, UCS2, UTF_16, + BIG5_HKSCS, + BOCU_1, + CESU_8, + CP864, + EUC_JP, + EUC_KR, + GB18030, + GBK, + HZ_GB_2312, + GB_2312, + ISO_2022_CN, + ISO_2022_CN_EXT, + ISO_2022_JP, + ISO_2022_KR, + ISO_8859_10, + ISO_8859_13, + ISO_8859_14, + ISO_8859_15, + ISO_8859_16, + KOI8_R, + KOI8_U, + MACINTOSH, + SCSU, + TIS_620, + UTF_16BE, + UTF_16LE, + UTF_32, + UTF_32BE, + UTF_32LE, + UTF_7, + WINDOWS_1250, + WINDOWS_1251, + WINDOWS_1252, + WINDOWS_1253, + WINDOWS_1254, + WINDOWS_1255, + WINDOWS_1256, + WINDOWS_1257, + WINDOWS_1258, }; /** @@ -94,6 +178,51 @@ public class CharacterSets { public static final String MIMENAME_UCS2 = "iso-10646-ucs-2"; public static final String MIMENAME_UTF_16 = "utf-16"; + /** + * Extend charsets. + * + * From http://www.iana.org/assignments/character-sets/ + */ + public static final String MIMENAME_BIG5_HKSCS = "Big5-HKSCS"; + public static final String MIMENAME_BOCU_1 = "BOCU-1"; + public static final String MIMENAME_CESU_8 = "CESU-8"; + public static final String MIMENAME_CP864 = "cp864"; + public static final String MIMENAME_EUC_JP = "EUC-JP"; + public static final String MIMENAME_EUC_KR = "EUC-KR"; + public static final String MIMENAME_GB18030 = "GB18030"; + public static final String MIMENAME_GBK = "GBK"; + public static final String MIMENAME_HZ_GB_2312 = "HZ-GB-2312"; + public static final String MIMENAME_GB_2312 = "GB2312"; + public static final String MIMENAME_ISO_2022_CN = "ISO-2022-CN"; + public static final String MIMENAME_ISO_2022_CN_EXT = "ISO-2022-CN-EXT"; + public static final String MIMENAME_ISO_2022_JP = "ISO-2022-JP"; + public static final String MIMENAME_ISO_2022_KR = "ISO-2022-KR"; + public static final String MIMENAME_ISO_8859_10 = "ISO-8859-10"; + public static final String MIMENAME_ISO_8859_13 = "ISO-8859-13"; + public static final String MIMENAME_ISO_8859_14 = "ISO-8859-14"; + public static final String MIMENAME_ISO_8859_15 = "ISO-8859-15"; + public static final String MIMENAME_ISO_8859_16 = "ISO-8859-16"; + public static final String MIMENAME_KOI8_R = "KOI8-R"; + public static final String MIMENAME_KOI8_U = "KOI8-U"; + public static final String MIMENAME_MACINTOSH = "macintosh"; + public static final String MIMENAME_SCSU = "SCSU"; + public static final String MIMENAME_TIS_620 = "TIS-620"; + public static final String MIMENAME_UTF_16BE = "UTF-16BE"; + public static final String MIMENAME_UTF_16LE = "UTF-16LE"; + public static final String MIMENAME_UTF_32 = "UTF-32"; + public static final String MIMENAME_UTF_32BE = "UTF-32BE"; + public static final String MIMENAME_UTF_32LE = "UTF-32LE"; + public static final String MIMENAME_UTF_7 = "UTF-7"; + public static final String MIMENAME_WINDOWS_1250 = "windows-1250"; + public static final String MIMENAME_WINDOWS_1251 = "windows-1251"; + public static final String MIMENAME_WINDOWS_1252 = "windows-1252"; + public static final String MIMENAME_WINDOWS_1253 = "windows-1253"; + public static final String MIMENAME_WINDOWS_1254 = "windows-1254"; + public static final String MIMENAME_WINDOWS_1255 = "windows-1255"; + public static final String MIMENAME_WINDOWS_1256 = "windows-1256"; + public static final String MIMENAME_WINDOWS_1257 = "windows-1257"; + public static final String MIMENAME_WINDOWS_1258 = "windows-1258"; + public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8; /** @@ -116,6 +245,45 @@ public class CharacterSets { MIMENAME_BIG5, MIMENAME_UCS2, MIMENAME_UTF_16, + MIMENAME_BIG5_HKSCS, + MIMENAME_BOCU_1, + MIMENAME_CESU_8, + MIMENAME_CP864, + MIMENAME_EUC_JP, + MIMENAME_EUC_KR, + MIMENAME_GB18030, + MIMENAME_GBK, + MIMENAME_HZ_GB_2312, + MIMENAME_GB_2312, + MIMENAME_ISO_2022_CN, + MIMENAME_ISO_2022_CN_EXT, + MIMENAME_ISO_2022_JP, + MIMENAME_ISO_2022_KR, + MIMENAME_ISO_8859_10, + MIMENAME_ISO_8859_13, + MIMENAME_ISO_8859_14, + MIMENAME_ISO_8859_15, + MIMENAME_ISO_8859_16, + MIMENAME_KOI8_R, + MIMENAME_KOI8_U, + MIMENAME_MACINTOSH, + MIMENAME_SCSU, + MIMENAME_TIS_620, + MIMENAME_UTF_16BE, + MIMENAME_UTF_16LE, + MIMENAME_UTF_32, + MIMENAME_UTF_32BE, + MIMENAME_UTF_32LE, + MIMENAME_UTF_7, + MIMENAME_WINDOWS_1250, + MIMENAME_WINDOWS_1251, + MIMENAME_WINDOWS_1252, + MIMENAME_WINDOWS_1253, + MIMENAME_WINDOWS_1254, + MIMENAME_WINDOWS_1255, + MIMENAME_WINDOWS_1256, + MIMENAME_WINDOWS_1257, + MIMENAME_WINDOWS_1258, }; private static final HashMap<Integer, String> MIBENUM_TO_NAME_MAP; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index e3d031dfa476..e520a02c2721 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -7761,9 +7761,8 @@ public class TelephonyManager { * * @hide */ - @SystemApi public static final int DEFAULT_PREFERRED_NETWORK_MODE = - RILConstants.DEFAULT_PREFERRED_NETWORK_MODE; + RILConstants.PREFERRED_NETWORK_MODE; /** * Get the preferred network type. diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 9ac8cb136c6b..c40573b25068 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -233,14 +233,11 @@ public interface RILConstants { /** NR 5G, LTE, TD-SCDMA, CDMA, EVDO, GSM and WCDMA */ int NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = 33; - /** Default preferred network mode */ - int DEFAULT_PREFERRED_NETWORK_MODE = NETWORK_MODE_WCDMA_PREF; - @UnsupportedAppUsage int PREFERRED_NETWORK_MODE = Optional.of(TelephonyProperties.default_network()) .filter(list -> !list.isEmpty()) .map(list -> list.get(0)) - .orElse(DEFAULT_PREFERRED_NETWORK_MODE); + .orElse(NETWORK_MODE_WCDMA_PREF); int BAND_MODE_UNSPECIFIED = 0; //"unspecified" (selected by baseband automatically) int BAND_MODE_EURO = 1; //"EURO band" (GSM-900 / DCS-1800 / WCDMA-IMT-2000) diff --git a/wifi/Android.bp b/wifi/Android.bp index 6a29b1c36052..1763975e7770 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -47,7 +47,7 @@ filegroup { // framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache // to a separate package. "java/android/net/wifi/WifiNetworkScoreCache.java", - "java/android/net/wifi/WifiOemConfigStoreMigrationHook.java", + "java/android/net/wifi/WifiOemMigrationHook.java", "java/android/net/wifi/wificond/*.java", ":libwificond_ipc_aidl", ], diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 1f1c0c12cf21..221f64440969 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -266,4 +266,8 @@ interface IWifiManager * Return the Map of {@link WifiNetworkSuggestion} and the list of <ScanResult> */ Map getMatchingScanResults(in List<WifiNetworkSuggestion> networkSuggestions, in List<ScanResult> scanResults, String callingPackage, String callingFeatureId); + + void setScanThrottleEnabled(boolean enable); + + boolean isScanThrottleEnabled(); } diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 7c031eaaeaf4..24b2a8e8994f 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -449,9 +449,8 @@ public class WifiInfo implements Parcelable { * <p> * If the SSID can be decoded as UTF-8, it will be returned surrounded by double * quotation marks. Otherwise, it is returned as a string of hex digits. - * The SSID may be - * <lt><unknown ssid>, if there is no network currently connected or if the caller has - * insufficient permissions to access the SSID.<lt> + * The SSID may be {@link WifiManager#UNKNOWN_SSID}, if there is no network currently connected + * or if the caller has insufficient permissions to access the SSID. * </p> * <p> * Prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index a3cce7c00b3a..5ccc3aa429c6 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -2982,7 +2982,7 @@ public class WifiManager { } /** - * Start Soft AP (hotspot) mode with the specified configuration. + * Start Soft AP (hotspot) mode for tethering purposes with the specified configuration. * Note that starting Soft AP mode may disable station mode operation if the device does not * support concurrency. * @param wifiConfig SSID, security and channel details as part of WifiConfiguration, or null to @@ -3278,7 +3278,7 @@ public class WifiManager { } /** - * Gets the Wi-Fi enabled state. + * Gets the tethered Wi-Fi hotspot enabled state. * @return One of {@link #WIFI_AP_STATE_DISABLED}, * {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED}, * {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED} @@ -3297,8 +3297,8 @@ public class WifiManager { } /** - * Return whether Wi-Fi AP is enabled or disabled. - * @return {@code true} if Wi-Fi AP is enabled + * Return whether tethered Wi-Fi AP is enabled or disabled. + * @return {@code true} if tethered Wi-Fi AP is enabled * @see #getWifiApState() * * @hide @@ -3310,7 +3310,7 @@ public class WifiManager { } /** - * Gets the Wi-Fi AP Configuration. + * Gets the tethered Wi-Fi AP Configuration. * @return AP details in WifiConfiguration * * Note that AP detail may contain configuration which is cannot be represented @@ -3332,7 +3332,7 @@ public class WifiManager { } /** - * Gets the Wi-Fi AP Configuration. + * Gets the Wi-Fi tethered AP Configuration. * @return AP details in {@link SoftApConfiguration} * * @hide @@ -3349,7 +3349,7 @@ public class WifiManager { } /** - * Sets the Wi-Fi AP Configuration. + * Sets the tethered Wi-Fi AP Configuration. * @return {@code true} if the operation succeeded, {@code false} otherwise * * @deprecated This API is deprecated. Use {@link #setSoftApConfiguration(SoftApConfiguration)} @@ -3368,9 +3368,9 @@ public class WifiManager { } /** - * Sets the Wi-Fi AP Configuration. + * Sets the tethered Wi-Fi AP Configuration. * - * If the API is called while the soft AP is enabled, the configuration will apply to + * If the API is called while the tethered soft AP is enabled, the configuration will apply to * the current soft AP if the new configuration only includes * {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)} * or {@link SoftApConfiguration.Builder#setShutdownTimeoutMillis(int)} @@ -6118,4 +6118,48 @@ public class WifiManager { throw e.rethrowFromSystemServer(); } } + + /** + * Enable/disable wifi scan throttling from 3rd party apps. + * + * <p> + * The throttling limits for apps are described in + * <a href="Wi-Fi Scan Throttling"> + * https://developer.android.com/guide/topics/connectivity/wifi-scan#wifi-scan-throttling</a> + * </p> + * + * @param enable true to allow scan throttling, false to disallow scan throttling. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void setScanThrottleEnabled(boolean enable) { + try { + mService.setScanThrottleEnabled(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the persisted Wi-Fi scan throttle state. Defaults to true, unless changed by the user via + * Developer options. + * + * <p> + * The throttling limits for apps are described in + * <a href="Wi-Fi Scan Throttling"> + * https://developer.android.com/guide/topics/connectivity/wifi-scan#wifi-scan-throttling</a> + * </p> + * + * @return true to indicate that scan throttling is enabled, false to indicate that scan + * throttling is disabled. + */ + @RequiresPermission(ACCESS_WIFI_STATE) + public boolean isScanThrottleEnabled() { + try { + return mService.isScanThrottleEnabled(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java b/wifi/java/android/net/wifi/WifiOemMigrationHook.java index 642dcb9211ae..22d778637101 100755 --- a/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java +++ b/wifi/java/android/net/wifi/WifiOemMigrationHook.java @@ -28,30 +28,17 @@ import java.util.List; /** * Class used to provide one time hooks for existing OEM devices to migrate their config store - * data to the wifi mainline module. - * <p> - * Note: - * <li> OEM's need to implement {@link #load()} only if their - * existing config store format or file locations differs from the vanilla AOSP implementation ( - * which is what the wifi mainline module understands). - * </li> - * <li> The wifi mainline module will invoke {@link #load()} method on every bootup, its - * the responsibility of the OEM implementation to ensure that this method returns non-null data - * only on the first bootup. Once the migration is done, the OEM can safely delete their config - * store files and then return null on any subsequent reboots. The first & only relevant invocation - * of {@link #load()} occurs when a previously released device upgrades to the wifi - * mainline module from an OEM implementation of the wifi stack. - * </li> + * data and other settings to the wifi mainline module. * @hide */ @SystemApi -public final class WifiOemConfigStoreMigrationHook { +public final class WifiOemMigrationHook { /** * Container for all the wifi config data to migrate. */ - public static final class MigrationData implements Parcelable { + public static final class ConfigStoreMigrationData implements Parcelable { /** - * Builder to create instance of {@link MigrationData}. + * Builder to create instance of {@link ConfigStoreMigrationData}. */ public static final class Builder { private List<WifiConfiguration> mUserSavedNetworkConfigurations; @@ -92,39 +79,40 @@ public final class WifiOemConfigStoreMigrationHook { } /** - * Build an instance of {@link MigrationData}. + * Build an instance of {@link ConfigStoreMigrationData}. * - * @return Instance of {@link MigrationData}. + * @return Instance of {@link ConfigStoreMigrationData}. */ - public @NonNull MigrationData build() { - return new MigrationData(mUserSavedNetworkConfigurations, mUserSoftApConfiguration); + public @NonNull ConfigStoreMigrationData build() { + return new ConfigStoreMigrationData( + mUserSavedNetworkConfigurations, mUserSoftApConfiguration); } } private final List<WifiConfiguration> mUserSavedNetworkConfigurations; private final SoftApConfiguration mUserSoftApConfiguration; - private MigrationData( + private ConfigStoreMigrationData( @Nullable List<WifiConfiguration> userSavedNetworkConfigurations, @Nullable SoftApConfiguration userSoftApConfiguration) { mUserSavedNetworkConfigurations = userSavedNetworkConfigurations; mUserSoftApConfiguration = userSoftApConfiguration; } - public static final @NonNull Parcelable.Creator<MigrationData> CREATOR = - new Parcelable.Creator<MigrationData>() { + public static final @NonNull Parcelable.Creator<ConfigStoreMigrationData> CREATOR = + new Parcelable.Creator<ConfigStoreMigrationData>() { @Override - public MigrationData createFromParcel(Parcel in) { + public ConfigStoreMigrationData createFromParcel(Parcel in) { List<WifiConfiguration> userSavedNetworkConfigurations = in.readArrayList(null); SoftApConfiguration userSoftApConfiguration = in.readParcelable(null); - return new MigrationData( + return new ConfigStoreMigrationData( userSavedNetworkConfigurations, userSoftApConfiguration); } @Override - public MigrationData[] newArray(int size) { - return new MigrationData[size]; + public ConfigStoreMigrationData[] newArray(int size) { + return new ConfigStoreMigrationData[size]; } }; @@ -164,16 +152,29 @@ public final class WifiOemConfigStoreMigrationHook { } } - private WifiOemConfigStoreMigrationHook() { } + private WifiOemMigrationHook() { } /** * Load data from OEM's config store. + * <p> + * Note: + * <li> OEM's need to implement {@link #loadFromConfigStore()} ()} only if their + * existing config store format or file locations differs from the vanilla AOSP implementation ( + * which is what the wifi mainline module understands). + * </li> + * <li> The wifi mainline module will invoke {@link #loadFromConfigStore()} method on every + * bootup, its the responsibility of the OEM implementation to ensure that this method returns + * non-null data only on the first bootup. Once the migration is done, the OEM can safely delete + * their config store files and then return null on any subsequent reboots. The first & only + * relevant invocation of {@link #loadFromConfigStore()} occurs when a previously released + * device upgrades to the wifi mainline module from an OEM implementation of the wifi stack. + * </li> * - * @return Instance of {@link MigrationData} for migrating data, null if no + * @return Instance of {@link ConfigStoreMigrationData} for migrating data, null if no * migration is necessary. */ @Nullable - public static MigrationData load() { + public static ConfigStoreMigrationData loadFromConfigStore() { // Note: OEM's should add code to parse data from their config store format here! return null; } diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java index 36c7213b3799..d47989235f0b 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java @@ -17,6 +17,7 @@ package android.net.wifi.p2p; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -133,6 +134,7 @@ public class WifiP2pConfig implements Parcelable { * * By default this field is set to {@link #GROUP_OWNER_INTENT_AUTO}. */ + @IntRange(from = 0, to = 15) public int groupOwnerIntent = GROUP_OWNER_INTENT_AUTO; /** @hide */ diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java index cdb2806af0b1..8a86311defca 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java @@ -22,7 +22,9 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.LruCache; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; @@ -78,8 +80,8 @@ public final class WifiP2pGroupList implements Parcelable { * Get the list of P2P groups. */ @NonNull - public Collection<WifiP2pGroup> getGroupList() { - return mGroups.snapshot().values(); + public List<WifiP2pGroup> getGroupList() { + return new ArrayList<>(mGroups.snapshot().values()); } /** diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java index 0fe06756a969..9c2cad99614d 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java @@ -326,6 +326,12 @@ public class WifiP2pManager { /** * Broadcast intent action indicating that remembered persistent groups have changed. + * + * You can <em>not</em> receive this through components declared + * in manifests, only by explicitly registering for it with + * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver, + * android.content.IntentFilter) Context.registerReceiver()}. + * * @hide */ @SystemApi @@ -1347,20 +1353,33 @@ public class WifiP2pManager { } /** - * Force p2p to enter or exit listen state + * Force p2p to enter listen state * * @param c is the channel created at {@link #initialize(Context, Looper, ChannelListener)} - * @param enable enables or disables listening * @param listener for callbacks on success or failure. Can be null. * * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) - public void listen(@NonNull Channel c, boolean enable, @Nullable ActionListener listener) { + public void startListening(@NonNull Channel c, @Nullable ActionListener listener) { checkChannel(c); - c.mAsyncChannel.sendMessage(enable ? START_LISTEN : STOP_LISTEN, - 0, c.putListener(listener)); + c.mAsyncChannel.sendMessage(START_LISTEN, 0, c.putListener(listener)); + } + + /** + * Force p2p to exit listen state + * + * @param c is the channel created at {@link #initialize(Context, Looper, ChannelListener)} + * @param listener for callbacks on success or failure. Can be null. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void stopListening(@NonNull Channel c, @Nullable ActionListener listener) { + checkChannel(c); + c.mAsyncChannel.sendMessage(STOP_LISTEN, 0, c.putListener(listener)); } /** diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java b/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java index 5484d248429f..e399b5b9afa6 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java @@ -17,6 +17,7 @@ package android.net.wifi.p2p; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -34,7 +35,7 @@ import java.util.Locale; */ public final class WifiP2pWfdInfo implements Parcelable { - private boolean mWfdEnabled; + private boolean mEnabled; /** Device information bitmap */ private int mDeviceInfo; @@ -85,15 +86,15 @@ public final class WifiP2pWfdInfo implements Parcelable { /** @hide */ @UnsupportedAppUsage public WifiP2pWfdInfo(int devInfo, int ctrlPort, int maxTput) { - mWfdEnabled = true; + mEnabled = true; mDeviceInfo = devInfo; mCtrlPort = ctrlPort; mMaxThroughput = maxTput; } /** Returns true is Wifi Display is enabled, false otherwise. */ - public boolean isWfdEnabled() { - return mWfdEnabled; + public boolean isEnabled() { + return mEnabled; } /** @@ -101,8 +102,8 @@ public final class WifiP2pWfdInfo implements Parcelable { * * @param enabled true to enable Wifi Display, false to disable */ - public void setWfdEnabled(boolean enabled) { - mWfdEnabled = enabled; + public void setEnabled(boolean enabled) { + mEnabled = enabled; } /** @@ -177,12 +178,12 @@ public final class WifiP2pWfdInfo implements Parcelable { } /** Sets the TCP port at which the WFD Device listens for RTSP messages. */ - public void setControlPort(int port) { + public void setControlPort(@IntRange(from = 0) int port) { mCtrlPort = port; } /** Sets the maximum average throughput capability of the WFD Device, in megabits/second. */ - public void setMaxThroughput(int maxThroughput) { + public void setMaxThroughput(@IntRange(from = 0) int maxThroughput) { mMaxThroughput = maxThroughput; } @@ -200,7 +201,7 @@ public final class WifiP2pWfdInfo implements Parcelable { @Override public String toString() { StringBuffer sbuf = new StringBuffer(); - sbuf.append("WFD enabled: ").append(mWfdEnabled); + sbuf.append("WFD enabled: ").append(mEnabled); sbuf.append("WFD DeviceInfo: ").append(mDeviceInfo); sbuf.append("\n WFD CtrlPort: ").append(mCtrlPort); sbuf.append("\n WFD MaxThroughput: ").append(mMaxThroughput); @@ -215,7 +216,7 @@ public final class WifiP2pWfdInfo implements Parcelable { /** Copy constructor. */ public WifiP2pWfdInfo(@Nullable WifiP2pWfdInfo source) { if (source != null) { - mWfdEnabled = source.mWfdEnabled; + mEnabled = source.mEnabled; mDeviceInfo = source.mDeviceInfo; mCtrlPort = source.mCtrlPort; mMaxThroughput = source.mMaxThroughput; @@ -225,14 +226,14 @@ public final class WifiP2pWfdInfo implements Parcelable { /** Implement the Parcelable interface */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mWfdEnabled ? 1 : 0); + dest.writeInt(mEnabled ? 1 : 0); dest.writeInt(mDeviceInfo); dest.writeInt(mCtrlPort); dest.writeInt(mMaxThroughput); } private void readFromParcel(Parcel in) { - mWfdEnabled = (in.readInt() == 1); + mEnabled = (in.readInt() == 1); mDeviceInfo = in.readInt(); mCtrlPort = in.readInt(); mMaxThroughput = in.readInt(); diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java deleted file mode 100644 index 060c85cac209..000000000000 --- a/wifi/java/com/android/server/wifi/BaseWifiService.java +++ /dev/null @@ -1,641 +0,0 @@ -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License") { - * throw new UnsupportedOperationException(); - } - * 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.wifi; - -import android.content.pm.ParceledListSlice; -import android.net.DhcpInfo; -import android.net.Network; -import android.net.wifi.IActionListener; -import android.net.wifi.IDppCallback; -import android.net.wifi.ILocalOnlyHotspotCallback; -import android.net.wifi.INetworkRequestMatchCallback; -import android.net.wifi.IOnWifiActivityEnergyInfoListener; -import android.net.wifi.IOnWifiUsabilityStatsListener; -import android.net.wifi.IScanResultsCallback; -import android.net.wifi.ISoftApCallback; -import android.net.wifi.ISuggestionConnectionStatusListener; -import android.net.wifi.ITrafficStateCallback; -import android.net.wifi.ITxPacketCountListener; -import android.net.wifi.IWifiConnectedNetworkScorer; -import android.net.wifi.IWifiManager; -import android.net.wifi.ScanResult; -import android.net.wifi.SoftApConfiguration; -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import android.net.wifi.WifiNetworkSuggestion; -import android.net.wifi.hotspot2.IProvisioningCallback; -import android.net.wifi.hotspot2.OsuProvider; -import android.net.wifi.hotspot2.PasspointConfiguration; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.os.WorkSource; -import android.os.connectivity.WifiActivityEnergyInfo; - -import java.util.List; -import java.util.Map; - -/** - * Empty concrete class implementing IWifiManager with stub methods throwing runtime exceptions. - * - * This class is meant to be extended by real implementations of IWifiManager in order to facilitate - * cross-repo changes to WiFi internal APIs, including the introduction of new APIs, the removal of - * deprecated APIs, or the migration of existing API signatures. - * - * When an existing API is scheduled for removal, it can be removed from IWifiManager.aidl - * immediately and marked as @Deprecated first in this class. Children inheriting this class are - * then given a short grace period to update themselves before the @Deprecated stub is removed for - * good. If the API scheduled for removal has a replacement or an overload (signature change), - * these should be introduced before the stub is removed to allow children to migrate. - * - * When a new API is added to IWifiManager.aidl, a stub should be added in BaseWifiService as - * well otherwise compilation will fail. - */ -public class BaseWifiService extends IWifiManager.Stub { - - private static final String TAG = BaseWifiService.class.getSimpleName(); - - @Override - public long getSupportedFeatures() { - throw new UnsupportedOperationException(); - } - - /** @deprecated use {@link #getWifiActivityEnergyInfoAsync} instead */ - @Deprecated - public WifiActivityEnergyInfo reportActivityInfo() { - throw new UnsupportedOperationException(); - } - - /** @deprecated use {@link #getWifiActivityEnergyInfoAsync} instead */ - @Deprecated - public void requestActivityInfo(ResultReceiver result) { - throw new UnsupportedOperationException(); - } - - @Override - public void getWifiActivityEnergyInfoAsync(IOnWifiActivityEnergyInfoListener listener) { - throw new UnsupportedOperationException(); - } - - @Override - public ParceledListSlice getConfiguredNetworks(String packageName, String featureId) { - throw new UnsupportedOperationException(); - } - - @Override - public ParceledListSlice getPrivilegedConfiguredNetworks(String packageName, String featureId) { - throw new UnsupportedOperationException(); - } - - @Override - public Map<String, Map<Integer, List<ScanResult>>> getAllMatchingFqdnsForScanResults( - List<ScanResult> scanResults) { - throw new UnsupportedOperationException(); - } - - @Override - public Map<OsuProvider, List<ScanResult>> getMatchingOsuProviders( - List<ScanResult> scanResults) { - throw new UnsupportedOperationException(); - } - - @Override - public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders( - List<OsuProvider> osuProviders) { - throw new UnsupportedOperationException(); - } - - @Override - public int addOrUpdateNetwork(WifiConfiguration config, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean addOrUpdatePasspointConfiguration( - PasspointConfiguration config, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean removePasspointConfiguration(String fqdn, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public List<PasspointConfiguration> getPasspointConfigurations(String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public List<WifiConfiguration> getWifiConfigsForPasspointProfiles(List<String> fqdnList) { - throw new UnsupportedOperationException(); - } - - @Override - public void queryPasspointIcon(long bssid, String fileName) { - throw new UnsupportedOperationException(); - } - - @Override - public int matchProviderWithCurrentNetwork(String fqdn) { - throw new UnsupportedOperationException(); - } - - @Override - public void deauthenticateNetwork(long holdoff, boolean ess) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean removeNetwork(int netId, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean enableNetwork(int netId, boolean disableOthers, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean disableNetwork(int netId, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public void allowAutojoinGlobal(boolean choice) { - throw new UnsupportedOperationException(); - } - - @Override - public void allowAutojoin(int netId, boolean choice) { - throw new UnsupportedOperationException(); - } - - @Override - public void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin) { - throw new UnsupportedOperationException(); - } - - @Override - public void setMacRandomizationSettingPasspointEnabled(String fqdn, boolean enable) { - throw new UnsupportedOperationException(); - } - - @Override - public void setMeteredOverridePasspoint(String fqdn, int meteredOverride) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean startScan(String packageName, String featureId) { - throw new UnsupportedOperationException(); - } - - @Override - public List<ScanResult> getScanResults(String callingPackage, String callingFeatureId) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean disconnect(String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean reconnect(String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean reassociate(String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public WifiInfo getConnectionInfo(String callingPackage, String callingFeatureId) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean setWifiEnabled(String packageName, boolean enable) { - throw new UnsupportedOperationException(); - } - - @Override - public int getWifiEnabledState() { - throw new UnsupportedOperationException(); - } - - @Override - public String getCountryCode() { - throw new UnsupportedOperationException(); - } - - /** @deprecated use {@link #is5GHzBandSupported} instead */ - @Deprecated - public boolean isDualBandSupported() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean is5GHzBandSupported() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean is6GHzBandSupported() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isWifiStandardSupported(int standard) { - throw new UnsupportedOperationException(); - } - - /** @deprecated use {@link WifiManager#isStaApConcurrencySupported()} */ - @Deprecated - public boolean needs5GHzToAnyApBandConversion() { - throw new UnsupportedOperationException(); - } - - @Override - public DhcpInfo getDhcpInfo() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isScanAlwaysAvailable() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean acquireWifiLock(IBinder lock, int lockType, String tag, WorkSource ws) { - throw new UnsupportedOperationException(); - } - - @Override - public void updateWifiLockWorkSource(IBinder lock, WorkSource ws) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean releaseWifiLock(IBinder lock) { - throw new UnsupportedOperationException(); - } - - @Override - public void initializeMulticastFiltering() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isMulticastEnabled() { - throw new UnsupportedOperationException(); - } - - @Override - public void acquireMulticastLock(IBinder binder, String tag) { - throw new UnsupportedOperationException(); - } - - @Override - public void releaseMulticastLock(String tag) { - throw new UnsupportedOperationException(); - } - - @Override - public void updateInterfaceIpState(String ifaceName, int mode) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean startSoftAp(WifiConfiguration wifiConfig) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean startTetheredHotspot(SoftApConfiguration softApConfig) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean stopSoftAp() { - throw new UnsupportedOperationException(); - } - - @Override - public int startLocalOnlyHotspot(ILocalOnlyHotspotCallback callback, String packageName, - String featureId, SoftApConfiguration customConfig) { - throw new UnsupportedOperationException(); - } - - @Override - public void stopLocalOnlyHotspot() { - throw new UnsupportedOperationException(); - } - - @Override - public void startWatchLocalOnlyHotspot(ILocalOnlyHotspotCallback callback) { - throw new UnsupportedOperationException(); - } - - @Override - public void stopWatchLocalOnlyHotspot() { - throw new UnsupportedOperationException(); - } - - @Override - public int getWifiApEnabledState() { - throw new UnsupportedOperationException(); - } - - @Override - public WifiConfiguration getWifiApConfiguration() { - throw new UnsupportedOperationException(); - } - - @Override - public SoftApConfiguration getSoftApConfiguration() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean setWifiApConfiguration(WifiConfiguration wifiConfig, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean setSoftApConfiguration(SoftApConfiguration softApConfig, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public void notifyUserOfApBandConversion(String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public void enableTdls(String remoteIPAddress, boolean enable) { - throw new UnsupportedOperationException(); - } - - @Override - public void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable) { - throw new UnsupportedOperationException(); - } - - @Override - public String getCurrentNetworkWpsNfcConfigurationToken() { - throw new UnsupportedOperationException(); - } - - @Override - public void enableVerboseLogging(int verbose) { - throw new UnsupportedOperationException(); - } - - @Override - public int getVerboseLoggingLevel() { - throw new UnsupportedOperationException(); - } - - /** @deprecated use {@link #allowAutojoinGlobal(boolean)} instead */ - @Deprecated - public void enableWifiConnectivityManager(boolean enabled) { - throw new UnsupportedOperationException(); - } - - @Override - public void disableEphemeralNetwork(String SSID, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public void factoryReset(String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public Network getCurrentNetwork() { - throw new UnsupportedOperationException(); - } - - @Override - public byte[] retrieveBackupData() { - throw new UnsupportedOperationException(); - } - - @Override - public void restoreBackupData(byte[] data) { - throw new UnsupportedOperationException(); - } - - @Override - public byte[] retrieveSoftApBackupData() { - throw new UnsupportedOperationException(); - } - - @Override - public SoftApConfiguration restoreSoftApBackupData(byte[] data) { - throw new UnsupportedOperationException(); - } - - @Override - public void restoreSupplicantBackupData(byte[] supplicantData, byte[] ipConfigData) { - throw new UnsupportedOperationException(); - } - - @Override - public void startSubscriptionProvisioning( - OsuProvider provider, IProvisioningCallback callback) { - throw new UnsupportedOperationException(); - } - - @Override - public void registerSoftApCallback( - IBinder binder, ISoftApCallback callback, int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void unregisterSoftApCallback(int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void registerTrafficStateCallback( - IBinder binder, ITrafficStateCallback callback, int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void unregisterTrafficStateCallback(int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void registerNetworkRequestMatchCallback( - IBinder binder, INetworkRequestMatchCallback callback, int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void unregisterNetworkRequestMatchCallback(int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public int addNetworkSuggestions( - List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName, - String callingFeatureId) { - throw new UnsupportedOperationException(); - } - - @Override - public int removeNetworkSuggestions( - List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) { - throw new UnsupportedOperationException(); - } - - @Override - public List<WifiNetworkSuggestion> getNetworkSuggestions(String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public String[] getFactoryMacAddresses() { - throw new UnsupportedOperationException(); - } - - @Override - public void setDeviceMobilityState(int state) { - throw new UnsupportedOperationException(); - } - - @Override - public void startDppAsConfiguratorInitiator(IBinder binder, String enrolleeUri, - int selectedNetworkId, int netRole, IDppCallback callback) { - throw new UnsupportedOperationException(); - } - - @Override - public void startDppAsEnrolleeInitiator(IBinder binder, String configuratorUri, - IDppCallback callback) { - throw new UnsupportedOperationException(); - } - - @Override - public void stopDppSession() throws RemoteException { - throw new UnsupportedOperationException(); - } - - @Override - public void addOnWifiUsabilityStatsListener( - IBinder binder, IOnWifiUsabilityStatsListener listener, int listenerIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void removeOnWifiUsabilityStatsListener(int listenerIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) { - throw new UnsupportedOperationException(); - } - - @Override - public void connect(WifiConfiguration config, int netId, IBinder binder, - IActionListener callback, int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void save(WifiConfiguration config, IBinder binder, IActionListener callback, - int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void forget(int netId, IBinder binder, IActionListener callback, - int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void getTxPacketCount(String packageName, IBinder binder, - ITxPacketCountListener callback, int callbackIdentifier) { - throw new UnsupportedOperationException(); - } - - @Override - public void registerScanResultsCallback(IScanResultsCallback callback) { - throw new UnsupportedOperationException(); - } - - @Override - public void unregisterScanResultsCallback(IScanResultsCallback callback) { - throw new UnsupportedOperationException(); - } - - @Override - public void registerSuggestionConnectionStatusListener(IBinder binder, - ISuggestionConnectionStatusListener listener, - int listenerIdentifier, String packageName, String featureId) { - throw new UnsupportedOperationException(); - } - - @Override - public void unregisterSuggestionConnectionStatusListener(int listenerIdentifier, - String packageName) { - throw new UnsupportedOperationException(); - } - - @Override - public int calculateSignalLevel(int rssi) { - throw new UnsupportedOperationException(); - } - - @Override - public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser( - List<ScanResult> scanResults) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean setWifiConnectedNetworkScorer(IBinder binder, - IWifiConnectedNetworkScorer scorer) { - throw new UnsupportedOperationException(); - } - - @Override - public void clearWifiConnectedNetworkScorer() { - throw new UnsupportedOperationException(); - } - - @Override - public Map<WifiNetworkSuggestion, List<ScanResult>> getMatchingScanResults( - List<WifiNetworkSuggestion> networkSuggestions, - List<ScanResult> scanResults, - String callingPackage, String callingFeatureId) { - throw new UnsupportedOperationException(); - } -} diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 6320f85eb974..53e9755eaf52 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -2380,4 +2380,14 @@ public class WifiManagerTest { verify(mWifiConnectedNetworkScorer).start(0); verify(mWifiConnectedNetworkScorer).stop(10); } + + @Test + public void testScanThrottle() throws Exception { + mWifiManager.setScanThrottleEnabled(true); + verify(mWifiService).setScanThrottleEnabled(true); + + when(mWifiService.isScanThrottleEnabled()).thenReturn(false); + assertFalse(mWifiManager.isScanThrottleEnabled()); + verify(mWifiService).isScanThrottleEnabled(); + } } diff --git a/wifi/tests/src/android/net/wifi/p2p/WifiP2pDeviceTest.java b/wifi/tests/src/android/net/wifi/p2p/WifiP2pDeviceTest.java index 17ee75594c2f..6edc287068e8 100644 --- a/wifi/tests/src/android/net/wifi/p2p/WifiP2pDeviceTest.java +++ b/wifi/tests/src/android/net/wifi/p2p/WifiP2pDeviceTest.java @@ -45,7 +45,7 @@ public class WifiP2pDeviceTest { assertEquals(devA.groupCapability, devB.groupCapability); assertEquals(devA.status, devB.status); if (devA.wfdInfo != null) { - assertEquals(devA.wfdInfo.isWfdEnabled(), devB.wfdInfo.isWfdEnabled()); + assertEquals(devA.wfdInfo.isEnabled(), devB.wfdInfo.isEnabled()); assertEquals(devA.wfdInfo.getDeviceInfoHex(), devB.wfdInfo.getDeviceInfoHex()); assertEquals(devA.wfdInfo.getControlPort(), devB.wfdInfo.getControlPort()); assertEquals(devA.wfdInfo.getMaxThroughput(), devB.wfdInfo.getMaxThroughput()); diff --git a/wifi/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java b/wifi/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java index 15a0aacf6e5b..2a9b36b47172 100644 --- a/wifi/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java +++ b/wifi/tests/src/android/net/wifi/p2p/WifiP2pWfdInfoTest.java @@ -55,8 +55,8 @@ public class WifiP2pWfdInfoTest { public void testSettersGetters() throws Exception { WifiP2pWfdInfo info = new WifiP2pWfdInfo(); - info.setWfdEnabled(true); - assertTrue(info.isWfdEnabled()); + info.setEnabled(true); + assertTrue(info.isEnabled()); info.setDeviceType(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE); assertEquals(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE, info.getDeviceType()); |