diff options
1010 files changed, 28977 insertions, 13468 deletions
diff --git a/Android.bp b/Android.bp index 5796fb17046c..df852bda4904 100644 --- a/Android.bp +++ b/Android.bp @@ -263,8 +263,6 @@ filegroup { ":libcamera_client_aidl", ":libcamera_client_framework_aidl", ":libupdate_engine_aidl", - // TODO: this needs to be removed when statsd-framework.jar is separated out - ":statsd_java_aidl", ":storaged_aidl", ":vold_aidl", @@ -403,7 +401,6 @@ java_defaults { "unsupportedappusage", "framework-media-stubs-systemapi", "framework-mediaprovider-stubs-systemapi", - "framework-tethering", "framework-telephony-stubs", ], @@ -446,13 +443,6 @@ filegroup { } filegroup { - name: "graphicsstats_proto", - srcs: [ - "libs/hwui/protos/graphicsstats.proto", - ], -} - -filegroup { name: "libvibrator_aidl", srcs: [ "core/java/android/os/IExternalVibrationController.aidl", @@ -468,10 +458,11 @@ java_library { libs: [ "framework-appsearch-stubs", "framework-sdkextensions-stubs-systemapi", - "framework-statsd", // TODO(b/146167933): Use framework-statsd-stubs + "framework-statsd-stubs-module_libs_api", "framework-permission-stubs-systemapi", "framework-wifi-stubs", "ike-stubs", + "framework-tethering-stubs", ], installable: true, javac_shard_size: 150, @@ -496,6 +487,7 @@ java_library { "//frameworks/base/apex/blobstore/framework", "//frameworks/base/apex/jobscheduler/framework", "//frameworks/base/apex/statsd/service", + "//frameworks/base/packages/Tethering/tests/unit", ], } @@ -519,12 +511,10 @@ java_library { "framework-mediaprovider-stubs-systemapi", "framework-permission-stubs-systemapi", "framework-sdkextensions-stubs-systemapi", - // TODO(b/146167933): Use framework-statsd-stubs instead. - "framework-statsd", + "framework-statsd-stubs-module_libs_api", "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 @@ -625,6 +615,18 @@ genrule { out: ["com/android/internal/util/FrameworkStatsLog.java"], } +java_library { + name: "uieventloggerlib", + srcs: [ + "core/java/com/android/internal/logging/UiEvent.java", + "core/java/com/android/internal/logging/UiEventLogger.java", + "core/java/com/android/internal/logging/UiEventLoggerImpl.java", + "core/java/com/android/internal/logging/InstanceId.java", + "core/java/com/android/internal/logging/InstanceIdSequence.java", + ":statslog-framework-java-gen", + ], +} + gensrcs { name: "framework-javastream-protos", depfile: true, @@ -653,6 +655,33 @@ gensrcs { output_extension: "srcjar", } +gensrcs { + name: "framework-cppstream-protos", + depfile: true, + + tools: [ + "aprotoc", + "protoc-gen-cppstream", + ], + + cmd: "mkdir -p $(genDir) " + + "&& $(location aprotoc) " + + " --plugin=$(location protoc-gen-cppstream) " + + " --dependency_out=$(depfile) " + + " --cppstream_out=$(genDir) " + + " -Iexternal/protobuf/src " + + " -I . " + + " $(in)", + + srcs: [ + ":ipconnectivity-proto-src", + "core/proto/**/*.proto", + "libs/incident/**/*.proto", + ], + + output_extension: "proto.h", +} + filegroup { name: "framework-annotations", srcs: [ @@ -1010,43 +1039,6 @@ aidl_interface { }, } -gensrcs { - name: "gen-platform-proto-constants", - depfile: true, - - tools: [ - "aprotoc", - "protoc-gen-cppstream", - ], - - srcs: [ - "core/proto/android/os/backtrace.proto", - "core/proto/android/os/batterytype.proto", - "core/proto/android/os/cpufreq.proto", - "core/proto/android/os/cpuinfo.proto", - "core/proto/android/os/data.proto", - "core/proto/android/os/kernelwake.proto", - "core/proto/android/os/pagetypeinfo.proto", - "core/proto/android/os/procrank.proto", - "core/proto/android/os/ps.proto", - "core/proto/android/os/system_properties.proto", - "core/proto/android/util/event_log_tags.proto", - "core/proto/android/util/log.proto", - ], - - // Append protoc-gen-cppstream tool's PATH otherwise aprotoc can't find the plugin tool - cmd: "mkdir -p $(genDir) " + - "&& $(location aprotoc) " + - " --plugin=$(location protoc-gen-cppstream) " + - " --dependency_out=$(depfile) " + - " --cppstream_out=$(genDir) " + - " -Iexternal/protobuf/src " + - " -I . " + - " $(in)", - - output_extension: "proto.h", -} - subdirs = [ "cmds/*", diff --git a/StubLibraries.bp b/StubLibraries.bp index d4db7373504b..9b9311f6c3c2 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -190,7 +190,6 @@ droidstubs { droidstubs { name: "module-lib-api", defaults: ["metalava-api-stubs-default"], - libs: ["framework-all"], arg_files: ["core/res/AndroidManifest.xml"], args: metalava_framework_docs_args + module_libs, check_api: { @@ -222,7 +221,6 @@ droidstubs { droidstubs { name: "module-lib-api-stubs-docs", defaults: ["metalava-api-stubs-default"], - libs: ["framework-all"], arg_files: ["core/res/AndroidManifest.xml"], args: metalava_framework_docs_args + priv_apps + module_libs, } @@ -299,12 +297,12 @@ java_library_static { ], libs: [ "stub-annotations", - "framework-all", ], static_libs: [ "private-stub-annotations-jar", ], defaults: ["framework-stubs-default"], + sdk_version: "core_current", } ///////////////////////////////////////////////////////////////////// diff --git a/apct-tests/perftests/blobstore/Android.bp b/apct-tests/perftests/blobstore/Android.bp new file mode 100644 index 000000000000..be5072ce3d9d --- /dev/null +++ b/apct-tests/perftests/blobstore/Android.bp @@ -0,0 +1,28 @@ +// 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 { + name: "BlobStorePerfTests", + srcs: ["src/**/*.java"], + static_libs: [ + "BlobStoreTestUtils", + "androidx.test.rules", + "androidx.annotation_annotation", + "apct-perftests-utils", + "ub-uiautomator", + ], + platform_apis: true, + test_suites: ["device-tests"], + certificate: "platform", +}
\ No newline at end of file diff --git a/apct-tests/perftests/blobstore/AndroidManifest.xml b/apct-tests/perftests/blobstore/AndroidManifest.xml new file mode 100644 index 000000000000..21d0726927af --- /dev/null +++ b/apct-tests/perftests/blobstore/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?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.blob"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.perftests.blob"/> + +</manifest>
\ No newline at end of file diff --git a/apct-tests/perftests/blobstore/AndroidTest.xml b/apct-tests/perftests/blobstore/AndroidTest.xml new file mode 100644 index 000000000000..19456c6d81d7 --- /dev/null +++ b/apct-tests/perftests/blobstore/AndroidTest.xml @@ -0,0 +1,28 @@ +<?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 BlobStorePerfTests 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="BlobStorePerfTests.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.perftests.blob" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration>
\ No newline at end of file diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java new file mode 100644 index 000000000000..0208dab33746 --- /dev/null +++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java @@ -0,0 +1,120 @@ +/* + * 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.perftests.blob; + +import android.app.Instrumentation; +import android.app.UiAutomation; +import android.os.ParcelFileDescriptor; +import android.perftests.utils.TraceMarkParser; +import android.perftests.utils.TraceMarkParser.TraceMarkSlice; +import android.support.test.uiautomator.UiDevice; +import android.util.Log; + +import androidx.test.platform.app.InstrumentationRegistry; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; +import java.util.function.BiConsumer; + +// Copy of com.android.frameworks.perftests.am.util.AtraceUtils. TODO: avoid this duplication. +public class AtraceUtils { + private static final String TAG = "AtraceUtils"; + private static final boolean VERBOSE = true; + + private static final String ATRACE_START = "atrace --async_start -b %d -c %s"; + private static final String ATRACE_DUMP = "atrace --async_dump"; + private static final String ATRACE_STOP = "atrace --async_stop"; + private static final int DEFAULT_ATRACE_BUF_SIZE = 1024; + + private UiAutomation mAutomation; + private static AtraceUtils sUtils = null; + private boolean mStarted = false; + + private AtraceUtils(Instrumentation instrumentation) { + mAutomation = instrumentation.getUiAutomation(); + } + + public static AtraceUtils getInstance(Instrumentation instrumentation) { + if (sUtils == null) { + sUtils = new AtraceUtils(instrumentation); + } + return sUtils; + } + + /** + * @param categories The list of the categories to trace, separated with space. + */ + public void startTrace(String categories) { + synchronized (this) { + if (mStarted) { + throw new IllegalStateException("atrace already started"); + } + runShellCommand(String.format( + ATRACE_START, DEFAULT_ATRACE_BUF_SIZE, categories)); + mStarted = true; + } + } + + public void stopTrace() { + synchronized (this) { + mStarted = false; + runShellCommand(ATRACE_STOP); + } + } + + private String runShellCommand(String cmd) { + try { + return UiDevice.getInstance( + InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * @param parser The function that can accept the buffer of atrace dump and parse it. + * @param handler The parse result handler + */ + public void performDump(TraceMarkParser parser, + BiConsumer<String, List<TraceMarkSlice>> handler) { + parser.reset(); + try { + if (VERBOSE) { + Log.i(TAG, "Collecting atrace dump..."); + } + writeDataToBuf(mAutomation.executeShellCommand(ATRACE_DUMP), parser); + } catch (IOException e) { + Log.e(TAG, "Error in reading dump", e); + } + parser.forAllSlices(handler); + } + + // The given file descriptor here will be closed by this function + private void writeDataToBuf(ParcelFileDescriptor pfDescriptor, + TraceMarkParser parser) throws IOException { + InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfDescriptor); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = reader.readLine()) != null) { + parser.visit(line); + } + } + } +} 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 new file mode 100644 index 000000000000..8e0ea9888e4c --- /dev/null +++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java @@ -0,0 +1,146 @@ +/* + * 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. + */ +package com.android.perftests.blob; + +import android.app.blob.BlobStoreManager; +import android.content.Context; +import android.perftests.utils.ManualBenchmarkState; +import android.perftests.utils.PerfManualStatusReporter; +import android.perftests.utils.TraceMarkParser; +import android.perftests.utils.TraceMarkParser.TraceMarkSlice; +import android.support.test.uiautomator.UiDevice; + +import androidx.test.filters.LargeTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.utils.blob.DummyBlobData; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +@LargeTest +@RunWith(Parameterized.class) +public class BlobStorePerfTests { + // From frameworks/native/cmds/atrace/atrace.cpp + private static final String ATRACE_CATEGORY_SYSTEM_SERVER = "ss"; + // From f/b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java + private static final String ATRACE_COMPUTE_DIGEST_PREFIX = "computeBlobDigest-"; + + private Context mContext; + private BlobStoreManager mBlobStoreManager; + private AtraceUtils mAtraceUtils; + private ManualBenchmarkState mState; + + @Rule + public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter(); + + @Parameterized.Parameter(0) + public int fileSizeInMb; + + @Parameterized.Parameters(name = "{0}MB") + public static Collection<Object[]> getParameters() { + return Arrays.asList(new Object[][] { + { 25 }, + { 50 }, + { 100 }, + { 200 }, + }); + } + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mBlobStoreManager = (BlobStoreManager) mContext.getSystemService( + Context.BLOB_STORE_SERVICE); + mAtraceUtils = AtraceUtils.getInstance(InstrumentationRegistry.getInstrumentation()); + mState = mPerfManualStatusReporter.getBenchmarkState(); + } + + @After + public void tearDown() { + // TODO: Add a blob_store shell command to trigger idle maintenance to avoid hardcoding + // job id like this. + // From BlobStoreConfig.IDLE_JOB_ID = 191934935. + runShellCommand("cmd jobscheduler run -f android 191934935"); + } + + @Test + public void testComputeDigest() throws Exception { + mAtraceUtils.startTrace(ATRACE_CATEGORY_SYSTEM_SERVER); + try { + final List<Long> durations = new ArrayList<>(); + final DummyBlobData blobData = prepareDataBlob(fileSizeInMb); + final TraceMarkParser parser = new TraceMarkParser( + line -> line.name.startsWith(ATRACE_COMPUTE_DIGEST_PREFIX)); + while (mState.keepRunning(durations)) { + commitBlob(blobData); + + durations.clear(); + collectDigestDurationsFromTrace(parser, durations); + // TODO: get and delete blobId before next iteration. + } + } finally { + mAtraceUtils.stopTrace(); + } + } + + private void collectDigestDurationsFromTrace(TraceMarkParser parser, List<Long> durations) { + mAtraceUtils.performDump(parser, (key, slices) -> { + for (TraceMarkSlice slice : slices) { + durations.add(TimeUnit.MICROSECONDS.toNanos(slice.getDurationInMicroseconds())); + } + }); + } + + private DummyBlobData prepareDataBlob(int fileSizeInMb) throws Exception { + final DummyBlobData blobData = new DummyBlobData(mContext, + fileSizeInMb * 1024 * 1024 /* bytes */); + blobData.prepare(); + return blobData; + } + + private void commitBlob(DummyBlobData blobData) throws Exception { + final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle()); + try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) { + blobData.writeToSession(session); + final CompletableFuture<Integer> callback = new CompletableFuture<>(); + session.commit(mContext.getMainExecutor(), callback::complete); + // Ignore commit callback result. + callback.get(); + } + } + + private String runShellCommand(String cmd) { + try { + return UiDevice.getInstance( + InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} 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/core/src/android/view/CutoutSpecificationBenchmark.java b/apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java new file mode 100644 index 000000000000..14282bfbd24e --- /dev/null +++ b/apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java @@ -0,0 +1,240 @@ +/* + * 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; + +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.PathParser; + +import androidx.test.filters.LargeTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class CutoutSpecificationBenchmark { + private static final String TAG = "CutoutSpecificationBenchmark"; + + private static final String BOTTOM_MARKER = "@bottom"; + private static final String DP_MARKER = "@dp"; + private static final String RIGHT_MARKER = "@right"; + private static final String LEFT_MARKER = "@left"; + + private static final String DOUBLE_CUTOUT_SPEC = "M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, 20.0595537175\n" + + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n" + + "L 56.8, 32.0\n" + + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@bottom\n" + + "M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, -20.0595537175\n" + + "C -69.1582133885, -28.4178661152 -65.2, -32.0 -56.8, -32.0\n" + + "L 56.8, -32.0\n" + + "C 65.2, -32.0 69.1582133885, -28.4178661152 69.9940446283, -20.0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@dp"; + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + private Context mContext; + private DisplayMetrics mDisplayMetrics; + + /** + * Setup the necessary member field used by test methods. + */ + @Before + public void setUp() { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + mDisplayMetrics = new DisplayMetrics(); + mContext.getDisplay().getRealMetrics(mDisplayMetrics); + } + + + private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) { + final RectF rectF = new RectF(); + p.computeBounds(rectF, false /* unused */); + rectF.round(inoutRect); + inoutRegion.op(inoutRect, Region.Op.UNION); + } + + private static void oldMethodParsingSpec(String spec, int displayWidth, int displayHeight, + float density) { + Path p = null; + Rect boundTop = null; + Rect boundBottom = null; + Rect safeInset = new Rect(); + String bottomSpec = null; + if (!TextUtils.isEmpty(spec)) { + spec = spec.trim(); + final float offsetX; + if (spec.endsWith(RIGHT_MARKER)) { + offsetX = displayWidth; + spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim(); + } else if (spec.endsWith(LEFT_MARKER)) { + offsetX = 0; + spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim(); + } else { + offsetX = displayWidth / 2f; + } + final boolean inDp = spec.endsWith(DP_MARKER); + if (inDp) { + spec = spec.substring(0, spec.length() - DP_MARKER.length()); + } + + if (spec.contains(BOTTOM_MARKER)) { + String[] splits = spec.split(BOTTOM_MARKER, 2); + spec = splits[0].trim(); + bottomSpec = splits[1].trim(); + } + + final Matrix m = new Matrix(); + final Region r = Region.obtain(); + if (!spec.isEmpty()) { + try { + p = PathParser.createPathFromPathData(spec); + } catch (Throwable e) { + Log.wtf(TAG, "Could not inflate cutout: ", e); + } + + if (p != null) { + if (inDp) { + m.postScale(density, density); + } + m.postTranslate(offsetX, 0); + p.transform(m); + + boundTop = new Rect(); + toRectAndAddToRegion(p, r, boundTop); + safeInset.top = boundTop.bottom; + } + } + + if (bottomSpec != null) { + int bottomInset = 0; + Path bottomPath = null; + try { + bottomPath = PathParser.createPathFromPathData(bottomSpec); + } catch (Throwable e) { + Log.wtf(TAG, "Could not inflate bottom cutout: ", e); + } + + if (bottomPath != null) { + // Keep top transform + m.postTranslate(0, displayHeight); + bottomPath.transform(m); + p.addPath(bottomPath); + boundBottom = new Rect(); + toRectAndAddToRegion(bottomPath, r, boundBottom); + bottomInset = displayHeight - boundBottom.top; + } + safeInset.bottom = bottomInset; + } + } + } + + @Test + public void parseByOldMethodForDoubleCutout() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + oldMethodParsingSpec(DOUBLE_CUTOUT_SPEC, mDisplayMetrics.widthPixels, + mDisplayMetrics.heightPixels, mDisplayMetrics.density); + } + } + + @Test + public void parseByNewMethodForDoubleCutout() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + new CutoutSpecification.Parser(mDisplayMetrics.density, + mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels) + .parse(DOUBLE_CUTOUT_SPEC); + } + } + + @Test + public void parseLongEdgeCutout() { + final String spec = "M 0,0\n" + + "H 48\n" + + "V 48\n" + + "H -48\n" + + "Z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "H 48\n" + + "V 48\n" + + "H -48\n" + + "Z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "H -48\n" + + "V 48\n" + + "H 48\n" + + "Z\n" + + "@right\n" + + "@dp"; + + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + new CutoutSpecification.Parser(mDisplayMetrics.density, + mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec); + } + } + + @Test + public void parseShortEdgeCutout() { + final String spec = "M 0,0\n" + + "H 48\n" + + "V 48\n" + + "H -48\n" + + "Z\n" + + "@bottom\n" + + "M 0,0\n" + + "H 48\n" + + "V -48\n" + + "H -48\n" + + "Z\n" + + "@dp"; + + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + new CutoutSpecification.Parser(mDisplayMetrics.density, + mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec); + } + } +} diff --git a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java index b6e39e14602a..8633c9613138 100644 --- a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java @@ -125,7 +125,9 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase { final WindowManager.LayoutParams mParams; final int mWidth; final int mHeight; + final Point mOutSurfaceSize = new Point(); final SurfaceControl mOutSurfaceControl; + final SurfaceControl mOutBlastSurfaceControl = new SurfaceControl(); final IntSupplier mViewVisibility; @@ -150,7 +152,8 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase { mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrame, mOutContentInsets, mOutVisibleInsets, mOutStableInsets, mOutBackDropFrame, mOutDisplayCutout, mOutMergedConfiguration, - mOutSurfaceControl, mOutInsetsState, new Point(), new SurfaceControl()); + mOutSurfaceControl, mOutInsetsState, mOutSurfaceSize, + mOutBlastSurfaceControl); } } } diff --git a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java index 62e9ba84530c..9e17e940a06b 100644 --- a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java +++ b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java @@ -20,15 +20,19 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import android.app.Activity; import android.app.UiAutomation; +import android.content.Context; import android.content.Intent; +import android.os.BatteryManager; import android.os.ParcelFileDescriptor; import android.perftests.utils.PerfTestActivity; +import android.provider.Settings; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.lifecycle.ActivityLifecycleCallback; import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; import androidx.test.runner.lifecycle.Stage; +import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.runner.Description; import org.junit.runners.model.Statement; @@ -52,18 +56,36 @@ public class WindowManagerPerfTestBase { */ static final File BASE_OUT_PATH = new File("/data/local/CorePerfTests"); + private static int sOriginalStayOnWhilePluggedIn; + @BeforeClass public static void setUpOnce() { + final Context context = getInstrumentation().getContext(); + sOriginalStayOnWhilePluggedIn = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); + // Keep the device awake during testing. + setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_USB); + if (!BASE_OUT_PATH.exists()) { executeShellCommand("mkdir -p " + BASE_OUT_PATH); } // In order to be closer to the real use case. executeShellCommand("input keyevent KEYCODE_WAKEUP"); executeShellCommand("wm dismiss-keyguard"); - getInstrumentation().getContext().startActivity(new Intent(Intent.ACTION_MAIN) + context.startActivity(new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } + @AfterClass + public static void tearDownOnce() { + setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn); + } + + private static void setStayOnWhilePluggedIn(int value) { + executeShellCommand(String.format("settings put global %s %d", + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, value)); + } + /** * Executes shell command with reading the output. It may also used to block until the current * command is completed. @@ -97,7 +119,7 @@ public class WindowManagerPerfTestBase { */ static class PerfTestActivityRule extends ActivityTestRule<PerfTestActivity> { private final Intent mStartIntent = - new Intent().putExtra(PerfTestActivity.INTENT_EXTRA_KEEP_SCREEN_ON, true); + new Intent(getInstrumentation().getTargetContext(), PerfTestActivity.class); private final LifecycleListener mLifecycleListener = new LifecycleListener(); PerfTestActivityRule() { 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/blobstore/TEST_MAPPING b/apex/blobstore/TEST_MAPPING index cfe19a530b27..25a15371ae47 100644 --- a/apex/blobstore/TEST_MAPPING +++ b/apex/blobstore/TEST_MAPPING @@ -4,7 +4,7 @@ "name": "CtsBlobStoreTestCases" }, { - "name": "FrameworksServicesTests", + "name": "FrameworksMockingServicesTests", "options": [ { "include-filter": "com.android.server.blob" diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java index f110b36c7e90..d339afac5c77 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java @@ -257,6 +257,11 @@ public final class BlobHandle implements Parcelable { return Base64.encodeToString(digest, Base64.NO_WRAP); } + /** @hide */ + public boolean isExpired() { + return expiryTimeMillis != 0 && expiryTimeMillis < System.currentTimeMillis(); + } + public static final @NonNull Creator<BlobHandle> CREATOR = new Creator<BlobHandle>() { @Override public @NonNull BlobHandle createFromParcel(@NonNull Parcel source) { diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java index aba3e8cadfa3..c12e0ec8aec9 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -64,9 +64,9 @@ class BlobMetadata { private final Context mContext; - public final long blobId; - public final BlobHandle blobHandle; - public final int userId; + private final long mBlobId; + private final BlobHandle mBlobHandle; + private final int mUserId; @GuardedBy("mMetadataLock") private final ArraySet<Committer> mCommitters = new ArraySet<>(); @@ -90,9 +90,21 @@ class BlobMetadata { BlobMetadata(Context context, long blobId, BlobHandle blobHandle, int userId) { mContext = context; - this.blobId = blobId; - this.blobHandle = blobHandle; - this.userId = userId; + this.mBlobId = blobId; + this.mBlobHandle = blobHandle; + this.mUserId = userId; + } + + long getBlobId() { + return mBlobId; + } + + BlobHandle getBlobHandle() { + return mBlobHandle; + } + + int getUserId() { + return mUserId; } void addCommitter(@NonNull Committer committer) { @@ -159,7 +171,7 @@ class BlobMetadata { boolean hasLeases() { synchronized (mMetadataLock) { - return mLeasees.isEmpty(); + return !mLeasees.isEmpty(); } } @@ -196,7 +208,7 @@ class BlobMetadata { File getBlobFile() { if (mBlobFile == null) { - mBlobFile = BlobStoreConfig.getBlobFile(blobId); + mBlobFile = BlobStoreConfig.getBlobFile(mBlobId); } return mBlobFile; } @@ -244,7 +256,7 @@ class BlobMetadata { void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { fout.println("blobHandle:"); fout.increaseIndent(); - blobHandle.dump(fout, dumpArgs.shouldDumpFull()); + mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); fout.decreaseIndent(); fout.println("Committers:"); @@ -274,11 +286,11 @@ class BlobMetadata { void writeToXml(XmlSerializer out) throws IOException { synchronized (mMetadataLock) { - XmlUtils.writeLongAttribute(out, ATTR_ID, blobId); - XmlUtils.writeIntAttribute(out, ATTR_USER_ID, userId); + XmlUtils.writeLongAttribute(out, ATTR_ID, mBlobId); + XmlUtils.writeIntAttribute(out, ATTR_USER_ID, mUserId); out.startTag(null, TAG_BLOB_HANDLE); - blobHandle.writeToXml(out); + mBlobHandle.writeToXml(out); out.endTag(null, TAG_BLOB_HANDLE); for (int i = 0, count = mCommitters.size(); i < count; ++i) { diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java index eb414b0f11a6..ba2e559afdab 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java @@ -18,12 +18,15 @@ package com.android.server.blob; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Environment; +import android.util.Log; import android.util.Slog; import java.io.File; +import java.util.concurrent.TimeUnit; class BlobStoreConfig { public static final String TAG = "BlobStore"; + public static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE); public static final int CURRENT_XML_VERSION = 1; @@ -32,6 +35,20 @@ class BlobStoreConfig { private static final String SESSIONS_INDEX_FILE_NAME = "sessions_index.xml"; private static final String BLOBS_INDEX_FILE_NAME = "blobs_index.xml"; + /** + * Job Id for idle maintenance job ({@link BlobStoreIdleJobService}). + */ + public static final int IDLE_JOB_ID = 0xB70B1D7; // 191934935L + /** + * Max time period (in millis) between each idle maintenance job run. + */ + public static final long IDLE_JOB_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1); + + /** + * Timeout in millis after which sessions with no updates will be deleted. + */ + public static final long SESSION_EXPIRY_TIMEOUT_MILLIS = TimeUnit.DAYS.toMillis(7); + @Nullable public static File prepareBlobFile(long sessionId) { final File blobsDir = prepareBlobsDir(); diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java new file mode 100644 index 000000000000..460e776b9ff6 --- /dev/null +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java @@ -0,0 +1,70 @@ +/* + * 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. + */ +package com.android.server.blob; + +import static com.android.server.blob.BlobStoreConfig.IDLE_JOB_ID; +import static com.android.server.blob.BlobStoreConfig.IDLE_JOB_PERIOD_MILLIS; +import static com.android.server.blob.BlobStoreConfig.LOGV; +import static com.android.server.blob.BlobStoreConfig.TAG; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.os.AsyncTask; +import android.util.Slog; + +import com.android.server.LocalServices; + +/** + * Maintenance job to clean up stale sessions and blobs. + */ +public class BlobStoreIdleJobService extends JobService { + @Override + public boolean onStartJob(final JobParameters params) { + AsyncTask.execute(() -> { + final BlobStoreManagerInternal blobStoreManagerInternal = LocalServices.getService( + BlobStoreManagerInternal.class); + blobStoreManagerInternal.onIdleMaintenance(); + jobFinished(params, false); + }); + return false; + } + + @Override + public boolean onStopJob(final JobParameters params) { + Slog.d(TAG, "Idle maintenance job is stopped; id=" + params.getJobId() + + ", reason=" + JobParameters.getReasonCodeDescription(params.getStopReason())); + return false; + } + + static void schedule(Context context) { + final JobScheduler jobScheduler = (JobScheduler) context.getSystemService( + Context.JOB_SCHEDULER_SERVICE); + final JobInfo job = new JobInfo.Builder(IDLE_JOB_ID, + new ComponentName(context, BlobStoreIdleJobService.class)) + .setRequiresDeviceIdle(true) + .setRequiresCharging(true) + .setPeriodic(IDLE_JOB_PERIOD_MILLIS) + .build(); + jobScheduler.schedule(job); + if (LOGV) { + Slog.v(TAG, "Scheduling the idle maintenance job"); + } + } +} diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerInternal.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerInternal.java new file mode 100644 index 000000000000..5358245f517f --- /dev/null +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerInternal.java @@ -0,0 +1,28 @@ +/* + * 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. + */ +package com.android.server.blob; + +/** + * BlobStoreManager local system service interface. + * + * Only for use within the system server. + */ +public abstract class BlobStoreManagerInternal { + /** + * Triggered from idle maintenance job to cleanup stale blobs and sessions. + */ + public abstract void onIdleMaintenance(); +} diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java index 13f095e5a503..0ba34cab6560 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -28,6 +28,8 @@ import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.os.UserHandle.USER_NULL; import static com.android.server.blob.BlobStoreConfig.CURRENT_XML_VERSION; +import static com.android.server.blob.BlobStoreConfig.LOGV; +import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED; import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED; @@ -61,6 +63,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManagerInternal; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.ExceptionUtils; import android.util.LongSparseArray; @@ -94,8 +97,10 @@ import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Service responsible for maintaining and facilitating access to data blobs published by apps. @@ -115,6 +120,10 @@ public class BlobStoreManagerService extends SystemService { @GuardedBy("mBlobsLock") private final SparseArray<ArrayMap<BlobHandle, BlobMetadata>> mBlobsMap = new SparseArray<>(); + // Contains all ids that are currently in use. + @GuardedBy("mBlobsLock") + private final ArraySet<Long> mKnownBlobIds = new ArraySet<>(); + private final Context mContext; private final Handler mHandler; private final Injector mInjector; @@ -151,6 +160,7 @@ public class BlobStoreManagerService extends SystemService { @Override public void onStart() { publishBinderService(Context.BLOB_STORE_SERVICE, new Stub()); + LocalServices.addService(BlobStoreManagerInternal.class, new LocalService()); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); registerReceivers(); @@ -164,6 +174,8 @@ public class BlobStoreManagerService extends SystemService { readBlobSessionsLocked(allPackages); readBlobsInfoLocked(allPackages); } + } else if (phase == PHASE_BOOT_COMPLETED) { + BlobStoreIdleJobService.schedule(mContext); } } @@ -215,6 +227,40 @@ public class BlobStoreManagerService extends SystemService { } } + @VisibleForTesting + void addKnownIdsForTest(long... knownIds) { + synchronized (mBlobsLock) { + for (long id : knownIds) { + mKnownBlobIds.add(id); + } + } + } + + @VisibleForTesting + Set<Long> getKnownIdsForTest() { + synchronized (mBlobsLock) { + return mKnownBlobIds; + } + } + + @GuardedBy("mBlobsLock") + private void addSessionForUserLocked(BlobStoreSession session, int userId) { + getUserSessionsLocked(userId).put(session.getSessionId(), session); + mKnownBlobIds.add(session.getSessionId()); + } + + @GuardedBy("mBlobsLock") + private void addBlobForUserLocked(BlobMetadata blobMetadata, int userId) { + addBlobForUserLocked(blobMetadata, getUserBlobsLocked(userId)); + } + + @GuardedBy("mBlobsLock") + private void addBlobForUserLocked(BlobMetadata blobMetadata, + ArrayMap<BlobHandle, BlobMetadata> userBlobs) { + userBlobs.put(blobMetadata.getBlobHandle(), blobMetadata); + mKnownBlobIds.add(blobMetadata.getBlobId()); + } + private long createSessionInternal(BlobHandle blobHandle, int callingUid, String callingPackage) { synchronized (mBlobsLock) { @@ -223,7 +269,11 @@ public class BlobStoreManagerService extends SystemService { final BlobStoreSession session = new BlobStoreSession(mContext, sessionId, blobHandle, callingUid, callingPackage, mSessionStateChangeListener); - getUserSessionsLocked(UserHandle.getUserId(callingUid)).put(sessionId, session); + addSessionForUserLocked(session, UserHandle.getUserId(callingUid)); + if (LOGV) { + Slog.v(TAG, "Created session for " + blobHandle + + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); + } writeBlobSessionsAsync(); return sessionId; } @@ -251,7 +301,10 @@ public class BlobStoreManagerService extends SystemService { callingUid, callingPackage); session.open(); session.abandon(); - + if (LOGV) { + Slog.v(TAG, "Deleted session with id " + sessionId + + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); + } writeBlobSessionsAsync(); } } @@ -286,6 +339,10 @@ public class BlobStoreManagerService extends SystemService { } blobMetadata.addLeasee(callingPackage, callingUid, descriptionResId, leaseExpiryTimeMillis); + if (LOGV) { + Slog.v(TAG, "Acquired lease on " + blobHandle + + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); + } writeBlobsInfoAsync(); } } @@ -301,6 +358,10 @@ public class BlobStoreManagerService extends SystemService { + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); } blobMetadata.removeLeasee(callingPackage, callingUid); + if (LOGV) { + Slog.v(TAG, "Released lease on " + blobHandle + + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); + } writeBlobsInfoAsync(); } } @@ -329,6 +390,10 @@ public class BlobStoreManagerService extends SystemService { session.getSessionFile().delete(); getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) .remove(session.getSessionId()); + mKnownBlobIds.remove(session.getSessionId()); + if (LOGV) { + Slog.v(TAG, "Session is invalid; deleted " + session); + } break; case STATE_COMMITTED: session.verifyBlobData(); @@ -340,7 +405,7 @@ public class BlobStoreManagerService extends SystemService { if (blob == null) { blob = new BlobMetadata(mContext, session.getSessionId(), session.getBlobHandle(), userId); - userBlobs.put(session.getBlobHandle(), blob); + addBlobForUserLocked(blob, userBlobs); } final Committer newCommitter = new Committer(session.getOwnerPackageName(), session.getOwnerUid(), session.getBlobAccessMode()); @@ -355,6 +420,9 @@ public class BlobStoreManagerService extends SystemService { } getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) .remove(session.getSessionId()); + if (LOGV) { + Slog.v(TAG, "Successfully committed session " + session); + } break; default: Slog.wtf(TAG, "Invalid session state: " @@ -397,6 +465,9 @@ public class BlobStoreManagerService extends SystemService { out.endTag(null, TAG_SESSIONS); out.endDocument(); sessionsIndexFile.finishWrite(fos); + if (LOGV) { + Slog.v(TAG, "Finished persisting sessions data"); + } } catch (Exception e) { sessionsIndexFile.failWrite(fos); Slog.wtf(TAG, "Error writing sessions data", e); @@ -437,8 +508,8 @@ public class BlobStoreManagerService extends SystemService { if (userPackages != null && session.getOwnerPackageName().equals( userPackages.get(session.getOwnerUid()))) { - getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())).put( - session.getSessionId(), session); + addSessionForUserLocked(session, + UserHandle.getUserId(session.getOwnerUid())); } else { // Unknown package or the session data does not belong to this package. session.getSessionFile().delete(); @@ -446,6 +517,9 @@ public class BlobStoreManagerService extends SystemService { mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, session.getSessionId()); } } + if (LOGV) { + Slog.v(TAG, "Finished reading sessions data"); + } } catch (Exception e) { Slog.wtf(TAG, "Error reading sessions data", e); } @@ -479,6 +553,9 @@ public class BlobStoreManagerService extends SystemService { out.endTag(null, TAG_BLOBS); out.endDocument(); blobsIndexFile.finishWrite(fos); + if (LOGV) { + Slog.v(TAG, "Finished persisting blobs data"); + } } catch (Exception e) { blobsIndexFile.failWrite(fos); Slog.wtf(TAG, "Error writing blobs data", e); @@ -510,18 +587,21 @@ public class BlobStoreManagerService extends SystemService { if (TAG_BLOB.equals(in.getName())) { final BlobMetadata blobMetadata = BlobMetadata.createFromXml(mContext, in); - final SparseArray<String> userPackages = allPackages.get(blobMetadata.userId); + final SparseArray<String> userPackages = allPackages.get( + blobMetadata.getUserId()); if (userPackages == null) { blobMetadata.getBlobFile().delete(); } else { - getUserBlobsLocked(blobMetadata.userId).put( - blobMetadata.blobHandle, blobMetadata); + addBlobForUserLocked(blobMetadata, blobMetadata.getUserId()); blobMetadata.removeInvalidCommitters(userPackages); blobMetadata.removeInvalidLeasees(userPackages); } - mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.blobId); + mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId()); } } + if (LOGV) { + Slog.v(TAG, "Finished reading blobs data"); + } } catch (Exception e) { Slog.wtf(TAG, "Error reading blobs data", e); } @@ -614,6 +694,7 @@ public class BlobStoreManagerService extends SystemService { if (session.getOwnerUid() == uid && session.getOwnerPackageName().equals(packageName)) { session.getSessionFile().delete(); + mKnownBlobIds.remove(session.getSessionId()); indicesToRemove.add(i); } } @@ -633,6 +714,7 @@ public class BlobStoreManagerService extends SystemService { // Delete the blob if it doesn't have any active leases. if (!blobMetadata.hasLeases()) { blobMetadata.getBlobFile().delete(); + mKnownBlobIds.remove(blobMetadata.getBlobId()); indicesToRemove.add(i); } } @@ -640,6 +722,10 @@ public class BlobStoreManagerService extends SystemService { userBlobs.removeAt(indicesToRemove.get(i)); } writeBlobsInfoAsync(); + if (LOGV) { + Slog.v(TAG, "Removed blobs data associated with pkg=" + + packageName + ", uid=" + uid); + } } } @@ -651,6 +737,7 @@ public class BlobStoreManagerService extends SystemService { for (int i = 0, count = userSessions.size(); i < count; ++i) { final BlobStoreSession session = userSessions.valueAt(i); session.getSessionFile().delete(); + mKnownBlobIds.remove(session.getSessionId()); } } @@ -660,9 +747,105 @@ public class BlobStoreManagerService extends SystemService { for (int i = 0, count = userBlobs.size(); i < count; ++i) { final BlobMetadata blobMetadata = userBlobs.valueAt(i); blobMetadata.getBlobFile().delete(); + mKnownBlobIds.remove(blobMetadata.getBlobId()); + } + } + if (LOGV) { + Slog.v(TAG, "Removed blobs data in user " + userId); + } + } + } + + @GuardedBy("mBlobsLock") + @VisibleForTesting + void handleIdleMaintenanceLocked() { + // Cleanup any left over data on disk that is not part of index. + final ArrayList<Long> deletedBlobIds = new ArrayList<>(); + final ArrayList<File> filesToDelete = new ArrayList<>(); + final File blobsDir = BlobStoreConfig.getBlobsDir(); + if (blobsDir.exists()) { + for (File file : blobsDir.listFiles()) { + try { + final long id = Long.parseLong(file.getName()); + if (mKnownBlobIds.indexOf(id) < 0) { + filesToDelete.add(file); + deletedBlobIds.add(id); + } + } catch (NumberFormatException e) { + Slog.wtf(TAG, "Error parsing the file name: " + file, e); + filesToDelete.add(file); + } + } + for (int i = 0, count = filesToDelete.size(); i < count; ++i) { + filesToDelete.get(i).delete(); + } + } + + // Cleanup any stale blobs. + for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { + final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); + userBlobs.entrySet().removeIf(entry -> { + final BlobHandle blobHandle = entry.getKey(); + final BlobMetadata blobMetadata = entry.getValue(); + boolean shouldRemove = false; + + // Cleanup expired data blobs. + if (blobHandle.isExpired()) { + shouldRemove = true; + } + + // Cleanup blobs with no active leases. + // TODO: Exclude blobs which were just committed. + if (!blobMetadata.hasLeases()) { + shouldRemove = true; + } + + if (shouldRemove) { + blobMetadata.getBlobFile().delete(); + mKnownBlobIds.remove(blobMetadata.getBlobId()); + deletedBlobIds.add(blobMetadata.getBlobId()); + } + return shouldRemove; + }); + } + writeBlobsInfoAsync(); + + // Cleanup any stale sessions. + final ArrayList<Integer> indicesToRemove = new ArrayList<>(); + for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { + final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); + indicesToRemove.clear(); + for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { + final BlobStoreSession blobStoreSession = userSessions.valueAt(j); + boolean shouldRemove = false; + + // Cleanup sessions which haven't been modified in a while. + if (blobStoreSession.getSessionFile().lastModified() + < System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS) { + shouldRemove = true; } + + // Cleanup sessions with already expired data. + if (blobStoreSession.getBlobHandle().isExpired()) { + shouldRemove = true; + } + + if (shouldRemove) { + blobStoreSession.getSessionFile().delete(); + mKnownBlobIds.remove(blobStoreSession.getSessionId()); + indicesToRemove.add(j); + deletedBlobIds.add(blobStoreSession.getSessionId()); + } + } + for (int j = 0; j < indicesToRemove.size(); ++j) { + userSessions.removeAt(indicesToRemove.get(j)); } } + if (LOGV) { + Slog.v(TAG, "Completed idle maintenance; deleted " + + Arrays.toString(deletedBlobIds.toArray())); + } + writeBlobSessionsAsync(); } void runClearAllSessions(@UserIdInt int userId) { @@ -727,10 +910,10 @@ public class BlobStoreManagerService extends SystemService { fout.increaseIndent(); for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { final BlobMetadata blobMetadata = userBlobs.valueAt(j); - if (!dumpArgs.shouldDumpBlob(blobMetadata.blobId)) { + if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) { continue; } - fout.println("Blob #" + blobMetadata.blobId); + fout.println("Blob #" + blobMetadata.getBlobId()); fout.increaseIndent(); blobMetadata.dump(fout, dumpArgs); fout.decreaseIndent(); @@ -742,6 +925,9 @@ public class BlobStoreManagerService extends SystemService { private class PackageChangedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + if (LOGV) { + Slog.v(TAG, "Received " + intent); + } switch (intent.getAction()) { case Intent.ACTION_PACKAGE_FULLY_REMOVED: case Intent.ACTION_PACKAGE_DATA_CLEARED: @@ -893,6 +1079,14 @@ public class BlobStoreManagerService extends SystemService { final DumpArgs dumpArgs = DumpArgs.parse(args); final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " "); + if (dumpArgs.shouldDumpHelp()) { + writer.println("dumpsys blob_store [options]:"); + fout.increaseIndent(); + dumpArgs.dumpArgsUsage(fout); + fout.decreaseIndent(); + return; + } + synchronized (mBlobsLock) { fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId); fout.println(); @@ -926,6 +1120,7 @@ public class BlobStoreManagerService extends SystemService { private boolean mDumpOnlySelectedSections; private boolean mDumpSessions; private boolean mDumpBlobs; + private boolean mDumpHelp; public boolean shouldDumpSession(String packageName, int uid, long blobId) { if (!CollectionUtils.isEmpty(mDumpPackages) @@ -971,6 +1166,10 @@ public class BlobStoreManagerService extends SystemService { || mDumpUserIds.indexOf(userId) >= 0; } + public boolean shouldDumpHelp() { + return mDumpHelp; + } + private DumpArgs() {} public static DumpArgs parse(String[] args) { @@ -1000,6 +1199,8 @@ public class BlobStoreManagerService extends SystemService { dumpArgs.mDumpUserIds.add(getIntArgRequired(args, ++i, "userId")); } else if ("--blob".equals(opt) || "-b".equals(opt)) { dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, ++i, "blobId")); + } else if ("--help".equals(opt) || "-h".equals(opt)) { + dumpArgs.mDumpHelp = true; } else { // Everything else is assumed to be blob ids. dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, i, "blobId")); @@ -1040,6 +1241,40 @@ public class BlobStoreManagerService extends SystemService { } return value; } + + private void dumpArgsUsage(IndentingPrintWriter pw) { + pw.println("--help | -h"); + printWithIndent(pw, "Dump this help text"); + pw.println("--sessions"); + printWithIndent(pw, "Dump only the sessions info"); + pw.println("--blobs"); + printWithIndent(pw, "Dump only the committed blobs info"); + pw.println("--package | -p [package-name]"); + printWithIndent(pw, "Dump blobs info associated with the given package"); + pw.println("--uid | -u [uid]"); + printWithIndent(pw, "Dump blobs info associated with the given uid"); + pw.println("--user [user-id]"); + printWithIndent(pw, "Dump blobs info in the given user"); + pw.println("--blob | -b [session-id | blob-id]"); + printWithIndent(pw, "Dump blob info corresponding to the given ID"); + pw.println("--full | -f"); + printWithIndent(pw, "Dump full unredacted blobs data"); + } + + private void printWithIndent(IndentingPrintWriter pw, String str) { + pw.increaseIndent(); + pw.println(str); + pw.decreaseIndent(); + } + } + + private class LocalService extends BlobStoreManagerInternal { + @Override + public void onIdleMaintenance() { + synchronized (mBlobsLock) { + handleIdleMaintenanceLocked(); + } + } } @VisibleForTesting diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java index 54a299722754..bd35b86babd8 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -21,11 +21,13 @@ import static android.app.blob.XmlTags.ATTR_PACKAGE; import static android.app.blob.XmlTags.ATTR_UID; import static android.app.blob.XmlTags.TAG_ACCESS_MODE; import static android.app.blob.XmlTags.TAG_BLOB_HANDLE; +import static android.os.Trace.TRACE_TAG_SYSTEM_SERVER; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_RDWR; import static android.system.OsConstants.SEEK_SET; +import static com.android.server.blob.BlobStoreConfig.LOGV; import static com.android.server.blob.BlobStoreConfig.TAG; import android.annotation.BytesLong; @@ -40,6 +42,7 @@ import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.RevocableFileDescriptor; +import android.os.Trace; import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; @@ -381,15 +384,22 @@ class BlobStoreSession extends IBlobStoreSession.Stub { void verifyBlobData() { byte[] actualDigest = null; try { + Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, + "computeBlobDigest-i" + mSessionId + "-l" + getSessionFile().length()); actualDigest = FileUtils.digest(getSessionFile(), mBlobHandle.algorithm); } catch (IOException | NoSuchAlgorithmException e) { Slog.e(TAG, "Error computing the digest", e); + } finally { + Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER); } synchronized (mSessionLock) { if (actualDigest != null && Arrays.equals(actualDigest, mBlobHandle.digest)) { mState = STATE_VERIFIED_VALID; // Commit callback will be sent once the data is persisted. } else { + if (LOGV) { + Slog.v(TAG, "Digest of the data didn't match the given BlobHandle.digest"); + } mState = STATE_VERIFIED_INVALID; sendCommitCallbackResult(COMMIT_RESULT_ERROR); } @@ -447,6 +457,16 @@ class BlobStoreSession extends IBlobStoreSession.Stub { } } + @Override + public String toString() { + return "BlobStoreSession {" + + "id:" + mSessionId + + ",handle:" + mBlobHandle + + ",uid:" + mOwnerUid + + ",pkg:" + mOwnerPackageName + + "}"; + } + private void assertCallerIsOwner() { final int callingUid = Binder.getCallingUid(); if (callingUid != mOwnerUid) { diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 0bb07caf0b00..088cadba89ab 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -1287,7 +1287,7 @@ public class JobInfo implements Parcelable { * {@link #setPeriodic(long)} or {@link #setPersisted(boolean)}. To continually monitor * for content changes, you need to schedule a new JobInfo observing the same URIs * before you finish execution of the JobService handling the most recent changes. - * Following this pattern will ensure you do not lost any content changes: while your + * Following this pattern will ensure you do not lose any content changes: while your * job is running, the system will continue monitoring for content changes, and propagate * any it sees over to the next job you schedule.</p> * 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/media/framework/Android.bp b/apex/media/framework/Android.bp index 91df09865037..23ae8afc1694 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -38,6 +38,12 @@ java_library { "android.media", ], + optimize: { + enabled: true, + shrink: true, + proguard_flags_files: ["updatable-media-proguard.flags"], + }, + installable: true, // TODO: build against stable API surface. Use core_platform for now to avoid diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java index d59270c6a51b..96110e1541f8 100644 --- a/apex/media/framework/java/android/media/MediaParser.java +++ b/apex/media/framework/java/android/media/MediaParser.java @@ -15,6 +15,7 @@ */ package android.media; +import android.annotation.CheckResult; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.Uri; @@ -32,6 +33,7 @@ import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.amr.AmrExtractor; +import com.google.android.exoplayer2.extractor.flac.FlacExtractor; import com.google.android.exoplayer2.extractor.flv.FlvExtractor; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; @@ -382,6 +384,7 @@ public final class MediaParser { * parse the input. */ @NonNull + @CheckResult private static UnrecognizedInputFormatException createForExtractors( @NonNull String... extractorNames) { StringBuilder builder = new StringBuilder(); @@ -536,7 +539,7 @@ public final class MediaParser { } } if (mExtractor == null) { - UnrecognizedInputFormatException.createForExtractors(mExtractorNamesPool); + throw UnrecognizedInputFormatException.createForExtractors(mExtractorNamesPool); } return true; } @@ -912,6 +915,7 @@ public final class MediaParser { extractorFactoriesByName.put("exo.Ac4Extractor", Ac4Extractor::new); extractorFactoriesByName.put("exo.AdtsExtractor", AdtsExtractor::new); extractorFactoriesByName.put("exo.AmrExtractor", AmrExtractor::new); + extractorFactoriesByName.put("exo.FlacExtractor", FlacExtractor::new); extractorFactoriesByName.put("exo.FlvExtractor", FlvExtractor::new); extractorFactoriesByName.put("exo.FragmentedMp4Extractor", FragmentedMp4Extractor::new); extractorFactoriesByName.put("exo.MatroskaExtractor", MatroskaExtractor::new); diff --git a/apex/media/framework/updatable-media-proguard.flags b/apex/media/framework/updatable-media-proguard.flags new file mode 100644 index 000000000000..4e7d8422bf44 --- /dev/null +++ b/apex/media/framework/updatable-media-proguard.flags @@ -0,0 +1,2 @@ +# Keep all symbols in android.media. +-keep class android.media.* {*;} diff --git a/apex/permission/testing/Android.bp b/apex/permission/testing/Android.bp index f8978dcd83ee..63bf0a08e956 100644 --- a/apex/permission/testing/Android.bp +++ b/apex/permission/testing/Android.bp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -apex { +apex_test { name: "test_com.android.permission", visibility: [ "//system/apex/tests", diff --git a/apex/sdkextensions/derive_sdk/derive_sdk.cpp b/apex/sdkextensions/derive_sdk/derive_sdk.cpp index 6fb7ef43416e..900193a6bd0d 100644 --- a/apex/sdkextensions/derive_sdk/derive_sdk.cpp +++ b/apex/sdkextensions/derive_sdk/derive_sdk.cpp @@ -69,7 +69,7 @@ int main(int, char**) { auto itr = std::min_element(versions.begin(), versions.end()); std::string prop_value = itr == versions.end() ? "0" : std::to_string(*itr); - if (!android::base::SetProperty("ro.build.version.extensions.r", prop_value)) { + if (!android::base::SetProperty("build.version.extensions.r", prop_value)) { LOG(ERROR) << "failed to set sdk_info prop"; return EXIT_FAILURE; } diff --git a/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java index a8a7effa9b6c..103b53e81db5 100644 --- a/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java +++ b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java @@ -38,7 +38,7 @@ public class SdkExtensions { private static final int R_EXTENSION_INT; static { - R_EXTENSION_INT = SystemProperties.getInt("ro.build.version.extensions.r", 0); + R_EXTENSION_INT = SystemProperties.getInt("build.version.extensions.r", 0); } /** 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/apex/sdkextensions/testing/Android.bp b/apex/sdkextensions/testing/Android.bp index e6451cc29bc2..f2f5b321fafe 100644 --- a/apex/sdkextensions/testing/Android.bp +++ b/apex/sdkextensions/testing/Android.bp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -apex { +apex_test { name: "test_com.android.sdkext", visibility: [ "//system/apex/tests" ], defaults: ["com.android.sdkext-defaults"], diff --git a/apex/statsd/aidl/Android.bp b/apex/statsd/aidl/Android.bp index 6d639fddd043..db5f439cd40e 100644 --- a/apex/statsd/aidl/Android.bp +++ b/apex/statsd/aidl/Android.bp @@ -13,35 +13,31 @@ // See the License for the specific language governing permissions and // limitations under the License. // - -// TODO(b/145815909): move StatsDimensionsValue.aidl here filegroup { - name: "statsd_aidl", + name: "statsd_java_aidl", + srcs: ["**/*.aidl"], +} + +aidl_interface { + name: "statsd-aidl", srcs: [ "android/os/IPendingIntentRef.aidl", "android/os/IPullAtomCallback.aidl", "android/os/IPullAtomResultReceiver.aidl", "android/os/IStatsCompanionService.aidl", "android/os/IStatsd.aidl", + "android/os/StatsDimensionsValueParcel.aidl", "android/util/StatsEventParcel.aidl", ], -} - -filegroup { - name: "statsd_java_aidl", - srcs: ["**/*.aidl"], -} - -// This library is currently unused -aidl_interface { - name: "stats-event-parcel-aidl", - srcs: ["android/util/StatsEventParcel.aidl"], backend: { java: { - sdk_version: "28", + enabled: false, // the platform uses statsd_java_aidl }, cpp: { - enabled: false, + enabled: true, + }, + ndk: { + enabled: true, } } } diff --git a/apex/statsd/aidl/android/os/IPendingIntentRef.aidl b/apex/statsd/aidl/android/os/IPendingIntentRef.aidl index 6b9e467a7e15..000a69992a49 100644 --- a/apex/statsd/aidl/android/os/IPendingIntentRef.aidl +++ b/apex/statsd/aidl/android/os/IPendingIntentRef.aidl @@ -16,7 +16,7 @@ package android.os; -import android.os.StatsDimensionsValue; +import android.os.StatsDimensionsValueParcel; /** * Binder interface to hold a PendingIntent for StatsCompanionService. @@ -42,5 +42,5 @@ interface IPendingIntentRef { */ oneway void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId, long subscriptionRuleId, in String[] cookies, - in StatsDimensionsValue dimensionsValue); -}
\ No newline at end of file + in StatsDimensionsValueParcel dimensionsValueParcel); +} diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl index a2564212366f..10b1e5b26d12 100644 --- a/apex/statsd/aidl/android/os/IStatsd.aidl +++ b/apex/statsd/aidl/android/os/IStatsd.aidl @@ -222,18 +222,6 @@ interface IStatsd { const int FLAG_REQUIRE_LOW_LATENCY_MONITOR = 0x04; /** - * Logs an event for binary push for module updates. - */ - oneway void sendBinaryPushStateChangedAtom(in String trainName, in long trainVersionCode, - in int options, in int state, in long[] experimentId); - - /** - * Logs an event for watchdog rollbacks. - */ - oneway void sendWatchdogRollbackOccurredAtom(in int rollbackType, in String packageName, - in long packageVersionCode, in int rollbackReason, in String failingPackageName); - - /** * Returns the most recently registered experiment IDs. */ long[] getRegisteredExperimentIds(); diff --git a/apex/statsd/aidl/android/os/StatsDimensionsValueParcel.aidl b/apex/statsd/aidl/android/os/StatsDimensionsValueParcel.aidl new file mode 100644 index 000000000000..a8685e34dd52 --- /dev/null +++ b/apex/statsd/aidl/android/os/StatsDimensionsValueParcel.aidl @@ -0,0 +1,21 @@ +package android.os; + +/** + * @hide + */ +parcelable StatsDimensionsValueParcel { + /** + * Field equals: + * - atomTag for top level StatsDimensionsValueParcel + * - position in dimension for all other levels + */ + int field; + int valueType; + + String stringValue; + int intValue; + long longValue; + boolean boolValue; + float floatValue; + StatsDimensionsValueParcel[] tupleValue; +} diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp index d85ae69b799f..80def475efb9 100644 --- a/apex/statsd/framework/Android.bp +++ b/apex/statsd/framework/Android.bp @@ -12,12 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. +genrule { + name: "statslog-statsd-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module statsd" + + " --javaPackage com.android.internal.util --javaClass StatsdStatsLog", + out: ["com/android/internal/util/StatsdStatsLog.java"], +} + +java_library_static { + name: "statslog-statsd", + srcs: [ + ":statslog-statsd-java-gen", + ], +} + filegroup { name: "framework-statsd-sources", srcs: [ - "java/**/*.java", + "java/**/*.java", + ":statsd_java_aidl", + ":statslog-statsd-java-gen", ], - path: "java", } java_library { @@ -42,8 +58,7 @@ java_library { hostdex: true, // for hiddenapi check visibility: [ "//frameworks/base/apex/statsd:__subpackages__", - //TODO(b/146167933) remove this when framework is built with framework-statsd-stubs - "//frameworks/base", + //TODO(b/146167933) remove this "//frameworks/opt/net/wifi/service", ], apex_available: [ @@ -103,7 +118,6 @@ java_library { sdk_version: "core_platform", } -// TODO(b/146167933): Use these stubs in frameworks/base/Android.bp java_library { name: "framework-statsd-stubs-systemapi", srcs: [ ":framework-statsd-stubs-srcs-systemapi" ], diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java index a1de330c300a..526d17ff0d71 100644 --- a/apex/statsd/framework/java/android/app/StatsManager.java +++ b/apex/statsd/framework/java/android/app/StatsManager.java @@ -32,7 +32,7 @@ import android.os.IStatsd; import android.os.RemoteException; import android.os.StatsFrameworkInitializer; import android.util.AndroidException; -import android.util.Slog; +import android.util.Log; import android.util.StatsEvent; import android.util.StatsEventParcel; @@ -155,7 +155,7 @@ public final class StatsManager { // can throw IllegalArgumentException service.addConfiguration(configKey, config, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when adding configuration"); + Log.e(TAG, "Failed to connect to statsmanager when adding configuration"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -191,7 +191,7 @@ public final class StatsManager { IStatsManagerService service = getIStatsManagerServiceLocked(); service.removeConfiguration(configKey, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when removing configuration"); + Log.e(TAG, "Failed to connect to statsmanager when removing configuration"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -258,7 +258,7 @@ public final class StatsManager { mContext.getOpPackageName()); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber", + Log.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber", e); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { @@ -311,7 +311,7 @@ public final class StatsManager { } } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when registering data listener."); + Log.e(TAG, "Failed to connect to statsmanager when registering data listener."); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -348,7 +348,7 @@ public final class StatsManager { } } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager " + Log.e(TAG, "Failed to connect to statsmanager " + "when registering active configs listener."); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { @@ -387,7 +387,7 @@ public final class StatsManager { IStatsManagerService service = getIStatsManagerServiceLocked(); return service.getData(configKey, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when getting data"); + Log.e(TAG, "Failed to connect to statsmanager when getting data"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -424,7 +424,7 @@ public final class StatsManager { IStatsManagerService service = getIStatsManagerServiceLocked(); return service.getMetadata(mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when getting metadata"); + Log.e(TAG, "Failed to connect to statsmanager when getting metadata"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -464,7 +464,7 @@ public final class StatsManager { return service.getRegisteredExperimentIds(); } catch (RemoteException e) { if (DEBUG) { - Slog.d(TAG, + Log.d(TAG, "Failed to connect to StatsManagerService when getting " + "registered experiment IDs"); } @@ -476,7 +476,7 @@ public final class StatsManager { /** * Registers a callback for an atom when that atom is to be pulled. The stats service will * invoke pullData in the callback when the stats service determines that this atom needs to be - * pulled. + * pulled. This method should not be called by third-party apps. * * @param atomTag The tag of the atom for this puller callback. * @param metadata Optional metadata specifying the timeout, cool down time, and @@ -485,6 +485,7 @@ public final class StatsManager { * @param executor The executor in which to run the callback. * */ + @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void registerPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata, @NonNull @CallbackExecutor Executor executor, @NonNull StatsPullAtomCallback callback) { @@ -510,11 +511,12 @@ public final class StatsManager { /** * Unregisters a callback for an atom when that atom is to be pulled. Note that any ongoing - * pulls will still occur. + * pulls will still occur. This method should not be called by third-party apps. * * @param atomTag The tag of the atom of which to unregister * */ + @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void unregisterPullAtomCallback(int atomTag) { synchronized (sLock) { try { @@ -553,7 +555,7 @@ public final class StatsManager { try { resultReceiver.pullFinished(atomTag, success, parcels); } catch (RemoteException e) { - Slog.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId); + Log.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId); } }); } finally { diff --git a/apex/statsd/framework/java/android/os/StatsDimensionsValue.java b/apex/statsd/framework/java/android/os/StatsDimensionsValue.java index 886130fc5f14..35273da96413 100644 --- a/apex/statsd/framework/java/android/os/StatsDimensionsValue.java +++ b/apex/statsd/framework/java/android/os/StatsDimensionsValue.java @@ -16,7 +16,7 @@ package android.os; import android.annotation.SystemApi; -import android.util.Slog; +import android.util.Log; import java.util.ArrayList; import java.util.List; @@ -96,6 +96,47 @@ public final class StatsDimensionsValue implements Parcelable { } /** + * Creates a {@code StatsDimensionsValue} from a StatsDimensionsValueParcel + * TODO(b/149103391): Make StatsDimensionsValue a wrapper on top of + * StatsDimensionsValueParcel. + * + * @hide + */ + public StatsDimensionsValue(StatsDimensionsValueParcel parcel) { + mField = parcel.field; + mValueType = parcel.valueType; + switch (mValueType) { + case STRING_VALUE_TYPE: + mValue = parcel.stringValue; + break; + case INT_VALUE_TYPE: + mValue = parcel.intValue; + break; + case LONG_VALUE_TYPE: + mValue = parcel.longValue; + break; + case BOOLEAN_VALUE_TYPE: + mValue = parcel.boolValue; + break; + case FLOAT_VALUE_TYPE: + mValue = parcel.floatValue; + break; + case TUPLE_VALUE_TYPE: + StatsDimensionsValue[] values = new StatsDimensionsValue[parcel.tupleValue.length]; + for (int i = 0; i < parcel.tupleValue.length; i++) { + values[i] = new StatsDimensionsValue(parcel.tupleValue[i]); + } + mValue = values; + break; + default: + Log.w(TAG, "StatsDimensionsValueParcel contains bad valueType: " + mValueType); + mValue = null; + break; + } + } + + + /** * Return the field, i.e. the tag of a statsd atom. * * @return the field @@ -114,7 +155,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == STRING_VALUE_TYPE) return (String) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return null; } @@ -128,7 +169,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == INT_VALUE_TYPE) return (Integer) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return 0; } @@ -142,7 +183,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == LONG_VALUE_TYPE) return (Long) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return 0; } @@ -157,7 +198,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == BOOLEAN_VALUE_TYPE) return (Boolean) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return false; } @@ -171,7 +212,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == FLOAT_VALUE_TYPE) return (Float) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return 0; } @@ -197,7 +238,7 @@ public final class StatsDimensionsValue implements Parcelable { } return copy; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); return null; } } @@ -256,7 +297,7 @@ public final class StatsDimensionsValue implements Parcelable { } return sb.toString(); } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return ""; } @@ -316,11 +357,11 @@ public final class StatsDimensionsValue implements Parcelable { return true; } default: - Slog.w(TAG, "readValue of an impossible type " + valueType); + Log.w(TAG, "readValue of an impossible type " + valueType); return false; } } catch (ClassCastException e) { - Slog.w(TAG, "writeValue cast failed", e); + Log.w(TAG, "writeValue cast failed", e); return false; } } @@ -347,7 +388,7 @@ public final class StatsDimensionsValue implements Parcelable { return values; } default: - Slog.w(TAG, "readValue of an impossible type " + valueType); + Log.w(TAG, "readValue of an impossible type " + valueType); return null; } } diff --git a/apex/statsd/framework/java/android/os/StatsFrameworkInitializer.java b/apex/statsd/framework/java/android/os/StatsFrameworkInitializer.java index 3d955336b45c..8dc91239c2e0 100644 --- a/apex/statsd/framework/java/android/os/StatsFrameworkInitializer.java +++ b/apex/statsd/framework/java/android/os/StatsFrameworkInitializer.java @@ -17,6 +17,7 @@ package android.os; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.annotation.SystemApi.Client; import android.app.StatsManager; import android.app.SystemServiceRegistry; import android.content.Context; @@ -24,10 +25,9 @@ import android.content.Context; /** * Class for performing registration for all stats services * - * TODO(b/148225705) Change to @SystemApi(client=MODULE_LIBRARIES) when the build system is ready. * @hide */ -@SystemApi +@SystemApi(client = Client.MODULE_LIBRARIES) public class StatsFrameworkInitializer { private StatsFrameworkInitializer() { } diff --git a/core/java/android/util/StatsLog.java b/apex/statsd/framework/java/android/util/StatsLog.java index feeff6c47972..511bc01f521e 100644 --- a/core/java/android/util/StatsLog.java +++ b/apex/statsd/framework/java/android/util/StatsLog.java @@ -26,9 +26,10 @@ import android.annotation.SystemApi; import android.content.Context; import android.os.IStatsd; import android.os.RemoteException; -import android.os.ServiceManager; +import android.os.StatsFrameworkInitializer; +import android.util.proto.ProtoOutputStream; -import com.android.internal.util.FrameworkStatsLog; +import com.android.internal.util.StatsdStatsLog; /** * StatsLog provides an API for developers to send events to statsd. The events can be used to @@ -37,6 +38,7 @@ import com.android.internal.util.FrameworkStatsLog; public final class StatsLog { private static final String TAG = "StatsLog"; private static final boolean DEBUG = false; + private static final int EXPERIMENT_IDS_FIELD_ID = 1; private static IStatsd sService; @@ -57,17 +59,17 @@ public final class StatsLog { IStatsd service = getIStatsdLocked(); if (service == null) { if (DEBUG) { - Slog.d(TAG, "Failed to find statsd when logging start"); + Log.d(TAG, "Failed to find statsd when logging start"); } return false; } service.sendAppBreadcrumbAtom(label, - FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__START); + StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START); return true; } catch (RemoteException e) { sService = null; if (DEBUG) { - Slog.d(TAG, "Failed to connect to statsd when logging start"); + Log.d(TAG, "Failed to connect to statsd when logging start"); } return false; } @@ -86,17 +88,17 @@ public final class StatsLog { IStatsd service = getIStatsdLocked(); if (service == null) { if (DEBUG) { - Slog.d(TAG, "Failed to find statsd when logging stop"); + Log.d(TAG, "Failed to find statsd when logging stop"); } return false; } service.sendAppBreadcrumbAtom( - label, FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP); + label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP); return true; } catch (RemoteException e) { sService = null; if (DEBUG) { - Slog.d(TAG, "Failed to connect to statsd when logging stop"); + Log.d(TAG, "Failed to connect to statsd when logging stop"); } return false; } @@ -115,17 +117,17 @@ public final class StatsLog { IStatsd service = getIStatsdLocked(); if (service == null) { if (DEBUG) { - Slog.d(TAG, "Failed to find statsd when logging event"); + Log.d(TAG, "Failed to find statsd when logging event"); } return false; } service.sendAppBreadcrumbAtom( - label, FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED); + label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED); return true; } catch (RemoteException e) { sService = null; if (DEBUG) { - Slog.d(TAG, "Failed to connect to statsd when logging event"); + Log.d(TAG, "Failed to connect to statsd when logging event"); } return false; } @@ -152,77 +154,36 @@ public final class StatsLog { public static boolean logBinaryPushStateChanged(@NonNull String trainName, long trainVersionCode, int options, int state, @NonNull long[] experimentIds) { - synchronized (sLogLock) { - try { - IStatsd service = getIStatsdLocked(); - if (service == null) { - if (DEBUG) { - Slog.d(TAG, "Failed to find statsd when logging event"); - } - return false; - } - service.sendBinaryPushStateChangedAtom( - trainName, trainVersionCode, options, state, experimentIds); - return true; - } catch (RemoteException e) { - sService = null; - if (DEBUG) { - Slog.d(TAG, - "Failed to connect to StatsCompanionService when logging " - + "BinaryPushStateChanged"); - } - return false; - } + ProtoOutputStream proto = new ProtoOutputStream(); + for (long id : experimentIds) { + proto.write( + ProtoOutputStream.FIELD_TYPE_INT64 + | ProtoOutputStream.FIELD_COUNT_REPEATED + | EXPERIMENT_IDS_FIELD_ID, + id); } + StatsdStatsLog.write(StatsdStatsLog.BINARY_PUSH_STATE_CHANGED, + trainName, + trainVersionCode, + (options & IStatsd.FLAG_REQUIRE_STAGING) > 0, + (options & IStatsd.FLAG_ROLLBACK_ENABLED) > 0, + (options & IStatsd.FLAG_REQUIRE_LOW_LATENCY_MONITOR) > 0, + state, + proto.getBytes(), + 0, + 0, + false); + return true; } - /** - * Logs an event for watchdog rollbacks. - * - * @param rollbackType state of the rollback. - * @param packageName package name being rolled back. - * @param packageVersionCode version of the package being rolled back. - * @param rollbackReason reason the package is being rolled back. - * @param failingPackageName the package name causing the failure. - * - * @return True if the log request was sent to statsd. - * - * @hide - */ - @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS}) - public static boolean logWatchdogRollbackOccurred(int rollbackType, String packageName, - long packageVersionCode, int rollbackReason, String failingPackageName) { - synchronized (sLogLock) { - try { - IStatsd service = getIStatsdLocked(); - if (service == null) { - if (DEBUG) { - Slog.d(TAG, "Failed to find statsd when logging event"); - } - return false; - } - - service.sendWatchdogRollbackOccurredAtom(rollbackType, packageName, - packageVersionCode, rollbackReason, failingPackageName); - return true; - } catch (RemoteException e) { - sService = null; - if (DEBUG) { - Slog.d(TAG, - "Failed to connect to StatsCompanionService when logging " - + "WatchdogRollbackOccurred"); - } - return false; - } - } - } - - private static IStatsd getIStatsdLocked() throws RemoteException { if (sService != null) { return sService; } - sService = IStatsd.Stub.asInterface(ServiceManager.getService("stats")); + sService = IStatsd.Stub.asInterface(StatsFrameworkInitializer + .getStatsServiceManager() + .getStatsdServiceRegisterer() + .get()); return sService; } diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp index 910384845810..0f8a151eed7c 100644 --- a/apex/statsd/service/Android.bp +++ b/apex/statsd/service/Android.bp @@ -1,17 +1,30 @@ // Statsd Service jar, which will eventually be put in the statsd mainline apex. // service-statsd needs to be added to PRODUCT_UPDATABLE_SYSTEM_SERVER_JARS. // This jar will contain StatsCompanionService + +filegroup { + name: "service-statsd-sources", + srcs: [ + "java/**/*.java", + ], +} + java_library { name: "service-statsd", installable: true, srcs: [ - "java/**/*.java", + ":service-statsd-sources", ], - // TODO: link against the proper stubs (b/146084685). + // TODO(b/146209659): Use system_current instead once framework-statsd compiles against + // system_current. + sdk_version: "core_platform", libs: [ - "framework-minus-apex", - "services.core", + "framework-annotations-lib", + "framework-statsd", + // TODO(b/146758669): Remove this line after nullability annotations are system APIs. + "android_system_stubs_current", + "services-stubs", ], apex_available: [ "com.android.os.statsd", diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java index 4383b503bfe7..c1ba73f03c06 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java @@ -24,7 +24,8 @@ import android.os.Binder; import android.os.IPendingIntentRef; import android.os.Process; import android.os.StatsDimensionsValue; -import android.util.Slog; +import android.os.StatsDimensionsValueParcel; +import android.util.Log; import com.android.server.SystemService; @@ -38,11 +39,18 @@ public class StatsCompanion { private static final String TAG = "StatsCompanion"; private static final boolean DEBUG = false; - static void enforceStatsCompanionPermission(Context context) { + private static final int AID_STATSD = 1066; + + private static final String STATS_COMPANION_SERVICE = "statscompanion"; + private static final String STATS_MANAGER_SERVICE = "statsmanager"; + + static void enforceStatsdCallingUid() { if (Binder.getCallingPid() == Process.myPid()) { return; } - context.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null); + if (Binder.getCallingUid() != AID_STATSD) { + throw new SecurityException("Not allowed to access StatsCompanion"); + } } /** @@ -64,14 +72,12 @@ public class StatsCompanion { mStatsManagerService.setStatsCompanionService(mStatsCompanionService); try { - publishBinderService(Context.STATS_COMPANION_SERVICE, - mStatsCompanionService); - if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE); - publishBinderService(Context.STATS_MANAGER_SERVICE, - mStatsManagerService); - if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_MANAGER_SERVICE); + publishBinderService(STATS_COMPANION_SERVICE, mStatsCompanionService); + if (DEBUG) Log.d(TAG, "Published " + STATS_COMPANION_SERVICE); + publishBinderService(STATS_MANAGER_SERVICE, mStatsManagerService); + if (DEBUG) Log.d(TAG, "Published " + STATS_MANAGER_SERVICE); } catch (Exception e) { - Slog.e(TAG, "Failed to publishBinderService", e); + Log.e(TAG, "Failed to publishBinderService", e); } } @@ -114,35 +120,37 @@ public class StatsCompanion { @Override public void sendDataBroadcast(long lastReportTimeNs) { - enforceStatsCompanionPermission(mContext); + enforceStatsdCallingUid(); Intent intent = new Intent(); intent.putExtra(EXTRA_LAST_REPORT_TIME, lastReportTimeNs); try { mPendingIntent.send(mContext, CODE_DATA_BROADCAST, intent, null, null); } catch (PendingIntent.CanceledException e) { - Slog.w(TAG, "Unable to send PendingIntent"); + Log.w(TAG, "Unable to send PendingIntent"); } } @Override public void sendActiveConfigsChangedBroadcast(long[] configIds) { - enforceStatsCompanionPermission(mContext); + enforceStatsdCallingUid(); Intent intent = new Intent(); intent.putExtra(StatsManager.EXTRA_STATS_ACTIVE_CONFIG_KEYS, configIds); try { mPendingIntent.send(mContext, CODE_ACTIVE_CONFIGS_BROADCAST, intent, null, null); if (DEBUG) { - Slog.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds)); + Log.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds)); } } catch (PendingIntent.CanceledException e) { - Slog.w(TAG, "Unable to send active configs changed broadcast using PendingIntent"); + Log.w(TAG, "Unable to send active configs changed broadcast using PendingIntent"); } } @Override public void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId, - long subscriptionRuleId, String[] cookies, StatsDimensionsValue dimensionsValue) { - enforceStatsCompanionPermission(mContext); + long subscriptionRuleId, String[] cookies, + StatsDimensionsValueParcel dimensionsValueParcel) { + enforceStatsdCallingUid(); + StatsDimensionsValue dimensionsValue = new StatsDimensionsValue(dimensionsValueParcel); Intent intent = new Intent() .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid) @@ -158,7 +166,7 @@ public class StatsCompanion { StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookieList); if (DEBUG) { - Slog.d(TAG, + Log.d(TAG, String.format( "Statsd sendSubscriberBroadcast with params {%d %d %d %d %s %s}", configUid, configId, subscriptionId, subscriptionRuleId, @@ -168,7 +176,7 @@ public class StatsCompanion { try { mPendingIntent.send(mContext, CODE_SUBSCRIBER_BROADCAST, intent, null, null); } catch (PendingIntent.CanceledException e) { - Slog.w(TAG, + Log.w(TAG, "Unable to send using PendingIntent from uid " + configUid + "; presumably it had been cancelled."); } diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index 3e9a488fb5b8..cb167c30e30f 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -40,14 +40,10 @@ import android.os.StatsFrameworkInitializer; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; -import android.util.Slog; +import android.util.Log; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; -import com.android.internal.os.LooperStats; -import com.android.internal.util.DumpUtils; -import com.android.server.BinderCallsStatsService; -import com.android.server.LocalServices; import libcore.io.IoUtils; @@ -89,6 +85,12 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public static final int DEATH_THRESHOLD = 10; + // TODO(b/149090705): Implement an alternative to sending broadcast with @hide flag + // FLAG_RECEIVER_INCLUDE_BACKGROUND. Instead of using the flag, find the + // list of registered broadcast receivers and send them directed broadcasts + // to wake them up. See b/147374337. + private static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000; + static final class CompanionHandler extends Handler { CompanionHandler(Looper looper) { super(looper); @@ -126,7 +128,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public void onReceive(Context context, Intent intent) { synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd for UserUpdateReceiver"); + Log.w(TAG, "Could not access statsd for UserUpdateReceiver"); return; } try { @@ -134,14 +136,14 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { // Needed since the new user basically has a version of every app. informAllUidsLocked(context); } catch (RemoteException e) { - Slog.e(TAG, "Failed to inform statsd latest update of all apps", e); + Log.e(TAG, "Failed to inform statsd latest update of all apps", e); forgetEverythingLocked(); } } } }; mShutdownEventReceiver = new ShutdownEventReceiver(); - if (DEBUG) Slog.d(TAG, "Registered receiver for ACTION_PACKAGE_REPLACED and ADDED."); + if (DEBUG) Log.d(TAG, "Registered receiver for ACTION_PACKAGE_REPLACED and ADDED."); HandlerThread handlerThread = new HandlerThread(TAG); handlerThread.start(); mHandler = new CompanionHandler(handlerThread.getLooper()); @@ -171,21 +173,21 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { PackageManager pm = context.getPackageManager(); final List<UserHandle> users = um.getUserHandles(true); if (DEBUG) { - Slog.d(TAG, "Iterating over " + users.size() + " userHandles."); + Log.d(TAG, "Iterating over " + users.size() + " userHandles."); } ParcelFileDescriptor[] fds; try { fds = ParcelFileDescriptor.createPipe(); } catch (IOException e) { - Slog.e(TAG, "Failed to create a pipe to send uid map data.", e); + Log.e(TAG, "Failed to create a pipe to send uid map data.", e); return; } sStatsd.informAllUidData(fds[0]); try { fds[0].close(); } catch (IOException e) { - Slog.e(TAG, "Failed to close the read side of the pipe.", e); + Log.e(TAG, "Failed to close the read side of the pipe.", e); } final ParcelFileDescriptor writeFd = fds[1]; HandlerThread backgroundThread = new HandlerThread( @@ -239,7 +241,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } output.flush(); if (DEBUG) { - Slog.d(TAG, "Sent data for " + numRecords + " apps"); + Log.d(TAG, "Sent data for " + numRecords + " apps"); } } finally { IoUtils.closeQuietly(fout); @@ -261,10 +263,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { return; // Keep only replacing or normal add and remove. } - if (DEBUG) Slog.d(TAG, "StatsCompanionService noticed an app was updated."); + if (DEBUG) Log.d(TAG, "StatsCompanionService noticed an app was updated."); synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of an app update"); + Log.w(TAG, "Could not access statsd to inform it of an app update"); return; } try { @@ -299,7 +301,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { installer == null ? "" : installer); } } catch (Exception e) { - Slog.w(TAG, "Failed to inform statsd of an app update", e); + Log.w(TAG, "Failed to inform statsd of an app update", e); } } } @@ -308,18 +310,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public final static class AnomalyAlarmListener implements OnAlarmListener { @Override public void onAlarm() { - Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred at time " + Log.i(TAG, "StatsCompanionService believes an anomaly has occurred at time " + System.currentTimeMillis() + "ms."); synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing"); + Log.w(TAG, "Could not access statsd to inform it of anomaly alarm firing"); return; } try { // Two-way call to statsd to retain AlarmManager wakelock sStatsd.informAnomalyAlarmFired(); } catch (RemoteException e) { - Slog.w(TAG, "Failed to inform statsd of anomaly alarm firing", e); + Log.w(TAG, "Failed to inform statsd of anomaly alarm firing", e); } } // AlarmManager releases its own wakelock here. @@ -330,18 +332,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override public void onAlarm() { if (DEBUG) { - Slog.d(TAG, "Time to poll something."); + Log.d(TAG, "Time to poll something."); } synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing."); + Log.w(TAG, "Could not access statsd to inform it of pulling alarm firing."); return; } try { // Two-way call to statsd to retain AlarmManager wakelock sStatsd.informPollAlarmFired(); } catch (RemoteException e) { - Slog.w(TAG, "Failed to inform statsd of pulling alarm firing.", e); + Log.w(TAG, "Failed to inform statsd of pulling alarm firing.", e); } } } @@ -351,18 +353,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override public void onAlarm() { if (DEBUG) { - Slog.d(TAG, "Time to trigger periodic alarm."); + Log.d(TAG, "Time to trigger periodic alarm."); } synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of periodic alarm firing."); + Log.w(TAG, "Could not access statsd to inform it of periodic alarm firing."); return; } try { // Two-way call to statsd to retain AlarmManager wakelock sStatsd.informAlarmForSubscriberTriggeringFired(); } catch (RemoteException e) { - Slog.w(TAG, "Failed to inform statsd of periodic alarm firing.", e); + Log.w(TAG, "Failed to inform statsd of periodic alarm firing.", e); } } // AlarmManager releases its own wakelock here. @@ -381,16 +383,16 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { return; } - Slog.i(TAG, "StatsCompanionService noticed a shutdown."); + Log.i(TAG, "StatsCompanionService noticed a shutdown."); synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of a shutdown event."); + Log.w(TAG, "Could not access statsd to inform it of a shutdown event."); return; } try { sStatsd.informDeviceShutdown(); } catch (Exception e) { - Slog.w(TAG, "Failed to inform statsd of a shutdown event.", e); + Log.w(TAG, "Failed to inform statsd of a shutdown event.", e); } } } @@ -398,8 +400,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void setAnomalyAlarm(long timestampMs) { - StatsCompanion.enforceStatsCompanionPermission(mContext); - if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs); + StatsCompanion.enforceStatsdCallingUid(); + if (DEBUG) Log.d(TAG, "Setting anomaly alarm for " + timestampMs); final long callingToken = Binder.clearCallingIdentity(); try { // using ELAPSED_REALTIME, not ELAPSED_REALTIME_WAKEUP, so if device is asleep, will @@ -414,8 +416,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void cancelAnomalyAlarm() { - StatsCompanion.enforceStatsCompanionPermission(mContext); - if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm"); + StatsCompanion.enforceStatsdCallingUid(); + if (DEBUG) Log.d(TAG, "Cancelling anomaly alarm"); final long callingToken = Binder.clearCallingIdentity(); try { mAlarmManager.cancel(mAnomalyAlarmListener); @@ -426,9 +428,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void setAlarmForSubscriberTriggering(long timestampMs) { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, + Log.d(TAG, "Setting periodic alarm in about " + (timestampMs - SystemClock.elapsedRealtime())); } @@ -445,9 +447,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void cancelAlarmForSubscriberTriggering() { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, "Cancelling periodic alarm"); + Log.d(TAG, "Cancelling periodic alarm"); } final long callingToken = Binder.clearCallingIdentity(); try { @@ -459,9 +461,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void setPullingAlarm(long nextPullTimeMs) { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, "Setting pulling alarm in about " + Log.d(TAG, "Setting pulling alarm in about " + (nextPullTimeMs - SystemClock.elapsedRealtime())); } final long callingToken = Binder.clearCallingIdentity(); @@ -477,9 +479,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void cancelPullingAlarm() { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, "Cancelling pulling alarm"); + Log.d(TAG, "Cancelling pulling alarm"); } final long callingToken = Binder.clearCallingIdentity(); try { @@ -491,25 +493,25 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void statsdReady() { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, "learned that statsdReady"); + Log.d(TAG, "learned that statsdReady"); } sayHiToStatsd(); // tell statsd that we're ready too and link to it mContext.sendBroadcastAsUser(new Intent(StatsManager.ACTION_STATSD_STARTED) - .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND), + .addFlags(FLAG_RECEIVER_INCLUDE_BACKGROUND), UserHandle.SYSTEM, android.Manifest.permission.DUMP); } @Override public void triggerUidSnapshot() { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); synchronized (sStatsdLock) { final long token = Binder.clearCallingIdentity(); try { informAllUidsLocked(mContext); } catch (RemoteException e) { - Slog.e(TAG, "Failed to trigger uid snapshot.", e); + Log.e(TAG, "Failed to trigger uid snapshot.", e); } finally { restoreCallingIdentity(token); } @@ -518,7 +520,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public boolean checkPermission(String permission, int pid, int uid) { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); return mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED; } @@ -540,7 +542,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { * Now that the android system is ready, StatsCompanion is ready too, so inform statsd. */ void systemReady() { - if (DEBUG) Slog.d(TAG, "Learned that systemReady"); + if (DEBUG) Log.d(TAG, "Learned that systemReady"); sayHiToStatsd(); } @@ -555,27 +557,27 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private void sayHiToStatsd() { synchronized (sStatsdLock) { if (sStatsd != null) { - Slog.e(TAG, "Trying to fetch statsd, but it was already fetched", + Log.e(TAG, "Trying to fetch statsd, but it was already fetched", new IllegalStateException( "sStatsd is not null when being fetched")); return; } sStatsd = fetchStatsdService(); if (sStatsd == null) { - Slog.i(TAG, + Log.i(TAG, "Could not yet find statsd to tell it that StatsCompanion is " + "alive."); return; } mStatsManagerService.statsdReady(sStatsd); - if (DEBUG) Slog.d(TAG, "Saying hi to statsd"); + if (DEBUG) Log.d(TAG, "Saying hi to statsd"); try { sStatsd.statsCompanionReady(); // If the statsCompanionReady two-way binder call returns, link to statsd. try { sStatsd.asBinder().linkToDeath(new StatsdDeathRecipient(), 0); } catch (RemoteException e) { - Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e); + Log.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e); forgetEverythingLocked(); } // Setup broadcast receiver for updates. @@ -605,9 +607,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } finally { restoreCallingIdentity(token); } - Slog.i(TAG, "Told statsd that StatsCompanionService is alive."); + Log.i(TAG, "Told statsd that StatsCompanionService is alive."); } catch (RemoteException e) { - Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e); + Log.e(TAG, "Failed to inform statsd that statscompanion is ready", e); forgetEverythingLocked(); } } @@ -616,7 +618,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private class StatsdDeathRecipient implements IBinder.DeathRecipient { @Override public void binderDied() { - Slog.i(TAG, "Statsd is dead - erase all my knowledge, except pullers"); + Log.i(TAG, "Statsd is dead - erase all my knowledge, except pullers"); synchronized (sStatsdLock) { long now = SystemClock.elapsedRealtime(); for (Long timeMillis : mDeathTimeMillis) { @@ -656,22 +658,15 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { cancelAnomalyAlarm(); cancelPullingAlarm(); - BinderCallsStatsService.Internal binderStats = - LocalServices.getService(BinderCallsStatsService.Internal.class); - if (binderStats != null) { - binderStats.reset(); - } - - LooperStats looperStats = LocalServices.getService(LooperStats.class); - if (looperStats != null) { - looperStats.reset(); - } mStatsManagerService.statsdNotReady(); } @Override protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + return; + } synchronized (sStatsdLock) { writer.println( diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java index 04d8b006f51d..4e4bc40b727f 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java @@ -30,7 +30,7 @@ import android.os.IStatsd; import android.os.Process; import android.os.RemoteException; import android.util.ArrayMap; -import android.util.Slog; +import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -171,8 +171,8 @@ public class StatsManagerService extends IStatsManagerService.Stub { @Override public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, int[] additiveFields, IPullAtomCallback pullerCallback) { + enforceRegisterStatsPullAtomPermission(); int callingUid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); PullerKey key = new PullerKey(callingUid, atomTag); PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback); @@ -187,11 +187,12 @@ public class StatsManagerService extends IStatsManagerService.Stub { return; } + final long token = Binder.clearCallingIdentity(); try { statsd.registerPullAtomCallback( callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback); } catch (RemoteException e) { - Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); + Log.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); } finally { Binder.restoreCallingIdentity(token); } @@ -199,8 +200,8 @@ public class StatsManagerService extends IStatsManagerService.Stub { @Override public void unregisterPullAtomCallback(int atomTag) { + enforceRegisterStatsPullAtomPermission(); int callingUid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); PullerKey key = new PullerKey(callingUid, atomTag); // Always remove the puller from StatsManagerService even if statsd is down. When statsd @@ -214,10 +215,11 @@ public class StatsManagerService extends IStatsManagerService.Stub { return; } + final long token = Binder.clearCallingIdentity(); try { statsd.unregisterPullAtomCallback(callingUid, atomTag); } catch (RemoteException e) { - Slog.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag); + Log.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag); } finally { Binder.restoreCallingIdentity(token); } @@ -241,7 +243,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { statsd.setDataFetchOperation(configId, pir, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to setDataFetchOperation with statsd"); + Log.e(TAG, "Failed to setDataFetchOperation with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -262,7 +264,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { statsd.removeDataFetchOperation(configId, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to removeDataFetchOperation with statsd"); + Log.e(TAG, "Failed to removeDataFetchOperation with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -285,7 +287,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return statsd.setActiveConfigsChangedOperation(pir, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to setActiveConfigsChangedOperation with statsd"); + Log.e(TAG, "Failed to setActiveConfigsChangedOperation with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -306,7 +308,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { statsd.removeActiveConfigsChangedOperation(callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to removeActiveConfigsChangedOperation with statsd"); + Log.e(TAG, "Failed to removeActiveConfigsChangedOperation with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -334,7 +336,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { configId, subscriberId, pir, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to setBroadcastSubscriber with statsd"); + Log.e(TAG, "Failed to setBroadcastSubscriber with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -360,7 +362,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { statsd.unsetBroadcastSubscriber(configId, subscriberId, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to unsetBroadcastSubscriber with statsd"); + Log.e(TAG, "Failed to unsetBroadcastSubscriber with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -376,7 +378,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return statsd.getRegisteredExperimentIds(); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to getRegisteredExperimentIds with statsd"); + Log.e(TAG, "Failed to getRegisteredExperimentIds with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -394,7 +396,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return statsd.getMetadata(); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to getMetadata with statsd"); + Log.e(TAG, "Failed to getMetadata with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -413,7 +415,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return statsd.getData(key, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to getData with statsd"); + Log.e(TAG, "Failed to getData with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -434,7 +436,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return; } } catch (RemoteException e) { - Slog.e(TAG, "Failed to addConfiguration with statsd"); + Log.e(TAG, "Failed to addConfiguration with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -455,7 +457,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return; } } catch (RemoteException e) { - Slog.e(TAG, "Failed to removeConfiguration with statsd"); + Log.e(TAG, "Failed to removeConfiguration with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -502,6 +504,13 @@ public class StatsManagerService extends IStatsManagerService.Stub { } } + private void enforceRegisterStatsPullAtomPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.REGISTER_STATS_PULL_ATOM, + "Need REGISTER_STATS_PULL_ATOM permission."); + } + + /** * Clients should call this if blocking until statsd to be ready is desired * @@ -513,7 +522,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { try { mLock.wait(STATSD_TIMEOUT_MILLIS); } catch (InterruptedException e) { - Slog.e(TAG, "wait for statsd interrupted"); + Log.e(TAG, "wait for statsd interrupted"); } } return mStatsd; @@ -569,7 +578,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { registerAllActiveConfigsChangedOperations(statsd); registerAllBroadcastSubscribers(statsd); } catch (RemoteException e) { - Slog.e(TAG, "StatsManager failed to (re-)register data with statsd"); + Log.e(TAG, "StatsManager failed to (re-)register data with statsd"); } finally { Binder.restoreCallingIdentity(token); } diff --git a/apex/statsd/testing/Android.bp b/apex/statsd/testing/Android.bp index 22e73015ba39..a9cd0ccb53e8 100644 --- a/apex/statsd/testing/Android.bp +++ b/apex/statsd/testing/Android.bp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -apex { +apex_test { name: "test_com.android.os.statsd", visibility: [ "//system/apex/tests", diff --git a/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp b/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp index e4ab823f345a..22daa8eb6b3e 100644 --- a/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp +++ b/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp @@ -46,15 +46,15 @@ static void init() { } } -static status_pull_atom_return_t pullAtomCallback(int32_t atomTag, pulled_stats_event_list* data, - void* /*cookie*/) { +static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atomTag, AStatsEventList* data, + void* /*cookie*/) { sNumPulls++; sleep_for(std::chrono::milliseconds(sLatencyMillis)); for (int i = 0; i < sAtomsPerPull; i++) { - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, atomTag); - stats_event_write_int64(event, (int64_t) sNumPulls); - stats_event_build(event); + AStatsEvent* event = AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId(event, atomTag); + AStatsEvent_writeInt64(event, (int64_t) sNumPulls); + AStatsEvent_build(event); } return sPullReturnVal; } @@ -71,11 +71,12 @@ Java_com_android_internal_os_statsd_libstats_LibStatsPullTests_registerStatsPull sLatencyMillis = latencyMillis; sAtomsPerPull = atomsPerPull; sNumPulls = 0; - pull_atom_metadata metadata = {.cool_down_ns = coolDownNs, - .timeout_ns = timeoutNs, - .additive_fields = nullptr, - .additive_fields_size = 0}; - register_stats_pull_atom_callback(sAtomTag, &pullAtomCallback, &metadata, nullptr); + AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain(); + AStatsManager_PullAtomMetadata_setCoolDownNs(metadata, coolDownNs); + AStatsManager_PullAtomMetadata_setTimeoutNs(metadata, timeoutNs); + + AStatsManager_registerPullAtomCallback(sAtomTag, &pullAtomCallback, metadata, nullptr); + AStatsManager_PullAtomMetadata_release(metadata); } extern "C" @@ -83,6 +84,6 @@ JNIEXPORT void JNICALL Java_com_android_internal_os_statsd_libstats_LibStatsPullTests_unregisterStatsPuller( JNIEnv* /*env*/, jobject /* this */, jint /*atomTag*/) { - unregister_stats_pull_atom_callback(sAtomTag); + AStatsManager_unregisterPullAtomCallback(sAtomTag); } -} // namespace
\ No newline at end of file +} // namespace diff --git a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java index dbd636d2e95c..e119b4c47604 100644 --- a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java +++ b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java @@ -71,7 +71,6 @@ public class LibStatsPullTests { */ @Before public void setup() { -// Debug.waitForDebugger(); mContext = InstrumentationRegistry.getTargetContext(); assertThat(InstrumentationRegistry.getInstrumentation()).isNotNull(); sPullReturnValue = StatsManager.PULL_SUCCESS; diff --git a/api/current.txt b/api/current.txt index 87a5cd7a648b..826d409b0a2d 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 @@ -2888,6 +2888,13 @@ package android.accessibilityservice { field public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32; // 0x20 field public static final int GESTURE_3_FINGER_SWIPE_UP = 29; // 0x1d field public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; // 0x18 + field public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38; // 0x26 + field public static final int GESTURE_4_FINGER_SINGLE_TAP = 37; // 0x25 + field public static final int GESTURE_4_FINGER_SWIPE_DOWN = 34; // 0x22 + field public static final int GESTURE_4_FINGER_SWIPE_LEFT = 35; // 0x23 + field public static final int GESTURE_4_FINGER_SWIPE_RIGHT = 36; // 0x24 + field public static final int GESTURE_4_FINGER_SWIPE_UP = 33; // 0x21 + field public static final int GESTURE_4_FINGER_TRIPLE_TAP = 39; // 0x27 field public static final int GESTURE_DOUBLE_TAP = 17; // 0x11 field public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18; // 0x12 field public static final int GESTURE_SWIPE_DOWN = 2; // 0x2 @@ -2945,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); @@ -3850,7 +3863,7 @@ package android.app { method public void onPerformDirectAction(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.os.Bundle>); method public void onPictureInPictureModeChanged(boolean, android.content.res.Configuration); method @Deprecated public void onPictureInPictureModeChanged(boolean); - method public void onPictureInPictureRequested(); + method public boolean onPictureInPictureRequested(); method @CallSuper protected void onPostCreate(@Nullable android.os.Bundle); method public void onPostCreate(@Nullable android.os.Bundle, @Nullable android.os.PersistableBundle); method @CallSuper protected void onPostResume(); @@ -4002,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(); @@ -4542,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 @@ -6803,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 { @@ -26799,7 +26813,6 @@ package android.media { method public abstract void onSelectRoute(@NonNull String, @NonNull String); method public abstract void onSetVolume(@NonNull String, int); method public abstract void onTransferToRoute(@NonNull String, @NonNull String); - method public abstract void onUpdateVolume(@NonNull String, int); field public static final long REQUEST_ID_UNKNOWN = 0L; // 0x0L field public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService"; } @@ -28772,6 +28785,7 @@ package android.media.tv { field public static final String COLUMN_DESCRIPTION = "description"; field public static final String COLUMN_DISPLAY_NAME = "display_name"; field public static final String COLUMN_DISPLAY_NUMBER = "display_number"; + field public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id"; field public static final String COLUMN_INPUT_ID = "input_id"; field public static final String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data"; field public static final String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1"; @@ -28797,6 +28811,7 @@ package android.media.tv { field public static final String SERVICE_TYPE_AUDIO_VIDEO = "SERVICE_TYPE_AUDIO_VIDEO"; field public static final String SERVICE_TYPE_OTHER = "SERVICE_TYPE_OTHER"; field public static final String TYPE_1SEG = "TYPE_1SEG"; + field public static final String TYPE_ATSC3_T = "TYPE_ATSC3_T"; field public static final String TYPE_ATSC_C = "TYPE_ATSC_C"; field public static final String TYPE_ATSC_M_H = "TYPE_ATSC_M_H"; field public static final String TYPE_ATSC_T = "TYPE_ATSC_T"; @@ -28890,6 +28905,7 @@ package android.media.tv { field public static final String COLUMN_SEASON_TITLE = "season_title"; field public static final String COLUMN_SERIES_ID = "series_id"; field public static final String COLUMN_SHORT_DESCRIPTION = "short_description"; + field public static final String COLUMN_SPLIT_ID = "split_id"; field public static final String COLUMN_STARTING_PRICE = "starting_price"; field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio"; field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; @@ -28937,6 +28953,8 @@ package android.media.tv { field public static final String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number"; field @Deprecated public static final String COLUMN_EPISODE_NUMBER = "episode_number"; field public static final String COLUMN_EPISODE_TITLE = "episode_title"; + field public static final String COLUMN_EVENT_ID = "event_id"; + field public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id"; field public static final String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data"; field public static final String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1"; field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2"; @@ -28953,6 +28971,7 @@ package android.media.tv { field public static final String COLUMN_SEASON_TITLE = "season_title"; field public static final String COLUMN_SERIES_ID = "series_id"; field public static final String COLUMN_SHORT_DESCRIPTION = "short_description"; + field public static final String COLUMN_SPLIT_ID = "split_id"; field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis"; field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; field public static final String COLUMN_TITLE = "title"; @@ -29018,6 +29037,7 @@ package android.media.tv { field public static final String COLUMN_SEASON_TITLE = "season_title"; field public static final String COLUMN_SERIES_ID = "series_id"; field public static final String COLUMN_SHORT_DESCRIPTION = "short_description"; + field public static final String COLUMN_SPLIT_ID = "split_id"; field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis"; field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; field public static final String COLUMN_TITLE = "title"; @@ -29078,6 +29098,7 @@ package android.media.tv { field public static final String COLUMN_SEASON_TITLE = "season_title"; field public static final String COLUMN_SERIES_ID = "series_id"; field public static final String COLUMN_SHORT_DESCRIPTION = "short_description"; + field public static final String COLUMN_SPLIT_ID = "split_id"; field public static final String COLUMN_STARTING_PRICE = "starting_price"; field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio"; field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; @@ -29682,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 @@ -30119,7 +30140,7 @@ package android.net { method @NonNull public android.net.NetworkCapabilities setLinkDownstreamBandwidthKbps(int); method @NonNull public android.net.NetworkCapabilities setLinkUpstreamBandwidthKbps(int); method @NonNull public android.net.NetworkCapabilities setNetworkSpecifier(@NonNull android.net.NetworkSpecifier); - method public void setOwnerUid(int); + method @NonNull public android.net.NetworkCapabilities setOwnerUid(int); method @NonNull public android.net.NetworkCapabilities setSignalStrength(int); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkCapabilities> CREATOR; @@ -30759,7 +30780,6 @@ package android.net.sip { method public void close(); method public void continueCall(int) throws android.net.sip.SipException; method public void endCall() throws android.net.sip.SipException; - method @Nullable public android.net.rtp.AudioGroup getAudioGroup(); method public android.net.sip.SipProfile getLocalProfile(); method public android.net.sip.SipProfile getPeerProfile(); method public int getState(); @@ -30770,7 +30790,6 @@ package android.net.sip { method public void makeCall(android.net.sip.SipProfile, android.net.sip.SipSession, int) throws android.net.sip.SipException; method public void sendDtmf(int); method public void sendDtmf(int, android.os.Message); - method public void setAudioGroup(@NonNull android.net.rtp.AudioGroup); method public void setListener(android.net.sip.SipAudioCall.Listener); method public void setListener(android.net.sip.SipAudioCall.Listener, boolean); method public void setSpeakerMode(boolean); @@ -30819,7 +30838,6 @@ package android.net.sip { method public void close(String) throws android.net.sip.SipException; method public android.net.sip.SipSession createSipSession(android.net.sip.SipProfile, android.net.sip.SipSession.Listener) throws android.net.sip.SipException; method public static String getCallId(android.content.Intent); - method @NonNull public java.util.List<android.net.sip.SipProfile> getListOfProfiles() throws android.net.sip.SipException; method public static String getOfferSessionDescription(android.content.Intent); method public android.net.sip.SipSession getSessionFor(android.content.Intent) throws android.net.sip.SipException; method public static boolean isApiSupported(android.content.Context); @@ -30837,11 +30855,6 @@ package android.net.sip { method public void setRegistrationListener(String, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException; method public android.net.sip.SipAudioCall takeAudioCall(android.content.Intent, android.net.sip.SipAudioCall.Listener) throws android.net.sip.SipException; method public void unregister(android.net.sip.SipProfile, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException; - field public static final String ACTION_SIP_CALL_OPTION_CHANGED = "android.net.sip.action.SIP_CALL_OPTION_CHANGED"; - field public static final String ACTION_SIP_INCOMING_CALL = "android.net.sip.action.SIP_INCOMING_CALL"; - field public static final String ACTION_SIP_REMOVE_PROFILE = "android.net.sip.action.SIP_REMOVE_PROFILE"; - field public static final String ACTION_SIP_SERVICE_UP = "android.net.sip.action.SIP_SERVICE_UP"; - field public static final String ACTION_START_SIP = "android.net.sip.action.START_SIP"; field public static final String EXTRA_CALL_ID = "android:sipCallID"; field public static final String EXTRA_OFFER_SD = "android:sipOfferSD"; field public static final int INCOMING_CALL_RESULT_CODE = 101; // 0x65 @@ -30851,7 +30864,6 @@ package android.net.sip { method public int describeContents(); method public String getAuthUserName(); method public boolean getAutoRegistration(); - method public int getCallingUid(); method public String getDisplayName(); method public String getPassword(); method public int getPort(); @@ -31275,6 +31287,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(); @@ -31747,7 +31760,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; } @@ -31939,14 +31952,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 @@ -35680,6 +35693,7 @@ package android.os { field public static final String INCREMENTAL; field public static final int PREVIEW_SDK_INT; field public static final String RELEASE; + field @NonNull public static final String RELEASE_OR_CODENAME; field @Deprecated public static final String SDK; field public static final int SDK_INT; field public static final String SECURITY_PATCH; @@ -37024,6 +37038,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 { @@ -40394,6 +40417,7 @@ package android.provider { field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled"; field public static final String EXTRA_BIOMETRIC_MINIMUM_STRENGTH_REQUIRED = "android.provider.extra.BIOMETRIC_MINIMUM_STRENGTH_REQUIRED"; field public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID"; + field public static final String EXTRA_CONVERSATION_ID = "android.provider.extra.CONVERSATION_ID"; field public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled"; field public static final String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes"; field public static final String EXTRA_EASY_CONNECT_ATTEMPTED_SSID = "android.provider.extra.EASY_CONNECT_ATTEMPTED_SSID"; @@ -43973,7 +43997,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"; } } @@ -44054,8 +44078,8 @@ package android.service.voice { } public static final class AlwaysOnHotwordDetector.ModelParamRange { - method public int end(); - method public int start(); + method public int getEnd(); + method public int getStart(); } public class VoiceInteractionService extends android.app.Service { @@ -47290,7 +47314,7 @@ package android.telephony { method public void onSignalStrengthsChanged(android.telephony.SignalStrength); method public void onUserMobileDataStateChanged(boolean); field public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 4194304; // 0x400000 - field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int LISTEN_BARRING_INFO = -2147483648; // 0x80000000 + field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_BARRING_INFO = -2147483648; // 0x80000000 field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000 field public static final int LISTEN_CALL_FORWARDING_INDICATOR = 8; // 0x8 field public static final int LISTEN_CALL_STATE = 32; // 0x20 @@ -47303,7 +47327,7 @@ package android.telephony { field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4 field public static final int LISTEN_NONE = 0; // 0x0 field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000 - field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 1073741824; // 0x40000000 + field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 1073741824; // 0x40000000 field public static final int LISTEN_SERVICE_STATE = 1; // 0x1 field @Deprecated public static final int LISTEN_SIGNAL_STRENGTH = 2; // 0x2 field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100 @@ -60813,7 +60837,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 } @@ -60868,7 +60891,7 @@ package android.widget { method public int getGravity(); method public float getHorizontalMargin(); method public float getVerticalMargin(); - method @Deprecated public android.view.View getView(); + method @Deprecated @Nullable public android.view.View getView(); method public int getXOffset(); method public int getYOffset(); method public static android.widget.Toast makeText(android.content.Context, CharSequence, int); diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index 90531b16a516..e5a1f28262a2 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -1,4 +1,157 @@ // 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 { + + public class StatsFrameworkInitializer { + method public static void registerServiceWrappers(); + method public static void setStatsServiceManager(@NonNull android.os.StatsServiceManager); + } + + public class StatsServiceManager { + method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsCompanionServiceRegisterer(); + method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsManagerServiceRegisterer(); + method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsdServiceRegisterer(); + } + + public static class StatsServiceManager.ServiceNotFoundException extends java.lang.Exception { + ctor public StatsServiceManager.ServiceNotFoundException(@NonNull String); + } + + public static final class StatsServiceManager.ServiceRegisterer { + method @Nullable public android.os.IBinder get(); + method @Nullable public android.os.IBinder getOrThrow() throws android.os.StatsServiceManager.ServiceNotFoundException; + } + +} + +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 1128556135a0..68ec02dc0aeb 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -188,6 +188,7 @@ package android { field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER"; field public static final String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER"; field public static final String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION"; + field public static final String REGISTER_STATS_PULL_ATOM = "android.permission.REGISTER_STATS_PULL_ATOM"; field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER"; field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; @@ -584,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); @@ -692,7 +689,7 @@ package android.app { method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] getRegisteredExperimentIds() throws android.app.StatsManager.StatsUnavailableException; method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getReports(long) throws android.app.StatsManager.StatsUnavailableException; method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException; - method public void registerPullAtomCallback(int, @Nullable android.app.StatsManager.PullAtomMetadata, @NonNull java.util.concurrent.Executor, @NonNull android.app.StatsManager.StatsPullAtomCallback); + method @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void registerPullAtomCallback(int, @Nullable android.app.StatsManager.PullAtomMetadata, @NonNull java.util.concurrent.Executor, @NonNull android.app.StatsManager.StatsPullAtomCallback); method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException; method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean removeConfiguration(long); method @NonNull @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException; @@ -700,7 +697,7 @@ package android.app { method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent); method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setDataFetchOperation(long, android.app.PendingIntent); method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setFetchReportsOperation(android.app.PendingIntent, long) throws android.app.StatsManager.StatsUnavailableException; - method public void unregisterPullAtomCallback(int); + method @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void unregisterPullAtomCallback(int); field public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED"; field public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = "android.app.extra.STATS_ACTIVE_CONFIG_KEYS"; field public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES"; @@ -1151,6 +1148,16 @@ package android.app.backup { } +package android.app.compat { + + public final class CompatChanges { + method public static boolean isChangeEnabled(long); + method public static boolean isChangeEnabled(long, @NonNull String, @NonNull android.os.UserHandle); + method public static boolean isChangeEnabled(long, int); + } + +} + package android.app.contentsuggestions { public final class ClassificationsRequest implements android.os.Parcelable { @@ -1472,9 +1479,9 @@ package android.bluetooth { public final class BluetoothAdapter { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean addOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean connectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean connectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice); method public boolean disableBLE(); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean disconnectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean disconnectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice); method public boolean enableBLE(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enableNoAutoConnect(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean factoryReset(); @@ -2098,6 +2105,10 @@ package android.content.pm { field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.AppUsageLimit> CREATOR; } + public static class LauncherApps.ShortcutQuery { + method @NonNull public android.content.pm.LauncherApps.ShortcutQuery setLocusIds(@Nullable java.util.List<android.content.LocusId>); + } + public class PackageInstaller { method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean); field public static final int DATA_LOADER_TYPE_INCREMENTAL = 2; // 0x2 @@ -2197,10 +2208,13 @@ 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 field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000 + field public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE = 131072; // 0x20000 + field public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET = 262144; // 0x40000 field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20 field public static final int FLAG_PERMISSION_GRANTED_BY_ROLE = 32768; // 0x8000 field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000 @@ -2284,7 +2298,7 @@ package android.content.pm { method public void onPermissionsChanged(int); } - @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags { + @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_DONT_AUTO_REVOKE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags { } public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { @@ -3678,17 +3692,17 @@ package android.hardware.soundtrigger { } public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable { + method public int getEnd(); + method public int getStart(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModelParamRange> CREATOR; - field public final int end; - field public final int start; } 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; @@ -3848,6 +3862,20 @@ package android.location { method @NonNull public android.location.GnssReflectingPlane.Builder setLongitudeDegrees(@FloatRange(from=-180.0F, to=180.0f) double); } + public final class GnssRequest implements android.os.Parcelable { + method public int describeContents(); + method public boolean isFullTracking(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssRequest> CREATOR; + } + + public static final class GnssRequest.Builder { + ctor public GnssRequest.Builder(); + ctor public GnssRequest.Builder(@NonNull android.location.GnssRequest); + method @NonNull public android.location.GnssRequest build(); + method @NonNull public android.location.GnssRequest.Builder setFullTracking(boolean); + } + public final class GnssSingleSatCorrection implements android.os.Parcelable { method public int describeContents(); method @FloatRange(from=0.0f, fromInclusive=false) public float getCarrierFrequencyHz(); @@ -4121,6 +4149,7 @@ package android.location { method public boolean isProviderEnabledForUser(@NonNull String, @NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@NonNull String); method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean registerGnssBatchedLocationCallback(long, boolean, @NonNull android.location.BatchedLocationCallback, @Nullable android.os.Handler); + method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.LOCATION_HARDWARE}) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.GnssMeasurementsEvent.Callback); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.app.PendingIntent); @@ -4198,15 +4227,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 } @@ -4243,11 +4272,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(); @@ -4261,7 +4290,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); @@ -4317,7 +4346,7 @@ package android.media { } public final class AudioRecordingConfiguration implements android.os.Parcelable { - method public int getClientUid(); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getClientUid(); } public class HwAudioSource { @@ -4357,7 +4386,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); } } @@ -5728,7 +5757,6 @@ package android.media.tv.tuner.frontend { method public int getFreqOffset(); method public int getHierarchy(); method @NonNull public boolean[] getLayerErrors(); - method public int getLberCn(); method public int getLnbVoltage(); method public int getMer(); method public int getModulation(); @@ -5740,37 +5768,32 @@ package android.media.tv.tuner.frontend { method public int getSnr(); method public int getSpectralInversion(); method public int getSymbolRate(); - method public int getVberCn(); - method public int getXerCn(); method public boolean isDemodLocked(); method public boolean isEwbs(); method public boolean isLnaOn(); method public boolean isRfLock(); field public static final int FRONTEND_STATUS_TYPE_AGC = 14; // 0xe - field public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO = 24; // 0x18 + field public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO = 21; // 0x15 field public static final int FRONTEND_STATUS_TYPE_BER = 2; // 0x2 field public static final int FRONTEND_STATUS_TYPE_DEMOD_LOCK = 0; // 0x0 field public static final int FRONTEND_STATUS_TYPE_EWBS = 13; // 0xd field public static final int FRONTEND_STATUS_TYPE_FEC = 8; // 0x8 - field public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET = 21; // 0x15 - field public static final int FRONTEND_STATUS_TYPE_HIERARCHY = 22; // 0x16 + field public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET = 18; // 0x12 + field public static final int FRONTEND_STATUS_TYPE_HIERARCHY = 19; // 0x13 field public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR = 16; // 0x10 - field public static final int FRONTEND_STATUS_TYPE_LBER_CN = 18; // 0x12 field public static final int FRONTEND_STATUS_TYPE_LNA = 15; // 0xf field public static final int FRONTEND_STATUS_TYPE_LNB_VOLTAGE = 11; // 0xb - field public static final int FRONTEND_STATUS_TYPE_MER = 20; // 0x14 + field public static final int FRONTEND_STATUS_TYPE_MER = 17; // 0x11 field public static final int FRONTEND_STATUS_TYPE_MODULATION = 9; // 0x9 field public static final int FRONTEND_STATUS_TYPE_PER = 3; // 0x3 field public static final int FRONTEND_STATUS_TYPE_PLP_ID = 12; // 0xc field public static final int FRONTEND_STATUS_TYPE_PRE_BER = 4; // 0x4 - field public static final int FRONTEND_STATUS_TYPE_RF_LOCK = 23; // 0x17 + field public static final int FRONTEND_STATUS_TYPE_RF_LOCK = 20; // 0x14 field public static final int FRONTEND_STATUS_TYPE_SIGNAL_QUALITY = 5; // 0x5 field public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH = 6; // 0x6 field public static final int FRONTEND_STATUS_TYPE_SNR = 1; // 0x1 field public static final int FRONTEND_STATUS_TYPE_SPECTRAL = 10; // 0xa field public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE = 7; // 0x7 - field public static final int FRONTEND_STATUS_TYPE_VBER_CN = 17; // 0x11 - field public static final int FRONTEND_STATUS_TYPE_XER_CN = 19; // 0x13 } public static class FrontendStatus.Atsc3PlpInfo { @@ -6290,7 +6313,9 @@ package android.net { method @Nullable public String getSSID(); method @NonNull public int[] getTransportTypes(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); - method public void setAdministratorUids(@NonNull java.util.List<java.lang.Integer>); + method @NonNull public android.net.NetworkCapabilities setAdministratorUids(@NonNull java.util.List<java.lang.Integer>); + method @NonNull public android.net.NetworkCapabilities setRequestorPackageName(@NonNull String); + method @NonNull public android.net.NetworkCapabilities setRequestorUid(int); method @NonNull public android.net.NetworkCapabilities setSSID(@Nullable String); method @NonNull public android.net.NetworkCapabilities setTransportInfo(@NonNull android.net.TransportInfo); field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16 @@ -6342,6 +6367,8 @@ package android.net { } public class NetworkRequest implements android.os.Parcelable { + method @Nullable public String getRequestorPackageName(); + method public int getRequestorUid(); method public boolean satisfiedBy(@Nullable android.net.NetworkCapabilities); } @@ -6426,7 +6453,6 @@ package android.net { } public abstract class NetworkSpecifier { - method public void assertValidFromUid(int); method @Nullable public android.net.NetworkSpecifier redact(); method public abstract boolean satisfiedBy(@Nullable android.net.NetworkSpecifier); } @@ -7189,6 +7215,28 @@ package android.net.netstats.provider { } +package android.net.sip { + + public class SipAudioCall { + method @Nullable public android.net.rtp.AudioGroup getAudioGroup(); + method public void setAudioGroup(@NonNull android.net.rtp.AudioGroup); + } + + public class SipManager { + method @NonNull public java.util.List<android.net.sip.SipProfile> getProfiles() throws android.net.sip.SipException; + field public static final String ACTION_SIP_CALL_OPTION_CHANGED = "android.net.sip.action.SIP_CALL_OPTION_CHANGED"; + field public static final String ACTION_SIP_INCOMING_CALL = "android.net.sip.action.SIP_INCOMING_CALL"; + field public static final String ACTION_SIP_REMOVE_PROFILE = "android.net.sip.action.SIP_REMOVE_PROFILE"; + field public static final String ACTION_SIP_SERVICE_UP = "android.net.sip.action.SIP_SERVICE_UP"; + field public static final String ACTION_START_SIP = "android.net.sip.action.START_SIP"; + } + + public class SipProfile implements java.lang.Cloneable android.os.Parcelable java.io.Serializable { + method public int getCallingUid(); + } + +} + package android.net.util { public final class SocketUtils { @@ -7517,9 +7565,6 @@ package android.net.wifi { method @Deprecated public boolean isNoInternetAccessExpected(); method @Deprecated public void setIpConfiguration(@Nullable android.net.IpConfiguration); method @Deprecated public void setNetworkSelectionStatus(@NonNull android.net.wifi.WifiConfiguration.NetworkSelectionStatus); - field @Deprecated public static final int AP_BAND_2GHZ = 0; // 0x0 - field @Deprecated public static final int AP_BAND_5GHZ = 1; // 0x1 - field @Deprecated public static final int AP_BAND_ANY = -1; // 0xffffffff field @Deprecated public static final int INVALID_NETWORK_ID = -1; // 0xffffffff field @Deprecated public static final int METERED_OVERRIDE_METERED = 1; // 0x1 field @Deprecated public static final int METERED_OVERRIDE_NONE = 0; // 0x0 @@ -7529,7 +7574,6 @@ package android.net.wifi { field @Deprecated public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17; // 0x11 field @Deprecated public static final int RECENT_FAILURE_NONE = 0; // 0x0 field @Deprecated public boolean allowAutojoin; - field @Deprecated public int apBand; field @Deprecated public int carrierId; field @Deprecated public String creatorName; field @Deprecated public int creatorUid; @@ -7558,13 +7602,12 @@ package android.net.wifi { @Deprecated public static class WifiConfiguration.NetworkSelectionStatus { method @Deprecated public int getDisableReasonCounter(int); method @Deprecated public long getDisableTime(); + method @Deprecated public static int getMaxNetworkSelectionDisableReason(); method @Deprecated @Nullable public static String getNetworkDisableReasonString(int); method @Deprecated public int getNetworkSelectionDisableReason(); method @Deprecated public int getNetworkSelectionStatus(); method @Deprecated @NonNull public String getNetworkStatusString(); method @Deprecated public boolean hasEverConnected(); - method @Deprecated public boolean isNetworkEnabled(); - method @Deprecated public boolean isNetworkPermanentlyDisabled(); field @Deprecated public static final int DISABLED_ASSOCIATION_REJECTION = 1; // 0x1 field @Deprecated public static final int DISABLED_AUTHENTICATION_FAILURE = 2; // 0x2 field @Deprecated public static final int DISABLED_AUTHENTICATION_NO_CREDENTIALS = 5; // 0x5 @@ -7575,7 +7618,6 @@ package android.net.wifi { field @Deprecated public static final int DISABLED_NONE = 0; // 0x0 field @Deprecated public static final int DISABLED_NO_INTERNET_PERMANENT = 6; // 0x6 field @Deprecated public static final int DISABLED_NO_INTERNET_TEMPORARY = 4; // 0x4 - field @Deprecated public static final int NETWORK_SELECTION_DISABLED_MAX = 10; // 0xa field @Deprecated public static final int NETWORK_SELECTION_ENABLED = 0; // 0x0 field @Deprecated public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2; // 0x2 field @Deprecated public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1; // 0x1 @@ -7678,6 +7720,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); @@ -7822,23 +7865,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 { @@ -8096,7 +8139,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; } @@ -8104,12 +8147,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 @@ -8199,7 +8243,7 @@ package android.net.wifi.wificond { ctor public NativeScanResult(); method public int describeContents(); method @NonNull public byte[] getBssid(); - method @NonNull public java.util.BitSet getCapabilities(); + method @NonNull public int getCapabilities(); method public int getFrequencyMhz(); method @NonNull public byte[] getInformationElements(); method @NonNull public java.util.List<android.net.wifi.wificond.RadioChainInfo> getRadioChainInfos(); @@ -8235,12 +8279,12 @@ package android.net.wifi.wificond { public final class PnoSettings implements android.os.Parcelable { ctor public PnoSettings(); method public int describeContents(); - method public int getIntervalMillis(); + method public long getIntervalMillis(); method public int getMin2gRssiDbm(); method public int getMin5gRssiDbm(); method public int getMin6gRssiDbm(); method @NonNull public java.util.List<android.net.wifi.wificond.PnoNetwork> getPnoNetworks(); - method public void setIntervalMillis(int); + method public void setIntervalMillis(long); method public void setMin2gRssiDbm(int); method public void setMin5gRssiDbm(int); method public void setMin6gRssiDbm(int); @@ -8265,10 +8309,10 @@ package android.net.wifi.wificond { method @Nullable public android.net.wifi.wificond.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String); method @NonNull public java.util.List<android.net.wifi.wificond.NativeScanResult> getScanResults(@NonNull String, int); method @Nullable public android.net.wifi.wificond.WifiCondManager.TxPacketCounters getTxPacketCounters(@NonNull String); - method public boolean initialize(@NonNull Runnable); method @Nullable public static android.net.wifi.wificond.WifiCondManager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]); method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SoftApCallback); method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SendMgmtFrameCallback); + method public void setOnServiceDeadCallback(@NonNull Runnable); method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback); method public boolean setupInterfaceForSoftApMode(@NonNull String); method @Nullable public android.net.wifi.wificond.WifiCondManager.SignalPollResult signalPoll(@NonNull String); @@ -8830,26 +8874,6 @@ package android.os { field public static final int TUPLE_VALUE_TYPE = 7; // 0x7 } - public class StatsFrameworkInitializer { - method public static void registerServiceWrappers(); - method public static void setStatsServiceManager(@NonNull android.os.StatsServiceManager); - } - - public class StatsServiceManager { - method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsCompanionServiceRegisterer(); - method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsManagerServiceRegisterer(); - method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsdServiceRegisterer(); - } - - public static class StatsServiceManager.ServiceNotFoundException extends java.lang.Exception { - ctor public StatsServiceManager.ServiceNotFoundException(@NonNull String); - } - - public static final class StatsServiceManager.ServiceRegisterer { - method @Nullable public android.os.IBinder get(); - method @Nullable public android.os.IBinder getOrThrow() throws android.os.StatsServiceManager.ServiceNotFoundException; - } - public class SystemConfigManager { method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps(); method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); @@ -9129,6 +9153,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 { @@ -12409,7 +12442,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"; @@ -14115,6 +14147,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/system-lint-baseline.txt b/api/system-lint-baseline.txt index 2f1889cea4eb..0caee6bebbda 100644 --- a/api/system-lint-baseline.txt +++ b/api/system-lint-baseline.txt @@ -64,10 +64,6 @@ GenericException: android.service.autofill.augmented.FillWindow#finalize(): -HeavyBitSet: android.net.wifi.wificond.NativeScanResult#getCapabilities(): - - - IntentBuilderName: android.content.Context#registerReceiverForAllUsers(android.content.BroadcastReceiver, android.content.IntentFilter, String, android.os.Handler): Methods creating an Intent should be named `create<Foo>Intent()`, was `registerReceiverForAllUsers` @@ -200,8 +196,6 @@ MutableBareField: android.net.IpConfiguration#staticIpConfiguration: MutableBareField: android.net.wifi.WifiConfiguration#allowAutojoin: -MutableBareField: android.net.wifi.WifiConfiguration#apBand: - MutableBareField: android.net.wifi.WifiConfiguration#carrierId: MutableBareField: android.net.wifi.WifiConfiguration#fromWifiNetworkSpecifier: diff --git a/api/test-current.txt b/api/test-current.txt index d3e7ea161fc6..e352cb65156e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -183,6 +183,9 @@ package android.app { field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0 field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1 field public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; // 0x2 + field public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time"; + field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time"; + field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time"; field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover"; field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; field public static final String OPSTR_ACTIVATE_VPN = "android:activate_vpn"; @@ -920,6 +923,7 @@ package android.content.pm { field public static final int FLAG_PERMISSION_USER_SET = 1; // 0x1 field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 field public static final int MATCH_KNOWN_PACKAGES = 4202496; // 0x402000 + field public static final int MODULE_APEX_NAME = 1; // 0x1 field public static final String SYSTEM_SHARED_LIBRARY_SERVICES = "android.ext.services"; field public static final String SYSTEM_SHARED_LIBRARY_SHARED = "android.ext.shared"; } @@ -2536,6 +2540,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/incident/Android.bp b/cmds/incident/Android.bp index 9e9dac14c802..94855aa0311f 100644 --- a/cmds/incident/Android.bp +++ b/cmds/incident/Android.bp @@ -26,7 +26,7 @@ cc_binary { "libcutils", "liblog", "libutils", - "libincident", + "libincidentpriv", ], static_libs: [ diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp index 64f4c667820d..f07743ec2ee6 100644 --- a/cmds/incident_helper/Android.bp +++ b/cmds/incident_helper/Android.bp @@ -44,7 +44,7 @@ cc_defaults { "src/ih_util.cpp", ], - generated_headers: ["gen-platform-proto-constants"], + generated_headers: ["framework-cppstream-protos"], shared_libs: [ "libbase", diff --git a/cmds/incidentd/Android.bp b/cmds/incidentd/Android.bp index 25e0328b4f38..c47526abad53 100644 --- a/cmds/incidentd/Android.bp +++ b/cmds/incidentd/Android.bp @@ -43,7 +43,7 @@ cc_binary { ], local_include_dirs: ["src"], - generated_headers: ["gen-platform-proto-constants"], + generated_headers: ["framework-cppstream-protos"], proto: { type: "lite", @@ -54,7 +54,7 @@ cc_binary { "libbinder", "libdebuggerd_client", "libdumputils", - "libincident", + "libincidentpriv", "liblog", "libprotoutil", "libservices", @@ -98,7 +98,7 @@ cc_test { ], local_include_dirs: ["src"], - generated_headers: ["gen-platform-proto-constants"], + generated_headers: ["framework-cppstream-protos"], srcs: [ "tests/**/*.cpp", @@ -128,7 +128,7 @@ cc_test { "libbinder", "libdebuggerd_client", "libdumputils", - "libincident", + "libincidentpriv", "liblog", "libprotobuf-cpp-full", "libprotoutil", diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 2237bf2b2acb..956fd29205cb 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -44,12 +44,8 @@ cc_library_host_shared { cc_defaults { name: "statsd_defaults", - aidl: { - include_dirs: ["frameworks/base/core/java"], - }, srcs: [ - ":statsd_aidl", "src/active_config_list.proto", "src/anomaly/AlarmMonitor.cpp", "src/anomaly/AlarmTracker.cpp", @@ -64,7 +60,7 @@ cc_defaults { "src/config/ConfigKey.cpp", "src/config/ConfigListener.cpp", "src/config/ConfigManager.cpp", - "src/external/GpuStatsPuller.cpp", + "src/experiment_ids.proto", "src/external/Perfetto.cpp", "src/external/PullResultReceiver.cpp", "src/external/puller_util.cpp", @@ -110,7 +106,7 @@ cc_defaults { ], cflags: [ - // "-DNEW_ENCODING_SCHEME", + "-DNEW_ENCODING_SCHEME", ], local_include_dirs: [ @@ -118,23 +114,20 @@ cc_defaults { ], static_libs: [ - "android.frameworks.stats@1.0", "libbase", "libcutils", - "liblog", "libprotoutil", "libstatslog", - "libstatssocket", + "libstatsmetadata", "libsysutils", + "libutils", ], shared_libs: [ "libbinder", - "libgraphicsenv", - "libhidlbase", "libincident", - "libservices", - "libstatsmetadata", - "libutils", + "liblog", + "libstatssocket", + "statsd-aidl-cpp", ], } @@ -160,7 +153,7 @@ genrule { ], } -cc_library_shared { +cc_library_static { name: "libstatsmetadata", host_supported: true, generated_sources: [ @@ -223,8 +216,6 @@ cc_binary { shared_libs: ["libgtest_prod"], - vintf_fragments: ["android.frameworks.stats@1.0-service.xml"], - init_rc: ["statsd.rc"], } @@ -277,8 +268,6 @@ cc_test { "tests/e2e/PartialBucket_e2e_test.cpp", "tests/e2e/ValueMetric_pull_e2e_test.cpp", "tests/e2e/WakelockDuration_e2e_test.cpp", - "tests/external/GpuStatsPuller_test.cpp", - "tests/external/IncidentReportArgs_test.cpp", "tests/external/puller_util_test.cpp", "tests/external/StatsCallbackPuller_test.cpp", "tests/external/StatsPuller_test.cpp", 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/benchmark/log_event_benchmark.cpp b/cmds/statsd/benchmark/log_event_benchmark.cpp index 30dfe3279829..8b687438ca27 100644 --- a/cmds/statsd/benchmark/log_event_benchmark.cpp +++ b/cmds/statsd/benchmark/log_event_benchmark.cpp @@ -23,14 +23,14 @@ namespace os { namespace statsd { static size_t createAndParseStatsEvent(uint8_t* msg) { - struct stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, 100); - stats_event_write_int32(event, 2); - stats_event_write_float(event, 2.0); - stats_event_build(event); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + AStatsEvent_writeInt32(event, 2); + AStatsEvent_writeFloat(event, 2.0); + AStatsEvent_build(event); size_t size; - uint8_t* buf = stats_event_get_buffer(event, &size); + uint8_t* buf = AStatsEvent_getBuffer(event, &size); memcpy(msg, buf, size); return size; } diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp index 5b75b97a0764..6b9d0e4fdac0 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -26,6 +26,86 @@ namespace statsd { using std::string; using std::vector; +// These constants must be kept in sync with those in StatsDimensionsValue.java +const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2; +const static int STATS_DIMENSIONS_VALUE_INT_TYPE = 3; +const static int STATS_DIMENSIONS_VALUE_LONG_TYPE = 4; +// const static int STATS_DIMENSIONS_VALUE_BOOL_TYPE = 5; (commented out because +// unused -- statsd does not correctly support bool types) +const static int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6; +const static int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7; + +/** + * Recursive helper function that populates a parent StatsDimensionsValueParcel + * with children StatsDimensionsValueParcels. + * + * \param dims vector of FieldValues stored by HashableDimensionKey + * \param index positions in dims vector to start reading children from + * \param depth level of parent parcel in the full StatsDimensionsValueParcel + * tree + */ +static void populateStatsDimensionsValueParcelChildren(StatsDimensionsValueParcel &parentParcel, + const vector<FieldValue>& dims, size_t& index, + int depth, int prefix) { + while (index < dims.size()) { + const FieldValue& dim = dims[index]; + int fieldDepth = dim.mField.getDepth(); + int fieldPrefix = dim.mField.getPrefix(depth); + StatsDimensionsValueParcel childParcel; + childParcel.field = dim.mField.getPosAtDepth(depth); + if (depth > 2) { + ALOGE("Depth > 2 not supported by StatsDimensionsValueParcel."); + return; + } + if (depth == fieldDepth && prefix == fieldPrefix) { + switch (dim.mValue.getType()) { + case INT: + childParcel.valueType = STATS_DIMENSIONS_VALUE_INT_TYPE; + childParcel.intValue = dim.mValue.int_value; + break; + case LONG: + childParcel.valueType = STATS_DIMENSIONS_VALUE_LONG_TYPE; + childParcel.longValue = dim.mValue.long_value; + break; + case FLOAT: + childParcel.valueType = STATS_DIMENSIONS_VALUE_FLOAT_TYPE; + childParcel.floatValue = dim.mValue.float_value; + break; + case STRING: + childParcel.valueType = STATS_DIMENSIONS_VALUE_STRING_TYPE; + childParcel.stringValue = String16(dim.mValue.str_value.c_str()); + break; + default: + ALOGE("Encountered FieldValue with unsupported value type."); + break; + } + index++; + parentParcel.tupleValue.push_back(childParcel); + } else if (fieldDepth > depth && fieldPrefix == prefix) { + childParcel.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; + populateStatsDimensionsValueParcelChildren(childParcel, dims, index, depth + 1, + dim.mField.getPrefix(depth + 1)); + parentParcel.tupleValue.push_back(childParcel); + } else { + return; + } + } +} + +StatsDimensionsValueParcel HashableDimensionKey::toStatsDimensionsValueParcel() const { + StatsDimensionsValueParcel parcel; + if (mValues.size() == 0) { + return parcel; + } + + parcel.field = mValues[0].mField.getTag(); + parcel.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; + + size_t index = 0; + populateStatsDimensionsValueParcelChildren(parcel, mValues, index, /*depth=*/0, /*prefix=*/0); + return parcel; +} + android::hash_t hashDimension(const HashableDimensionKey& value) { android::hash_t hash = 0; for (const auto& fieldValue : value.getValues()) { diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h index 654e1358f2a1..4adcf967555e 100644 --- a/cmds/statsd/src/HashableDimensionKey.h +++ b/cmds/statsd/src/HashableDimensionKey.h @@ -16,10 +16,11 @@ #pragma once +#include <android/os/StatsDimensionsValueParcel.h> #include <utils/JenkinsHash.h> #include <vector> -#include "FieldValue.h" #include "android-base/stringprintf.h" +#include "FieldValue.h" #include "logd/LogEvent.h" namespace android { @@ -69,6 +70,8 @@ public: return nullptr; } + StatsDimensionsValueParcel toStatsDimensionsValueParcel() const; + std::string toString() const; bool operator!=(const HashableDimensionKey& that) const; diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index 879b3c3e52aa..6e7f08135f1e 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -20,13 +20,17 @@ #include "StatsLogProcessor.h" #include <android-base/file.h> +#include <cutils/multiuser.h> #include <frameworks/base/cmds/statsd/src/active_config_list.pb.h> +#include <frameworks/base/cmds/statsd/src/experiment_ids.pb.h> #include "android-base/stringprintf.h" #include "atoms_info.h" #include "external/StatsPullerManager.h" #include "guardrail/StatsdStats.h" +#include "logd/LogEvent.h" #include "metrics/CountMetricProducer.h" +#include "StatsService.h" #include "state/StateManager.h" #include "stats_log_util.h" #include "stats_util.h" @@ -68,6 +72,10 @@ const int FIELD_ID_STRINGS = 9; // for ActiveConfigList const int FIELD_ID_ACTIVE_CONFIG_LIST_CONFIG = 1; +// for permissions checks +constexpr const char* kPermissionDump = "android.permission.DUMP"; +constexpr const char* kPermissionUsage = "android.permission.PACKAGE_USAGE_STATS"; + #define NS_PER_HOUR 3600 * NS_PER_SEC #define STATS_ACTIVE_METRIC_DIR "/data/misc/stats-active-metric" @@ -181,6 +189,196 @@ void StatsLogProcessor::onIsolatedUidChangedEventLocked(const LogEvent& event) { } } +void StatsLogProcessor::onBinaryPushStateChangedEventLocked(LogEvent* event) { + pid_t pid = event->GetPid(); + uid_t uid = event->GetUid(); + if (!checkPermissionForIds(kPermissionDump, pid, uid) || + !checkPermissionForIds(kPermissionUsage, pid, uid)) { + return; + } + // The Get* functions don't modify the status on success, they only write in + // failure statuses, so we can use one status variable for all calls then + // check if it is no longer NO_ERROR. + status_t err = NO_ERROR; + InstallTrainInfo trainInfo; + trainInfo.trainName = string(event->GetString(1 /*train name field id*/, &err)); + trainInfo.trainVersionCode = event->GetLong(2 /*train version field id*/, &err); + trainInfo.requiresStaging = event->GetBool(3 /*requires staging field id*/, &err); + trainInfo.rollbackEnabled = event->GetBool(4 /*rollback enabled field id*/, &err); + trainInfo.requiresLowLatencyMonitor = + event->GetBool(5 /*requires low latency monitor field id*/, &err); + trainInfo.status = int32_t(event->GetLong(6 /*state field id*/, &err)); +#ifdef NEW_ENCODING_SCHEME + std::vector<uint8_t> trainExperimentIdBytes = + event->GetStorage(7 /*experiment ids field id*/, &err); +#else + string trainExperimentIdString = event->GetString(7 /*experiment ids field id*/, &err); +#endif + bool is_rollback = event->GetBool(10 /*is rollback field id*/, &err); + + if (err != NO_ERROR) { + ALOGE("Failed to parse fields in binary push state changed log event"); + return; + } + ExperimentIds trainExperimentIds; +#ifdef NEW_ENCODING_SCHEME + if (!trainExperimentIds.ParseFromArray(trainExperimentIdBytes.data(), + trainExperimentIdBytes.size())) { +#else + if (!trainExperimentIds.ParseFromString(trainExperimentIdString)) { +#endif + ALOGE("Failed to parse experimentids in binary push state changed."); + return; + } + trainInfo.experimentIds = {trainExperimentIds.experiment_id().begin(), + trainExperimentIds.experiment_id().end()}; + + // Update the train info on disk and get any data the logevent is missing. + getAndUpdateTrainInfoOnDisk(is_rollback, &trainInfo); + + std::vector<uint8_t> trainExperimentIdProto; + writeExperimentIdsToProto(trainInfo.experimentIds, &trainExperimentIdProto); + int32_t userId = multiuser_get_user_id(uid); + + event->updateValue(2 /*train version field id*/, trainInfo.trainVersionCode, LONG); +#ifdef NEW_ENCODING_SCHEME + event->updateValue(7 /*experiment ids field id*/, trainExperimentIdProto, STORAGE); +#else + event->updateValue(7 /*experiment ids field id*/, trainExperimentIdProto, STRING); +#endif + event->updateValue(8 /*user id field id*/, userId, INT); + + if (is_rollback) { + int bit = trainInfo.requiresStaging ? 1 : 0; + event->updateValue(3 /*requires staging field id*/, bit, INT); + bit = trainInfo.rollbackEnabled ? 1 : 0; + event->updateValue(4 /*rollback enabled field id*/, bit, INT); + bit = trainInfo.requiresLowLatencyMonitor ? 1 : 0; + event->updateValue(5 /*requires low latency monitor field id*/, bit, INT); + } +} + +void StatsLogProcessor::getAndUpdateTrainInfoOnDisk(bool is_rollback, + InstallTrainInfo* trainInfo) { + // If the train name is empty, we don't know which train to attribute the + // event to, so return early. + if (trainInfo->trainName.empty()) { + return; + } + bool readTrainInfoSuccess = false; + InstallTrainInfo trainInfoOnDisk; + readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfo->trainName, trainInfoOnDisk); + + bool resetExperimentIds = false; + if (readTrainInfoSuccess) { + // Keep the old train version if we received an empty version. + if (trainInfo->trainVersionCode == -1) { + trainInfo->trainVersionCode = trainInfoOnDisk.trainVersionCode; + } else if (trainInfo->trainVersionCode != trainInfoOnDisk.trainVersionCode) { + // Reset experiment ids if we receive a new non-empty train version. + resetExperimentIds = true; + } + + // Reset if we received a different experiment id. + if (!trainInfo->experimentIds.empty() && + (trainInfoOnDisk.experimentIds.empty() || + trainInfo->experimentIds.at(0) != trainInfoOnDisk.experimentIds[0])) { + resetExperimentIds = true; + } + } + + // Find the right experiment IDs + if ((!resetExperimentIds || is_rollback) && readTrainInfoSuccess) { + trainInfo->experimentIds = trainInfoOnDisk.experimentIds; + } + + if (!trainInfo->experimentIds.empty()) { + int64_t firstId = trainInfo->experimentIds.at(0); + switch (trainInfo->status) { + case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS: + trainInfo->experimentIds.push_back(firstId + 1); + break; + case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED: + trainInfo->experimentIds.push_back(firstId + 2); + break; + case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS: + trainInfo->experimentIds.push_back(firstId + 3); + break; + } + } + + if (is_rollback) { + trainInfo->requiresStaging = trainInfoOnDisk.requiresStaging; + trainInfo->rollbackEnabled = trainInfoOnDisk.rollbackEnabled; + trainInfo->requiresLowLatencyMonitor = trainInfoOnDisk.requiresLowLatencyMonitor; + } + + StorageManager::writeTrainInfo(*trainInfo); +} + +void StatsLogProcessor::onWatchdogRollbackOccurredLocked(LogEvent* event) { + pid_t pid = event->GetPid(); + uid_t uid = event->GetUid(); + if (!checkPermissionForIds(kPermissionDump, pid, uid) || + !checkPermissionForIds(kPermissionUsage, pid, uid)) { + return; + } + // The Get* functions don't modify the status on success, they only write in + // failure statuses, so we can use one status variable for all calls then + // check if it is no longer NO_ERROR. + status_t err = NO_ERROR; + int32_t rollbackType = int32_t(event->GetInt(1 /*rollback type field id*/, &err)); + string packageName = string(event->GetString(2 /*package name field id*/, &err)); + + if (err != NO_ERROR) { + ALOGE("Failed to parse fields in watchdog rollback occurred log event"); + return; + } + + vector<int64_t> experimentIds = + processWatchdogRollbackOccurred(rollbackType, packageName); + vector<uint8_t> experimentIdProto; + writeExperimentIdsToProto(experimentIds, &experimentIdProto); + +#ifdef NEW_ENCODING_SCHEME + event->updateValue(6 /*experiment ids field id*/, experimentIdProto, STORAGE); +#else + event->updateValue(6 /*experiment ids field id*/, experimentIdProto, STRING); +#endif +} + +vector<int64_t> StatsLogProcessor::processWatchdogRollbackOccurred(const int32_t rollbackTypeIn, + const string& packageNameIn) { + // If the package name is empty, we can't attribute it to any train, so + // return early. + if (packageNameIn.empty()) { + return vector<int64_t>(); + } + bool readTrainInfoSuccess = false; + InstallTrainInfo trainInfoOnDisk; + readTrainInfoSuccess = StorageManager::readTrainInfo(packageNameIn, trainInfoOnDisk); + + if (!readTrainInfoSuccess) { + return vector<int64_t>(); + } + + if (trainInfoOnDisk.experimentIds.empty()) { + return vector<int64_t>(); + } + switch (rollbackTypeIn) { + case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE: + trainInfoOnDisk.experimentIds.push_back(trainInfoOnDisk.experimentIds[0] + 4); + StorageManager::writeTrainInfo(trainInfoOnDisk); + break; + case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS: + trainInfoOnDisk.experimentIds.push_back(trainInfoOnDisk.experimentIds[0] + 5); + StorageManager::writeTrainInfo(trainInfoOnDisk); + break; + } + + return trainInfoOnDisk.experimentIds; +} + void StatsLogProcessor::resetConfigs() { std::lock_guard<std::mutex> lock(mMetricsMutex); resetConfigsLocked(getElapsedRealtimeNs()); @@ -201,6 +399,18 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) { std::lock_guard<std::mutex> lock(mMetricsMutex); + // Hard-coded logic to update train info on disk and fill in any information + // this log event may be missing. + if (event->GetTagId() == android::util::BINARY_PUSH_STATE_CHANGED) { + onBinaryPushStateChangedEventLocked(event); + } + + // Hard-coded logic to update experiment ids on disk for certain rollback + // types and fill the rollback atom with experiment ids + if (event->GetTagId() == android::util::WATCHDOG_ROLLBACK_OCCURRED) { + onWatchdogRollbackOccurredLocked(event); + } + #ifdef VERY_VERBOSE_PRINTING if (mPrintAllLogs) { ALOGI("%s", event->ToString().c_str()); diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index c569bc1e33f7..42e56760fb89 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -18,6 +18,7 @@ #include <gtest/gtest_prod.h> #include "config/ConfigListener.h" +#include "logd/LogEvent.h" #include "metrics/MetricsManager.h" #include "packages/UidMap.h" #include "external/StatsPullerManager.h" @@ -196,6 +197,22 @@ private: // Handler over the isolated uid change event. void onIsolatedUidChangedEventLocked(const LogEvent& event); + // Handler over the binary push state changed event. + void onBinaryPushStateChangedEventLocked(LogEvent* event); + + // Handler over the watchdog rollback occurred event. + void onWatchdogRollbackOccurredLocked(LogEvent* event); + + // Updates train info on disk based on binary push state changed info and + // write disk info into parameters. + void getAndUpdateTrainInfoOnDisk(bool is_rollback, InstallTrainInfo* trainInfoIn); + + // Gets experiment ids on disk for associated train and updates them + // depending on rollback type. Then writes them back to disk and returns + // them. + std::vector<int64_t> processWatchdogRollbackOccurred(const int32_t rollbackTypeIn, + const string& packageName); + // Reset all configs. void resetConfigsLocked(const int64_t timestampNs); // Reset the specified configs. diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 8a8c1e6ff0ac..168833fc8c8b 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -70,25 +70,12 @@ static binder::Status exception(uint32_t code, const std::string& msg) { return binder::Status::fromExceptionCode(code, String8(msg.c_str())); } - static bool checkPermission(const char* permission) { - sp<IStatsCompanionService> scs = getStatsCompanionService(); - if (scs == nullptr) { - return false; - } - - bool success; pid_t pid = IPCThreadState::self()->getCallingPid(); uid_t uid = IPCThreadState::self()->getCallingUid(); - - binder::Status status = scs->checkPermission(String16(permission), pid, uid, &success); - if (!status.isOk()) { - return false; - } - return success; + return checkPermissionForIds(permission, pid, uid); } - binder::Status checkUid(uid_t expectedUid) { uid_t uid = IPCThreadState::self()->getCallingUid(); if (uid == expectedUid || uid == AID_ROOT) { @@ -870,18 +857,8 @@ status_t StatsService::cmd_log_binary_push(int out, const Vector<String8>& args) dprintf(out, "Incorrect number of argument supplied\n"); return UNKNOWN_ERROR; } - android::String16 trainName = android::String16(args[1].c_str()); + string trainName = string(args[1].c_str()); int64_t trainVersion = strtoll(args[2].c_str(), nullptr, 10); - int options = 0; - if (args[3] == "1") { - options = options | IStatsd::FLAG_REQUIRE_STAGING; - } - if (args[4] == "1") { - options = options | IStatsd::FLAG_ROLLBACK_ENABLED; - } - if (args[5] == "1") { - options = options | IStatsd::FLAG_REQUIRE_LOW_LATENCY_MONITOR; - } int32_t state = atoi(args[6].c_str()); vector<int64_t> experimentIds; if (argCount == 8) { @@ -892,7 +869,10 @@ status_t StatsService::cmd_log_binary_push(int out, const Vector<String8>& args) } } dprintf(out, "Logging BinaryPushStateChanged\n"); - sendBinaryPushStateChangedAtom(trainName, trainVersion, options, state, experimentIds); + vector<uint8_t> experimentIdBytes; + writeExperimentIdsToProto(experimentIds, &experimentIdBytes); + LogEvent event(trainName, trainVersion, args[3], args[4], args[5], state, experimentIdBytes, 0); + mProcessor->OnLogEvent(&event); return NO_ERROR; } @@ -1313,275 +1293,25 @@ Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) { return Status::ok(); } -Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainNameIn, - const int64_t trainVersionCodeIn, - const int options, - const int32_t state, - const std::vector<int64_t>& experimentIdsIn) { - // Note: We skip the usage stats op check here since we do not have a package name. - // This is ok since we are overloading the usage_stats permission. - // This method only sends data, it does not receive it. - pid_t pid = IPCThreadState::self()->getCallingPid(); - uid_t uid = IPCThreadState::self()->getCallingUid(); - // Root, system, and shell always have access - if (uid != AID_ROOT && uid != AID_SYSTEM && uid != AID_SHELL) { - // Caller must be granted these permissions - if (!checkPermission(kPermissionDump)) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, - kPermissionDump)); - } - if (!checkPermission(kPermissionUsage)) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, - kPermissionUsage)); - } - } - - bool readTrainInfoSuccess = false; - InstallTrainInfo trainInfoOnDisk; - readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk); - - bool resetExperimentIds = false; - int64_t trainVersionCode = trainVersionCodeIn; - std::string trainNameUtf8 = std::string(String8(trainNameIn).string()); - if (readTrainInfoSuccess) { - // Keep the old train version if we received an empty version. - if (trainVersionCodeIn == -1) { - trainVersionCode = trainInfoOnDisk.trainVersionCode; - } else if (trainVersionCodeIn != trainInfoOnDisk.trainVersionCode) { - // Reset experiment ids if we receive a new non-empty train version. - resetExperimentIds = true; - } - - // Keep the old train name if we received an empty train name. - if (trainNameUtf8.size() == 0) { - trainNameUtf8 = trainInfoOnDisk.trainName; - } else if (trainNameUtf8 != trainInfoOnDisk.trainName) { - // Reset experiment ids if we received a new valid train name. - resetExperimentIds = true; - } - - // Reset if we received a different experiment id. - if (!experimentIdsIn.empty() && - (trainInfoOnDisk.experimentIds.empty() || - experimentIdsIn[0] != trainInfoOnDisk.experimentIds[0])) { - resetExperimentIds = true; - } - } - - // Find the right experiment IDs - std::vector<int64_t> experimentIds; - if (resetExperimentIds || !readTrainInfoSuccess) { - experimentIds = experimentIdsIn; - } else { - experimentIds = trainInfoOnDisk.experimentIds; - } - - if (!experimentIds.empty()) { - int64_t firstId = experimentIds[0]; - switch (state) { - case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS: - experimentIds.push_back(firstId + 1); - break; - case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED: - experimentIds.push_back(firstId + 2); - break; - case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS: - experimentIds.push_back(firstId + 3); - break; - } - } - - // Flatten the experiment IDs to proto - vector<uint8_t> experimentIdsProtoBuffer; - writeExperimentIdsToProto(experimentIds, &experimentIdsProtoBuffer); - StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds); - - userid_t userId = multiuser_get_user_id(uid); - bool requiresStaging = options & IStatsd::FLAG_REQUIRE_STAGING; - bool rollbackEnabled = options & IStatsd::FLAG_ROLLBACK_ENABLED; - bool requiresLowLatencyMonitor = options & IStatsd::FLAG_REQUIRE_LOW_LATENCY_MONITOR; - LogEvent event(trainNameUtf8, trainVersionCode, requiresStaging, rollbackEnabled, - requiresLowLatencyMonitor, state, experimentIdsProtoBuffer, userId); - mProcessor->OnLogEvent(&event); - return Status::ok(); -} - -Status StatsService::sendWatchdogRollbackOccurredAtom(const int32_t rollbackTypeIn, - const android::String16& packageNameIn, - const int64_t packageVersionCodeIn, - const int32_t rollbackReasonIn, - const android::String16& - failingPackageNameIn) { - // Note: We skip the usage stats op check here since we do not have a package name. - // This is ok since we are overloading the usage_stats permission. - // This method only sends data, it does not receive it. - pid_t pid = IPCThreadState::self()->getCallingPid(); - uid_t uid = IPCThreadState::self()->getCallingUid(); - // Root, system, and shell always have access - if (uid != AID_ROOT && uid != AID_SYSTEM && uid != AID_SHELL) { - // Caller must be granted these permissions - if (!checkPermission(kPermissionDump)) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, - kPermissionDump)); - } - if (!checkPermission(kPermissionUsage)) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, - kPermissionUsage)); - } - } - - android::util::stats_write(android::util::WATCHDOG_ROLLBACK_OCCURRED, - rollbackTypeIn, String8(packageNameIn).string(), packageVersionCodeIn, - rollbackReasonIn, String8(failingPackageNameIn).string()); - - // Fast return to save disk read. - if (rollbackTypeIn != android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS - && rollbackTypeIn != - android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE) { - return Status::ok(); - } - - bool readTrainInfoSuccess = false; - InstallTrainInfo trainInfoOnDisk; - readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk); - - if (!readTrainInfoSuccess) { - return Status::ok(); - } - std::vector<int64_t> experimentIds = trainInfoOnDisk.experimentIds; - if (experimentIds.empty()) { - return Status::ok(); - } - switch (rollbackTypeIn) { - case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE: - experimentIds.push_back(experimentIds[0] + 4); - break; - case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS: - experimentIds.push_back(experimentIds[0] + 5); - break; - } - StorageManager::writeTrainInfo(trainInfoOnDisk.trainVersionCode, trainInfoOnDisk.trainName, - trainInfoOnDisk.status, experimentIds); - return Status::ok(); -} - - Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) { ENFORCE_UID(AID_SYSTEM); // TODO: add verifier permission + experimentIdsOut->clear(); // Read the latest train info - InstallTrainInfo trainInfo; - if (!StorageManager::readTrainInfo(trainInfo)) { + vector<InstallTrainInfo> trainInfoList = StorageManager::readAllTrainInfo(); + if (trainInfoList.empty()) { // No train info means no experiment IDs, return an empty list - experimentIdsOut->clear(); return Status::ok(); } // Copy the experiment IDs to the out vector - experimentIdsOut->assign(trainInfo.experimentIds.begin(), trainInfo.experimentIds.end()); - 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(); + for (InstallTrainInfo& trainInfo : trainInfoList) { + experimentIdsOut->insert(experimentIdsOut->end(), + trainInfo.experimentIds.begin(), + trainInfo.experimentIds.end()); } - LogEvent event(getWallClockSec() * NS_PER_SEC, getElapsedRealtimeNs(), vendorAtom); - mProcessor->OnLogEvent(&event); - - return hardware::Void(); + return Status::ok(); } void StatsService::binderDied(const wp <IBinder>& who) { diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 3bfaa9842950..0527d435fdf4 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); @@ -193,85 +187,10 @@ public: virtual Status unregisterNativePullAtomCallback(int32_t atomTag) override; /** - * Binder call to log BinaryPushStateChanged atom. - */ - virtual Status sendBinaryPushStateChangedAtom( - const android::String16& trainNameIn, - const int64_t trainVersionCodeIn, - const int options, - const int32_t state, - const std::vector<int64_t>& experimentIdsIn) override; - - /** - * Binder call to log WatchdogRollbackOccurred atom. - */ - virtual Status sendWatchdogRollbackOccurredAtom( - const int32_t rollbackTypeIn, - const android::String16& packageNameIn, - const int64_t packageVersionCodeIn, - const int32_t rollbackReasonIn, - const android::String16& failingPackageNameIn) override; - - /** * Binder call to get registered experiment IDs. */ 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/atoms.proto b/cmds/statsd/src/atoms.proto index fccefdc07363..447775431fc7 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -127,7 +127,7 @@ message Atom { WallClockTimeShifted wall_clock_time_shifted = 45 [(module) = "framework"]; AnomalyDetected anomaly_detected = 46; AppBreadcrumbReported app_breadcrumb_reported = - 47 [(allow_from_any_uid) = true, (module) = "framework"]; + 47 [(allow_from_any_uid) = true, (module) = "statsd"]; AppStartOccurred app_start_occurred = 48 [(module) = "framework"]; AppStartCanceled app_start_canceled = 49 [(module) = "framework"]; AppStartFullyDrawn app_start_fully_drawn = 50 [(module) = "framework"]; @@ -188,7 +188,7 @@ message Atom { ServiceStateChanged service_state_changed = 99 [(module) = "framework"]; ServiceLaunchReported service_launch_reported = 100 [(module) = "framework"]; FlagFlipUpdateOccurred flag_flip_update_occurred = 101 [(module) = "framework"]; - BinaryPushStateChanged binary_push_state_changed = 102 [(module) = "framework"]; + BinaryPushStateChanged binary_push_state_changed = 102 [(module) = "statsd"]; DevicePolicyEvent device_policy_event = 103 [(module) = "framework"]; DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = 104 [(module) = "docsui"]; DocsUIFileOperationCopyMoveModeReported docs_ui_file_op_copy_move_mode_reported = @@ -202,7 +202,8 @@ message Atom { DocsUIStartupMsReported docs_ui_startup_ms = 111 [(module) = "docsui"]; DocsUIUserActionReported docs_ui_user_action_reported = 112 [(module) = "docsui"]; WifiEnabledStateChanged wifi_enabled_state_changed = 113 [(module) = "framework"]; - WifiRunningStateChanged wifi_running_state_changed = 114 [(module) = "framework"]; + WifiRunningStateChanged wifi_running_state_changed = 114 + [(module) = "framework", deprecated = true]; AppCompacted app_compacted = 115 [(module) = "framework"]; NetworkDnsEventReported network_dns_event_reported = 116 [(module) = "resolv"]; DocsUIPickerLaunchedFromReported docs_ui_picker_launched_from_reported = @@ -390,6 +391,8 @@ message Atom { WifiHealthStatReported wifi_health_stat_reported = 251 [(module) = "wifi"]; WifiFailureStatReported wifi_failure_stat_reported = 252 [(module) = "wifi"]; WifiConnectionResultReported wifi_connection_result_reported = 253 [(module) = "wifi"]; + AppFreezeChanged app_freeze_changed = 254 [(module) = "framework"]; + SdkExtensionStatus sdk_extension_status = 354; } // Pulled events will start at field 10000. @@ -1258,6 +1261,8 @@ message WifiEnabledStateChanged { } /** + * This atom is deprecated starting in R. + * * Logs when an app causes Wifi to run. In this context, 'to run' means to use Wifi Client Mode. * TODO: Include support for Hotspot, perhaps by using an extra field to denote 'mode'. * Note that Wifi Scanning is monitored separately in WifiScanStateChanged. @@ -1870,6 +1875,8 @@ message WatchdogRollbackOccurred { // Set by RollbackPackageHealthObserver to be the package that is failing when a rollback // is initiated. Empty if the package is unknown. optional string failing_package_name = 5; + + optional TrainExperimentIds experiment_ids = 6 [(log_mode) = MODE_BYTES]; } /** @@ -3706,6 +3713,7 @@ message FlagFlipUpdateOccurred { /** * Potential experiment ids that goes with a train install. + * Should be kept in sync with experiment_ids.proto. */ message TrainExperimentIds { repeated int64 experiment_id = 1; @@ -3765,6 +3773,8 @@ message BinaryPushStateChanged { // user id optional int32 user_id = 8; optional int32 reason = 9; + // Whether or not this is a rollback event + optional bool is_rollback = 10; } /* Test atom, is not logged anywhere */ @@ -8333,3 +8343,52 @@ message CellBroadcastMessageError { // Exception message (or log message) associated with the error (max 1000 chars) optional string exception_message = 2; } + +/** + * Logs when the SDK Extensions test app has polled the current version. + * This is atom ID 354. + * + * Logged from: + * vendor/google_testing/integration/packages/apps/SdkExtensionsTestApp/ + */ +message SdkExtensionStatus { + enum ApiCallStatus { + CALL_NOT_ATTEMPTED = 0; + CALL_SUCCESSFUL = 1; + CALL_FAILED = 2; + } + + optional ApiCallStatus result = 1; + + // The R extension version, i.e. android.os.ext.SdkExtension.getExtensionVersion(R). + optional int32 r_extension_version = 2; + + // A number identifying which particular symbol's call failed, if any. 0 means no missing symbol. + // "Failed" here can mean a symbol that wasn't meant to be visible was, or the other way around. + optional int32 failed_call_symbol = 3; +} + +/** + * Logs when an app is frozen or unfrozen. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java + */ +message AppFreezeChanged { + // The type of event. + enum Action { + UNKNOWN = 0; + FREEZE_APP = 1; + UNFREEZE_APP = 2; + } + optional Action action = 1; + + // Pid of the process being frozen. + optional int32 pid = 2; + + // Name of the process being frozen. + optional string process_name = 3; + + // Time since last unfrozen. + optional int64 time_unfrozen_millis = 4; +} diff --git a/cmds/statsd/src/experiment_ids.proto b/cmds/statsd/src/experiment_ids.proto new file mode 100644 index 000000000000..c2036314cf58 --- /dev/null +++ b/cmds/statsd/src/experiment_ids.proto @@ -0,0 +1,29 @@ +/* + * 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. + */ + +syntax = "proto2"; + +package android.os.statsd; + +option java_package = "com.android.internal.os"; +option java_outer_classname = "ExperimentIdsProto"; + +// StatsLogProcessor uses the proto to parse experiment ids from +// BinaryPushStateChanged atoms. This needs to be in sync with +// TrainExperimentIds in atoms.proto. +message ExperimentIds { + repeated int64 experiment_id = 1; +} diff --git a/cmds/statsd/src/external/GpuStatsPuller.cpp b/cmds/statsd/src/external/GpuStatsPuller.cpp deleted file mode 100644 index 3229ba82fe3c..000000000000 --- a/cmds/statsd/src/external/GpuStatsPuller.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "GpuStatsPuller.h" - -#include <binder/IServiceManager.h> -#include <graphicsenv/GpuStatsInfo.h> -#include <graphicsenv/IGpuService.h> - -#include "logd/LogEvent.h" - -#include "stats_log_util.h" -#include "statslog.h" - -namespace android { -namespace os { -namespace statsd { - -using android::util::ProtoReader; - -GpuStatsPuller::GpuStatsPuller(const int tagId) : StatsPuller(tagId) { -} - -static sp<IGpuService> getGpuService() { - const sp<IBinder> binder = defaultServiceManager()->checkService(String16("gpu")); - if (!binder) { - ALOGE("Failed to get gpu service"); - return nullptr; - } - - return interface_cast<IGpuService>(binder); -} - -static bool pullGpuStatsGlobalInfo(const sp<IGpuService>& gpuService, - std::vector<std::shared_ptr<LogEvent>>* data) { - std::vector<GpuStatsGlobalInfo> stats; - status_t status = gpuService->getGpuStatsGlobalInfo(&stats); - if (status != OK) { - return false; - } - - data->clear(); - data->reserve(stats.size()); - for (const auto& info : stats) { - std::shared_ptr<LogEvent> event = make_shared<LogEvent>( - android::util::GPU_STATS_GLOBAL_INFO, getWallClockNs(), getElapsedRealtimeNs()); - if (!event->write(info.driverPackageName)) return false; - if (!event->write(info.driverVersionName)) return false; - if (!event->write((int64_t)info.driverVersionCode)) return false; - if (!event->write(info.driverBuildTime)) return false; - if (!event->write((int64_t)info.glLoadingCount)) return false; - if (!event->write((int64_t)info.glLoadingFailureCount)) return false; - if (!event->write((int64_t)info.vkLoadingCount)) return false; - if (!event->write((int64_t)info.vkLoadingFailureCount)) return false; - if (!event->write(info.vulkanVersion)) return false; - if (!event->write(info.cpuVulkanVersion)) return false; - if (!event->write(info.glesVersion)) return false; - if (!event->write((int64_t)info.angleLoadingCount)) return false; - if (!event->write((int64_t)info.angleLoadingFailureCount)) return false; - event->init(); - data->emplace_back(event); - } - - return true; -} - -static bool pullGpuStatsAppInfo(const sp<IGpuService>& gpuService, - std::vector<std::shared_ptr<LogEvent>>* data) { - std::vector<GpuStatsAppInfo> stats; - status_t status = gpuService->getGpuStatsAppInfo(&stats); - if (status != OK) { - return false; - } - - data->clear(); - data->reserve(stats.size()); - for (const auto& info : stats) { - std::shared_ptr<LogEvent> event = make_shared<LogEvent>( - android::util::GPU_STATS_APP_INFO, getWallClockNs(), getElapsedRealtimeNs()); - if (!event->write(info.appPackageName)) return false; - if (!event->write((int64_t)info.driverVersionCode)) return false; - if (!event->writeBytes(int64VectorToProtoByteString(info.glDriverLoadingTime))) { - return false; - } - if (!event->writeBytes(int64VectorToProtoByteString(info.vkDriverLoadingTime))) { - return false; - } - if (!event->writeBytes(int64VectorToProtoByteString(info.angleDriverLoadingTime))) { - return false; - } - if (!event->write(info.cpuVulkanInUse)) return false; - if (!event->write(info.falsePrerotation)) return false; - if (!event->write(info.gles1InUse)) return false; - event->init(); - data->emplace_back(event); - } - - return true; -} - -bool GpuStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) { - const sp<IGpuService> gpuService = getGpuService(); - if (!gpuService) { - return false; - } - - switch (mTagId) { - case android::util::GPU_STATS_GLOBAL_INFO: - return pullGpuStatsGlobalInfo(gpuService, data); - case android::util::GPU_STATS_APP_INFO: - return pullGpuStatsAppInfo(gpuService, data); - default: - break; - } - - return false; -} - -static std::string protoOutputStreamToByteString(ProtoOutputStream& proto) { - if (!proto.size()) return ""; - - std::string byteString; - sp<ProtoReader> reader = proto.data(); - while (reader->readBuffer() != nullptr) { - const size_t toRead = reader->currentToRead(); - byteString.append((char*)reader->readBuffer(), toRead); - reader->move(toRead); - } - - if (byteString.size() != proto.size()) return ""; - - return byteString; -} - -std::string int64VectorToProtoByteString(const std::vector<int64_t>& value) { - if (value.empty()) return ""; - - ProtoOutputStream proto; - for (const auto& ele : value) { - proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | - 1 /* field id */, - (long long)ele); - } - - return protoOutputStreamToByteString(proto); -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/GpuStatsPuller.h b/cmds/statsd/src/external/GpuStatsPuller.h deleted file mode 100644 index 2da199c51e0f..000000000000 --- a/cmds/statsd/src/external/GpuStatsPuller.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "StatsPuller.h" - -namespace android { -namespace os { -namespace statsd { - -/** - * Pull GpuStats from GpuService. - */ -class GpuStatsPuller : public StatsPuller { -public: - explicit GpuStatsPuller(const int tagId); - bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override; -}; - -// convert a int64_t vector into a byte string for proto message like: -// message RepeatedInt64Wrapper { -// repeated int64 value = 1; -// } -std::string int64VectorToProtoByteString(const std::vector<int64_t>& value); - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 15d7e33d63fd..3ceff7529440 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -32,7 +32,6 @@ #include "../logd/LogEvent.h" #include "../stats_log_util.h" #include "../statscompanion_util.h" -#include "GpuStatsPuller.h" #include "StatsCallbackPuller.h" #include "TrainInfoPuller.h" #include "statslog.h" @@ -51,15 +50,6 @@ StatsPullerManager::StatsPullerManager() : kAllPullAtomInfo({ // TrainInfo. {{.atomTag = android::util::TRAIN_INFO}, new TrainInfoPuller()}, - - // GpuStatsGlobalInfo - {{.atomTag = android::util::GPU_STATS_GLOBAL_INFO}, - new GpuStatsPuller(android::util::GPU_STATS_GLOBAL_INFO)}, - - // GpuStatsAppInfo - {{.atomTag = android::util::GPU_STATS_APP_INFO}, - new GpuStatsPuller(android::util::GPU_STATS_APP_INFO)}, - }), mNextPullTimeNs(NO_ALARM_UPDATE) { } diff --git a/cmds/statsd/src/external/TrainInfoPuller.cpp b/cmds/statsd/src/external/TrainInfoPuller.cpp index 9d0924297912..a7d8d4ebebae 100644 --- a/cmds/statsd/src/external/TrainInfoPuller.cpp +++ b/cmds/statsd/src/external/TrainInfoPuller.cpp @@ -37,14 +37,16 @@ TrainInfoPuller::TrainInfoPuller() : } bool TrainInfoPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { - InstallTrainInfo trainInfo; - bool ret = StorageManager::readTrainInfo(trainInfo); - if (!ret) { - ALOGW("Failed to read train info."); - return false; + vector<InstallTrainInfo> trainInfoList = + StorageManager::readAllTrainInfo(); + if (trainInfoList.empty()) { + ALOGW("Train info was empty."); + return true; + } + for (InstallTrainInfo& trainInfo : trainInfoList) { + auto event = make_shared<LogEvent>(getWallClockNs(), getElapsedRealtimeNs(), trainInfo); + data->push_back(event); } - auto event = make_shared<LogEvent>(getWallClockNs(), getElapsedRealtimeNs(), trainInfo); - data->push_back(event); return true; } diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 9a0693a84e65..103eb0c78bd5 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -35,6 +35,34 @@ using android::util::ProtoOutputStream; using std::string; using std::vector; +// stats_event.h socket types. Keep in sync. +/* ERRORS */ +#define ERROR_NO_TIMESTAMP 0x1 +#define ERROR_NO_ATOM_ID 0x2 +#define ERROR_OVERFLOW 0x4 +#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8 +#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10 +#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20 +#define ERROR_INVALID_ANNOTATION_ID 0x40 +#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80 +#define ERROR_TOO_MANY_ANNOTATIONS 0x100 +#define ERROR_TOO_MANY_FIELDS 0x200 +#define ERROR_INVALID_VALUE_TYPE 0x400 +#define ERROR_STRING_NOT_NULL_TERMINATED 0x800 + +/* TYPE IDS */ +#define INT32_TYPE 0x00 +#define INT64_TYPE 0x01 +#define STRING_TYPE 0x02 +#define LIST_TYPE 0x03 +#define FLOAT_TYPE 0x04 +#define BOOL_TYPE 0x05 +#define BYTE_ARRAY_TYPE 0x06 +#define OBJECT_TYPE 0x07 +#define KEY_VALUE_PAIRS_TYPE 0x08 +#define ATTRIBUTION_CHAIN_TYPE 0x09 +#define ERROR_TYPE 0x0F + // Msg is expected to begin at the start of the serialized atom -- it should not // include the android_log_header_t or the StatsEventTag. LogEvent::LogEvent(uint8_t* msg, uint32_t len, int32_t uid, int32_t pid) @@ -167,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; @@ -809,6 +806,26 @@ float LogEvent::GetFloat(size_t key, status_t* err) const { return 0.0; } +std::vector<uint8_t> LogEvent::GetStorage(size_t key, status_t* err) const { + int field = getSimpleField(key); + for (const auto& value : mValues) { + if (value.mField.getField() == field) { + if (value.mValue.getType() == STORAGE) { + return value.mValue.storage_value; + } else { + *err = BAD_TYPE; + return vector<uint8_t>(); + } + } + if ((size_t)value.mField.getPosAtDepth(0) > key) { + break; + } + } + + *err = BAD_INDEX; + return vector<uint8_t>(); +} + string LogEvent::ToString() const { string result; result += StringPrintf("{ uid(%d) %lld %lld (%d)", mLogUid, (long long)mLogdTimestampNs, diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 3db26765326d..a67bef451be7 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 { @@ -59,6 +56,9 @@ struct InstallTrainInfo { std::string trainName; int32_t status; std::vector<int64_t> experimentIds; + bool requiresStaging; + bool rollbackEnabled; + bool requiresLowLatencyMonitor; }; /** @@ -103,9 +103,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(); @@ -144,6 +141,7 @@ public: const char* GetString(size_t key, status_t* err) const; bool GetBool(size_t key, status_t* err) const; float GetFloat(size_t key, status_t* err) const; + std::vector<uint8_t> GetStorage(size_t key, status_t* err) const; /** * Write test data to the LogEvent. This can only be used when the LogEvent is constructed @@ -214,6 +212,22 @@ public: return LogEvent(*this); } + template <class T> + status_t updateValue(size_t key, T& value, Type type) { + int field = getSimpleField(key); + for (auto& fieldValue : mValues) { + if (fieldValue.mField.getField() == field) { + if (fieldValue.mValue.getType() == type) { + fieldValue.mValue = Value(value); + return OK; + } else { + return BAD_TYPE; + } + } + } + return BAD_INDEX; + } + private: /** * Only use this if copy is absolutely needed. 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/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp index 8e0c62869932..73f640e130a8 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -21,6 +21,8 @@ #include <set> #include <utils/SystemClock.h> +#include "statscompanion_util.h" + using android::util::AtomsInfo; using android::util::FIELD_COUNT_REPEATED; using android::util::FIELD_TYPE_BOOL; @@ -584,6 +586,21 @@ int64_t MillisToNano(const int64_t millis) { return millis * 1000000; } +bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid) { + sp<IStatsCompanionService> scs = getStatsCompanionService(); + if (scs == nullptr) { + return false; + } + + bool success; + binder::Status status = scs->checkPermission(String16(permission), pid, uid, &success); + if (!status.isOk()) { + return false; + } + + return success; +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h index 5fdf6e260247..aec09561b7c6 100644 --- a/cmds/statsd/src/stats_log_util.h +++ b/cmds/statsd/src/stats_log_util.h @@ -95,6 +95,9 @@ bool parseProtoOutputStream(util::ProtoOutputStream& protoOutput, T* message) { // Returns the truncated timestamp to the nearest 5 minutes if needed. int64_t truncateTimestampIfNecessary(int atomId, int64_t timestampNs); +// Checks permission for given pid and uid. +bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid); + inline bool isVendorPulledAtom(int atomId) { return atomId >= StatsdStats::kVendorPulledAtomStartTag && atomId < StatsdStats::kMaxAtomTag; } diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 507297c6c401..1bac19ed2c5d 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -44,7 +44,7 @@ using std::map; #define TRAIN_INFO_PATH "/data/misc/train-info/train-info.bin" // Magic word at the start of the train info file, change this if changing the file format -const uint32_t TRAIN_INFO_FILE_MAGIC = 0xff7447ff; +const uint32_t TRAIN_INFO_FILE_MAGIC = 0xfb7447bf; // for ConfigMetricsReportList const int FIELD_ID_REPORTS = 2; @@ -75,6 +75,29 @@ string StorageManager::getDataHistoryFileName(long wallClockSec, int uid, int64_ (long long)id); } +static const char* findTrainInfoFileNameLocked(const string& trainName) { + unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir); + if (dir == NULL) { + VLOG("Path %s does not exist", TRAIN_INFO_DIR); + return nullptr; + } + dirent* de; + while ((de = readdir(dir.get()))) { + char* fileName = de->d_name; + if (fileName[0] == '.') continue; + + size_t fileNameLength = strlen(fileName); + if (fileNameLength >= trainName.length()) { + if (0 == strncmp(fileName + fileNameLength - trainName.length(), trainName.c_str(), + trainName.length())) { + return fileName; + } + } + } + + return nullptr; +} + // Returns array of int64_t which contains timestamp in seconds, uid, // configID and whether the file is a local history file. static void parseFileName(char* name, FileName* output) { @@ -123,20 +146,25 @@ void StorageManager::writeFile(const char* file, const void* buffer, int numByte close(fd); } -bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& trainName, - int32_t status, const std::vector<int64_t>& experimentIds) { +bool StorageManager::writeTrainInfo(const InstallTrainInfo& trainInfo) { std::lock_guard<std::mutex> lock(sTrainInfoMutex); - deleteAllFiles(TRAIN_INFO_DIR); + if (trainInfo.trainName.empty()) { + return false; + } + deleteSuffixedFiles(TRAIN_INFO_DIR, trainInfo.trainName.c_str()); - int fd = open(TRAIN_INFO_PATH, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); + std::string fileName = + StringPrintf("%s/%ld_%s", TRAIN_INFO_DIR, (long) getWallClockSec(), + trainInfo.trainName.c_str()); + + int fd = open(fileName.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); if (fd == -1) { - VLOG("Attempt to access %s but failed", TRAIN_INFO_PATH); + VLOG("Attempt to access %s but failed", fileName.c_str()); return false; } size_t result; - // Write the magic word result = write(fd, &TRAIN_INFO_FILE_MAGIC, sizeof(TRAIN_INFO_FILE_MAGIC)); if (result != sizeof(TRAIN_INFO_FILE_MAGIC)) { @@ -146,8 +174,8 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write the train version - const size_t trainVersionCodeByteCount = sizeof(trainVersionCode); - result = write(fd, &trainVersionCode, trainVersionCodeByteCount); + const size_t trainVersionCodeByteCount = sizeof(trainInfo.trainVersionCode); + result = write(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount); if (result != trainVersionCodeByteCount) { VLOG("Failed to wrtie train version code"); close(fd); @@ -155,7 +183,7 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write # of bytes in trainName to file - const size_t trainNameSize = trainName.size(); + const size_t trainNameSize = trainInfo.trainName.size(); const size_t trainNameSizeByteCount = sizeof(trainNameSize); result = write(fd, (uint8_t*)&trainNameSize, trainNameSizeByteCount); if (result != trainNameSizeByteCount) { @@ -165,7 +193,7 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write trainName to file - result = write(fd, trainName.c_str(), trainNameSize); + result = write(fd, trainInfo.trainName.c_str(), trainNameSize); if (result != trainNameSize) { VLOG("Failed to write train name"); close(fd); @@ -173,8 +201,8 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write status to file - const size_t statusByteCount = sizeof(status); - result = write(fd, (uint8_t*)&status, statusByteCount); + const size_t statusByteCount = sizeof(trainInfo.status); + result = write(fd, (uint8_t*)&trainInfo.status, statusByteCount); if (result != statusByteCount) { VLOG("Failed to write status"); close(fd); @@ -182,7 +210,7 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write experiment id count to file. - const size_t experimentIdsCount = experimentIds.size(); + const size_t experimentIdsCount = trainInfo.experimentIds.size(); const size_t experimentIdsCountByteCount = sizeof(experimentIdsCount); result = write(fd, (uint8_t*) &experimentIdsCount, experimentIdsCountByteCount); if (result != experimentIdsCountByteCount) { @@ -193,7 +221,7 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& // Write experimentIds to file for (size_t i = 0; i < experimentIdsCount; i++) { - const int64_t experimentId = experimentIds[i]; + const int64_t experimentId = trainInfo.experimentIds[i]; const size_t experimentIdByteCount = sizeof(experimentId); result = write(fd, &experimentId, experimentIdByteCount); if (result == experimentIdByteCount) { @@ -205,23 +233,47 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } } - result = fchown(fd, AID_STATSD, AID_STATSD); - if (result) { - VLOG("Failed to chown train info file to statsd"); - close(fd); - return false; + // Write bools to file + const size_t boolByteCount = sizeof(trainInfo.requiresStaging); + result = write(fd, (uint8_t*)&trainInfo.requiresStaging, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to write requires staging"); + close(fd); + return false; + } + + result = write(fd, (uint8_t*)&trainInfo.rollbackEnabled, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to write rollback enabled"); + close(fd); + return false; + } + + result = write(fd, (uint8_t*)&trainInfo.requiresLowLatencyMonitor, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to write requires log latency monitor"); + close(fd); + return false; } close(fd); return true; } -bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) { +bool StorageManager::readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo) { std::lock_guard<std::mutex> lock(sTrainInfoMutex); + return readTrainInfoLocked(trainName, trainInfo); +} - int fd = open(TRAIN_INFO_PATH, O_RDONLY | O_CLOEXEC); +bool StorageManager::readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo) { + trimToFit(TRAIN_INFO_DIR, /*parseTimestampOnly=*/ true); + const char* fileName = findTrainInfoFileNameLocked(trainName); + if (fileName == nullptr) { + return false; + } + int fd = open(StringPrintf("%s/%s", TRAIN_INFO_DIR, fileName).c_str(), O_RDONLY | O_CLOEXEC); if (fd == -1) { - VLOG("Failed to open train-info.bin"); + VLOG("Failed to open %s", fileName); return false; } @@ -297,6 +349,29 @@ bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) { trainInfo.experimentIds.push_back(experimentId); } + // Read bools + const size_t boolByteCount = sizeof(trainInfo.requiresStaging); + result = read(fd, &trainInfo.requiresStaging, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to read requires requires staging from train info file"); + close(fd); + return false; + } + + result = read(fd, &trainInfo.rollbackEnabled, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to read requires rollback enabled from train info file"); + close(fd); + return false; + } + + result = read(fd, &trainInfo.requiresLowLatencyMonitor, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to read requires requires low latency monitor from train info file"); + close(fd); + return false; + } + // Expect to be at EOF. char c; result = read(fd, &c, 1); @@ -311,6 +386,32 @@ bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) { return true; } +vector<InstallTrainInfo> StorageManager::readAllTrainInfo() { + std::lock_guard<std::mutex> lock(sTrainInfoMutex); + vector<InstallTrainInfo> trainInfoList; + unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir); + if (dir == NULL) { + VLOG("Directory does not exist: %s", TRAIN_INFO_DIR); + return trainInfoList; + } + + dirent* de; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') { + continue; + } + + InstallTrainInfo trainInfo; + bool readSuccess = StorageManager::readTrainInfoLocked(name, trainInfo); + if (!readSuccess) { + continue; + } + trainInfoList.push_back(trainInfo); + } + return trainInfoList; +} + void StorageManager::deleteFile(const char* file) { if (remove(file) != 0) { VLOG("Attempt to delete %s but is not found", file); @@ -574,7 +675,7 @@ void StorageManager::sortFiles(vector<FileInfo>* fileNames) { }); } -void StorageManager::trimToFit(const char* path) { +void StorageManager::trimToFit(const char* path, bool parseTimestampOnly) { unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir); if (dir == NULL) { VLOG("Path %s does not exist", path); @@ -589,7 +690,12 @@ void StorageManager::trimToFit(const char* path) { if (name[0] == '.') continue; FileName output; - parseFileName(name, &output); + if (parseTimestampOnly) { + output.mTimestampSec = StrToInt64(strtok(name, "_")); + output.mIsHistory = false; + } else { + parseFileName(name, &output); + } if (output.mTimestampSec == -1) continue; string file_name = output.getFullFileName(path); diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h index 69b41c2cb974..d59046dfbb99 100644 --- a/cmds/statsd/src/storage/StorageManager.h +++ b/cmds/statsd/src/storage/StorageManager.h @@ -52,13 +52,22 @@ public: /** * Writes train info. */ - static bool writeTrainInfo(int64_t trainVersionCode, const std::string& trainName, - int32_t status, const std::vector<int64_t>& experimentIds); + static bool writeTrainInfo(const InstallTrainInfo& trainInfo); /** * Reads train info. */ - static bool readTrainInfo(InstallTrainInfo& trainInfo); + static bool readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo); + + /** + * Reads train info assuming lock is obtained. + */ + static bool readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo); + + /** + * Reads all train info and returns a vector of train info. + */ + static vector<InstallTrainInfo> readAllTrainInfo(); /** * Reads the file content to the buffer. @@ -124,7 +133,7 @@ public: * Trims files in the provided directory to limit the total size, number of * files, accumulation of outdated files. */ - static void trimToFit(const char* dir); + static void trimToFit(const char* dir, bool parseTimestampOnly = false); /** * Returns true if there already exists identical configuration on device. diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp index d86e29131661..30c90b1e1f71 100644 --- a/cmds/statsd/src/subscriber/IncidentdReporter.cpp +++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp @@ -21,10 +21,8 @@ #include "packages/UidMap.h" #include "stats_log_util.h" -#include <android/os/IIncidentManager.h> -#include <android/os/IncidentReportArgs.h> #include <android/util/ProtoOutputStream.h> -#include <binder/IServiceManager.h> +#include <incident/incident_report.h> #include <vector> @@ -132,7 +130,7 @@ bool GenerateIncidentReport(const IncidentdDetails& config, int64_t rule_id, int return false; } - IncidentReportArgs incidentReport; + android::os::IncidentReportRequest incidentReport; vector<uint8_t> protoData; getProtoData(rule_id, metricId, dimensionKey, metricValue, configKey, @@ -146,30 +144,21 @@ bool GenerateIncidentReport(const IncidentdDetails& config, int64_t rule_id, int uint8_t dest; switch (config.dest()) { case IncidentdDetails_Destination_AUTOMATIC: - dest = android::os::PRIVACY_POLICY_AUTOMATIC; + dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC; break; case IncidentdDetails_Destination_EXPLICIT: - dest = android::os::PRIVACY_POLICY_EXPLICIT; + dest = INCIDENT_REPORT_PRIVACY_POLICY_EXPLICIT; break; default: - dest = android::os::PRIVACY_POLICY_AUTOMATIC; + dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC; } incidentReport.setPrivacyPolicy(dest); - incidentReport.setReceiverPkg(config.receiver_pkg()); + incidentReport.setReceiverPackage(config.receiver_pkg()); - incidentReport.setReceiverCls(config.receiver_cls()); + incidentReport.setReceiverClass(config.receiver_cls()); - sp<IIncidentManager> service = interface_cast<IIncidentManager>( - defaultServiceManager()->getService(android::String16("incident"))); - if (service == nullptr) { - ALOGW("Failed to fetch incident service."); - return false; - } - VLOG("Calling incidentd %p", service.get()); - binder::Status s = service->reportIncident(incidentReport); - VLOG("Report incident status: %s", s.toString8().string()); - return s.isOk(); + return incidentReport.takeReport() == NO_ERROR; } } // namespace statsd diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp index 160b57e27c6c..8fd6b46d0716 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp +++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp @@ -121,7 +121,7 @@ void SubscriberReporter::sendBroadcastLocked(const sp<IPendingIntentRef>& pir, subscription.id(), subscription.rule_id(), cookies, - getStatsDimensionsValue(dimKey.getDimensionKeyInWhat())); + dimKey.getDimensionKeyInWhat().toStatsDimensionsValueParcel()); } sp<IPendingIntentRef> SubscriberReporter::getBroadcastSubscriber(const ConfigKey& configKey, @@ -138,61 +138,6 @@ sp<IPendingIntentRef> SubscriberReporter::getBroadcastSubscriber(const ConfigKey return pirMapIt->second; } -void getStatsDimensionsValueHelper(const vector<FieldValue>& dims, size_t* index, int depth, - int prefix, vector<StatsDimensionsValue>* output) { - size_t count = dims.size(); - while (*index < count) { - const auto& dim = dims[*index]; - const int valueDepth = dim.mField.getDepth(); - const int valuePrefix = dim.mField.getPrefix(depth); - if (valueDepth > 2) { - ALOGE("Depth > 2 not supported"); - return; - } - if (depth == valueDepth && valuePrefix == prefix) { - switch (dim.mValue.getType()) { - case INT: - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), - dim.mValue.int_value)); - break; - case LONG: - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), - dim.mValue.long_value)); - break; - case FLOAT: - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), - dim.mValue.float_value)); - break; - case STRING: - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), - String16(dim.mValue.str_value.c_str()))); - break; - default: - break; - } - (*index)++; - } else if (valueDepth > depth && valuePrefix == prefix) { - vector<StatsDimensionsValue> childOutput; - getStatsDimensionsValueHelper(dims, index, depth + 1, dim.mField.getPrefix(depth + 1), - &childOutput); - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), childOutput)); - } else { - return; - } - } -} - -StatsDimensionsValue SubscriberReporter::getStatsDimensionsValue(const HashableDimensionKey& dim) { - if (dim.getValues().size() == 0) { - return StatsDimensionsValue(); - } - - vector<StatsDimensionsValue> fields; - size_t index = 0; - getStatsDimensionsValueHelper(dim.getValues(), &index, 0, 0, &fields); - return StatsDimensionsValue(dim.getValues()[0].mField.getTag(), fields); -} - } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h index 087a1b84b91f..42599f508313 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.h +++ b/cmds/statsd/src/subscriber/SubscriberReporter.h @@ -22,7 +22,6 @@ #include "config/ConfigKey.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // subscription -#include "android/os/StatsDimensionsValue.h" #include "HashableDimensionKey.h" #include <mutex> @@ -70,8 +69,6 @@ public: sp<IPendingIntentRef> getBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId); - static StatsDimensionsValue getStatsDimensionsValue(const HashableDimensionKey& dim); - private: SubscriberReporter() {}; diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp index f4a59ed14d10..9e69d977f351 100644 --- a/cmds/statsd/tests/FieldValue_test.cpp +++ b/cmds/statsd/tests/FieldValue_test.cpp @@ -290,33 +290,34 @@ TEST(AtomMatcherTest, TestWriteDimensionPath) { } } -TEST(AtomMatcherTest, TestSubscriberDimensionWrite) { - HashableDimensionKey dim; - - int pos1[] = {1, 1, 1}; - int pos2[] = {1, 1, 2}; - int pos3[] = {1, 1, 3}; - int pos4[] = {2, 0, 0}; - - Field field1(10, pos1, 2); - Field field2(10, pos2, 2); - Field field3(10, pos3, 2); - Field field4(10, pos4, 0); - - Value value1((int32_t)10025); - Value value2("tag"); - Value value3((int32_t)987654); - Value value4((int32_t)99999); - - dim.addValue(FieldValue(field1, value1)); - dim.addValue(FieldValue(field2, value2)); - dim.addValue(FieldValue(field3, value3)); - dim.addValue(FieldValue(field4, value4)); - - SubscriberReporter::getStatsDimensionsValue(dim); - // TODO(b/110562792): can't test anything here because StatsDimensionsValue class doesn't - // have any read api. -} +//TODO(b/149050405) Update this test for StatsDimensionValueParcel +//TEST(AtomMatcherTest, TestSubscriberDimensionWrite) { +// HashableDimensionKey dim; +// +// int pos1[] = {1, 1, 1}; +// int pos2[] = {1, 1, 2}; +// int pos3[] = {1, 1, 3}; +// int pos4[] = {2, 0, 0}; +// +// Field field1(10, pos1, 2); +// Field field2(10, pos2, 2); +// Field field3(10, pos3, 2); +// Field field4(10, pos4, 0); +// +// Value value1((int32_t)10025); +// Value value2("tag"); +// Value value3((int32_t)987654); +// Value value4((int32_t)99999); +// +// dim.addValue(FieldValue(field1, value1)); +// dim.addValue(FieldValue(field2, value2)); +// dim.addValue(FieldValue(field3, value3)); +// dim.addValue(FieldValue(field4, value4)); +// +// SubscriberReporter::getStatsDimensionsValue(dim); +// // TODO(b/110562792): can't test anything here because StatsDimensionsValue class doesn't +// // have any read api. +//} TEST(AtomMatcherTest, TestWriteDimensionToProto) { HashableDimensionKey dim; diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp index 35b0396e2fb1..f624e12c9623 100644 --- a/cmds/statsd/tests/LogEvent_test.cpp +++ b/cmds/statsd/tests/LogEvent_test.cpp @@ -46,16 +46,16 @@ Field getField(int32_t tag, const vector<int32_t>& pos, int32_t depth, const vec } TEST(LogEventTest, TestPrimitiveParsing) { - struct stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, 100); - stats_event_write_int32(event, 10); - stats_event_write_int64(event, 0x123456789); - stats_event_write_float(event, 2.0); - stats_event_write_bool(event, true); - stats_event_build(event); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + AStatsEvent_writeInt32(event, 10); + AStatsEvent_writeInt64(event, 0x123456789); + AStatsEvent_writeFloat(event, 2.0); + AStatsEvent_writeBool(event, true); + AStatsEvent_build(event); size_t size; - uint8_t* buf = stats_event_get_buffer(event, &size); + uint8_t* buf = AStatsEvent_getBuffer(event, &size); LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); @@ -90,20 +90,20 @@ TEST(LogEventTest, TestPrimitiveParsing) { EXPECT_EQ(Type::INT, boolItem.mValue.getType()); // FieldValue does not support boolean type EXPECT_EQ(1, boolItem.mValue.int_value); - stats_event_release(event); + AStatsEvent_release(event); } TEST(LogEventTest, TestStringAndByteArrayParsing) { - struct stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, 100); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); string str = "test"; - stats_event_write_string8(event, str.c_str()); - stats_event_write_byte_array(event, (uint8_t*)str.c_str(), str.length()); - stats_event_build(event); + AStatsEvent_writeString(event, str.c_str()); + AStatsEvent_writeByteArray(event, (uint8_t*)str.c_str(), str.length()); + AStatsEvent_build(event); size_t size; - uint8_t* buf = stats_event_get_buffer(event, &size); + uint8_t* buf = AStatsEvent_getBuffer(event, &size); LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); @@ -127,18 +127,18 @@ TEST(LogEventTest, TestStringAndByteArrayParsing) { vector<uint8_t> expectedValue = {'t', 'e', 's', 't'}; EXPECT_EQ(expectedValue, storageItem.mValue.storage_value); - stats_event_release(event); + AStatsEvent_release(event); } TEST(LogEventTest, TestEmptyString) { - struct stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, 100); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); string empty = ""; - stats_event_write_string8(event, empty.c_str()); - stats_event_build(event); + AStatsEvent_writeString(event, empty.c_str()); + AStatsEvent_build(event); size_t size; - uint8_t* buf = stats_event_get_buffer(event, &size); + uint8_t* buf = AStatsEvent_getBuffer(event, &size); LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); @@ -155,18 +155,18 @@ TEST(LogEventTest, TestEmptyString) { EXPECT_EQ(Type::STRING, item.mValue.getType()); EXPECT_EQ(empty, item.mValue.str_value); - stats_event_release(event); + AStatsEvent_release(event); } TEST(LogEventTest, TestByteArrayWithNullCharacter) { - struct stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, 100); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); uint8_t message[] = {'\t', 'e', '\0', 's', 't'}; - stats_event_write_byte_array(event, message, 5); - stats_event_build(event); + AStatsEvent_writeByteArray(event, message, 5); + AStatsEvent_build(event); size_t size; - uint8_t* buf = stats_event_get_buffer(event, &size); + uint8_t* buf = AStatsEvent_getBuffer(event, &size); LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); @@ -184,79 +184,12 @@ TEST(LogEventTest, TestByteArrayWithNullCharacter) { vector<uint8_t> expectedValue(message, message + 5); EXPECT_EQ(expectedValue, item.mValue.storage_value); - stats_event_release(event); -} - -TEST(LogEventTest, TestKeyValuePairs) { - struct stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, 100); - - struct key_value_pair pairs[4]; - pairs[0] = {.key = 0, .valueType = INT32_TYPE, .int32Value = 1}; - pairs[1] = {.key = 1, .valueType = INT64_TYPE, .int64Value = 0x123456789}; - pairs[2] = {.key = 2, .valueType = FLOAT_TYPE, .floatValue = 2.0}; - string str = "test"; - pairs[3] = {.key = 3, .valueType = STRING_TYPE, .stringValue = str.c_str()}; - - stats_event_write_key_value_pairs(event, pairs, 4); - stats_event_build(event); - - size_t size; - uint8_t* buf = stats_event_get_buffer(event, &size); - - LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); - EXPECT_TRUE(logEvent.isValid()); - EXPECT_EQ(100, logEvent.GetTagId()); - EXPECT_EQ(1000, logEvent.GetUid()); - EXPECT_EQ(1001, logEvent.GetPid()); - - const vector<FieldValue>& values = logEvent.getValues(); - EXPECT_EQ(8, values.size()); // 2 FieldValues per key-value pair - - // Check the keys first - for (int i = 0; i < values.size() / 2; i++) { - const FieldValue& item = values[2 * i]; - int32_t depth1Pos = i + 1; - bool depth1Last = i == (values.size() / 2 - 1); - Field expectedField = getField(100, {1, depth1Pos, 1}, 2, {true, depth1Last, false}); - - EXPECT_EQ(expectedField, item.mField); - EXPECT_EQ(Type::INT, item.mValue.getType()); - EXPECT_EQ(i, item.mValue.int_value); - } - - // Check the values now - // Note: pos[2] = index of type in KeyValuePair in atoms.proto - const FieldValue& int32Item = values[1]; - Field expectedField = getField(100, {1, 1, 2}, 2, {true, false, true}); - EXPECT_EQ(expectedField, int32Item.mField); - EXPECT_EQ(Type::INT, int32Item.mValue.getType()); - EXPECT_EQ(1, int32Item.mValue.int_value); - - const FieldValue& int64Item = values[3]; - expectedField = getField(100, {1, 2, 3}, 2, {true, false, true}); - EXPECT_EQ(expectedField, int64Item.mField); - EXPECT_EQ(Type::LONG, int64Item.mValue.getType()); - EXPECT_EQ(0x123456789, int64Item.mValue.long_value); - - const FieldValue& floatItem = values[5]; - expectedField = getField(100, {1, 3, 5}, 2, {true, false, true}); - EXPECT_EQ(expectedField, floatItem.mField); - EXPECT_EQ(Type::FLOAT, floatItem.mValue.getType()); - EXPECT_EQ(2.0, floatItem.mValue.float_value); - - const FieldValue& stringItem = values[7]; - expectedField = getField(100, {1, 4, 4}, 2, {true, true, true}); - EXPECT_EQ(expectedField, stringItem.mField); - EXPECT_EQ(Type::STRING, stringItem.mValue.getType()); - EXPECT_EQ(str, stringItem.mValue.str_value); - - stats_event_release(event); + AStatsEvent_release(event); } TEST(LogEventTest, TestAttributionChain) { - struct stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, 100); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); string tag1 = "tag1"; string tag2 = "tag2"; @@ -264,11 +197,11 @@ TEST(LogEventTest, TestAttributionChain) { uint32_t uids[] = {1001, 1002}; const char* tags[] = {tag1.c_str(), tag2.c_str()}; - stats_event_write_attribution_chain(event, uids, tags, 2); - stats_event_build(event); + AStatsEvent_writeAttributionChain(event, uids, tags, 2); + AStatsEvent_build(event); size_t size; - uint8_t* buf = stats_event_get_buffer(event, &size); + uint8_t* buf = AStatsEvent_getBuffer(event, &size); LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001); EXPECT_TRUE(logEvent.isValid()); @@ -305,7 +238,7 @@ TEST(LogEventTest, TestAttributionChain) { EXPECT_EQ(Type::STRING, tag2Item.mValue.getType()); EXPECT_EQ(tag2, tag2Item.mValue.str_value); - stats_event_release(event); + AStatsEvent_release(event); } #else // NEW_ENCODING_SCHEME diff --git a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp deleted file mode 100644 index ae92705aff4c..000000000000 --- a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#undef LOG_TAG -#define LOG_TAG "GpuStatsPuller_test" - -#include <gmock/gmock.h> -#include <gtest/gtest.h> - -#include <graphicsenv/GpuStatsInfo.h> -#include <log/log.h> - -#include "src/external/GpuStatsPuller.h" -#include "statslog.h" - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -// clang-format off -static const std::string DRIVER_PACKAGE_NAME = "TEST_DRIVER"; -static const std::string DRIVER_VERSION_NAME = "TEST_DRIVER_VERSION"; -static const std::string APP_PACKAGE_NAME = "TEST_APP"; -static const int64_t TIMESTAMP_WALLCLOCK = 111; -static const int64_t TIMESTAMP_ELAPSED = 222; -static const int64_t DRIVER_VERSION_CODE = 333; -static const int64_t DRIVER_BUILD_TIME = 444; -static const int64_t GL_LOADING_COUNT = 3; -static const int64_t GL_LOADING_FAILURE_COUNT = 1; -static const int64_t VK_LOADING_COUNT = 4; -static const int64_t VK_LOADING_FAILURE_COUNT = 0; -static const int64_t ANGLE_LOADING_COUNT = 2; -static const int64_t ANGLE_LOADING_FAILURE_COUNT = 1; -static const int64_t GL_DRIVER_LOADING_TIME_0 = 555; -static const int64_t GL_DRIVER_LOADING_TIME_1 = 666; -static const int64_t VK_DRIVER_LOADING_TIME_0 = 777; -static const int64_t VK_DRIVER_LOADING_TIME_1 = 888; -static const int64_t VK_DRIVER_LOADING_TIME_2 = 999; -static const int64_t ANGLE_DRIVER_LOADING_TIME_0 = 1010; -static const int64_t ANGLE_DRIVER_LOADING_TIME_1 = 1111; -static const int32_t VULKAN_VERSION = 1; -static const int32_t CPU_VULKAN_VERSION = 2; -static const int32_t GLES_VERSION = 3; -static const bool CPU_VULKAN_IN_USE = true; -static const bool FALSE_PREROTATION = true; -static const bool GLES_1_IN_USE = true; -static const size_t NUMBER_OF_VALUES_GLOBAL = 13; -static const size_t NUMBER_OF_VALUES_APP = 8; -// clang-format on - -class MockGpuStatsPuller : public GpuStatsPuller { -public: - MockGpuStatsPuller(const int tagId, vector<std::shared_ptr<LogEvent>>* data) - : GpuStatsPuller(tagId), mData(data){}; - -private: - bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override { - *data = *mData; - return true; - } - - vector<std::shared_ptr<LogEvent>>* mData; -}; - -class GpuStatsPuller_test : public ::testing::Test { -public: - GpuStatsPuller_test() { - const ::testing::TestInfo* const test_info = - ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - } - - ~GpuStatsPuller_test() { - const ::testing::TestInfo* const test_info = - ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); - } -}; - -TEST_F(GpuStatsPuller_test, PullGpuStatsGlobalInfo) { - vector<std::shared_ptr<LogEvent>> inData, outData; - std::shared_ptr<LogEvent> event = make_shared<LogEvent>(android::util::GPU_STATS_GLOBAL_INFO, - TIMESTAMP_WALLCLOCK, TIMESTAMP_ELAPSED); - EXPECT_TRUE(event->write(DRIVER_PACKAGE_NAME)); - EXPECT_TRUE(event->write(DRIVER_VERSION_NAME)); - EXPECT_TRUE(event->write(DRIVER_VERSION_CODE)); - EXPECT_TRUE(event->write(DRIVER_BUILD_TIME)); - EXPECT_TRUE(event->write(GL_LOADING_COUNT)); - EXPECT_TRUE(event->write(GL_LOADING_FAILURE_COUNT)); - EXPECT_TRUE(event->write(VK_LOADING_COUNT)); - EXPECT_TRUE(event->write(VK_LOADING_FAILURE_COUNT)); - EXPECT_TRUE(event->write(VULKAN_VERSION)); - EXPECT_TRUE(event->write(CPU_VULKAN_VERSION)); - EXPECT_TRUE(event->write(GLES_VERSION)); - EXPECT_TRUE(event->write(ANGLE_LOADING_COUNT)); - EXPECT_TRUE(event->write(ANGLE_LOADING_FAILURE_COUNT)); - event->init(); - inData.emplace_back(event); - MockGpuStatsPuller mockPuller(android::util::GPU_STATS_GLOBAL_INFO, &inData); - mockPuller.ForceClearCache(); - mockPuller.Pull(&outData); - - ASSERT_EQ(1, outData.size()); - EXPECT_EQ(android::util::GPU_STATS_GLOBAL_INFO, outData[0]->GetTagId()); - ASSERT_EQ(NUMBER_OF_VALUES_GLOBAL, outData[0]->size()); - EXPECT_EQ(DRIVER_PACKAGE_NAME, outData[0]->getValues()[0].mValue.str_value); - EXPECT_EQ(DRIVER_VERSION_NAME, outData[0]->getValues()[1].mValue.str_value); - EXPECT_EQ(DRIVER_VERSION_CODE, outData[0]->getValues()[2].mValue.long_value); - EXPECT_EQ(DRIVER_BUILD_TIME, outData[0]->getValues()[3].mValue.long_value); - EXPECT_EQ(GL_LOADING_COUNT, outData[0]->getValues()[4].mValue.long_value); - EXPECT_EQ(GL_LOADING_FAILURE_COUNT, outData[0]->getValues()[5].mValue.long_value); - EXPECT_EQ(VK_LOADING_COUNT, outData[0]->getValues()[6].mValue.long_value); - EXPECT_EQ(VK_LOADING_FAILURE_COUNT, outData[0]->getValues()[7].mValue.long_value); - EXPECT_EQ(VULKAN_VERSION, outData[0]->getValues()[8].mValue.int_value); - EXPECT_EQ(CPU_VULKAN_VERSION, outData[0]->getValues()[9].mValue.int_value); - EXPECT_EQ(GLES_VERSION, outData[0]->getValues()[10].mValue.int_value); - EXPECT_EQ(ANGLE_LOADING_COUNT, outData[0]->getValues()[11].mValue.long_value); - EXPECT_EQ(ANGLE_LOADING_FAILURE_COUNT, outData[0]->getValues()[12].mValue.long_value); -} - -TEST_F(GpuStatsPuller_test, PullGpuStatsAppInfo) { - vector<std::shared_ptr<LogEvent>> inData, outData; - std::shared_ptr<LogEvent> event = make_shared<LogEvent>(android::util::GPU_STATS_APP_INFO, - TIMESTAMP_WALLCLOCK, TIMESTAMP_ELAPSED); - EXPECT_TRUE(event->write(APP_PACKAGE_NAME)); - EXPECT_TRUE(event->write(DRIVER_VERSION_CODE)); - std::vector<int64_t> glDriverLoadingTime; - glDriverLoadingTime.emplace_back(GL_DRIVER_LOADING_TIME_0); - glDriverLoadingTime.emplace_back(GL_DRIVER_LOADING_TIME_1); - std::vector<int64_t> vkDriverLoadingTime; - vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_0); - vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_1); - vkDriverLoadingTime.emplace_back(VK_DRIVER_LOADING_TIME_2); - std::vector<int64_t> angleDriverLoadingTime; - angleDriverLoadingTime.emplace_back(ANGLE_DRIVER_LOADING_TIME_0); - angleDriverLoadingTime.emplace_back(ANGLE_DRIVER_LOADING_TIME_1); - EXPECT_TRUE(event->write(int64VectorToProtoByteString(glDriverLoadingTime))); - EXPECT_TRUE(event->write(int64VectorToProtoByteString(vkDriverLoadingTime))); - EXPECT_TRUE(event->write(int64VectorToProtoByteString(angleDriverLoadingTime))); - EXPECT_TRUE(event->write(CPU_VULKAN_IN_USE)); - EXPECT_TRUE(event->write(FALSE_PREROTATION)); - EXPECT_TRUE(event->write(GLES_1_IN_USE)); - event->init(); - inData.emplace_back(event); - MockGpuStatsPuller mockPuller(android::util::GPU_STATS_APP_INFO, &inData); - mockPuller.ForceClearCache(); - mockPuller.Pull(&outData); - - ASSERT_EQ(1, outData.size()); - EXPECT_EQ(android::util::GPU_STATS_APP_INFO, outData[0]->GetTagId()); - ASSERT_EQ(NUMBER_OF_VALUES_APP, outData[0]->size()); - EXPECT_EQ(APP_PACKAGE_NAME, outData[0]->getValues()[0].mValue.str_value); - EXPECT_EQ(DRIVER_VERSION_CODE, outData[0]->getValues()[1].mValue.long_value); - EXPECT_EQ(int64VectorToProtoByteString(glDriverLoadingTime), - outData[0]->getValues()[2].mValue.str_value); - EXPECT_EQ(int64VectorToProtoByteString(vkDriverLoadingTime), - outData[0]->getValues()[3].mValue.str_value); - EXPECT_EQ(int64VectorToProtoByteString(angleDriverLoadingTime), - outData[0]->getValues()[4].mValue.str_value); - EXPECT_EQ(CPU_VULKAN_IN_USE, outData[0]->getValues()[5].mValue.int_value); - EXPECT_EQ(FALSE_PREROTATION, outData[0]->getValues()[6].mValue.int_value); - EXPECT_EQ(GLES_1_IN_USE, outData[0]->getValues()[7].mValue.int_value); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp index 2576cf5b1339..a011692ee625 100644 --- a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp +++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp @@ -50,11 +50,11 @@ int64_t pullTimeoutNs; int64_t pullCoolDownNs; std::thread pullThread; -stats_event* createSimpleEvent(int64_t value) { - stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, pullTagId); - stats_event_write_int64(event, value); - stats_event_build(event); +AStatsEvent* createSimpleEvent(int64_t value) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, pullTagId); + AStatsEvent_writeInt64(event, value); + AStatsEvent_build(event); return event; } @@ -62,16 +62,16 @@ void executePull(const sp<IPullAtomResultReceiver>& resultReceiver) { // Convert stats_events into StatsEventParcels. std::vector<android::util::StatsEventParcel> parcels; for (int i = 0; i < values.size(); i++) { - stats_event* event = createSimpleEvent(values[i]); + AStatsEvent* event = createSimpleEvent(values[i]); size_t size; - uint8_t* buffer = stats_event_get_buffer(event, &size); + uint8_t* buffer = AStatsEvent_getBuffer(event, &size); android::util::StatsEventParcel p; // vector.assign() creates a copy, but this is inevitable unless // stats_event.h/c uses a vector as opposed to a buffer. p.buffer.assign(buffer, buffer + size); parcels.push_back(std::move(p)); - stats_event_release(event); + AStatsEvent_release(event); } sleep_for(std::chrono::nanoseconds(pullDelayNs)); diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 6e1890ad63a9..db09ee9311ba 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -953,24 +953,24 @@ binder::Status FakeSubsystemSleepCallback::onPullAtom( // Convert stats_events into StatsEventParcels. std::vector<android::util::StatsEventParcel> parcels; for (int i = 1; i < 3; i++) { - stats_event* event = stats_event_obtain(); - stats_event_set_atom_id(event, atomTag); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, atomTag); std::string subsystemName = "subsystem_name_"; subsystemName = subsystemName + std::to_string(i); - stats_event_write_string8(event, subsystemName.c_str()); - stats_event_write_string8(event, "subsystem_subname foo"); - stats_event_write_int64(event, /*count= */ i); - stats_event_write_int64(event, /*time_millis= */ i * 100); - stats_event_build(event); + AStatsEvent_writeString(event, subsystemName.c_str()); + AStatsEvent_writeString(event, "subsystem_subname foo"); + AStatsEvent_writeInt64(event, /*count= */ i); + AStatsEvent_writeInt64(event, /*time_millis= */ i * 100); + AStatsEvent_build(event); size_t size; - uint8_t* buffer = stats_event_get_buffer(event, &size); + uint8_t* buffer = AStatsEvent_getBuffer(event, &size); android::util::StatsEventParcel p; // vector.assign() creates a copy, but this is inevitable unless // stats_event.h/c uses a vector as opposed to a buffer. p.buffer.assign(buffer, buffer + size); parcels.push_back(std::move(p)); - stats_event_release(event); + AStatsEvent_write(event); } resultReceiver->pullFinished(atomTag, /*success=*/true, parcels); return binder::Status::ok(); diff --git a/cmds/statsd/tests/storage/StorageManager_test.cpp b/cmds/statsd/tests/storage/StorageManager_test.cpp index b91e5a0ad3a1..27a86e4230d8 100644 --- a/cmds/statsd/tests/storage/StorageManager_test.cpp +++ b/cmds/statsd/tests/storage/StorageManager_test.cpp @@ -40,40 +40,12 @@ TEST(StorageManagerTest, TrainInfoReadWriteTest) { bool result; - result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName, - trainInfo.status, trainInfo.experimentIds); + result = StorageManager::writeTrainInfo(trainInfo); EXPECT_TRUE(result); InstallTrainInfo trainInfoResult; - result = StorageManager::readTrainInfo(trainInfoResult); - EXPECT_TRUE(result); - - EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode); - EXPECT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size()); - EXPECT_EQ(trainInfo.trainName, trainInfoResult.trainName); - EXPECT_EQ(trainInfo.status, trainInfoResult.status); - EXPECT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size()); - EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds); -} - -TEST(StorageManagerTest, TrainInfoReadWriteEmptyTrainNameTest) { - InstallTrainInfo trainInfo; - trainInfo.trainVersionCode = 12345; - trainInfo.trainName = ""; - trainInfo.status = 1; - const char* expIds = "test_ids"; - trainInfo.experimentIds.assign(expIds, expIds + strlen(expIds)); - - bool result; - - result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName, - trainInfo.status, trainInfo.experimentIds); - - EXPECT_TRUE(result); - - InstallTrainInfo trainInfoResult; - result = StorageManager::readTrainInfo(trainInfoResult); + result = StorageManager::readTrainInfo(trainInfo.trainName, trainInfoResult); EXPECT_TRUE(result); EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode); @@ -94,13 +66,12 @@ TEST(StorageManagerTest, TrainInfoReadWriteTrainNameSizeOneTest) { bool result; - result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName, - trainInfo.status, trainInfo.experimentIds); + result = StorageManager::writeTrainInfo(trainInfo); EXPECT_TRUE(result); InstallTrainInfo trainInfoResult; - result = StorageManager::readTrainInfo(trainInfoResult); + result = StorageManager::readTrainInfo(trainInfo.trainName, trainInfoResult); EXPECT_TRUE(result); EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode); diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java index 9cf1de93e344..ace13513e39d 100644 --- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java +++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java @@ -31,6 +31,13 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_RIGHT; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_UP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN; @@ -105,7 +112,14 @@ public final class AccessibilityGestureEvent implements Parcelable { GESTURE_3_FINGER_SWIPE_DOWN, GESTURE_3_FINGER_SWIPE_LEFT, GESTURE_3_FINGER_SWIPE_RIGHT, - GESTURE_3_FINGER_SWIPE_UP + GESTURE_3_FINGER_SWIPE_UP, + GESTURE_4_FINGER_DOUBLE_TAP, + GESTURE_4_FINGER_SINGLE_TAP, + GESTURE_4_FINGER_SWIPE_DOWN, + GESTURE_4_FINGER_SWIPE_LEFT, + GESTURE_4_FINGER_SWIPE_RIGHT, + GESTURE_4_FINGER_SWIPE_UP, + GESTURE_4_FINGER_TRIPLE_TAP }) @Retention(RetentionPolicy.SOURCE) public @interface GestureId {} @@ -165,6 +179,9 @@ public final class AccessibilityGestureEvent implements Parcelable { case GESTURE_3_FINGER_SINGLE_TAP: return "GESTURE_3_FINGER_SINGLE_TAP"; case GESTURE_3_FINGER_DOUBLE_TAP: return "GESTURE_3_FINGER_DOUBLE_TAP"; case GESTURE_3_FINGER_TRIPLE_TAP: return "GESTURE_3_FINGER_TRIPLE_TAP"; + case GESTURE_4_FINGER_SINGLE_TAP: return "GESTURE_4_FINGER_SINGLE_TAP"; + case GESTURE_4_FINGER_DOUBLE_TAP: return "GESTURE_4_FINGER_DOUBLE_TAP"; + case GESTURE_4_FINGER_TRIPLE_TAP: return "GESTURE_4_FINGER_TRIPLE_TAP"; case GESTURE_DOUBLE_TAP: return "GESTURE_DOUBLE_TAP"; case GESTURE_DOUBLE_TAP_AND_HOLD: return "GESTURE_DOUBLE_TAP_AND_HOLD"; case GESTURE_SWIPE_DOWN: return "GESTURE_SWIPE_DOWN"; @@ -191,6 +208,10 @@ public final class AccessibilityGestureEvent implements Parcelable { case GESTURE_3_FINGER_SWIPE_LEFT: return "GESTURE_3_FINGER_SWIPE_LEFT"; case GESTURE_3_FINGER_SWIPE_RIGHT: return "GESTURE_3_FINGER_SWIPE_RIGHT"; case GESTURE_3_FINGER_SWIPE_UP: return "GESTURE_3_FINGER_SWIPE_UP"; + case GESTURE_4_FINGER_SWIPE_DOWN: return "GESTURE_4_FINGER_SWIPE_DOWN"; + case GESTURE_4_FINGER_SWIPE_LEFT: return "GESTURE_4_FINGER_SWIPE_LEFT"; + case GESTURE_4_FINGER_SWIPE_RIGHT: return "GESTURE_4_FINGER_SWIPE_RIGHT"; + case GESTURE_4_FINGER_SWIPE_UP: return "GESTURE_4_FINGER_SWIPE_UP"; default: return Integer.toHexString(eventType); } } diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 2165fb35a0e5..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; @@ -388,6 +390,27 @@ public abstract class AccessibilityService extends Service { */ public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32; + /** The user has performed a four-finger swipe up gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_SWIPE_UP = 33; + + /** The user has performed a four-finger swipe down gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_SWIPE_DOWN = 34; + + /** The user has performed a four-finger swipe left gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_SWIPE_LEFT = 35; + + /** The user has performed a four-finger swipe right gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_SWIPE_RIGHT = 36; + + /** The user has performed a four-finger single tap gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_SINGLE_TAP = 37; + + /** The user has performed a four-finger double tap gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38; + + /** The user has performed a four-finger triple tap gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_TRIPLE_TAP = 39; + /** * The {@link Intent} that must be declared as handled by the service. */ @@ -564,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. @@ -1867,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 @@ -1886,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 = @@ -1896,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); @@ -2302,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/GestureDescription.java b/core/java/android/accessibilityservice/GestureDescription.java index 3b79d217c513..a821dadf4948 100644 --- a/core/java/android/accessibilityservice/GestureDescription.java +++ b/core/java/android/accessibilityservice/GestureDescription.java @@ -40,7 +40,7 @@ import java.util.List; */ public final class GestureDescription { /** Gestures may contain no more than this many strokes */ - private static final int MAX_STROKE_COUNT = 10; + private static final int MAX_STROKE_COUNT = 20; /** * Upper bound on total gesture duration. Nearly all gestures will be much shorter. @@ -194,7 +194,10 @@ public final class GestureDescription { public Builder addStroke(@NonNull StrokeDescription strokeDescription) { if (mStrokes.size() >= MAX_STROKE_COUNT) { throw new IllegalStateException( - "Attempting to add too many strokes to a gesture"); + "Attempting to add too many strokes to a gesture. Maximum is " + + MAX_STROKE_COUNT + + ", got " + + mStrokes.size()); } mStrokes.add(strokeDescription); 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/Activity.java b/core/java/android/app/Activity.java index f31c6148f89f..642f51b6bb63 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2880,13 +2880,14 @@ public class Activity extends ContextThemeWrapper * {@link #enterPictureInPictureMode(PictureInPictureParams)} at this time. For example, the * system will call this method when the activity is being put into the background, so the app * developer might want to switch an activity into PIP mode instead.</p> + * + * @return {@code true} if the activity received this callback regardless of if it acts on it + * or not. If {@code false}, the framework will assume the app hasn't been updated to leverage + * this callback and will in turn send a legacy callback of {@link #onUserLeaveHint()} for the + * app to enter picture-in-picture mode. */ - public void onPictureInPictureRequested() { - // Previous recommendation was for apps to enter picture-in-picture in onUserLeaveHint() - // which is sent after onPause(). This new method allows the system to request the app to - // go into picture-in-picture decoupling it from life cycle events. For backwards - // compatibility we schedule the life cycle events if the app didn't override this method. - mMainThread.schedulePauseAndReturnToCurrentState(mToken); + public boolean onPictureInPictureRequested() { + return false; } void dispatchMovedToDisplay(int displayId, Configuration config) { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 206c7710c12f..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; @@ -1472,7 +1473,7 @@ public class ActivityManager { dest.writeInt(1); dest.writeString(mLabel); } - if (mIcon == null) { + if (mIcon == null || mIcon.isRecycled()) { dest.writeInt(0); } else { dest.writeInt(1); @@ -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/ActivityThread.java b/core/java/android/app/ActivityThread.java index c901d2a29821..192156726984 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3772,7 +3772,15 @@ public final class ActivityThread extends ClientTransactionHandler { return; } - r.activity.onPictureInPictureRequested(); + final boolean receivedByApp = r.activity.onPictureInPictureRequested(); + if (!receivedByApp) { + // Previous recommendation was for apps to enter picture-in-picture in + // onUserLeavingHint() for cases such as the app being put into the background. For + // backwards compatibility with apps that are not using the newer + // onPictureInPictureRequested() callback, we schedule the life cycle events needed to + // trigger onUserLeavingHint(), then we return the activity to its previous state. + schedulePauseWithUserLeaveHintAndReturnToCurrentState(r); + } } /** @@ -3780,18 +3788,7 @@ public final class ActivityThread extends ClientTransactionHandler { * return to its previous state. This allows activities that rely on onUserLeaveHint instead of * onPictureInPictureRequested to enter picture-in-picture. */ - public void schedulePauseAndReturnToCurrentState(IBinder token) { - final ActivityClientRecord r = mActivities.get(token); - if (r == null) { - Log.w(TAG, "Activity to request pause with user leaving hint to no longer exists"); - return; - } - - if (r.mIsUserLeaving) { - // The activity is about to perform user leaving, so there's no need to cycle ourselves. - return; - } - + private void schedulePauseWithUserLeaveHintAndReturnToCurrentState(ActivityClientRecord r) { final int prevState = r.getLifecycleState(); if (prevState != ON_RESUME && prevState != ON_PAUSE) { return; @@ -4544,7 +4541,6 @@ public final class ActivityThread extends ClientTransactionHandler { if (r != null) { if (userLeaving) { performUserLeavingActivity(r); - r.mIsUserLeaving = false; } r.activity.mConfigChangeFlags |= configChanges; @@ -4559,7 +4555,6 @@ public final class ActivityThread extends ClientTransactionHandler { } final void performUserLeavingActivity(ActivityClientRecord r) { - r.mIsUserLeaving = true; mInstrumentation.callActivityOnPictureInPictureRequested(r.activity); mInstrumentation.callActivityOnUserLeaving(r.activity); } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 46f86690a753..fc37af9d213e 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -27,6 +27,8 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.usage.UsageStatsManager; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.Context; @@ -88,14 +90,31 @@ import java.util.function.Consumer; import java.util.function.Supplier; /** - * API for interacting with "application operation" tracking. + * AppOps are mappings of [package/uid, op-name] -> [mode]. The list of existing appops is defined + * by the system and cannot be amended by apps. Only system apps can change appop-modes. * - * <p>This API is not generally intended for third party application developers; most - * features are only available to system applications. + * <p>Beside a mode the system tracks when an op was {@link #noteOp noted}. The tracked data can + * only be read by system components. + * + * <p>Installed apps can usually only listen to changes and events on their own ops. E.g. + * {@link AppOpsCollector} allows to get a callback each time an app called {@link #noteOp} or + * {@link #startOp} for an op belonging to the app. */ @SystemService(Context.APP_OPS_SERVICE) public class AppOpsManager { /** + * This is a subtle behavior change to {@link #startWatchingMode}. + * + * Before this change the system called back for the switched op. After the change the system + * will call back for the actually requested op or all switched ops if no op is specified. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + public static final long CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE = 148180766L; + + /** * <p>App ops allows callers to:</p> * * <ul> @@ -142,6 +161,38 @@ public class AppOpsManager { static IBinder sClientId; + /** + * How many seconds we want for a drop in uid state from top to settle before applying it. + * + * <>Set a parameter to {@link android.provider.Settings.Global#APP_OPS_CONSTANTS} + * + * @hide + */ + @TestApi + public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time"; + + /** + * How many second we want for a drop in uid state from foreground to settle before applying it. + * + * <>Set a parameter to {@link android.provider.Settings.Global#APP_OPS_CONSTANTS} + * + * @hide + */ + @TestApi + public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = + "fg_service_state_settle_time"; + + /** + * How many seconds we want for a drop in uid state from background to settle before applying + * it. + * + * <>Set a parameter to {@link android.provider.Settings.Global#APP_OPS_CONSTANTS} + * + * @hide + */ + @TestApi + public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time"; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "HISTORICAL_MODE_" }, value = { @@ -2803,7 +2854,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no access * * @see #getLastAccessForegroundTime(int) * @see #getLastAccessBackgroundTime(int) @@ -2820,7 +2871,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no foreground access * * @see #getLastAccessTime(int) * @see #getLastAccessBackgroundTime(int) @@ -2838,7 +2889,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no background access * * @see #getLastAccessTime(int) * @see #getLastAccessForegroundTime(int) @@ -2855,7 +2906,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the last access event of {@code null} + * @return the last access event of {@code null} if there was no access */ private @Nullable NoteOpEvent getLastAccessEvent(@UidState int fromUidState, @UidState int toUidState, @OpFlags int flags) { @@ -2870,7 +2921,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no access * * @see #getLastAccessTime(int) * @see #getLastAccessForegroundTime(int) @@ -2893,7 +2944,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no rejection * * @see #getLastRejectForegroundTime(int) * @see #getLastRejectBackgroundTime(int) @@ -2910,7 +2961,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no foreground rejection * * @see #getLastRejectTime(int) * @see #getLastRejectBackgroundTime(int) @@ -2928,7 +2979,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no background rejection * * @see #getLastRejectTime(int) * @see #getLastRejectForegroundTime(int) @@ -2945,8 +2996,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * @return the last rejection event of {@code null} if there was no rejection * * @see #getLastRejectTime(int) * @see #getLastRejectForegroundTime(int) @@ -2965,7 +3015,8 @@ public class AppOpsManager { * @param toUidState The highest UID state for which to query (inclusive) * @param flags The op flags * - * @return the last access time (in milliseconds since epoch) or {@code -1} + * @return the last access time (in milliseconds since epoch) or {@code -1} if there was no + * rejection * * @see #getLastRejectTime(int) * @see #getLastRejectForegroundTime(int) @@ -2988,7 +3039,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no rejection * * @see #getLastForegroundDuration(int) * @see #getLastBackgroundDuration(int) @@ -3004,7 +3055,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no foreground rejection * * @see #getLastDuration(int) * @see #getLastBackgroundDuration(int) @@ -3021,7 +3072,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no background rejection * * @see #getLastDuration(int) * @see #getLastForegroundDuration(int) @@ -3040,7 +3091,7 @@ public class AppOpsManager { * @param toUidState The highest UID state for which to query (inclusive) * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no rejection * * @see #getLastDuration(int) * @see #getLastForegroundDuration(int) @@ -3064,7 +3115,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no proxy access * * @see #getLastForegroundProxyInfo(int) * @see #getLastBackgroundProxyInfo(int) @@ -3081,7 +3132,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no proxy access * * @see #getLastProxyInfo(int) * @see #getLastBackgroundProxyInfo(int) @@ -3099,7 +3150,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no proxy background access * * @see #getLastProxyInfo(int) * @see #getLastForegroundProxyInfo(int) @@ -3119,7 +3170,7 @@ public class AppOpsManager { * @param toUidState The highest UID state for which to query (inclusive) * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no proxy foreground access * * @see #getLastProxyInfo(int) * @see #getLastForegroundProxyInfo(int) @@ -3375,7 +3426,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no access * * @see #getLastAccessForegroundTime(int) * @see #getLastAccessBackgroundTime(int) @@ -3392,7 +3443,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no foreground access * * @see #getLastAccessTime(int) * @see #getLastAccessBackgroundTime(int) @@ -3410,7 +3461,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no background access * * @see #getLastAccessTime(int) * @see #getLastAccessForegroundTime(int) @@ -3427,7 +3478,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the last access event of {@code null} + * @return the last access event of {@code null} if there was no access */ private @Nullable NoteOpEvent getLastAccessEvent(@UidState int fromUidState, @UidState int toUidState, @OpFlags int flags) { @@ -3453,7 +3504,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last access time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no access * * @see #getLastAccessTime(int) * @see #getLastAccessForegroundTime(int) @@ -3489,7 +3540,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no rejection * * @see #getLastRejectForegroundTime(int) * @see #getLastRejectBackgroundTime(int) @@ -3506,7 +3557,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no foreground rejection * * @see #getLastRejectTime(int) * @see #getLastRejectBackgroundTime(int) @@ -3524,7 +3575,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no background rejection * * @see #getLastRejectTime(int) * @see #getLastRejectForegroundTime(int) @@ -3541,7 +3592,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the last reject event of {@code null} + * @return the last reject event of {@code null} if there was no rejection */ private @Nullable NoteOpEvent getLastRejectEvent(@UidState int fromUidState, @UidState int toUidState, @OpFlags int flags) { @@ -3567,7 +3618,7 @@ public class AppOpsManager { * @param flags The op flags * * @return the last rejection time (in milliseconds since epoch start (January 1, 1970 - * 00:00:00.000 GMT - Gregorian)) or {@code -1} + * 00:00:00.000 GMT - Gregorian)) or {@code -1} if there was no rejection * * @see #getLastRejectTime(int) * @see #getLastRejectForegroundTime(int) @@ -3611,7 +3662,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no access * * @see #getLastForegroundDuration(int) * @see #getLastBackgroundDuration(int) @@ -3627,7 +3678,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no foreground access * * @see #getLastDuration(int) * @see #getLastBackgroundDuration(int) @@ -3644,7 +3695,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no background access * * @see #getLastDuration(int) * @see #getLastForegroundDuration(int) @@ -3663,7 +3714,7 @@ public class AppOpsManager { * @param toUidState The highest UID state for which to query (inclusive) * @param flags The op flags * - * @return the duration in milliseconds or {@code -1} + * @return the duration in milliseconds or {@code -1} if there was no access * * @see #getLastDuration(int) * @see #getLastForegroundDuration(int) @@ -3738,7 +3789,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no proxy access * * @see #getLastForegroundProxyInfo(int) * @see #getLastBackgroundProxyInfo(int) @@ -3755,7 +3806,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no foreground proxy access * * @see #getLastProxyInfo(int) * @see #getLastBackgroundProxyInfo(int) @@ -3773,7 +3824,7 @@ public class AppOpsManager { * * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no background proxy access * * @see #getLastProxyInfo(int) * @see #getLastForegroundProxyInfo(int) @@ -3793,7 +3844,7 @@ public class AppOpsManager { * @param toUidState The highest UID state for which to query (inclusive) * @param flags The op flags * - * @return The proxy name or {@code null} + * @return The proxy info or {@code null} if there was no proxy access * * @see #getLastProxyInfo(int) * @see #getLastForegroundProxyInfo(int) @@ -3865,7 +3916,10 @@ public class AppOpsManager { } /** - * The features that have been used when checking the op + * The features that have been used when checking the op keyed by id of the feature. + * + * @see Context#createFeatureContext(String) + * @see #noteOp(String, int, String, String, String) */ @DataClass.Generated.Member public @NonNull Map<String,OpFeatureEntry> getFeatures() { @@ -6774,6 +6828,9 @@ public class AppOpsManager { * succeeds, the last execution time of the operation for this app will be updated to * the current time. * + * <p>If this is a check that is not preceding the protected operation, use + * {@link #unsafeCheckOp} instead. + * * @param op The operation to note. One of the OPSTR_* constants. * @param uid The user id of the application attempting to perform the operation. * @param packageName The name of the application attempting to perform the operation. @@ -6798,6 +6855,9 @@ public class AppOpsManager { * succeeds, the last execution time of the operation for this app will be updated to * the current time. * + * <p>If this is a check that is not preceding the protected operation, use + * {@link #unsafeCheckOp} instead. + * * @param op The operation to note. One of the OP_* constants. * @param uid The user id of the application attempting to perform the operation. * @param packageName The name of the application attempting to perform the operation. @@ -7757,9 +7817,38 @@ public class AppOpsManager { /** * Callback an app can choose to {@link #setNotedAppOpsCollector register} to monitor it's noted - * appops. + * appops. I.e. each time any app calls {@link #noteOp} or {@link #startOp} one of the callback + * methods of this object is called. * * <p><b>Only appops related to dangerous permissions are collected.</b> + * + * <pre> + * setNotedAppOpsCollector(new AppOpsCollector() { + * ArraySet<Pair<String, String>> opsNotedForThisProcess = new ArraySet<>(); + * + * private synchronized void addAccess(String op, String accessLocation) { + * // Ops are often noted when permission protected APIs were called. + * // In this case permissionToOp() allows to resolve the permission<->op + * opsNotedForThisProcess.add(new Pair(accessType, accessLocation)); + * } + * + * public void onNoted(SyncNotedAppOp op) { + * // Accesses is currently happening, hence stack trace describes location of access + * addAccess(op.getOp(), Arrays.toString(Thread.currentThread().getStackTrace())); + * } + * + * public void onSelfNoted(SyncNotedAppOp op) { + * onNoted(op); + * } + * + * public void onAsyncNoted(AsyncNotedAppOp asyncOp) { + * // Stack trace is not useful for async ops as accessed happened on different thread + * addAccess(asyncOp.getOp(), asyncOp.getMessage()); + * } + * }); + * </pre> + * + * @see #setNotedAppOpsCollector */ public abstract static class AppOpsCollector { /** Callback registered with the system. This will receive the async notes ops */ 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/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 4b1ba0278682..1d4a1acde434 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -48,6 +48,8 @@ interface INotificationManager void clearData(String pkg, int uid, boolean fromApp); void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, int displayId, @nullable ITransientNotificationCallback callback); void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId); + // TODO(b/144152069): Remove this after assessing impact on dogfood. + void enqueueTextOrCustomToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId, boolean isCustom); void cancelToast(String pkg, IBinder token); void finishToken(String pkg, IBinder token); @@ -86,6 +88,7 @@ interface INotificationManager void createNotificationChannelGroups(String pkg, in ParceledListSlice channelGroupList); void createNotificationChannels(String pkg, in ParceledListSlice channelsList); void createNotificationChannelsForPackage(String pkg, int uid, in ParceledListSlice channelsList); + ParceledListSlice getConversationsForPackage(String pkg, int uid); ParceledListSlice getNotificationChannelGroupsForPackage(String pkg, int uid, boolean includeDeleted); NotificationChannelGroup getNotificationChannelGroupForPackage(String groupId, String pkg, int uid); NotificationChannelGroup getPopulatedNotificationChannelGroupForPackage(String pkg, int uid, String groupId, boolean includeDeleted); @@ -94,7 +97,7 @@ interface INotificationManager NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId); NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, boolean returnParentIfNoConversationChannel, String conversationId); void createConversationNotificationChannelForPackage(String pkg, int uid, String triggeringKey, in NotificationChannel parentChannel, String conversationId); - NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted); + NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, String conversationId, boolean includeDeleted); void deleteNotificationChannel(String pkg, String channelId); void deleteConversationNotificationChannels(String pkg, int uid, String conversationId); ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId); @@ -111,6 +114,7 @@ interface INotificationManager int getAppsBypassingDndCount(int uid); ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int userId); boolean isPackagePaused(String pkg); + void deleteNotificationHistoryItem(String pkg, int uid, long postedTime); void silenceNotificationSound(); diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl index 168f782d02a6..5d5956e4dca4 100644 --- a/core/java/android/app/ITaskOrganizerController.aidl +++ b/core/java/android/app/ITaskOrganizerController.aidl @@ -31,8 +31,19 @@ interface ITaskOrganizerController { */ void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode); - /** Apply multiple WindowContainer operations at once. */ - void applyContainerTransaction(in WindowContainerTransaction t); + /** + * Apply multiple WindowContainer operations at once. + * @param organizer If non-null this transaction will use the synchronization + * scheme described in BLASTSyncEngine.java. The SurfaceControl transaction + * containing the effects of this WindowContainer transaction will be passed + * to the organizers Transaction ready callback. If null the transaction + * will apply with non particular synchronization constraints (other than + * it will all apply at once). + * @return If organizer was non-null returns an ID for the sync operation which will + * later be passed to transactionReady. This lets TaskOrganizer implementations + * differentiate overlapping sync operations. + */ + int applyContainerTransaction(in WindowContainerTransaction t, ITaskOrganizer organizer); /** Creates a persistent root task in WM for a particular windowing-mode. */ ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode); @@ -40,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/NotificationHistory.java b/core/java/android/app/NotificationHistory.java index 909a476f3c94..f26e62874347 100644 --- a/core/java/android/app/NotificationHistory.java +++ b/core/java/android/app/NotificationHistory.java @@ -346,6 +346,26 @@ public final class NotificationHistory implements Parcelable { } /** + * Removes an individual historical notification and regenerates the string pool + */ + public boolean removeNotificationFromWrite(String packageName, long postedTime) { + boolean removed = false; + for (int i = mNotificationsToWrite.size() - 1; i >= 0; i--) { + HistoricalNotification hn = mNotificationsToWrite.get(i); + if (packageName.equals(hn.getPackage()) + && postedTime == hn.getPostedTimeMs()) { + removed = true; + mNotificationsToWrite.remove(i); + } + } + if (removed) { + poolStringsFromNotifications(); + } + + return removed; + } + + /** * Gets pooled strings in order to write them to disk */ public @NonNull String[] getPooledStringsToWrite() { diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 6f1effd228e3..5f74d2e1dd57 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -598,7 +598,8 @@ public class WallpaperManager { * is not able to access the wallpaper. */ public Drawable getDrawable() { - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, mCmProxy); + final ColorManagementProxy cmProxy = getColorManagementProxy(); + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, cmProxy); if (bm != null) { Drawable dr = new BitmapDrawable(mContext.getResources(), bm); dr.setDither(false); @@ -829,7 +830,8 @@ public class WallpaperManager { * null pointer if these is none. */ public Drawable peekDrawable() { - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, mCmProxy); + final ColorManagementProxy cmProxy = getColorManagementProxy(); + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, cmProxy); if (bm != null) { Drawable dr = new BitmapDrawable(mContext.getResources(), bm); dr.setDither(false); @@ -853,7 +855,8 @@ public class WallpaperManager { */ @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public Drawable getFastDrawable() { - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, mCmProxy); + final ColorManagementProxy cmProxy = getColorManagementProxy(); + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, cmProxy); if (bm != null) { return new FastBitmapDrawable(bm); } @@ -869,7 +872,8 @@ public class WallpaperManager { */ @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public Drawable peekFastDrawable() { - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, mCmProxy); + final ColorManagementProxy cmProxy = getColorManagementProxy(); + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, cmProxy); if (bm != null) { return new FastBitmapDrawable(bm); } @@ -892,10 +896,11 @@ public class WallpaperManager { if (!shouldEnableWideColorGamut()) { return false; } - Bitmap bitmap = sGlobals.peekWallpaperBitmap(mContext, false, which, mCmProxy); + final ColorManagementProxy cmProxy = getColorManagementProxy(); + Bitmap bitmap = sGlobals.peekWallpaperBitmap(mContext, false, which, cmProxy); return bitmap != null && bitmap.getColorSpace() != null && bitmap.getColorSpace() != ColorSpace.get(ColorSpace.Named.SRGB) - && mCmProxy.isSupportedColorSpace(bitmap.getColorSpace()); + && cmProxy.isSupportedColorSpace(bitmap.getColorSpace()); } /** @@ -928,8 +933,8 @@ public class WallpaperManager { * @hide */ public Bitmap getBitmapAsUser(int userId, boolean hardware) { - return sGlobals.peekWallpaperBitmap( - mContext, true, FLAG_SYSTEM, userId, hardware, mCmProxy); + final ColorManagementProxy cmProxy = getColorManagementProxy(); + return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId, hardware, cmProxy); } /** @@ -2074,12 +2079,23 @@ public class WallpaperManager { } /** - * A private class to help Globals#getCurrentWallpaperLocked handle color management. + * Get the instance of {@link ColorManagementProxy}. + * + * @return instance of {@link ColorManagementProxy}. + * @hide */ - private static class ColorManagementProxy { + public ColorManagementProxy getColorManagementProxy() { + return mCmProxy; + } + + /** + * A hidden class to help {@link Globals#getCurrentWallpaperLocked} handle color management. + * @hide + */ + public static class ColorManagementProxy { private final Set<ColorSpace> mSupportedColorSpaces = new HashSet<>(); - ColorManagementProxy(Context context) { + public ColorManagementProxy(@NonNull Context context) { // Get a list of supported wide gamut color spaces. Display display = context.getDisplay(); if (display != null) { @@ -2087,9 +2103,14 @@ public class WallpaperManager { } } + @NonNull + public Set<ColorSpace> getSupportedColorSpaces() { + return mSupportedColorSpaces; + } + boolean isSupportedColorSpace(ColorSpace colorSpace) { return colorSpace != null && (colorSpace == ColorSpace.get(ColorSpace.Named.SRGB) - || mSupportedColorSpaces.contains(colorSpace)); + || getSupportedColorSpaces().contains(colorSpace)); } void doColorManagement(ImageDecoder decoder, ImageDecoder.ImageInfo info) { 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/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 759979100483..9cec514ec592 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -9462,16 +9462,6 @@ public class DevicePolicyManager { * {@link android.os.Build.VERSION_CODES#M} the app-op matching the permission is set to * {@link android.app.AppOpsManager#MODE_IGNORED}, but the permission stays granted. * - * NOTE: Starting from Android R, location-related permissions cannot be granted by the - * admin: Calling this method with {@link #PERMISSION_GRANT_STATE_GRANTED} for any of the - * following permissions will return false: - * - * <ul> - * <li>{@code ACCESS_FINE_LOCATION}</li> - * <li>{@code ACCESS_BACKGROUND_LOCATION}</li> - * <li>{@code ACCESS_COARSE_LOCATION}</li> - * </ul> - * * @param admin Which profile or device owner this request is associated with. * @param packageName The application to grant or revoke a permission to. * @param permission The permission to grant or revoke. 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/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java new file mode 100644 index 000000000000..5b367894ce69 --- /dev/null +++ b/core/java/android/app/compat/CompatChanges.java @@ -0,0 +1,108 @@ +/* + * 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.app.compat; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.compat.Compatibility; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; + +import com.android.internal.compat.IPlatformCompat; + +/** + * CompatChanges APIs - to be used by platform code only (including mainline + * modules). + * + * @hide + */ +@SystemApi +public final class CompatChanges { + private CompatChanges() {} + + /** + * Query if a given compatibility change is enabled for the current process. This method is + * intended to be called by code running inside a process of the affected app only. + * + * <p>If this method returns {@code true}, the calling code should implement the compatibility + * change, resulting in differing behaviour compared to earlier releases. If this method returns + * {@code false}, the calling code should behave as it did in earlier releases. + * + * @param changeId The ID of the compatibility change in question. + * @return {@code true} if the change is enabled for the current app. + */ + public static boolean isChangeEnabled(long changeId) { + return Compatibility.isChangeEnabled(changeId); + } + + /** + * Same as {@code #isChangeEnabled(long)}, except this version should be called on behalf of an + * app from a different process that's performing work for the app. + * + * <p> Note that this involves a binder call to the system server (unless running in the system + * server). If the binder call fails, a {@code RuntimeException} will be thrown. + * + * <p> Caller must have android.permission.READ_COMPAT_CHANGE_CONFIG permission. If it + * doesn't, a {@code RuntimeException} will be thrown. + * + * @param changeId The ID of the compatibility change in question. + * @param packageName The package name of the app in question. + * @param user The user that the operation is done for. + * @return {@code true} if the change is enabled for the current app. + */ + public static boolean isChangeEnabled(long changeId, @NonNull String packageName, + @NonNull UserHandle user) { + IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + try { + return platformCompat.isChangeEnabledByPackageName(changeId, packageName, + user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Same as {@code #isChangeEnabled(long)}, except this version should be called on behalf of an + * app from a different process that's performing work for the app. + * + * <p> Note that this involves a binder call to the system server (unless running in the system + * server). If the binder call fails, {@code RuntimeException} will be thrown. + * + * <p> Caller must have android.permission.READ_COMPAT_CHANGE_CONFIG permission. If it + * doesn't, a {@code RuntimeException} will be thrown. + * + * <p> Returns {@code true} if there are no installed packages for the required UID, or if the + * change is enabled for ALL of the installed packages associated with the provided UID. Please + * use a more specific API if you want a different behaviour for multi-package UIDs. + * + * @param changeId The ID of the compatibility change in question. + * @param uid The UID of the app in question. + * @return {@code true} if the change is enabled for the current app. + */ + public static boolean isChangeEnabled(long changeId, int uid) { + IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + try { + return platformCompat.isChangeEnabledByUid(changeId, uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl index de8f4700de2d..5ead0c90c80e 100644 --- a/core/java/android/app/timedetector/ITimeDetectorService.aidl +++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl @@ -18,7 +18,7 @@ package android.app.timedetector; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; -import android.app.timedetector.PhoneTimeSuggestion; +import android.app.timedetector.TelephonyTimeSuggestion; /** * System private API to communicate with time detector service. @@ -34,7 +34,7 @@ import android.app.timedetector.PhoneTimeSuggestion; * {@hide} */ interface ITimeDetectorService { - void suggestPhoneTime(in PhoneTimeSuggestion timeSuggestion); void suggestManualTime(in ManualTimeSuggestion timeSuggestion); void suggestNetworkTime(in NetworkTimeSuggestion timeSuggestion); + void suggestTelephonyTime(in TelephonyTimeSuggestion timeSuggestion); } diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.aidl b/core/java/android/app/timedetector/TelephonyTimeSuggestion.aidl index f5e240549a9a..d9b038692c62 100644 --- a/core/java/android/app/timedetector/PhoneTimeSuggestion.aidl +++ b/core/java/android/app/timedetector/TelephonyTimeSuggestion.aidl @@ -16,4 +16,4 @@ package android.app.timedetector; -parcelable PhoneTimeSuggestion; +parcelable TelephonyTimeSuggestion; diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java index 0133a4472686..c0e8957727cf 100644 --- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java +++ b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java @@ -50,17 +50,17 @@ import java.util.Objects; * * @hide */ -public final class PhoneTimeSuggestion implements Parcelable { +public final class TelephonyTimeSuggestion implements Parcelable { /** @hide */ - public static final @NonNull Parcelable.Creator<PhoneTimeSuggestion> CREATOR = - new Parcelable.Creator<PhoneTimeSuggestion>() { - public PhoneTimeSuggestion createFromParcel(Parcel in) { - return PhoneTimeSuggestion.createFromParcel(in); + public static final @NonNull Parcelable.Creator<TelephonyTimeSuggestion> CREATOR = + new Parcelable.Creator<TelephonyTimeSuggestion>() { + public TelephonyTimeSuggestion createFromParcel(Parcel in) { + return TelephonyTimeSuggestion.createFromParcel(in); } - public PhoneTimeSuggestion[] newArray(int size) { - return new PhoneTimeSuggestion[size]; + public TelephonyTimeSuggestion[] newArray(int size) { + return new TelephonyTimeSuggestion[size]; } }; @@ -68,15 +68,15 @@ public final class PhoneTimeSuggestion implements Parcelable { @Nullable private final TimestampedValue<Long> mUtcTime; @Nullable private ArrayList<String> mDebugInfo; - private PhoneTimeSuggestion(Builder builder) { + private TelephonyTimeSuggestion(Builder builder) { mSlotIndex = builder.mSlotIndex; mUtcTime = builder.mUtcTime; mDebugInfo = builder.mDebugInfo != null ? new ArrayList<>(builder.mDebugInfo) : null; } - private static PhoneTimeSuggestion createFromParcel(Parcel in) { + private static TelephonyTimeSuggestion createFromParcel(Parcel in) { int slotIndex = in.readInt(); - PhoneTimeSuggestion suggestion = new PhoneTimeSuggestion.Builder(slotIndex) + TelephonyTimeSuggestion suggestion = new TelephonyTimeSuggestion.Builder(slotIndex) .setUtcTime(in.readParcelable(null /* classLoader */)) .build(); @SuppressWarnings("unchecked") @@ -102,7 +102,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Returns an identifier for the source of this suggestion. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code slotIndex}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code slotIndex}. */ public int getSlotIndex() { return mSlotIndex; @@ -111,7 +111,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Returns the suggested time or {@code null} if there isn't one. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code utcTime}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code utcTime}. */ @Nullable public TimestampedValue<Long> getUtcTime() { @@ -121,7 +121,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Returns debug metadata for the suggestion. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}. */ @NonNull public List<String> getDebugInfo() { @@ -132,7 +132,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Associates information with the instance that can be useful for debugging / logging. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}. */ public void addDebugInfo(@NonNull String debugInfo) { if (mDebugInfo == null) { @@ -144,7 +144,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Associates information with the instance that can be useful for debugging / logging. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}. */ public void addDebugInfo(@NonNull List<String> debugInfo) { if (mDebugInfo == null) { @@ -161,7 +161,7 @@ public final class PhoneTimeSuggestion implements Parcelable { if (o == null || getClass() != o.getClass()) { return false; } - PhoneTimeSuggestion that = (PhoneTimeSuggestion) o; + TelephonyTimeSuggestion that = (TelephonyTimeSuggestion) o; return mSlotIndex == that.mSlotIndex && Objects.equals(mUtcTime, that.mUtcTime); } @@ -173,7 +173,7 @@ public final class PhoneTimeSuggestion implements Parcelable { @Override public String toString() { - return "PhoneTimeSuggestion{" + return "TelephonyTimeSuggestion{" + "mSlotIndex='" + mSlotIndex + '\'' + ", mUtcTime=" + mUtcTime + ", mDebugInfo=" + mDebugInfo @@ -181,7 +181,7 @@ public final class PhoneTimeSuggestion implements Parcelable { } /** - * Builds {@link PhoneTimeSuggestion} instances. + * Builds {@link TelephonyTimeSuggestion} instances. * * @hide */ @@ -193,7 +193,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Creates a builder with the specified {@code slotIndex}. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code slotIndex}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code slotIndex}. */ public Builder(int slotIndex) { mSlotIndex = slotIndex; @@ -202,7 +202,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Returns the builder for call chaining. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code utcTime}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code utcTime}. */ @NonNull public Builder setUtcTime(@Nullable TimestampedValue<Long> utcTime) { @@ -218,7 +218,7 @@ public final class PhoneTimeSuggestion implements Parcelable { /** * Returns the builder for call chaining. * - * <p>See {@link PhoneTimeSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeSuggestion} for more information about {@code debugInfo}. */ @NonNull public Builder addDebugInfo(@NonNull String debugInfo) { @@ -229,10 +229,10 @@ public final class PhoneTimeSuggestion implements Parcelable { return this; } - /** Returns the {@link PhoneTimeSuggestion}. */ + /** Returns the {@link TelephonyTimeSuggestion}. */ @NonNull - public PhoneTimeSuggestion build() { - return new PhoneTimeSuggestion(this); + public TelephonyTimeSuggestion build() { + return new TelephonyTimeSuggestion(this); } } } diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java index df4f513ab095..84ad495da09b 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -45,12 +45,12 @@ public interface TimeDetector { } /** - * Suggests the current phone-signal derived time to the detector. The detector may ignore the - * signal if better signals are available such as those that come from more reliable sources or - * were determined more recently. + * Suggests a telephony-signal derived time to the detector. The detector may ignore the signal + * if better signals are available such as those that come from more reliable sources or were + * determined more recently. */ - @RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE) - void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion); + @RequiresPermission(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE) + void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion); /** * Suggests the user's manually entered current time to the detector. diff --git a/core/java/android/app/timedetector/TimeDetectorImpl.java b/core/java/android/app/timedetector/TimeDetectorImpl.java index 1683817740c3..c1d66672f9d2 100644 --- a/core/java/android/app/timedetector/TimeDetectorImpl.java +++ b/core/java/android/app/timedetector/TimeDetectorImpl.java @@ -40,12 +40,12 @@ public final class TimeDetectorImpl implements TimeDetector { } @Override - public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) { + public void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion) { if (DEBUG) { - Log.d(TAG, "suggestPhoneTime called: " + timeSuggestion); + Log.d(TAG, "suggestTelephonyTime called: " + timeSuggestion); } try { - mITimeDetectorService.suggestPhoneTime(timeSuggestion); + mITimeDetectorService.suggestTelephonyTime(timeSuggestion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl index df6438319ab7..b06f4b8ecdb8 100644 --- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl +++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl @@ -17,7 +17,7 @@ package android.app.timezonedetector; import android.app.timezonedetector.ManualTimeZoneSuggestion; -import android.app.timezonedetector.PhoneTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; /** * System private API to communicate with time zone detector service. @@ -34,5 +34,5 @@ import android.app.timezonedetector.PhoneTimeZoneSuggestion; */ interface ITimeZoneDetectorService { void suggestManualTimeZone(in ManualTimeZoneSuggestion timeZoneSuggestion); - void suggestPhoneTimeZone(in PhoneTimeZoneSuggestion timeZoneSuggestion); + void suggestTelephonyTimeZone(in TelephonyTimeZoneSuggestion timeZoneSuggestion); } diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.aidl index 3ad903bb5949..b57ad20734be 100644 --- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl +++ b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.aidl @@ -16,4 +16,4 @@ package android.app.timezonedetector; -parcelable PhoneTimeZoneSuggestion; +parcelable TelephonyTimeZoneSuggestion; diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java index 9147b4462492..150c01d59899 100644 --- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java +++ b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java @@ -56,18 +56,18 @@ import java.util.Objects; * * @hide */ -public final class PhoneTimeZoneSuggestion implements Parcelable { +public final class TelephonyTimeZoneSuggestion implements Parcelable { /** @hide */ @NonNull - public static final Creator<PhoneTimeZoneSuggestion> CREATOR = - new Creator<PhoneTimeZoneSuggestion>() { - public PhoneTimeZoneSuggestion createFromParcel(Parcel in) { - return PhoneTimeZoneSuggestion.createFromParcel(in); + public static final Creator<TelephonyTimeZoneSuggestion> CREATOR = + new Creator<TelephonyTimeZoneSuggestion>() { + public TelephonyTimeZoneSuggestion createFromParcel(Parcel in) { + return TelephonyTimeZoneSuggestion.createFromParcel(in); } - public PhoneTimeZoneSuggestion[] newArray(int size) { - return new PhoneTimeZoneSuggestion[size]; + public TelephonyTimeZoneSuggestion[] newArray(int size) { + return new TelephonyTimeZoneSuggestion[size]; } }; @@ -76,7 +76,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { * the same {@code slotIndex}. */ @NonNull - public static PhoneTimeZoneSuggestion createEmptySuggestion( + public static TelephonyTimeZoneSuggestion createEmptySuggestion( int slotIndex, @NonNull String debugInfo) { return new Builder(slotIndex).addDebugInfo(debugInfo).build(); } @@ -144,7 +144,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { @Quality private final int mQuality; @Nullable private List<String> mDebugInfo; - private PhoneTimeZoneSuggestion(Builder builder) { + private TelephonyTimeZoneSuggestion(Builder builder) { mSlotIndex = builder.mSlotIndex; mZoneId = builder.mZoneId; mMatchType = builder.mMatchType; @@ -153,15 +153,16 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { } @SuppressWarnings("unchecked") - private static PhoneTimeZoneSuggestion createFromParcel(Parcel in) { + private static TelephonyTimeZoneSuggestion createFromParcel(Parcel in) { // Use the Builder so we get validation during build(). int slotIndex = in.readInt(); - PhoneTimeZoneSuggestion suggestion = new Builder(slotIndex) + TelephonyTimeZoneSuggestion suggestion = new Builder(slotIndex) .setZoneId(in.readString()) .setMatchType(in.readInt()) .setQuality(in.readInt()) .build(); - List<String> debugInfo = in.readArrayList(PhoneTimeZoneSuggestion.class.getClassLoader()); + List<String> debugInfo = + in.readArrayList(TelephonyTimeZoneSuggestion.class.getClassLoader()); if (debugInfo != null) { suggestion.addDebugInfo(debugInfo); } @@ -185,7 +186,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Returns an identifier for the source of this suggestion. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code slotIndex}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code slotIndex}. */ public int getSlotIndex() { return mSlotIndex; @@ -195,7 +196,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { * Returns the suggested time zone Olson ID, e.g. "America/Los_Angeles". {@code null} means that * the caller is no longer sure what the current time zone is. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code zoneId}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code zoneId}. */ @Nullable public String getZoneId() { @@ -206,7 +207,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { * Returns information about how the suggestion was determined which could be used to rank * suggestions when several are available from different sources. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code matchType}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code matchType}. */ @MatchType public int getMatchType() { @@ -216,7 +217,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Returns information about the likelihood of the suggested zone being correct. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code quality}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code quality}. */ @Quality public int getQuality() { @@ -226,7 +227,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Returns debug metadata for the suggestion. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code debugInfo}. */ @NonNull public List<String> getDebugInfo() { @@ -237,7 +238,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Associates information with the instance that can be useful for debugging / logging. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code debugInfo}. */ public void addDebugInfo(@NonNull String debugInfo) { if (mDebugInfo == null) { @@ -249,7 +250,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Associates information with the instance that can be useful for debugging / logging. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code debugInfo}. */ public void addDebugInfo(@NonNull List<String> debugInfo) { if (mDebugInfo == null) { @@ -266,7 +267,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { if (o == null || getClass() != o.getClass()) { return false; } - PhoneTimeZoneSuggestion that = (PhoneTimeZoneSuggestion) o; + TelephonyTimeZoneSuggestion that = (TelephonyTimeZoneSuggestion) o; return mSlotIndex == that.mSlotIndex && mMatchType == that.mMatchType && mQuality == that.mQuality @@ -280,7 +281,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { @Override public String toString() { - return "PhoneTimeZoneSuggestion{" + return "TelephonyTimeZoneSuggestion{" + "mSlotIndex=" + mSlotIndex + ", mZoneId='" + mZoneId + '\'' + ", mMatchType=" + mMatchType @@ -290,7 +291,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { } /** - * Builds {@link PhoneTimeZoneSuggestion} instances. + * Builds {@link TelephonyTimeZoneSuggestion} instances. * * @hide */ @@ -304,7 +305,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Creates a builder with the specified {@code slotIndex}. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code slotIndex}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code slotIndex}. */ public Builder(int slotIndex) { mSlotIndex = slotIndex; @@ -313,7 +314,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Returns the builder for call chaining. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code zoneId}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code zoneId}. */ @NonNull public Builder setZoneId(@Nullable String zoneId) { @@ -324,7 +325,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Returns the builder for call chaining. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code matchType}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code matchType}. */ @NonNull public Builder setMatchType(@MatchType int matchType) { @@ -335,7 +336,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Returns the builder for call chaining. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code quality}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code quality}. */ @NonNull public Builder setQuality(@Quality int quality) { @@ -346,7 +347,7 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { /** * Returns the builder for call chaining. * - * <p>See {@link PhoneTimeZoneSuggestion} for more information about {@code debugInfo}. + * <p>See {@link TelephonyTimeZoneSuggestion} for more information about {@code debugInfo}. */ @NonNull public Builder addDebugInfo(@NonNull String debugInfo) { @@ -384,11 +385,11 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { } } - /** Returns the {@link PhoneTimeZoneSuggestion}. */ + /** Returns the {@link TelephonyTimeZoneSuggestion}. */ @NonNull - public PhoneTimeZoneSuggestion build() { + public TelephonyTimeZoneSuggestion build() { validate(); - return new PhoneTimeZoneSuggestion(this); + return new TelephonyTimeZoneSuggestion(this); } } } diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java index 6a3953eccb2d..20761ad2d447 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -47,8 +47,8 @@ public interface TimeZoneDetector { * * @hide */ - @RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE) - void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion); + @RequiresPermission(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE) + void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion timeZoneSuggestion); /** * Suggests the current time zone, determined for the user's manually information, to the diff --git a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java index 27b8374db172..0ada88500193 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java @@ -40,12 +40,12 @@ public final class TimeZoneDetectorImpl implements TimeZoneDetector { } @Override - public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) { + public void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion timeZoneSuggestion) { if (DEBUG) { - Log.d(TAG, "suggestPhoneTimeZone called: " + timeZoneSuggestion); + Log.d(TAG, "suggestTelephonyTimeZone called: " + timeZoneSuggestion); } try { - mITimeZoneDetectorService.suggestPhoneTimeZone(timeZoneSuggestion); + mITimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 2e93d43f7c15..01ccb86fb129 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1861,15 +1861,19 @@ public final class BluetoothAdapter { } /** - * Connects all enabled and supported bluetooth profiles between the local and remote device + * Connects all enabled and supported bluetooth profiles between the local and remote device. + * Connection is asynchronous and you should listen to each profile's broadcast intent + * ACTION_CONNECTION_STATE_CHANGED to verify whether connection was successful. For example, + * to verify a2dp is connected, you would listen for + * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED} * * @param device is the remote device with which to connect these profiles - * @return true if all profiles successfully connected, false if an error occurred + * @return true if message sent to try to connect all profiles, false if an error occurred * * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean connectAllEnabledProfiles(@NonNull BluetoothDevice device) { try { mServiceLock.readLock().lock(); @@ -1886,15 +1890,19 @@ public final class BluetoothAdapter { } /** - * Disconnects all enabled and supported bluetooth profiles between the local and remote device + * Disconnects all enabled and supported bluetooth profiles between the local and remote device. + * Disconnection is asynchronous and you should listen to each profile's broadcast intent + * ACTION_CONNECTION_STATE_CHANGED to verify whether disconnection was successful. For example, + * to verify a2dp is disconnected, you would listen for + * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED} * * @param device is the remote device with which to disconnect these profiles - * @return true if all profiles successfully disconnected, false if an error occurred + * @return true if message sent to try to disconnect all profiles, false if an error occurred * * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean disconnectAllEnabledProfiles(@NonNull BluetoothDevice device) { try { mServiceLock.readLock().lock(); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index f32a4ab43357..0e0161ff4e9f 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -700,6 +700,27 @@ public abstract class ContentResolver implements ContentInterface { /** @hide */ public static final String REMOTE_CALLBACK_RESULT = "result"; + /** + * How long we wait for an attached process to publish its content providers + * before we decide it must be hung. + * @hide + */ + public static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS = 10 * 1000; + + /** + * How long we wait for an provider to be published. Should be longer than + * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS}. + * @hide + */ + public static final int CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS = + CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS + 10 * 1000; + + // Should be >= {@link #CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS}, because that's how + // long ActivityManagerService is giving a content provider to get published if a new process + // needs to be started for that. + private static final int GET_TYPE_TIMEOUT_MILLIS = + CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS + 5 * 1000; + public ContentResolver(@Nullable Context context) { this(context, null); } @@ -849,8 +870,6 @@ public abstract class ContentResolver implements ContentInterface { } } - private static final int GET_TYPE_TIMEOUT_MILLIS = 3000; - private static class GetTypeResultListener implements RemoteCallback.OnResultListener { @GuardedBy("this") public boolean done; diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java index 351edc92888d..cd5117be6123 100644 --- a/core/java/android/content/integrity/AppInstallMetadata.java +++ b/core/java/android/content/integrity/AppInstallMetadata.java @@ -18,6 +18,7 @@ package android.content.integrity; import android.annotation.NonNull; +import java.util.List; import java.util.Objects; /** @@ -33,18 +34,18 @@ import java.util.Objects; public final class AppInstallMetadata { private final String mPackageName; // Raw string encoding for the SHA-256 hash of the certificate of the app. - private final String mAppCertificate; + private final List<String> mAppCertificates; private final String mInstallerName; // Raw string encoding for the SHA-256 hash of the certificate of the installer. - private final String mInstallerCertificate; + private final List<String> mInstallerCertificates; private final long mVersionCode; private final boolean mIsPreInstalled; private AppInstallMetadata(Builder builder) { this.mPackageName = builder.mPackageName; - this.mAppCertificate = builder.mAppCertificate; + this.mAppCertificates = builder.mAppCertificates; this.mInstallerName = builder.mInstallerName; - this.mInstallerCertificate = builder.mInstallerCertificate; + this.mInstallerCertificates = builder.mInstallerCertificates; this.mVersionCode = builder.mVersionCode; this.mIsPreInstalled = builder.mIsPreInstalled; } @@ -55,8 +56,8 @@ public final class AppInstallMetadata { } @NonNull - public String getAppCertificate() { - return mAppCertificate; + public List<String> getAppCertificates() { + return mAppCertificates; } @NonNull @@ -65,8 +66,8 @@ public final class AppInstallMetadata { } @NonNull - public String getInstallerCertificate() { - return mInstallerCertificate; + public List<String> getInstallerCertificates() { + return mInstallerCertificates; } /** @see AppInstallMetadata.Builder#setVersionCode(long) */ @@ -82,12 +83,12 @@ public final class AppInstallMetadata { @Override public String toString() { return String.format( - "AppInstallMetadata { PackageName = %s, AppCert = %s, InstallerName = %s," - + " InstallerCert = %s, VersionCode = %d, PreInstalled = %b }", + "AppInstallMetadata { PackageName = %s, AppCerts = %s, InstallerName = %s," + + " InstallerCerts = %s, VersionCode = %d, PreInstalled = %b }", mPackageName, - mAppCertificate, + mAppCertificates, mInstallerName == null ? "null" : mInstallerName, - mInstallerCertificate == null ? "null" : mInstallerCertificate, + mInstallerCertificates == null ? "null" : mInstallerCertificates, mVersionCode, mIsPreInstalled); } @@ -95,9 +96,9 @@ public final class AppInstallMetadata { /** Builder class for constructing {@link AppInstallMetadata} objects. */ public static final class Builder { private String mPackageName; - private String mAppCertificate; + private List<String> mAppCertificates; private String mInstallerName; - private String mInstallerCertificate; + private List<String> mInstallerCertificates; private long mVersionCode; private boolean mIsPreInstalled; @@ -118,11 +119,11 @@ public final class AppInstallMetadata { * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate * of the app. * - * @see AppInstallMetadata#getAppCertificate() + * @see AppInstallMetadata#getAppCertificates() */ @NonNull - public Builder setAppCertificate(@NonNull String appCertificate) { - this.mAppCertificate = Objects.requireNonNull(appCertificate); + public Builder setAppCertificates(@NonNull List<String> appCertificates) { + this.mAppCertificates = Objects.requireNonNull(appCertificates); return this; } @@ -143,11 +144,11 @@ public final class AppInstallMetadata { * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate * of the installer. * - * @see AppInstallMetadata#getInstallerCertificate() + * @see AppInstallMetadata#getInstallerCertificates() */ @NonNull - public Builder setInstallerCertificate(@NonNull String installerCertificate) { - this.mInstallerCertificate = Objects.requireNonNull(installerCertificate); + public Builder setInstallerCertificates(@NonNull List<String> installerCertificates) { + this.mInstallerCertificates = Objects.requireNonNull(installerCertificates); return this; } @@ -181,7 +182,7 @@ public final class AppInstallMetadata { @NonNull public AppInstallMetadata build() { Objects.requireNonNull(mPackageName); - Objects.requireNonNull(mAppCertificate); + Objects.requireNonNull(mAppCertificates); return new AppInstallMetadata(this); } } diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java index 439d53661b8e..42459779e212 100644 --- a/core/java/android/content/integrity/AtomicFormula.java +++ b/core/java/android/content/integrity/AtomicFormula.java @@ -30,6 +30,8 @@ import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -332,9 +334,12 @@ public abstract class AtomicFormula extends IntegrityFormula { * Constructs a new {@link StringAtomicFormula} together with handling the necessary * hashing for the given key. * - * <p> The value will be hashed with SHA256 and the hex digest will be computed; for - * all cases except when the key is PACKAGE_NAME or INSTALLER_NAME and the value - * is less than 33 characters. + * <p> The value will be automatically hashed with SHA256 and the hex digest will be + * computed when the key is PACKAGE_NAME or INSTALLER_NAME and the value is more than 32 + * characters. + * + * <p> The APP_CERTIFICATES and INSTALLER_CERTIFICATES are always delivered in hashed + * form. So the isHashedValue is set to true by default. * * @throws IllegalArgumentException if {@code key} cannot be used with string value. */ @@ -348,7 +353,10 @@ public abstract class AtomicFormula extends IntegrityFormula { String.format( "Key %s cannot be used with StringAtomicFormula", keyToString(key))); mValue = hashValue(key, value); - mIsHashedValue = !mValue.equals(value); + mIsHashedValue = + key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE + ? true + : !mValue.equals(value); } StringAtomicFormula(Parcel in) { @@ -381,7 +389,7 @@ public abstract class AtomicFormula extends IntegrityFormula { if (mValue == null || mIsHashedValue == null) { return false; } - return getStringMetadataValue(appInstallMetadata, getKey()).equals(mValue); + return getMetadataValue(appInstallMetadata, getKey()).contains(mValue); } @Override @@ -442,17 +450,17 @@ public abstract class AtomicFormula extends IntegrityFormula { return mIsHashedValue; } - private static String getStringMetadataValue( + private static List<String> getMetadataValue( AppInstallMetadata appInstallMetadata, int key) { switch (key) { case AtomicFormula.PACKAGE_NAME: - return appInstallMetadata.getPackageName(); + return Collections.singletonList(appInstallMetadata.getPackageName()); case AtomicFormula.APP_CERTIFICATE: - return appInstallMetadata.getAppCertificate(); + return appInstallMetadata.getAppCertificates(); case AtomicFormula.INSTALLER_CERTIFICATE: - return appInstallMetadata.getInstallerCertificate(); + return appInstallMetadata.getInstallerCertificates(); case AtomicFormula.INSTALLER_NAME: - return appInstallMetadata.getInstallerName(); + return Collections.singletonList(appInstallMetadata.getInstallerName()); default: throw new IllegalStateException( "Unexpected key in StringAtomicFormula: " + key); diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index de153d00b48b..edc20d9f65ad 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -323,6 +323,7 @@ public class CrossProfileApps { */ @RequiresPermission( allOf={android.Manifest.permission.MANAGE_APP_OPS_MODES, + android.Manifest.permission.UPDATE_APP_OPS_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setInteractAcrossProfilesAppOp(@NonNull String packageName, @Mode int newMode) { try { @@ -363,6 +364,7 @@ public class CrossProfileApps { */ @RequiresPermission( allOf={android.Manifest.permission.MANAGE_APP_OPS_MODES, + android.Manifest.permission.UPDATE_APP_OPS_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void resetInteractAcrossProfilesAppOps( @NonNull Collection<String> previousCrossProfilePackages, diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 50bb3c721763..38a9ac4a0d05 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -20,11 +20,13 @@ import android.app.IApplicationThread; import android.content.ComponentName; import android.content.Intent; import android.content.IntentSender; +import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IOnAppsChangedListener; import android.content.pm.LauncherApps; import android.content.pm.IPackageInstallerCallback; +import android.content.pm.IShortcutChangeCallback; import android.content.pm.PackageInstaller; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; @@ -66,7 +68,8 @@ interface ILauncherApps { in UserHandle user); ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName, - in List shortcutIds, in ComponentName componentName, int flags, in UserHandle user); + in List shortcutIds, in List<LocusId> locusIds, in ComponentName componentName, + int flags, in UserHandle user); void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds, in UserHandle user); boolean startShortcut(String callingPackage, String packageName, String id, @@ -89,4 +92,15 @@ interface ILauncherApps { void registerPackageInstallerCallback(String callingPackage, in IPackageInstallerCallback callback); ParceledListSlice getAllSessions(String callingPackage); + + void registerShortcutChangeCallback(String callingPackage, long changedSince, + String packageName, in List shortcutIds, in List<LocusId> locusIds, + in ComponentName componentName, int flags, in IShortcutChangeCallback callback, + int callbackId); + void unregisterShortcutChangeCallback(String callingPackage, int callbackId); + + void cacheShortcuts(String callingPackage, String packageName, in List<String> shortcutIds, + in UserHandle user); + void uncacheShortcuts(String callingPackage, String packageName, in List<String> shortcutIds, + in UserHandle user); } diff --git a/core/java/android/content/pm/IShortcutChangeCallback.aidl b/core/java/android/content/pm/IShortcutChangeCallback.aidl new file mode 100644 index 000000000000..fed4e4a796f9 --- /dev/null +++ b/core/java/android/content/pm/IShortcutChangeCallback.aidl @@ -0,0 +1,37 @@ +/** + * 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.content.pm; + +import android.content.pm.ParceledListSlice; +import android.content.pm.ShortcutInfo; +import android.os.UserHandle; + +import java.util.List; + +/** + * Interface for LauncherApps#ShortcutChangeCallbackProxy. + * + * @hide + */ +oneway interface IShortcutChangeCallback +{ + void onShortcutsAddedOrUpdated(String packageName, in List<ShortcutInfo> shortcuts, + in UserHandle user); + + void onShortcutsRemoved(String packageName, in List<ShortcutInfo> shortcuts, + in UserHandle user); +}
\ No newline at end of file diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl index 747e929eda98..9e85fc301a0c 100644 --- a/core/java/android/content/pm/IShortcutService.aidl +++ b/core/java/android/content/pm/IShortcutService.aidl @@ -29,10 +29,6 @@ interface IShortcutService { boolean setDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList, int userId); - ParceledListSlice getDynamicShortcuts(String packageName, int userId); - - ParceledListSlice getManifestShortcuts(String packageName, int userId); - boolean addDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList, int userId); @@ -40,8 +36,6 @@ interface IShortcutService { void removeAllDynamicShortcuts(String packageName, int userId); - ParceledListSlice getPinnedShortcuts(String packageName, int userId); - boolean updateShortcuts(String packageName, in ParceledListSlice shortcuts, int userId); boolean requestPinShortcut(String packageName, in ShortcutInfo shortcut, diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index cea0b6b5f3ad..d2532783f47c 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -20,6 +20,7 @@ import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; @@ -34,6 +35,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.LocusId; import android.content.pm.PackageInstaller.SessionCallback; import android.content.pm.PackageInstaller.SessionCallbackDelegate; import android.content.pm.PackageInstaller.SessionInfo; @@ -61,15 +63,21 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.DisplayMetrics; import android.util.Log; +import android.util.Pair; + +import com.android.internal.util.function.pooled.PooledLambda; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.Executor; @@ -152,6 +160,9 @@ public class LauncherApps { private final List<CallbackMessageHandler> mCallbacks = new ArrayList<>(); private final List<SessionCallbackDelegate> mDelegates = new ArrayList<>(); + private final Map<Integer, Pair<Executor, ShortcutChangeCallback>> + mShortcutChangeCallbacks = new HashMap<>(); + /** * Callbacks for package changes to this and related managed profiles. */ @@ -406,6 +417,9 @@ public class LauncherApps { List<String> mShortcutIds; @Nullable + List<LocusId> mLocusIds; + + @Nullable ComponentName mActivity; @QueryFlags @@ -442,6 +456,19 @@ public class LauncherApps { } /** + * If non-null, return only the specified shortcuts by locus ID. When setting this field, + * a package name must also be set with {@link #setPackage}. + * + * @hide + */ + @SystemApi + @NonNull + public ShortcutQuery setLocusIds(@Nullable List<LocusId> locusIds) { + mLocusIds = locusIds; + return this; + } + + /** * If non-null, returns only shortcuts associated with the activity; i.e. * {@link ShortcutInfo}s whose {@link ShortcutInfo#getActivity()} are equal * to {@code activity}. @@ -469,6 +496,95 @@ public class LauncherApps { } } + /** + * Callbacks for shortcut changes to this and related managed profiles. + * + * @hide + */ + public interface ShortcutChangeCallback { + /** + * Indicates that one or more shortcuts, that match the {@link ShortcutQuery} used to + * register this callback, have been added or updated. + * @see LauncherApps#registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery) + * + * <p>Only the applications that are allowed to access the shortcut information, + * as defined in {@link #hasShortcutHostPermission()}, will receive it. + * + * @param packageName The name of the package that has the shortcuts. + * @param shortcuts Shortcuts from the package that have updated or added. Only "key" + * information will be provided, as defined in {@link ShortcutInfo#hasKeyFieldsOnly()}. + * @param user The UserHandle of the profile that generated the change. + * + * @see ShortcutManager + */ + default void onShortcutsAddedOrUpdated(@NonNull String packageName, + @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {} + + /** + * Indicates that one or more shortcuts, that match the {@link ShortcutQuery} used to + * register this callback, have been removed. + * @see LauncherApps#registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery) + * + * <p>Only the applications that are allowed to access the shortcut information, + * as defined in {@link #hasShortcutHostPermission()}, will receive it. + * + * @param packageName The name of the package that has the shortcuts. + * @param shortcuts Shortcuts from the package that have been removed. Only "key" + * information will be provided, as defined in {@link ShortcutInfo#hasKeyFieldsOnly()}. + * @param user The UserHandle of the profile that generated the change. + * + * @see ShortcutManager + */ + default void onShortcutsRemoved(@NonNull String packageName, + @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {} + } + + /** + * Callback proxy class for {@link ShortcutChangeCallback} + * + * @hide + */ + private static class ShortcutChangeCallbackProxy extends + android.content.pm.IShortcutChangeCallback.Stub { + private final WeakReference<Pair<Executor, ShortcutChangeCallback>> mRemoteReferences; + + ShortcutChangeCallbackProxy(Pair<Executor, ShortcutChangeCallback> remoteReferences) { + mRemoteReferences = new WeakReference<>(remoteReferences); + } + + @Override + public void onShortcutsAddedOrUpdated(@NonNull String packageName, + @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) { + Pair<Executor, ShortcutChangeCallback> remoteReferences = mRemoteReferences.get(); + if (remoteReferences == null) { + // Binder is dead. + return; + } + + final Executor executor = remoteReferences.first; + final ShortcutChangeCallback callback = remoteReferences.second; + executor.execute( + PooledLambda.obtainRunnable(ShortcutChangeCallback::onShortcutsAddedOrUpdated, + callback, packageName, shortcuts, user).recycleOnUse()); + } + + @Override + public void onShortcutsRemoved(@NonNull String packageName, + @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) { + Pair<Executor, ShortcutChangeCallback> remoteReferences = mRemoteReferences.get(); + if (remoteReferences == null) { + // Binder is dead. + return; + } + + final Executor executor = remoteReferences.first; + final ShortcutChangeCallback callback = remoteReferences.second; + executor.execute( + PooledLambda.obtainRunnable(ShortcutChangeCallback::onShortcutsRemoved, + callback, packageName, shortcuts, user).recycleOnUse()); + } + } + /** @hide */ public LauncherApps(Context context, ILauncherApps service) { mContext = context; @@ -924,8 +1040,8 @@ public class LauncherApps { // changed callback, but that only returns shortcuts with the "key" information, so // that won't return disabled message. return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(), - query.mChangedSince, query.mPackage, query.mShortcutIds, query.mActivity, - query.mQueryFlags, user) + query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds, + query.mActivity, query.mQueryFlags, user) .getList()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -974,6 +1090,61 @@ public class LauncherApps { } /** + * Mark shortcuts as cached for a package. + * + * <p>Only dynamic long lived shortcuts can be cached. None dynamic or non long lived shortcuts + * in the list will be ignored. + * + * <p>Unlike pinned shortcuts, where different callers can have different sets of pinned + * shortcuts, cached state is per shortcut only, and even if multiple callers cache the same + * shortcut, it can be uncached by any valid caller. + * + * @param packageName The target package name. + * @param shortcutIds The IDs of the shortcut to be cached. + * @param user The UserHandle of the profile. + * @throws IllegalStateException when the user is locked, or when the {@code user} user + * is locked or not running. + * + * @see ShortcutManager + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) + public void cacheShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds, + @NonNull UserHandle user) { + logErrorForInvalidProfileAccess(user); + try { + mService.cacheShortcuts(mContext.getPackageName(), packageName, shortcutIds, user); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove cached flag from shortcuts for a package. + * + * @param packageName The target package name. + * @param shortcutIds The IDs of the shortcut to be uncached. + * @param user The UserHandle of the profile. + * @throws IllegalStateException when the user is locked, or when the {@code user} user + * is locked or not running. + * + * @see ShortcutManager + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) + public void uncacheShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds, + @NonNull UserHandle user) { + logErrorForInvalidProfileAccess(user); + try { + mService.uncacheShortcuts(mContext.getPackageName(), packageName, shortcutIds, user); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * @hide kept for testing. */ @Deprecated @@ -1560,6 +1731,63 @@ public class LauncherApps { } /** + * Register a callback to watch for shortcut change events in this user and managed profiles. + * + * @param callback The callback to register. + * @param query {@link ShortcutQuery} to match and filter the shortcut events. Only matching + * shortcuts will be returned by the callback. + * @param executor {@link Executor} to handle the callbacks. To dispatch callbacks to the main + * thread of your application, you can use {@link android.content.Context#getMainExecutor()}. + * + * @hide + */ + public void registerShortcutChangeCallback(@NonNull ShortcutChangeCallback callback, + @NonNull ShortcutQuery query, @NonNull @CallbackExecutor Executor executor) { + Objects.requireNonNull(callback, "Callback cannot be null"); + Objects.requireNonNull(query, "Query cannot be null"); + Objects.requireNonNull(executor, "Executor cannot be null"); + + synchronized (mShortcutChangeCallbacks) { + final int callbackId = callback.hashCode(); + final Pair<Executor, ShortcutChangeCallback> state = new Pair<>(executor, callback); + mShortcutChangeCallbacks.put(callbackId, state); + try { + mService.registerShortcutChangeCallback(mContext.getPackageName(), + query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds, + query.mActivity, query.mQueryFlags, new ShortcutChangeCallbackProxy(state), + callbackId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Unregisters a callback that was previously registered. + * @see #registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery, Executor) + * + * @param callback Callback to be unregistered. + * + * @hide + */ + public void unregisterShortcutChangeCallback(@NonNull ShortcutChangeCallback callback) { + Objects.requireNonNull(callback, "Callback cannot be null"); + + synchronized (mShortcutChangeCallbacks) { + final int callbackId = callback.hashCode(); + if (mShortcutChangeCallbacks.containsKey(callbackId)) { + mShortcutChangeCallbacks.remove(callbackId); + try { + mService.unregisterShortcutChangeCallback(mContext.getPackageName(), + callbackId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + + /** * A helper method to extract a {@link PinItemRequest} set to * the {@link #EXTRA_PIN_ITEM_REQUEST} extra. */ 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 51d5c3f6725b..e8668f1368fb 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; @@ -596,6 +597,7 @@ public abstract class PackageManager { * @hide */ @SystemApi + @TestApi public static final int MODULE_APEX_NAME = 0x00000001; /** @hide */ @@ -2997,6 +2999,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. * @@ -3354,6 +3368,22 @@ public abstract class PackageManager { public static final int FLAG_PERMISSION_ONE_TIME = 1 << 16; /** + * Permission flag: The permission is whitelisted to not be auto-revoked when app goes unused. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE = 1 << 17; + + /** + * Permission flag: Whether {@link #FLAG_PERMISSION_DONT_AUTO_REVOKE} state was set by user. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET = 1 << 18; + + /** * Permission flags: Reserved for use by the permission controller. * * @hide @@ -3404,7 +3434,9 @@ public abstract class PackageManager { | FLAG_PERMISSION_APPLY_RESTRICTION | FLAG_PERMISSION_GRANTED_BY_ROLE | FLAG_PERMISSION_REVOKED_COMPAT - | FLAG_PERMISSION_ONE_TIME; + | FLAG_PERMISSION_ONE_TIME + | FLAG_PERMISSION_DONT_AUTO_REVOKE + | FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET; /** * Injected activity in app that forwards user to setting activity of that app. @@ -4227,7 +4259,8 @@ public abstract class PackageManager { FLAG_PERMISSION_APPLY_RESTRICTION, FLAG_PERMISSION_GRANTED_BY_ROLE, FLAG_PERMISSION_REVOKED_COMPAT, - FLAG_PERMISSION_ONE_TIME + FLAG_PERMISSION_ONE_TIME, + FLAG_PERMISSION_DONT_AUTO_REVOKE }) @Retention(RetentionPolicy.SOURCE) public @interface PermissionFlags {} @@ -7364,6 +7397,8 @@ public abstract class PackageManager { case FLAG_PERMISSION_GRANTED_BY_ROLE: return "GRANTED_BY_ROLE"; case FLAG_PERMISSION_REVOKED_COMPAT: return "REVOKED_COMPAT"; case FLAG_PERMISSION_ONE_TIME: return "ONE_TIME"; + case FLAG_PERMISSION_DONT_AUTO_REVOKE: return "DONT_AUTO_REVOKE"; + case FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET: return "DONT_AUTO_REVOKE_USER_SET"; default: return Integer.toString(flag); } } diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java index e6f682d22b14..a69905eb3de4 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -23,6 +23,7 @@ import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.content.Intent; import android.content.IntentSender; +import android.content.LocusId; import android.content.pm.LauncherApps.ShortcutQuery; import android.os.Bundle; import android.os.ParcelFileDescriptor; @@ -45,8 +46,8 @@ public abstract class ShortcutServiceInternal { getShortcuts(int launcherUserId, @NonNull String callingPackage, long changedSince, @Nullable String packageName, @Nullable List<String> shortcutIds, - @Nullable ComponentName componentName, @ShortcutQuery.QueryFlags int flags, - int userId, int callingPid, int callingUid); + @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName, + @ShortcutQuery.QueryFlags int flags, int userId, int callingPid, int callingUid); public abstract boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, @@ -84,4 +85,11 @@ public abstract class ShortcutServiceInternal { public abstract boolean isForegroundDefaultLauncher(@NonNull String callingPackage, int callingUid); + + public abstract void cacheShortcuts(int launcherUserId, + @NonNull String callingPackage, @NonNull String packageName, + @NonNull List<String> shortcutIds, int userId); + public abstract void uncacheShortcuts(int launcherUserId, + @NonNull String callingPackage, @NonNull String packageName, + @NonNull List<String> shortcutIds, int userId); } diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index 0549c34cc034..6f30ecd9b281 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -15,5 +15,15 @@ "name": "FrameworksInstantAppResolverTests", "file_patterns": ["(/|^)InstantApp[^/]*"] } + ], + "postsubmit": [ + { + "name": "CtsAppSecurityHostTestCases", + "options": [ + { + "include-filter": "android.appsecurity.cts.AppSecurityTests#testPermissionDiffCert" + } + ] + } ] } diff --git a/core/java/android/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java index fbe5a48ad61e..da17ff3cdefc 100644 --- a/core/java/android/content/pm/parsing/AndroidPackage.java +++ b/core/java/android/content/pm/parsing/AndroidPackage.java @@ -286,6 +286,8 @@ public interface AndroidPackage extends Parcelable { List<String> getQueriesPackages(); + Set<String> getQueriesProviders(); + String getRealPackage(); // TODO(b/135203078): Rename to getRequiredFeatures? Somewhat ambiguous whether "Req" is diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java index 5c8c9a41a520..548d82a6ab76 100644 --- a/core/java/android/content/pm/parsing/ApkParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkParseUtils.java @@ -96,6 +96,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.StringTokenizer; /** @hide */ public class ApkParseUtils { @@ -1817,6 +1818,25 @@ public class ApkParseUtils { ); } parsingPackage.addQueriesPackage(packageName.intern()); + } else if (parser.getName().equals("provider")) { + final TypedArray sa = res.obtainAttributes(parser, + R.styleable.AndroidManifestQueriesProvider); + try { + final String authorities = + sa.getString(R.styleable.AndroidManifestQueriesProvider_authorities); + if (TextUtils.isEmpty(authorities)) { + return parseInput.error( + PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "Authority missing from provider tag." + ); + } + StringTokenizer authoritiesTokenizer = new StringTokenizer(authorities, ";"); + while (authoritiesTokenizer.hasMoreElements()) { + parsingPackage.addQueriesProvider(authoritiesTokenizer.nextToken()); + } + } finally { + sa.recycle(); + } } } return parseInput.success(parsingPackage); diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java index fe8307c7c8cd..0df950006f43 100644 --- a/core/java/android/content/pm/parsing/PackageImpl.java +++ b/core/java/android/content/pm/parsing/PackageImpl.java @@ -216,6 +216,9 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android private ArrayList<String> queriesPackages; @Nullable + private ArraySet<String> queriesProviders; + + @Nullable private ArrayMap<String, ComponentParseUtils.ParsedProcess> processes; private String[] splitClassLoaderNames; @@ -957,6 +960,12 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android } @Override + public ParsingPackage addQueriesProvider(String authority) { + this.queriesProviders = ArrayUtils.add(this.queriesProviders, authority); + return this; + } + + @Override public PackageImpl setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes) { this.processes = processes; return this; @@ -2975,6 +2984,11 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android return queriesPackages; } + @Override + public Set<String> getQueriesProviders() { + return queriesProviders; + } + private static void internStringArrayList(List<String> list) { if (list != null) { final int N = list.size(); diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java index 9ddcc0995fd4..a2fe064b66c3 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/core/java/android/content/pm/parsing/ParsingPackage.java @@ -100,6 +100,8 @@ public interface ParsingPackage extends AndroidPackage { ParsingPackage addQueriesPackage(String packageName); + ParsingPackage addQueriesProvider(String authority); + ParsingPackage setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes); ParsingPackage asSplit( 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/biometrics/BiometricNativeHandleUtils.java b/core/java/android/hardware/biometrics/BiometricNativeHandleUtils.java new file mode 100644 index 000000000000..5544eaeca7f3 --- /dev/null +++ b/core/java/android/hardware/biometrics/BiometricNativeHandleUtils.java @@ -0,0 +1,79 @@ +/* + * 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.hardware.biometrics; + +import android.os.NativeHandle; +import android.os.ParcelFileDescriptor; + +import java.io.IOException; + +/** + * A class that contains utilities for IBiometricNativeHandle. + * + * @hide + */ +public final class BiometricNativeHandleUtils { + + private BiometricNativeHandleUtils() { + } + + /** + * Converts a {@link NativeHandle} into an {@link IBiometricNativeHandle} by duplicating the + * underlying file descriptors. + * + * Both the original and new handle must be closed after use. + * + * @param h {@link NativeHandle}. Usually used to identify a WindowManager window. Can be null. + * @return A {@link IBiometricNativeHandle} representation of {@code h}. Will be null if + * {@code h} or its raw file descriptors are null. + */ + public static IBiometricNativeHandle dup(NativeHandle h) { + IBiometricNativeHandle handle = null; + if (h != null && h.getFileDescriptors() != null && h.getInts() != null) { + handle = new IBiometricNativeHandle(); + handle.ints = h.getInts().clone(); + handle.fds = new ParcelFileDescriptor[h.getFileDescriptors().length]; + for (int i = 0; i < h.getFileDescriptors().length; ++i) { + try { + handle.fds[i] = ParcelFileDescriptor.dup(h.getFileDescriptors()[i]); + } catch (IOException e) { + return null; + } + } + } + return handle; + } + + /** + * Closes the handle's file descriptors. + * + * @param h {@link IBiometricNativeHandle} handle. + */ + public static void close(IBiometricNativeHandle h) { + if (h != null) { + for (ParcelFileDescriptor fd : h.fds) { + if (fd != null) { + try { + fd.close(); + } catch (IOException e) { + // do nothing. + } + } + } + } + } +} diff --git a/core/java/android/hardware/biometrics/IBiometricNativeHandle.aidl b/core/java/android/hardware/biometrics/IBiometricNativeHandle.aidl new file mode 100644 index 000000000000..6dcdc1be3a50 --- /dev/null +++ b/core/java/android/hardware/biometrics/IBiometricNativeHandle.aidl @@ -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. + */ +package android.hardware.biometrics; + +/** + * Representation of a native handle. + * Copied from /common/aidl/android/hardware/common/NativeHandle.aidl + * @hide + */ +parcelable IBiometricNativeHandle { + ParcelFileDescriptor[] fds; + int[] ints; +} diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 55ebe285af1e..6bda46b0b692 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -29,7 +29,9 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.BiometricNativeHandleUtils; import android.hardware.biometrics.CryptoObject; +import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.os.Binder; import android.os.CancellationSignal; @@ -38,6 +40,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.Looper; +import android.os.NativeHandle; import android.os.PowerManager; import android.os.RemoteException; import android.os.Trace; @@ -245,6 +248,19 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** + * Defaults to {@link FaceManager#enroll(int, byte[], CancellationSignal, EnrollmentCallback, + * int[], NativeHandle)} with {@code windowId} set to null. + * + * @see FaceManager#enroll(int, byte[], CancellationSignal, EnrollmentCallback, int[], + * NativeHandle) + */ + @RequiresPermission(MANAGE_BIOMETRIC) + public void enroll(int userId, byte[] token, CancellationSignal cancel, + EnrollmentCallback callback, int[] disabledFeatures) { + enroll(userId, token, cancel, callback, disabledFeatures, null /* windowId */); + } + + /** * Request face authentication enrollment. This call operates the face authentication hardware * and starts capturing images. Progress will be indicated by callbacks to the * {@link EnrollmentCallback} object. It terminates when @@ -259,11 +275,13 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * @param flags optional flags * @param userId the user to whom this face will belong to * @param callback an object to receive enrollment events + * @param windowId optional ID of a camera preview window for a single-camera device. Must be + * null if not used. * @hide */ @RequiresPermission(MANAGE_BIOMETRIC) public void enroll(int userId, byte[] token, CancellationSignal cancel, - EnrollmentCallback callback, int[] disabledFeatures) { + EnrollmentCallback callback, int[] disabledFeatures, @Nullable NativeHandle windowId) { if (callback == null) { throw new IllegalArgumentException("Must supply an enrollment callback"); } @@ -278,20 +296,72 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } if (mService != null) { + IBiometricNativeHandle handle = BiometricNativeHandleUtils.dup(windowId); try { mEnrollmentCallback = callback; Trace.beginSection("FaceManager#enroll"); mService.enroll(userId, mToken, token, mServiceReceiver, - mContext.getOpPackageName(), disabledFeatures); + mContext.getOpPackageName(), disabledFeatures, handle); } catch (RemoteException e) { Log.w(TAG, "Remote exception in enroll: ", e); - if (callback != null) { - // Though this may not be a hardware issue, it will cause apps to give up or - // try again later. - callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE, - getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE, + // Though this may not be a hardware issue, it will cause apps to give up or + // try again later. + callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE, + getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */)); + } finally { + Trace.endSection(); + BiometricNativeHandleUtils.close(handle); + } + } + } + + /** + * Request face authentication enrollment for a remote client, for example Android Auto. + * This call operates the face authentication hardware and starts capturing images. + * Progress will be indicated by callbacks to the + * {@link EnrollmentCallback} object. It terminates when + * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} or + * {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at + * which point the object is no longer valid. The operation can be canceled by using the + * provided cancel object. + * + * @param token a unique token provided by a recent creation or verification of device + * credentials (e.g. pin, pattern or password). + * @param cancel an object that can be used to cancel enrollment + * @param userId the user to whom this face will belong to + * @param callback an object to receive enrollment events + * @hide + */ + @RequiresPermission(MANAGE_BIOMETRIC) + public void enrollRemotely(int userId, byte[] token, CancellationSignal cancel, + EnrollmentCallback callback, int[] disabledFeatures) { + if (callback == null) { + throw new IllegalArgumentException("Must supply an enrollment callback"); + } + + if (cancel != null) { + if (cancel.isCanceled()) { + Log.w(TAG, "enrollRemotely is already canceled."); + return; + } else { + cancel.setOnCancelListener(new OnEnrollCancelListener()); + } + } + + if (mService != null) { + try { + mEnrollmentCallback = callback; + Trace.beginSection("FaceManager#enrollRemotely"); + mService.enrollRemotely(userId, mToken, token, mServiceReceiver, + mContext.getOpPackageName(), disabledFeatures); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in enrollRemotely: ", e); + // Though this may not be a hardware issue, it will cause apps to give up or + // try again later. + callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE, + getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */)); - } } finally { Trace.endSection(); } diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index 68a4aef7af08..8ba24735c039 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -15,6 +15,7 @@ */ package android.hardware.face; +import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.face.IFaceServiceReceiver; @@ -51,6 +52,10 @@ interface IFaceService { // Start face enrollment void enroll(int userId, IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver, + String opPackageName, in int [] disabledFeatures, in IBiometricNativeHandle windowId); + + // Start remote face enrollment + void enrollRemotely(int userId, IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver, String opPackageName, in int [] disabledFeatures); // Cancel enrollment in progress diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index ff9d14510d4b..f6717c77f90e 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -32,7 +32,9 @@ import android.content.Context; import android.content.pm.PackageManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; +import android.hardware.biometrics.BiometricNativeHandleUtils; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.os.Binder; import android.os.CancellationSignal; @@ -41,6 +43,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.Looper; +import android.os.NativeHandle; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; @@ -403,15 +406,33 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } /** + * Defaults to {@link FingerprintManager#authenticate(CryptoObject, CancellationSignal, int, + * AuthenticationCallback, Handler, int, NativeHandle)} with {@code windowId} set to null. + * + * @see FingerprintManager#authenticate(CryptoObject, CancellationSignal, int, + * AuthenticationCallback, Handler, int, NativeHandle) + * + * @hide + */ + @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) + public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, + int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId) { + authenticate(crypto, cancel, flags, callback, handler, userId, null /* windowId */); + } + + /** * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject, * CancellationSignal, int, AuthenticationCallback, Handler)}. This version does not * display the BiometricPrompt. * @param userId the user ID that the fingerprint hardware will authenticate for. + * @param windowId for optical fingerprint sensors that require active illumination by the OLED + * display. Should be null for devices that don't require illumination. * @hide */ @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, - int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId) { + int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId, + @Nullable NativeHandle windowId) { if (callback == null) { throw new IllegalArgumentException("Must supply an authentication callback"); } @@ -425,26 +446,44 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } } - if (mService != null) try { - useHandler(handler); - mAuthenticationCallback = callback; - mCryptoObject = crypto; - long sessionId = crypto != null ? crypto.getOpId() : 0; - mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags, - mContext.getOpPackageName()); - } catch (RemoteException e) { - Slog.w(TAG, "Remote exception while authenticating: ", e); - if (callback != null) { + if (mService != null) { + IBiometricNativeHandle handle = BiometricNativeHandleUtils.dup(windowId); + try { + useHandler(handler); + mAuthenticationCallback = callback; + mCryptoObject = crypto; + long sessionId = crypto != null ? crypto.getOpId() : 0; + mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags, + mContext.getOpPackageName(), handle); + } catch (RemoteException e) { + Slog.w(TAG, "Remote exception while authenticating: ", e); // Though this may not be a hardware issue, it will cause apps to give up or try // again later. callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE, getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE, - 0 /* vendorCode */)); + 0 /* vendorCode */)); + } finally { + BiometricNativeHandleUtils.close(handle); } } } /** + * Defaults to {@link FingerprintManager#enroll(byte[], CancellationSignal, int, int, + * EnrollmentCallback, NativeHandle)} with {@code windowId} set to null. + * + * @see FingerprintManager#enroll(byte[], CancellationSignal, int, int, EnrollmentCallback, + * NativeHandle) + * + * @hide + */ + @RequiresPermission(MANAGE_FINGERPRINT) + public void enroll(byte [] token, CancellationSignal cancel, int flags, + int userId, EnrollmentCallback callback) { + enroll(token, cancel, flags, userId, callback, null /* windowId */); + } + + /** * Request fingerprint enrollment. This call warms up the fingerprint hardware * and starts scanning for fingerprints. Progress will be indicated by callbacks to the * {@link EnrollmentCallback} object. It terminates when @@ -462,7 +501,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing */ @RequiresPermission(MANAGE_FINGERPRINT) public void enroll(byte [] token, CancellationSignal cancel, int flags, - int userId, EnrollmentCallback callback) { + int userId, EnrollmentCallback callback, @Nullable NativeHandle windowId) { if (userId == UserHandle.USER_CURRENT) { userId = getCurrentUserId(); } @@ -479,18 +518,21 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } } - if (mService != null) try { - mEnrollmentCallback = callback; - mService.enroll(mToken, token, userId, mServiceReceiver, flags, - mContext.getOpPackageName()); - } catch (RemoteException e) { - Slog.w(TAG, "Remote exception in enroll: ", e); - if (callback != null) { + if (mService != null) { + IBiometricNativeHandle handle = BiometricNativeHandleUtils.dup(windowId); + try { + mEnrollmentCallback = callback; + mService.enroll(mToken, token, userId, mServiceReceiver, flags, + mContext.getOpPackageName(), handle); + } catch (RemoteException e) { + Slog.w(TAG, "Remote exception in enroll: ", e); // Though this may not be a hardware issue, it will cause apps to give up or try // again later. callback.onEnrollmentError(FINGERPRINT_ERROR_HW_UNAVAILABLE, getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE, - 0 /* vendorCode */)); + 0 /* vendorCode */)); + } finally { + BiometricNativeHandleUtils.close(handle); } } } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 1a7e12856753..f2ffd08d5bc8 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -15,6 +15,7 @@ */ package android.hardware.fingerprint; +import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.fingerprint.IFingerprintClientActiveCallback; @@ -31,7 +32,8 @@ interface IFingerprintService { // USE_FINGERPRINT/USE_BIOMETRIC permission. This is effectively deprecated, since it only comes // through FingerprintManager now. void authenticate(IBinder token, long sessionId, int userId, - IFingerprintServiceReceiver receiver, int flags, String opPackageName); + IFingerprintServiceReceiver receiver, int flags, String opPackageName, + in IBiometricNativeHandle windowId); // This method prepares the service to start authenticating, but doesn't start authentication. // This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be @@ -40,7 +42,7 @@ interface IFingerprintService { // startPreparedClient(). void prepareForAuthentication(IBinder token, long sessionId, int userId, IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, int cookie, - int callingUid, int callingPid, int callingUserId); + int callingUid, int callingPid, int callingUserId, in IBiometricNativeHandle windowId); // Starts authentication with the previously prepared client. void startPreparedClient(int cookie); @@ -55,7 +57,7 @@ interface IFingerprintService { // Start fingerprint enrollment void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver, - int flags, String opPackageName); + int flags, String opPackageName, in IBiometricNativeHandle windowId); // Cancel enrollment in progress void cancelEnrollment(IBinder token); 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 d505ae59dfaf..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; @@ -735,22 +735,40 @@ public class SoundTrigger { /** * The inclusive start of supported range. */ - public final int start; + private final int mStart; /** * The inclusive end of supported range. */ - public final int end; + private final int mEnd; ModelParamRange(int start, int end) { - this.start = start; - this.end = end; + this.mStart = start; + this.mEnd = end; } /** @hide */ private ModelParamRange(@NonNull Parcel in) { - this.start = in.readInt(); - this.end = in.readInt(); + this.mStart = in.readInt(); + this.mEnd = in.readInt(); + } + + /** + * Get the beginning of the param range + * + * @return The inclusive start of the supported range. + */ + public int getStart() { + return mStart; + } + + /** + * Get the end of the param range + * + * @return The inclusive end of the supported range. + */ + public int getEnd() { + return mEnd; } @NonNull @@ -780,8 +798,8 @@ public class SoundTrigger { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + (start); - result = prime * result + (end); + result = prime * result + (mStart); + result = prime * result + (mEnd); return result; } @@ -797,10 +815,10 @@ public class SoundTrigger { return false; } ModelParamRange other = (ModelParamRange) obj; - if (start != other.start) { + if (mStart != other.mStart) { return false; } - if (end != other.end) { + if (mEnd != other.mEnd) { return false; } return true; @@ -808,14 +826,14 @@ public class SoundTrigger { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(start); - dest.writeInt(end); + dest.writeInt(mStart); + dest.writeInt(mEnd); } @Override @NonNull public String toString() { - return "ModelParamRange [start=" + start + ", end=" + end + "]"; + return "ModelParamRange [start=" + mStart + ", end=" + mEnd + "]"; } } diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java index 9bd39925f83f..c1df5b6d2e7e 100644 --- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java +++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java @@ -221,10 +221,8 @@ public class SoundTriggerModule { /** * Set a model specific {@link ModelParams} with the given value. This * parameter will keep its value for the duration the model is loaded regardless of starting - * and - * stopping recognition. Once the model is unloaded, the value will be lost. - * {@link SoundTriggerModule#queryParameter(int, int)} should be checked first before calling - * this method. + * and stopping recognition. Once the model is unloaded, the value will be lost. + * {@link #queryParameter} should be checked first before calling this method. * * @param soundModelHandle handle of model to apply parameter * @param modelParam {@link ModelParams} @@ -251,22 +249,14 @@ public class SoundTriggerModule { * for the duration the model is loaded regardless of starting and stopping recognition. * Once the model is unloaded, the value will be lost. If the value is not set, a default * value is returned. See {@link ModelParams} for parameter default values. - * {@link SoundTriggerModule#queryParameter(int, int)} should be checked first before + * {@link #queryParameter} should be checked first before * calling this method. Otherwise, an exception can be thrown. * * @param soundModelHandle handle of model to get parameter * @param modelParam {@link ModelParams} * @return value of parameter - * @throws UnsupportedOperationException if hal or model do not support this API. - * {@link SoundTriggerModule#queryParameter(int, int)} - * should - * be checked first. - * @throws IllegalArgumentException if invalid model handle or parameter is passed. - * {@link SoundTriggerModule#queryParameter(int, int)} - * should be checked first. */ - public synchronized int getParameter(int soundModelHandle, @ModelParams int modelParam) - throws UnsupportedOperationException, IllegalArgumentException { + public synchronized int getParameter(int soundModelHandle, @ModelParams int modelParam) { try { return mService.getModelParameter(soundModelHandle, ConversionUtil.api2aidlModelParameter(modelParam)); @@ -276,9 +266,8 @@ public class SoundTriggerModule { } /** - * Determine if parameter control is supported for the given model handle. - * This method should be checked prior to calling {@link SoundTriggerModule#setParameter} or - * {@link SoundTriggerModule#getParameter}. + * Query the parameter support and range for a given {@link ModelParams}. + * This method should be check prior to calling {@link #setParameter} or {@link #getParameter}. * * @param soundModelHandle handle of model to get parameter * @param modelParam {@link ModelParams} 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 fa12c08f2277..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"); } /** @@ -3750,6 +3750,7 @@ public class ConnectivityManager { checkCallbackNotNull(callback); Preconditions.checkArgument(action == REQUEST || need != null, "null NetworkCapabilities"); final NetworkRequest request; + final String callingPackageName = mContext.getOpPackageName(); try { synchronized(sCallbacks) { if (callback.networkRequest != null @@ -3761,10 +3762,11 @@ public class ConnectivityManager { Messenger messenger = new Messenger(handler); Binder binder = new Binder(); if (action == LISTEN) { - request = mService.listenForNetwork(need, messenger, binder); + request = mService.listenForNetwork( + need, messenger, binder, callingPackageName); } else { request = mService.requestNetwork( - need, messenger, timeoutMs, binder, legacyType); + need, messenger, timeoutMs, binder, legacyType, callingPackageName); } if (request != null) { sCallbacks.put(request, callback); @@ -4037,8 +4039,10 @@ public class ConnectivityManager { @NonNull PendingIntent operation) { printStackTrace(); checkPendingIntentNotNull(operation); + final String callingPackageName = mContext.getOpPackageName(); try { - mService.pendingRequestForNetwork(request.networkCapabilities, operation); + mService.pendingRequestForNetwork( + request.networkCapabilities, operation, callingPackageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { @@ -4150,8 +4154,10 @@ public class ConnectivityManager { @NonNull PendingIntent operation) { printStackTrace(); checkPendingIntentNotNull(operation); + final String callingPackageName = mContext.getOpPackageName(); try { - mService.pendingListenForNetwork(request.networkCapabilities, operation); + mService.pendingListenForNetwork( + request.networkCapabilities, operation, callingPackageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 0fae607ca6ca..1c7628f6ad0a 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -167,18 +167,19 @@ interface IConnectivityManager in int factorySerialNumber); NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, - in Messenger messenger, int timeoutSec, in IBinder binder, int legacy); + in Messenger messenger, int timeoutSec, in IBinder binder, int legacy, + String callingPackageName); NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities, - in PendingIntent operation); + in PendingIntent operation, String callingPackageName); void releasePendingNetworkRequest(in PendingIntent operation); NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities, - in Messenger messenger, in IBinder binder); + in Messenger messenger, in IBinder binder, String callingPackageName); void pendingListenForNetwork(in NetworkCapabilities networkCapabilities, - in PendingIntent operation); + in PendingIntent operation, String callingPackageName); void releaseNetworkRequest(in NetworkRequest networkRequest); diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index cf5f2259af44..f8b51dd9906b 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -27,6 +27,7 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.os.Process; +import android.text.TextUtils; import android.util.ArraySet; import android.util.proto.ProtoOutputStream; @@ -63,6 +64,16 @@ public final class NetworkCapabilities implements Parcelable { // Set to true when private DNS is broken. private boolean mPrivateDnsBroken; + /** + * Uid of the app making the request. + */ + private int mRequestorUid; + + /** + * Package name of the app making the request. + */ + private String mRequestorPackageName; + public NetworkCapabilities() { clearAll(); mNetworkCapabilities = DEFAULT_CAPABILITIES; @@ -89,6 +100,8 @@ public final class NetworkCapabilities implements Parcelable { mOwnerUid = Process.INVALID_UID; mSSID = null; mPrivateDnsBroken = false; + mRequestorUid = Process.INVALID_UID; + mRequestorPackageName = null; } /** @@ -109,6 +122,8 @@ public final class NetworkCapabilities implements Parcelable { mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; mSSID = nc.mSSID; mPrivateDnsBroken = nc.mPrivateDnsBroken; + mRequestorUid = nc.mRequestorUid; + mRequestorPackageName = nc.mRequestorPackageName; } /** @@ -810,7 +825,7 @@ public final class NetworkCapabilities implements Parcelable { } /** - * UID of the app that owns this network, or INVALID_UID if none/unknown. + * UID of the app that owns this network, or Process#INVALID_UID if none/unknown. * * <p>This field keeps track of the UID of the app that created this network and is in charge of * its lifecycle. This could be the UID of apps such as the Wifi network suggestor, the running @@ -821,8 +836,9 @@ public final class NetworkCapabilities implements Parcelable { /** * Set the UID of the owner app. */ - public void setOwnerUid(final int uid) { + public @NonNull NetworkCapabilities setOwnerUid(final int uid) { mOwnerUid = uid; + return this; } /** @@ -865,9 +881,11 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ @SystemApi - public void setAdministratorUids(@NonNull final List<Integer> administratorUids) { + public @NonNull NetworkCapabilities setAdministratorUids( + @NonNull final List<Integer> administratorUids) { mAdministratorUids.clear(); mAdministratorUids.addAll(administratorUids); + return this; } /** @@ -1385,6 +1403,7 @@ public final class NetworkCapabilities implements Parcelable { combineSignalStrength(nc); combineUids(nc); combineSSIDs(nc); + combineRequestor(nc); } /** @@ -1404,7 +1423,8 @@ public final class NetworkCapabilities implements Parcelable { && satisfiedBySpecifier(nc) && (onlyImmutable || satisfiedBySignalStrength(nc)) && (onlyImmutable || satisfiedByUids(nc)) - && (onlyImmutable || satisfiedBySSID(nc))); + && (onlyImmutable || satisfiedBySSID(nc))) + && (onlyImmutable || satisfiedByRequestor(nc)); } /** @@ -1488,7 +1508,7 @@ public final class NetworkCapabilities implements Parcelable { public boolean equals(@Nullable Object obj) { if (obj == null || (obj instanceof NetworkCapabilities == false)) return false; NetworkCapabilities that = (NetworkCapabilities) obj; - return (equalsNetCapabilities(that) + return equalsNetCapabilities(that) && equalsTransportTypes(that) && equalsLinkBandwidths(that) && equalsSignalStrength(that) @@ -1496,7 +1516,8 @@ public final class NetworkCapabilities implements Parcelable { && equalsTransportInfo(that) && equalsUids(that) && equalsSSID(that) - && equalsPrivateDnsBroken(that)); + && equalsPrivateDnsBroken(that) + && equalsRequestor(that); } @Override @@ -1514,7 +1535,9 @@ public final class NetworkCapabilities implements Parcelable { + Objects.hashCode(mUids) * 31 + Objects.hashCode(mSSID) * 37 + Objects.hashCode(mTransportInfo) * 41 - + Objects.hashCode(mPrivateDnsBroken) * 43; + + Objects.hashCode(mPrivateDnsBroken) * 43 + + Objects.hashCode(mRequestorUid) * 47 + + Objects.hashCode(mRequestorPackageName) * 53; } @Override @@ -1537,6 +1560,8 @@ public final class NetworkCapabilities implements Parcelable { dest.writeBoolean(mPrivateDnsBroken); dest.writeList(mAdministratorUids); dest.writeInt(mOwnerUid); + dest.writeInt(mRequestorUid); + dest.writeString(mRequestorPackageName); } public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR = @@ -1559,6 +1584,8 @@ public final class NetworkCapabilities implements Parcelable { netCap.mPrivateDnsBroken = in.readBoolean(); netCap.setAdministratorUids(in.readArrayList(null)); netCap.mOwnerUid = in.readInt(); + netCap.mRequestorUid = in.readInt(); + netCap.mRequestorPackageName = in.readString(); return netCap; } @Override @@ -1624,6 +1651,9 @@ public final class NetworkCapabilities implements Parcelable { sb.append(" Private DNS is broken"); } + sb.append(" RequestorUid: ").append(mRequestorUid); + sb.append(" RequestorPackageName: ").append(mRequestorPackageName); + sb.append("]"); return sb.toString(); } @@ -1632,6 +1662,7 @@ public final class NetworkCapabilities implements Parcelable { private interface NameOf { String nameOf(int value); } + /** * @hide */ @@ -1799,4 +1830,120 @@ public final class NetworkCapabilities implements Parcelable { private boolean equalsPrivateDnsBroken(NetworkCapabilities nc) { return mPrivateDnsBroken == nc.mPrivateDnsBroken; } + + /** + * Set the uid of the app making the request. + * + * Note: This works only for {@link NetworkAgent} instances. Any capabilities passed in + * via the public {@link ConnectivityManager} API's will have this field overwritten. + * + * @param uid UID of the app. + * @hide + */ + @SystemApi + public @NonNull NetworkCapabilities setRequestorUid(int uid) { + mRequestorUid = uid; + return this; + } + + /** + * @return the uid of the app making the request. + * + * Note: This could return {@link Process#INVALID_UID} if the {@link NetworkRequest} + * object was not obtained from {@link ConnectivityManager}. + * @hide + */ + public int getRequestorUid() { + return mRequestorUid; + } + + /** + * Set the package name of the app making the request. + * + * Note: This works only for {@link NetworkAgent} instances. Any capabilities passed in + * via the public {@link ConnectivityManager} API's will have this field overwritten. + * + * @param packageName package name of the app. + * @hide + */ + @SystemApi + public @NonNull NetworkCapabilities setRequestorPackageName(@NonNull String packageName) { + mRequestorPackageName = packageName; + return this; + } + + /** + * @return the package name of the app making the request. + * + * Note: This could return {@code null} if the {@link NetworkRequest} object was not obtained + * from {@link ConnectivityManager}. + * @hide + */ + @Nullable + public String getRequestorPackageName() { + return mRequestorPackageName; + } + + /** + * Set the uid and package name of the app making the request. + * + * Note: This is intended to be only invoked from within connectivitiy service. + * + * @param uid UID of the app. + * @param packageName package name of the app. + * @hide + */ + public @NonNull NetworkCapabilities setRequestorUidAndPackageName( + int uid, @NonNull String packageName) { + return setRequestorUid(uid).setRequestorPackageName(packageName); + } + + /** + * Test whether the passed NetworkCapabilities satisfies the requestor restrictions of this + * capabilities. + * + * This method is called on the NetworkCapabilities embedded in a request with the + * capabilities of an available network. If the available network, sets a specific + * requestor (by uid and optionally package name), then this will only match a request from the + * same app. If either of the capabilities have an unset uid or package name, then it matches + * everything. + * <p> + * nc is assumed nonnull. Else, NPE. + */ + private boolean satisfiedByRequestor(NetworkCapabilities nc) { + // No uid set, matches everything. + if (mRequestorUid == Process.INVALID_UID || nc.mRequestorUid == Process.INVALID_UID) { + return true; + } + // uids don't match. + if (mRequestorUid != nc.mRequestorUid) return false; + // No package names set, matches everything + if (null == nc.mRequestorPackageName || null == mRequestorPackageName) return true; + // check for package name match. + return TextUtils.equals(mRequestorPackageName, nc.mRequestorPackageName); + } + + /** + * Combine requestor info of the capabilities. + * <p> + * This is only legal if either the requestor info of this object is reset, or both info are + * equal. + * nc is assumed nonnull. + */ + private void combineRequestor(@NonNull NetworkCapabilities nc) { + if (mRequestorUid != Process.INVALID_UID && mRequestorUid != nc.mOwnerUid) { + throw new IllegalStateException("Can't combine two uids"); + } + if (mRequestorPackageName != null + && !mRequestorPackageName.equals(nc.mRequestorPackageName)) { + throw new IllegalStateException("Can't combine two package names"); + } + setRequestorUid(nc.mRequestorUid); + setRequestorPackageName(nc.mRequestorPackageName); + } + + private boolean equalsRequestor(NetworkCapabilities nc) { + return mRequestorUid == nc.mRequestorUid + && TextUtils.equals(mRequestorPackageName, nc.mRequestorPackageName); + } } diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 301d20340643..964f13f39ec6 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -380,6 +380,7 @@ public class NetworkRequest implements Parcelable { dest.writeInt(requestId); dest.writeString(type.name()); } + public static final @android.annotation.NonNull Creator<NetworkRequest> CREATOR = new Creator<NetworkRequest>() { public NetworkRequest createFromParcel(Parcel in) { @@ -494,6 +495,31 @@ public class NetworkRequest implements Parcelable { return networkCapabilities.getNetworkSpecifier(); } + /** + * @return the uid of the app making the request. + * + * Note: This could return {@link Process#INVALID_UID} if the {@link NetworkRequest} object was + * not obtained from {@link ConnectivityManager}. + * @hide + */ + @SystemApi + public int getRequestorUid() { + return networkCapabilities.getRequestorUid(); + } + + /** + * @return the package name of the app making the request. + * + * Note: This could return {@code null} if the {@link NetworkRequest} object was not obtained + * from {@link ConnectivityManager}. + * @hide + */ + @SystemApi + @Nullable + public String getRequestorPackageName() { + return networkCapabilities.getRequestorPackageName(); + } + public String toString() { return "NetworkRequest [ " + type + " id=" + requestId + (legacyType != ConnectivityManager.TYPE_NONE ? ", legacyType=" + legacyType : "") + diff --git a/core/java/android/net/NetworkSpecifier.java b/core/java/android/net/NetworkSpecifier.java index cf31d217c967..2dd0c4e207fe 100644 --- a/core/java/android/net/NetworkSpecifier.java +++ b/core/java/android/net/NetworkSpecifier.java @@ -39,23 +39,6 @@ public abstract class NetworkSpecifier { /** * Optional method which can be overridden by concrete implementations of NetworkSpecifier to - * check a self-reported UID. A concrete implementation may contain a UID which would be self- - * reported by the caller (since NetworkSpecifier implementations should be non-mutable). This - * function is called by ConnectivityService and is passed the actual UID of the caller - - * allowing the verification of the self-reported UID. In cases of mismatch the implementation - * should throw a SecurityException. - * - * @param requestorUid The UID of the requestor as obtained from its binder. - * - * @hide - */ - @SystemApi - public void assertValidFromUid(int requestorUid) { - // empty - } - - /** - * Optional method which can be overridden by concrete implementations of NetworkSpecifier to * perform any redaction of information from the NetworkSpecifier, e.g. if it contains * sensitive information. The default implementation simply returns the object itself - i.e. * no information is redacted. A concrete implementation may return a modified (copy) of the diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index a29173469ffb..70b2db70a9e8 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -240,6 +240,13 @@ public class Build { public static final String RELEASE = getString("ro.build.version.release"); /** + * The version string we show to the user; may be {@link #RELEASE} or + * {@link #CODENAME} if not a final release build. + */ + @NonNull public static final String RELEASE_OR_CODENAME = getString( + "ro.build.version.release_or_codename"); + + /** * The base OS build the product is based on. */ public static final String BASE_OS = SystemProperties.get("ro.build.version.base_os", ""); diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 44f12a6adf60..21a1e0f0a108 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -34,9 +34,11 @@ import android.text.TextUtils; import android.util.Log; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; +import java.util.List; /** * Provides access to environment variables. @@ -539,12 +541,21 @@ public class Environment { @SystemApi public static @NonNull Collection<File> getInternalMediaDirectories() { final ArrayList<File> res = new ArrayList<>(); - res.add(new File(Environment.getRootDirectory(), "media")); - res.add(new File(Environment.getOemDirectory(), "media")); - res.add(new File(Environment.getProductDirectory(), "media")); + addCanonicalFile(res, new File(Environment.getRootDirectory(), "media")); + addCanonicalFile(res, new File(Environment.getOemDirectory(), "media")); + addCanonicalFile(res, new File(Environment.getProductDirectory(), "media")); return res; } + private static void addCanonicalFile(List<File> list, File file) { + try { + list.add(file.getCanonicalFile()); + } catch (IOException e) { + Log.w(TAG, "Failed to resolve " + file + ": " + e); + list.add(file); + } + } + /** * Return the primary shared/external storage directory. This directory may * not currently be accessible if it has been mounted by the user on their diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java index 26d9c7d687eb..75123528a6b8 100644 --- a/core/java/android/os/SharedMemory.java +++ b/core/java/android/os/SharedMemory.java @@ -27,6 +27,7 @@ import dalvik.system.VMRuntime; import java.io.Closeable; import java.io.FileDescriptor; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.DirectByteBuffer; import java.nio.NioUtils; @@ -272,6 +273,20 @@ public final class SharedMemory implements Parcelable, Closeable { dest.writeFileDescriptor(mFileDescriptor); } + /** + * Returns a dup'd ParcelFileDescriptor from the SharedMemory FileDescriptor. + * This obeys standard POSIX semantics, where the + * new file descriptor shared state such as file position with the + * original file descriptor. + * TODO: propose this method as a public or system API for next release to achieve parity with + * NDK ASharedMemory_dupFromJava. + * + * @hide + */ + public ParcelFileDescriptor getFdDup() throws IOException { + return ParcelFileDescriptor.dup(mFileDescriptor); + } + public static final @android.annotation.NonNull Parcelable.Creator<SharedMemory> CREATOR = new Parcelable.Creator<SharedMemory>() { @Override diff --git a/core/java/android/os/StatsServiceManager.java b/core/java/android/os/StatsServiceManager.java index d032e98da00c..de07e928fcd1 100644 --- a/core/java/android/os/StatsServiceManager.java +++ b/core/java/android/os/StatsServiceManager.java @@ -18,17 +18,16 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.SystemApi.Client; /** * Provides a way to register and obtain the system service binder objects managed by the stats * service. * * <p> Only the statsd mainline module will be able to access an instance of this class. - * - * TODO(b/148225705) Change to @SystemApi(client=MODULE_LIBRARIES) when the build system is ready. * @hide */ -@SystemApi +@SystemApi(client = Client.MODULE_LIBRARIES) public class StatsServiceManager { /** * @hide 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/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl index 21434a2aecba..9d98b3b7819b 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -103,4 +103,9 @@ interface IIncrementalService { * Deletes a storage given its ID. Deletes its bind mounts and unmount it. Stop its data loader. */ void deleteStorage(int storageId); + + /** + * Setting up native library directories and extract native libs onto a storage. + */ + boolean configureNativeBinaries(int storageId, in @utf8InCpp String apkFullPath, in @utf8InCpp String libDirRelativePath, in @utf8InCpp String abi); } diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 8990bdf06a6f..ba38268949b5 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -23,8 +23,6 @@ import android.annotation.SystemService; import android.content.Context; import android.content.pm.DataLoaderParams; import android.os.RemoteException; -import android.system.ErrnoException; -import android.system.Os; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -193,115 +191,78 @@ public final class IncrementalManager { } /** - * Renames an Incremental path to a new path. If source path is a file, make a link from the old - * Incremental file to the new one. If source path is a dir, unbind old dir from Incremental - * Storage and bind the new one. - * <ol> - * <li> For renaming a dir, dest dir will be created if not exists, and does not need to - * be on the same Incremental storage as the source. </li> - * <li> For renaming a file, dest file must be on the same Incremental storage as source. - * </li> - * </ol> + * Set up an app's code path. The expected outcome of this method is: + * 1) The actual apk directory under /data/incremental is bind-mounted to the parent directory + * of {@code afterCodeFile}. + * 2) All the files under {@code beforeCodeFile} will show up under {@code afterCodeFile}. * - * @param sourcePath Absolute path to the source. Should be the same type as the destPath (file - * or dir). Expected to already exist and is an Incremental path. - * @param destPath Absolute path to the destination. - * @throws IllegalArgumentException when 1) source does not exist, or 2) source and dest type - * mismatch (one is file and the other is dir), or 3) source - * path is not on Incremental File System, - * @throws IOException when 1) cannot find the root path of the Incremental storage - * of source, or 2) cannot retrieve the Incremental storage - * instance of the source, or 3) renaming a file, but dest is - * not on the same Incremental Storage, or 4) renaming a dir, - * dest dir does not exist but fails to be created. - * <p> - * TODO(b/136132412): add unit tests + * @param beforeCodeFile Path that is currently bind-mounted and have APKs under it. + * Should no longer have any APKs after this method is called. + * Example: /data/app/vmdl*tmp + * @param afterCodeFile Path that should will have APKs after this method is called. Its parent + * directory should be bind-mounted to a directory under /data/incremental. + * Example: /data/app/~~[randomStringA]/[packageName]-[randomStringB] + * @throws IllegalArgumentException + * @throws IOException + * TODO(b/147371381): add unit tests */ - public void rename(@NonNull String sourcePath, @NonNull String destPath) throws IOException { - final File source = new File(sourcePath); - final File dest = new File(destPath); - if (!source.exists()) { - throw new IllegalArgumentException("Path not exist: " + sourcePath); + public void renameCodePath(File beforeCodeFile, File afterCodeFile) + throws IllegalArgumentException, IOException { + final String beforeCodePath = beforeCodeFile.getAbsolutePath(); + final String afterCodePathParent = afterCodeFile.getParentFile().getAbsolutePath(); + if (!isIncrementalPath(beforeCodePath)) { + throw new IllegalArgumentException("Not an Incremental path: " + beforeCodePath); } - if (dest.exists()) { - throw new IllegalArgumentException("Target path already exists: " + destPath); + final String afterCodePathName = afterCodeFile.getName(); + final Path apkStoragePath = Paths.get(beforeCodePath); + if (apkStoragePath == null || apkStoragePath.toAbsolutePath() == null) { + throw new IOException("Invalid source storage path for: " + beforeCodePath); } - if (source.isDirectory() && dest.exists() && dest.isFile()) { - throw new IllegalArgumentException( - "Trying to rename a dir but destination is a file: " + destPath); - } - if (source.isFile() && dest.exists() && dest.isDirectory()) { - throw new IllegalArgumentException( - "Trying to rename a file but destination is a dir: " + destPath); - } - if (!isIncrementalPath(sourcePath)) { - throw new IllegalArgumentException("Not an Incremental path: " + sourcePath); - } - - Path storagePath = Paths.get(sourcePath); - if (source.isFile()) { - storagePath = getStoragePathForFile(source); - } - if (storagePath == null || storagePath.toAbsolutePath() == null) { - throw new IOException("Invalid source storage path for: " + sourcePath); - } - final IncrementalStorage storage = openStorage(storagePath.toAbsolutePath().toString()); - if (storage == null) { + final IncrementalStorage apkStorage = + openStorage(apkStoragePath.toAbsolutePath().toString()); + if (apkStorage == null) { throw new IOException("Failed to retrieve storage from Incremental Service."); } - - if (source.isFile()) { - renameFile(storage, storagePath, source, dest); - } else { - renameDir(storage, storagePath, source, dest); - } - } - - private void renameFile(IncrementalStorage storage, Path storagePath, - File source, File dest) throws IOException { - Path sourcePath = source.toPath(); - Path destPath = dest.toPath(); - if (!sourcePath.startsWith(storagePath)) { - throw new IOException("Path: " + source.getAbsolutePath() + " is not on storage at: " - + storagePath.toString()); - } - if (!destPath.startsWith(storagePath)) { - throw new IOException("Path: " + dest.getAbsolutePath() + " is not on storage at: " - + storagePath.toString()); + final IncrementalStorage linkedApkStorage = createStorage(afterCodePathParent, apkStorage, + IncrementalManager.CREATE_MODE_CREATE + | IncrementalManager.CREATE_MODE_PERMANENT_BIND); + if (linkedApkStorage == null) { + throw new IOException("Failed to create linked storage at dir: " + afterCodePathParent); } - final Path sourceRelativePath = storagePath.relativize(sourcePath); - final Path destRelativePath = storagePath.relativize(destPath); - storage.moveFile(sourceRelativePath.toString(), destRelativePath.toString()); - + linkFiles(apkStorage, beforeCodeFile, "", linkedApkStorage, afterCodePathName); + apkStorage.unBind(beforeCodePath); } - private void renameDir(IncrementalStorage storage, Path storagePath, - File source, File dest) throws IOException { - Path destPath = dest.toPath(); - boolean usedMkdir = false; - try { - Os.mkdir(dest.getAbsolutePath(), 0755); - usedMkdir = true; - } catch (ErrnoException e) { - // Traditional mkdir fails but maybe we can create it on Incremental File System if - // the dest path is on the same Incremental storage as the source. - if (destPath.startsWith(storagePath)) { - storage.makeDirectories(storagePath.relativize(destPath).toString()); - } else { - throw new IOException("Failed to create directory: " + dest.getAbsolutePath(), e); - } - } - try { - storage.moveDir(source.getAbsolutePath(), dest.getAbsolutePath()); - } catch (Exception ex) { - if (usedMkdir) { - try { - Os.remove(dest.getAbsolutePath()); - } catch (ErrnoException ignored) { - } + /** + * Recursively set up directories and link all the files from source storage to target storage. + * + * @param sourceStorage The storage that has all the files and directories underneath. + * @param sourceAbsolutePath The absolute path of the directory that holds all files and dirs. + * @param sourceRelativePath The relative path on the source directory, e.g., "" or "lib". + * @param targetStorage The target storage that will have the same files and directories. + * @param targetRelativePath The relative path to the directory on the target storage that + * should have all the files and dirs underneath, + * e.g., "packageName-random". + * @throws IOException When makeDirectory or makeLink fails on the Incremental File System. + */ + private void linkFiles(IncrementalStorage sourceStorage, File sourceAbsolutePath, + String sourceRelativePath, IncrementalStorage targetStorage, + String targetRelativePath) throws IOException { + targetStorage.makeDirectory(targetRelativePath); + final File[] entryList = sourceAbsolutePath.listFiles(); + for (int i = 0; i < entryList.length; i++) { + final File entry = entryList[i]; + final String entryName = entryList[i].getName(); + final String sourceEntryRelativePath = + sourceRelativePath.isEmpty() ? entryName : sourceRelativePath + "/" + entryName; + final String targetEntryRelativePath = targetRelativePath + "/" + entryName; + if (entry.isFile()) { + sourceStorage.makeLink( + sourceEntryRelativePath, targetStorage, targetEntryRelativePath); + } else if (entry.isDirectory()) { + linkFiles(sourceStorage, entry, sourceEntryRelativePath, targetStorage, + targetEntryRelativePath); } - throw new IOException( - "Failed to move " + source.getAbsolutePath() + " to " + dest.getAbsolutePath()); } } @@ -325,6 +286,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) { @@ -332,5 +300,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/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index c4b843b6ce33..5df44ff49059 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -416,4 +416,24 @@ public final class IncrementalStorage { return false; } } + + /** + * Configure all the lib files inside Incremental Service, e.g., create lib dirs, create new lib + * files, extract original lib file data from zip and then write data to the lib files on the + * Incremental File System. + * + * @param apkFullPath Source APK to extract native libs from. + * @param libDirRelativePath Target dir to put lib files, e.g., "lib" or "lib/arm". + * @param abi Target ABI of the native lib files. Only extract native libs of this ABI. + * @return Success of not. + */ + public boolean configureNativeBinaries(String apkFullPath, String libDirRelativePath, + String abi) { + try { + return mService.configureNativeBinaries(mId, apkFullPath, libDirRelativePath, abi); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return false; + } + } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 30e4eee08e8d..b9207e5a12d5 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1830,6 +1830,17 @@ public final class Settings { public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID"; /** + * Activity Extra: The {@link NotificationChannel#getConversationId()} of the notification + * conversation settings to display. + * <p> + * This is an optional extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}. If + * included the system will first look up notification settings by channel and conversation id, + * and will fall back to channel id if a specialized channel for this conversation doesn't + * exist, similar to {@link NotificationManager#getNotificationChannel(String, String)}. + */ + public static final String EXTRA_CONVERSATION_ID = "android.provider.extra.CONVERSATION_ID"; + + /** * Activity Action: Show notification redaction settings. * * @hide diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index b6f5138a6582..adfa885f121e 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -5205,6 +5205,7 @@ public final class Telephony { /** * TelephonyProvider column name for allowed network types. Indicate which network types * are allowed. Default is -1. + * <P>Type: BIGINT (long) </P> */ public static final String ALLOWED_NETWORK_TYPES = "allowed_network_types"; } diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index 368c94cdc790..79852d3c0fca 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -31,7 +31,6 @@ import android.content.ComponentName; import android.content.Intent; import android.graphics.Rect; import android.os.Build; -import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; @@ -93,7 +92,7 @@ public abstract class AugmentedAutofillService extends Service { // Used for metrics / debug only private ComponentName mServiceComponentName; - private final IAugmentedAutofillService mInterface = new IAugmentedAutofillService.Stub() { + private final class AugmentedAutofillServiceImpl extends IAugmentedAutofillService.Stub { @Override public void onConnected(boolean debug, boolean verbose) { @@ -137,7 +136,7 @@ public abstract class AugmentedAutofillService extends Service { public final IBinder onBind(Intent intent) { mServiceComponentName = intent.getComponent(); if (SERVICE_INTERFACE.equals(intent.getAction())) { - return mInterface.asBinder(); + return new AugmentedAutofillServiceImpl(); } Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent); return null; @@ -352,11 +351,13 @@ public abstract class AugmentedAutofillService extends Service { static final int REPORT_EVENT_NO_RESPONSE = 1; static final int REPORT_EVENT_UI_SHOWN = 2; static final int REPORT_EVENT_UI_DESTROYED = 3; + static final int REPORT_EVENT_INLINE_RESPONSE = 4; @IntDef(prefix = { "REPORT_EVENT_" }, value = { REPORT_EVENT_NO_RESPONSE, REPORT_EVENT_UI_SHOWN, - REPORT_EVENT_UI_DESTROYED + REPORT_EVENT_UI_DESTROYED, + REPORT_EVENT_INLINE_RESPONSE }) @Retention(RetentionPolicy.SOURCE) @interface ReportEvent{} @@ -365,8 +366,8 @@ public abstract class AugmentedAutofillService extends Service { private final Object mLock = new Object(); private final IAugmentedAutofillManagerClient mClient; private final int mSessionId; - public final int taskId; - public final ComponentName componentName; + public final int mTaskId; + public final ComponentName mComponentName; // Used for metrics / debug only private String mServicePackageName; @GuardedBy("mLock") @@ -406,8 +407,8 @@ public abstract class AugmentedAutofillService extends Service { mSessionId = sessionId; mClient = IAugmentedAutofillManagerClient.Stub.asInterface(client); mCallback = callback; - this.taskId = taskId; - this.componentName = componentName; + mTaskId = taskId; + mComponentName = componentName; mServicePackageName = serviceComponentName.getPackageName(); mFocusedId = focusedId; mFocusedValue = focusedValue; @@ -514,22 +515,24 @@ public abstract class AugmentedAutofillService extends Service { } } - public void onInlineSuggestionsDataReady(@NonNull List<Dataset> inlineSuggestionsData, - @Nullable Bundle clientState) { + void reportResult(@Nullable List<Dataset> inlineSuggestionsData) { try { - mCallback.onSuccess(inlineSuggestionsData.toArray(new Dataset[]{}), clientState); + final Dataset[] inlineSuggestions = (inlineSuggestionsData != null) + ? inlineSuggestionsData.toArray(new Dataset[inlineSuggestionsData.size()]) + : null; + mCallback.onSuccess(inlineSuggestions); } catch (RemoteException e) { Log.e(TAG, "Error calling back with the inline suggestions data: " + e); } } - // Used (mostly) for metrics. - public void report(@ReportEvent int event) { - if (sVerbose) Log.v(TAG, "report(): " + event); + void logEvent(@ReportEvent int event) { + if (sVerbose) Log.v(TAG, "returnAndLogResult(): " + event); long duration = -1; int type = MetricsEvent.TYPE_UNKNOWN; + switch (event) { - case REPORT_EVENT_NO_RESPONSE: + case REPORT_EVENT_NO_RESPONSE: { type = MetricsEvent.TYPE_SUCCESS; if (mFirstOnSuccessTime == 0) { mFirstOnSuccessTime = SystemClock.elapsedRealtime(); @@ -538,40 +541,49 @@ public abstract class AugmentedAutofillService extends Service { Log.d(TAG, "Service responded nothing in " + formatDuration(duration)); } } - try { - mCallback.onSuccess(/* inlineSuggestionsData= */null, /* clientState=*/ - null); - } catch (RemoteException e) { - Log.e(TAG, "Error reporting success: " + e); + } break; + + case REPORT_EVENT_INLINE_RESPONSE: { + // TODO: Define a constant and log this event + // type = MetricsEvent.TYPE_SUCCESS_INLINE; + if (mFirstOnSuccessTime == 0) { + mFirstOnSuccessTime = SystemClock.elapsedRealtime(); + duration = mFirstOnSuccessTime - mFirstRequestTime; + if (sDebug) { + Log.d(TAG, "Service responded nothing in " + formatDuration(duration)); + } } - break; - case REPORT_EVENT_UI_SHOWN: + } break; + + case REPORT_EVENT_UI_SHOWN: { type = MetricsEvent.TYPE_OPEN; if (mUiFirstShownTime == 0) { mUiFirstShownTime = SystemClock.elapsedRealtime(); duration = mUiFirstShownTime - mFirstRequestTime; if (sDebug) Log.d(TAG, "UI shown in " + formatDuration(duration)); } - break; - case REPORT_EVENT_UI_DESTROYED: + } break; + + case REPORT_EVENT_UI_DESTROYED: { type = MetricsEvent.TYPE_CLOSE; if (mUiFirstDestroyedTime == 0) { mUiFirstDestroyedTime = SystemClock.elapsedRealtime(); - duration = mUiFirstDestroyedTime - mFirstRequestTime; + duration = mUiFirstDestroyedTime - mFirstRequestTime; if (sDebug) Log.d(TAG, "UI destroyed in " + formatDuration(duration)); } - break; + } break; + default: Log.w(TAG, "invalid event reported: " + event); } - logResponse(type, mServicePackageName, componentName, mSessionId, duration); + logResponse(type, mServicePackageName, mComponentName, mSessionId, duration); } public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { pw.print(prefix); pw.print("sessionId: "); pw.println(mSessionId); - pw.print(prefix); pw.print("taskId: "); pw.println(taskId); + pw.print(prefix); pw.print("taskId: "); pw.println(mTaskId); pw.print(prefix); pw.print("component: "); - pw.println(componentName.flattenToShortString()); + pw.println(mComponentName.flattenToShortString()); pw.print(prefix); pw.print("focusedId: "); pw.println(mFocusedId); if (mFocusedValue != null) { pw.print(prefix); pw.print("focusedValue: "); pw.println(mFocusedValue); diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java index d0ffd7b4d8d4..19eff57269ae 100644 --- a/core/java/android/service/autofill/augmented/FillCallback.java +++ b/core/java/android/service/autofill/augmented/FillCallback.java @@ -54,13 +54,15 @@ public final class FillCallback { if (sDebug) Log.d(TAG, "onSuccess(): " + response); if (response == null) { - mProxy.report(AutofillProxy.REPORT_EVENT_NO_RESPONSE); + mProxy.logEvent(AutofillProxy.REPORT_EVENT_NO_RESPONSE); + mProxy.reportResult(null /*inlineSuggestions*/); return; } List<Dataset> inlineSuggestions = response.getInlineSuggestions(); if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) { - mProxy.onInlineSuggestionsDataReady(inlineSuggestions, response.getClientState()); + mProxy.logEvent(AutofillProxy.REPORT_EVENT_INLINE_RESPONSE); + mProxy.reportResult(inlineSuggestions); return; } diff --git a/core/java/android/service/autofill/augmented/FillController.java b/core/java/android/service/autofill/augmented/FillController.java index 63ec2d886caf..7d552d62fa72 100644 --- a/core/java/android/service/autofill/augmented/FillController.java +++ b/core/java/android/service/autofill/augmented/FillController.java @@ -62,12 +62,13 @@ public final class FillController { try { mProxy.autofill(values); - final FillWindow fillWindow = mProxy.getFillWindow(); - if (fillWindow != null) { - fillWindow.destroy(); - } } catch (RemoteException e) { e.rethrowAsRuntimeException(); } + + final FillWindow fillWindow = mProxy.getFillWindow(); + if (fillWindow != null) { + fillWindow.destroy(); + } } } diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java index ca49e7daf054..6927cf6541e0 100644 --- a/core/java/android/service/autofill/augmented/FillRequest.java +++ b/core/java/android/service/autofill/augmented/FillRequest.java @@ -53,7 +53,7 @@ public final class FillRequest { * Gets the task of the activity associated with this request. */ public int getTaskId() { - return mProxy.taskId; + return mProxy.mTaskId; } /** @@ -61,7 +61,7 @@ public final class FillRequest { */ @NonNull public ComponentName getActivityComponent() { - return mProxy.componentName; + return mProxy.mComponentName; } /** diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java index 5d003706ac83..077df6cf16ef 100644 --- a/core/java/android/service/autofill/augmented/FillWindow.java +++ b/core/java/android/service/autofill/augmented/FillWindow.java @@ -21,6 +21,7 @@ import static android.service.autofill.augmented.AugmentedAutofillService.sVerbo import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.graphics.Rect; @@ -41,6 +42,7 @@ import com.android.internal.util.Preconditions; import dalvik.system.CloseGuard; import java.io.PrintWriter; +import java.lang.ref.WeakReference; /** * Handle to a window used to display the augmented autofill UI. @@ -70,23 +72,22 @@ public final class FillWindow implements AutoCloseable { private final CloseGuard mCloseGuard = CloseGuard.get(); private final @NonNull Handler mUiThreadHandler = new Handler(Looper.getMainLooper()); - private final @NonNull FillWindowPresenter mFillWindowPresenter = new FillWindowPresenter(); @GuardedBy("mLock") - private WindowManager mWm; + private @NonNull WindowManager mWm; @GuardedBy("mLock") private View mFillView; @GuardedBy("mLock") private boolean mShowing; @GuardedBy("mLock") - private Rect mBounds; + private @Nullable Rect mBounds; @GuardedBy("mLock") private boolean mUpdateCalled; @GuardedBy("mLock") private boolean mDestroyed; - private AutofillProxy mProxy; + private @NonNull AutofillProxy mProxy; /** * Updates the content of the window. @@ -172,11 +173,11 @@ public final class FillWindow implements AutoCloseable { try { mProxy.requestShowFillUi(mBounds.right - mBounds.left, mBounds.bottom - mBounds.top, - /*anchorBounds=*/ null, mFillWindowPresenter); + /*anchorBounds=*/ null, new FillWindowPresenter(this)); } catch (RemoteException e) { Log.w(TAG, "Error requesting to show fill window", e); } - mProxy.report(AutofillProxy.REPORT_EVENT_UI_SHOWN); + mProxy.logEvent(AutofillProxy.REPORT_EVENT_UI_SHOWN); } } } @@ -244,7 +245,7 @@ public final class FillWindow implements AutoCloseable { if (mUpdateCalled) { mFillView.setOnClickListener(null); hide(); - mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED); + mProxy.logEvent(AutofillProxy.REPORT_EVENT_UI_DESTROYED); } mDestroyed = true; mCloseGuard.close(); @@ -254,9 +255,7 @@ public final class FillWindow implements AutoCloseable { @Override protected void finalize() throws Throwable { try { - if (mCloseGuard != null) { - mCloseGuard.warnIfOpen(); - } + mCloseGuard.warnIfOpen(); destroy(); } finally { super.finalize(); @@ -289,22 +288,36 @@ public final class FillWindow implements AutoCloseable { /** @hide */ @Override - public void close() throws Exception { + public void close() { destroy(); } - private final class FillWindowPresenter extends IAutofillWindowPresenter.Stub { + private static final class FillWindowPresenter extends IAutofillWindowPresenter.Stub { + private final @NonNull WeakReference<FillWindow> mFillWindowReference; + + FillWindowPresenter(@NonNull FillWindow fillWindow) { + mFillWindowReference = new WeakReference<>(fillWindow); + } + @Override public void show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows, int layoutDirection) { if (sDebug) Log.d(TAG, "FillWindowPresenter.show()"); - mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleShow, FillWindow.this, p)); + final FillWindow fillWindow = mFillWindowReference.get(); + if (fillWindow != null) { + fillWindow.mUiThreadHandler.sendMessage( + obtainMessage(FillWindow::handleShow, fillWindow, p)); + } } @Override public void hide(Rect transitionEpicenter) { if (sDebug) Log.d(TAG, "FillWindowPresenter.hide()"); - mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleHide, FillWindow.this)); + final FillWindow fillWindow = mFillWindowReference.get(); + if (fillWindow != null) { + fillWindow.mUiThreadHandler.sendMessage( + obtainMessage(FillWindow::handleHide, fillWindow)); + } } } } diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl index 31e77f358782..d9837211be19 100644 --- a/core/java/android/service/autofill/augmented/IFillCallback.aidl +++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl @@ -28,7 +28,7 @@ import android.service.autofill.Dataset; */ interface IFillCallback { void onCancellable(in ICancellationSignal cancellation); - void onSuccess(in @nullable Dataset[] inlineSuggestionsData, in @nullable Bundle clientState); + void onSuccess(in @nullable Dataset[] inlineSuggestionsData); boolean isCompleted(); void cancel(); } 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/notification/ConversationChannelWrapper.aidl b/core/java/android/service/notification/ConversationChannelWrapper.aidl new file mode 100644 index 000000000000..f3241586e252 --- /dev/null +++ b/core/java/android/service/notification/ConversationChannelWrapper.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.service.notification; + +parcelable ConversationChannelWrapper;
\ No newline at end of file diff --git a/core/java/android/service/notification/ConversationChannelWrapper.java b/core/java/android/service/notification/ConversationChannelWrapper.java new file mode 100644 index 000000000000..9847695ca5ee --- /dev/null +++ b/core/java/android/service/notification/ConversationChannelWrapper.java @@ -0,0 +1,122 @@ +/** + * 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.service.notification; + +import android.app.NotificationChannel; +import android.content.pm.ShortcutInfo; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * @hide + */ +public final class ConversationChannelWrapper implements Parcelable { + + private NotificationChannel mNotificationChannel; + private CharSequence mGroupLabel; + private CharSequence mParentChannelLabel; + private ShortcutInfo mShortcutInfo; + + public ConversationChannelWrapper() {} + + protected ConversationChannelWrapper(Parcel in) { + mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader()); + mGroupLabel = in.readCharSequence(); + mParentChannelLabel = in.readCharSequence(); + mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader()); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mNotificationChannel, flags); + dest.writeCharSequence(mGroupLabel); + dest.writeCharSequence(mParentChannelLabel); + dest.writeParcelable(mShortcutInfo, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<ConversationChannelWrapper> CREATOR = + new Creator<ConversationChannelWrapper>() { + @Override + public ConversationChannelWrapper createFromParcel(Parcel in) { + return new ConversationChannelWrapper(in); + } + + @Override + public ConversationChannelWrapper[] newArray(int size) { + return new ConversationChannelWrapper[size]; + } + }; + + + public NotificationChannel getNotificationChannel() { + return mNotificationChannel; + } + + public void setNotificationChannel( + NotificationChannel notificationChannel) { + mNotificationChannel = notificationChannel; + } + + public CharSequence getGroupLabel() { + return mGroupLabel; + } + + public void setGroupLabel(CharSequence groupLabel) { + mGroupLabel = groupLabel; + } + + public CharSequence getParentChannelLabel() { + return mParentChannelLabel; + } + + public void setParentChannelLabel(CharSequence parentChannelLabel) { + mParentChannelLabel = parentChannelLabel; + } + + public ShortcutInfo getShortcutInfo() { + return mShortcutInfo; + } + + public void setShortcutInfo(ShortcutInfo shortcutInfo) { + mShortcutInfo = shortcutInfo; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConversationChannelWrapper that = (ConversationChannelWrapper) o; + return Objects.equals(getNotificationChannel(), that.getNotificationChannel()) && + Objects.equals(getGroupLabel(), that.getGroupLabel()) && + Objects.equals(getParentChannelLabel(), that.getParentChannelLabel()) && + Objects.equals(getShortcutInfo(), that.getShortcutInfo()); + } + + @Override + public int hashCode() { + return Objects.hash(getNotificationChannel(), getGroupLabel(), getParentChannelLabel(), + getShortcutInfo()); + } +} diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index e053ed58a82b..0ff2e0386121 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -214,6 +214,32 @@ public abstract class NotificationListenerService extends Service { public static final int REASON_TIMEOUT = 19; /** + * @hide + */ + @IntDef(prefix = "REASON_", value = { + REASON_CLICK, + REASON_CANCEL, + REASON_CANCEL_ALL, + REASON_ERROR, + REASON_PACKAGE_CHANGED, + REASON_USER_STOPPED, + REASON_PACKAGE_BANNED, + REASON_APP_CANCEL, + REASON_APP_CANCEL_ALL, + REASON_LISTENER_CANCEL, + REASON_LISTENER_CANCEL_ALL, + REASON_GROUP_SUMMARY_CANCELED, + REASON_GROUP_OPTIMIZATION, + REASON_PACKAGE_SUSPENDED, + REASON_PROFILE_TURNED_OFF, + REASON_UNAUTOBUNDLED, + REASON_CHANNEL_BANNED, + REASON_SNOOZED, + REASON_TIMEOUT + }) + public @interface NotificationCancelReason{}; + + /** * The full trim of the StatusBarNotification including all its features. * * @hide 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/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java index 848868a5532f..3ff6f549e337 100644 --- a/core/java/android/service/textclassifier/TextClassifierService.java +++ b/core/java/android/service/textclassifier/TextClassifierService.java @@ -41,6 +41,7 @@ import android.util.Slog; import android.view.textclassifier.ConversationActions; import android.view.textclassifier.SelectionEvent; import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextClassificationConstants; import android.view.textclassifier.TextClassificationContext; import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassificationSessionId; @@ -404,22 +405,27 @@ public abstract class TextClassifierService extends Service { */ @NonNull public static TextClassifier getDefaultTextClassifierImplementation(@NonNull Context context) { - final String defaultTextClassifierPackageName = - context.getPackageManager().getDefaultTextClassifierPackageName(); - if (TextUtils.isEmpty(defaultTextClassifierPackageName)) { - return TextClassifier.NO_OP; - } - if (defaultTextClassifierPackageName.equals(context.getPackageName())) { - throw new RuntimeException( - "The default text classifier itself should not call the" - + "getDefaultTextClassifierImplementation() method."); - } final TextClassificationManager tcm = context.getSystemService(TextClassificationManager.class); - if (tcm != null) { + if (tcm == null) { + return TextClassifier.NO_OP; + } + TextClassificationConstants settings = new TextClassificationConstants(); + if (settings.getUseDefaultTextClassifierAsDefaultImplementation()) { + final String defaultTextClassifierPackageName = + context.getPackageManager().getDefaultTextClassifierPackageName(); + if (TextUtils.isEmpty(defaultTextClassifierPackageName)) { + return TextClassifier.NO_OP; + } + if (defaultTextClassifierPackageName.equals(context.getPackageName())) { + throw new RuntimeException( + "The default text classifier itself should not call the" + + "getDefaultTextClassifierImplementation() method."); + } return tcm.getTextClassifier(TextClassifier.DEFAULT_SERVICE); + } else { + return tcm.getTextClassifier(TextClassifier.LOCAL); } - return TextClassifier.NO_OP; } /** @hide **/ diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 1966f17aaf35..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) @@ -249,21 +249,21 @@ public class AlwaysOnHotwordDetector { } /** - * The inclusive start of supported range. + * Get the beginning of the param range * - * @return start of range + * @return The inclusive start of the supported range. */ - public int start() { - return mModelParamRange.start; + public int getStart() { + return mModelParamRange.getStart(); } /** - * The inclusive end of supported range. + * Get the end of the param range * - * @return end of range + * @return The inclusive end of the supported range. */ - public int end() { - return mModelParamRange.end; + public int getEnd() { + return mModelParamRange.getEnd(); } @Override diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index 531ade04fe02..e65bd9f20ec4 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -412,23 +412,23 @@ public class PhoneStateListener { * domain. This indication does not necessarily indicate a change of service state, which should * be tracked via {@link #LISTEN_SERVICE_STATE}. * - * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling - * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or + * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). * * @see #onRegistrationFailed() */ - @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 0x40000000; /** * Listen for Barring Information for the current registered / camped cell. * - * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling - * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or + * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). * * @see #onBarringInfoChanged() */ - @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_BARRING_INFO = 0x80000000; /* diff --git a/core/java/android/timezone/CountryTimeZones.java b/core/java/android/timezone/CountryTimeZones.java index ab2c4fc1bf23..ee3a8a79d5d7 100644 --- a/core/java/android/timezone/CountryTimeZones.java +++ b/core/java/android/timezone/CountryTimeZones.java @@ -18,7 +18,6 @@ package android.timezone; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; import android.icu.util.TimeZone; import java.util.ArrayList; @@ -206,27 +205,47 @@ public final class CountryTimeZones { } /** - * Returns a time zone for the country, if there is one, that matches the desired properties. If - * there are multiple matches and the {@code bias} is one of them then it is returned, otherwise - * an arbitrary match is returned based on the {@link #getEffectiveTimeZoneMappingsAt(long)} - * ordering. + * Returns a time zone for the country, if there is one, that matches the supplied properties. + * If there are multiple matches and the {@code bias} is one of them then it is returned, + * otherwise an arbitrary match is returned based on the {@link + * #getEffectiveTimeZoneMappingsAt(long)} ordering. * + * @param whenMillis the UTC time to match against + * @param bias the time zone to prefer, can be {@code null} to indicate there is no preference * @param totalOffsetMillis the offset from UTC at {@code whenMillis} * @param isDst the Daylight Savings Time state at {@code whenMillis}. {@code true} means DST, - * {@code false} means not DST, {@code null} means unknown - * @param dstOffsetMillis the part of {@code totalOffsetMillis} contributed by DST, only used if - * {@code isDst} is {@code true}. The value can be {@code null} if the DST offset is - * unknown - * @param whenMillis the UTC time to match against - * @param bias the time zone to prefer, can be {@code null} + * {@code false} means not DST + * @return an {@link OffsetResult} with information about a matching zone, or {@code null} if + * there is no match */ @Nullable - public OffsetResult lookupByOffsetWithBias(int totalOffsetMillis, @Nullable Boolean isDst, - @SuppressLint("AutoBoxing") @Nullable Integer dstOffsetMillis, long whenMillis, - @Nullable TimeZone bias) { + public OffsetResult lookupByOffsetWithBias(long whenMillis, @Nullable TimeZone bias, + int totalOffsetMillis, boolean isDst) { libcore.timezone.CountryTimeZones.OffsetResult delegateOffsetResult = mDelegate.lookupByOffsetWithBias( - totalOffsetMillis, isDst, dstOffsetMillis, whenMillis, bias); + whenMillis, bias, totalOffsetMillis, isDst); + return delegateOffsetResult == null ? null : + new OffsetResult( + delegateOffsetResult.getTimeZone(), delegateOffsetResult.isOnlyMatch()); + } + + /** + * Returns a time zone for the country, if there is one, that matches the supplied properties. + * If there are multiple matches and the {@code bias} is one of them then it is returned, + * otherwise an arbitrary match is returned based on the {@link + * #getEffectiveTimeZoneMappingsAt(long)} ordering. + * + * @param whenMillis the UTC time to match against + * @param bias the time zone to prefer, can be {@code null} to indicate there is no preference + * @param totalOffsetMillis the offset from UTC at {@code whenMillis} + * @return an {@link OffsetResult} with information about a matching zone, or {@code null} if + * there is no match + */ + @Nullable + public OffsetResult lookupByOffsetWithBias(long whenMillis, @Nullable TimeZone bias, + int totalOffsetMillis) { + libcore.timezone.CountryTimeZones.OffsetResult delegateOffsetResult = + mDelegate.lookupByOffsetWithBias(whenMillis, bias, totalOffsetMillis); return delegateOffsetResult == null ? null : new OffsetResult( delegateOffsetResult.getTimeZone(), delegateOffsetResult.isOnlyMatch()); diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 36f4e5361ea6..4b3afbaada64 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -80,7 +80,7 @@ public class TimeUtils { return null; } CountryTimeZones.OffsetResult offsetResult = countryTimeZones.lookupByOffsetWithBias( - offsetMillis, isDst, null /* dstOffsetMillis */, whenMillis, bias); + whenMillis, bias, offsetMillis, isDst); return offsetResult != null ? offsetResult.getTimeZone() : null; } 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/CutoutSpecification.java b/core/java/android/view/CutoutSpecification.java new file mode 100644 index 000000000000..d21a9520e12c --- /dev/null +++ b/core/java/android/view/CutoutSpecification.java @@ -0,0 +1,486 @@ +/* + * 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; + +import static android.view.Gravity.BOTTOM; +import static android.view.Gravity.LEFT; +import static android.view.Gravity.RIGHT; +import static android.view.Gravity.TOP; + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Insets; +import android.graphics.Matrix; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.text.TextUtils; +import android.util.Log; +import android.util.PathParser; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Locale; +import java.util.Objects; + +/** + * In order to accept the cutout specification for all of edges in devices, the specification + * parsing method is extracted from + * {@link android.view.DisplayCutout#fromResourcesRectApproximation(Resources, int, int)} to be + * the specified class for parsing the specification. + * BNF definition: + * <ul> + * <li>Cutouts Specification = ([Cutout Delimiter],Cutout Specification) {...}, [Dp] ; </li> + * <li>Cutout Specification = [Vertical Position], (SVG Path Element), [Horizontal Position] + * [Bind Cutout] ;</li> + * <li>Vertical Position = "@bottom" | "@center_vertical" ;</li> + * <li>Horizontal Position = "@left" | "@right" ;</li> + * <li>Bind Cutout = "@bind_left_cutout" | "@bind_right_cutout" ;</li> + * <li>Cutout Delimiter = "@cutout" ;</li> + * <li>Dp = "@dp"</li> + * </ul> + * + * <ul> + * <li>Vertical position is top by default if there is neither "@bottom" nor "@center_vertical" + * </li> + * <li>Horizontal position is center horizontal by default if there is neither "@left" nor + * "@right".</li> + * <li>@bottom make the cutout piece bind to bottom edge.</li> + * <li>both of @bind_left_cutout and @bind_right_cutout are use to claim the cutout belong to + * left or right edge cutout.</li> + * </ul> + * + * @hide + */ +@VisibleForTesting(visibility = PACKAGE) +public class CutoutSpecification { + private static final String TAG = "CutoutSpecification"; + private static final boolean DEBUG = false; + + private static final int MINIMAL_ACCEPTABLE_PATH_LENGTH = "H1V1Z".length(); + + private static final char MARKER_START_CHAR = '@'; + private static final String DP_MARKER = MARKER_START_CHAR + "dp"; + + private static final String BOTTOM_MARKER = MARKER_START_CHAR + "bottom"; + private static final String RIGHT_MARKER = MARKER_START_CHAR + "right"; + private static final String LEFT_MARKER = MARKER_START_CHAR + "left"; + private static final String CUTOUT_MARKER = MARKER_START_CHAR + "cutout"; + private static final String CENTER_VERTICAL_MARKER = MARKER_START_CHAR + "center_vertical"; + + /* By default, it's top bound cutout. That's why TOP_BOUND_CUTOUT_MARKER is not defined */ + private static final String BIND_RIGHT_CUTOUT_MARKER = MARKER_START_CHAR + "bind_right_cutout"; + private static final String BIND_LEFT_CUTOUT_MARKER = MARKER_START_CHAR + "bind_left_cutout"; + + private final Path mPath; + private final Rect mLeftBound; + private final Rect mTopBound; + private final Rect mRightBound; + private final Rect mBottomBound; + private final Insets mInsets; + + private CutoutSpecification(@NonNull Parser parser) { + mPath = parser.mPath; + mLeftBound = parser.mLeftBound; + mTopBound = parser.mTopBound; + mRightBound = parser.mRightBound; + mBottomBound = parser.mBottomBound; + mInsets = parser.mInsets; + + if (DEBUG) { + Log.d(TAG, String.format(Locale.ENGLISH, + "left cutout = %s, top cutout = %s, right cutout = %s, bottom cutout = %s", + mLeftBound != null ? mLeftBound.toString() : "", + mTopBound != null ? mTopBound.toString() : "", + mRightBound != null ? mRightBound.toString() : "", + mBottomBound != null ? mBottomBound.toString() : "")); + } + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Path getPath() { + return mPath; + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Rect getLeftBound() { + return mLeftBound; + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Rect getTopBound() { + return mTopBound; + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Rect getRightBound() { + return mRightBound; + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Rect getBottomBound() { + return mBottomBound; + } + + /** + * To count the safe inset according to the cutout bounds and waterfall inset. + * + * @return the safe inset. + */ + @VisibleForTesting(visibility = PACKAGE) + @NonNull + public Rect getSafeInset() { + return mInsets.toRect(); + } + + private static int decideWhichEdge(boolean isTopEdgeShortEdge, + boolean isShortEdge, boolean isStart) { + return (isTopEdgeShortEdge) + ? ((isShortEdge) ? (isStart ? TOP : BOTTOM) : (isStart ? LEFT : RIGHT)) + : ((isShortEdge) ? (isStart ? LEFT : RIGHT) : (isStart ? TOP : BOTTOM)); + } + + /** + * The CutoutSpecification Parser. + */ + @VisibleForTesting(visibility = PACKAGE) + public static class Parser { + private final boolean mIsShortEdgeOnTop; + private final float mDensity; + private final int mDisplayWidth; + private final int mDisplayHeight; + private final Matrix mMatrix; + private Insets mInsets; + private int mSafeInsetLeft; + private int mSafeInsetTop; + private int mSafeInsetRight; + private int mSafeInsetBottom; + + private final Rect mTmpRect = new Rect(); + private final RectF mTmpRectF = new RectF(); + + private boolean mInDp; + + private Path mPath; + private Rect mLeftBound; + private Rect mTopBound; + private Rect mRightBound; + private Rect mBottomBound; + + private boolean mPositionFromLeft = false; + private boolean mPositionFromRight = false; + private boolean mPositionFromBottom = false; + private boolean mPositionFromCenterVertical = false; + + private boolean mBindLeftCutout = false; + private boolean mBindRightCutout = false; + private boolean mBindBottomCutout = false; + + private boolean mIsTouchShortEdgeStart; + private boolean mIsTouchShortEdgeEnd; + private boolean mIsCloserToStartSide; + + /** + * The constructor of the CutoutSpecification parser to parse the specification of cutout. + * @param density the display density. + * @param displayWidth the display width. + * @param displayHeight the display height. + */ + @VisibleForTesting(visibility = PACKAGE) + public Parser(float density, int displayWidth, int displayHeight) { + mDensity = density; + mDisplayWidth = displayWidth; + mDisplayHeight = displayHeight; + mMatrix = new Matrix(); + mIsShortEdgeOnTop = mDisplayWidth < mDisplayHeight; + } + + private void computeBoundsRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) { + mTmpRectF.setEmpty(); + p.computeBounds(mTmpRectF, false /* unused */); + mTmpRectF.round(inoutRect); + inoutRegion.op(inoutRect, Region.Op.UNION); + } + + private void resetStatus(StringBuilder sb) { + sb.setLength(0); + mPositionFromBottom = false; + mPositionFromLeft = false; + mPositionFromRight = false; + mPositionFromCenterVertical = false; + + mBindLeftCutout = false; + mBindRightCutout = false; + mBindBottomCutout = false; + } + + private void translateMatrix() { + final float offsetX; + if (mPositionFromRight) { + offsetX = mDisplayWidth; + } else if (mPositionFromLeft) { + offsetX = 0; + } else { + offsetX = mDisplayWidth / 2f; + } + + final float offsetY; + if (mPositionFromBottom) { + offsetY = mDisplayHeight; + } else if (mPositionFromCenterVertical) { + offsetY = mDisplayHeight / 2f; + } else { + offsetY = 0; + } + + mMatrix.reset(); + if (mInDp) { + mMatrix.postScale(mDensity, mDensity); + } + mMatrix.postTranslate(offsetX, offsetY); + } + + private int computeSafeInsets(int gravity, Rect rect) { + if (gravity == LEFT && rect.right > 0 && rect.right < mDisplayWidth) { + return rect.right; + } else if (gravity == TOP && rect.bottom > 0 && rect.bottom < mDisplayHeight) { + return rect.bottom; + } else if (gravity == RIGHT && rect.left > 0 && rect.left < mDisplayWidth) { + return mDisplayWidth - rect.left; + } else if (gravity == BOTTOM && rect.top > 0 && rect.top < mDisplayHeight) { + return mDisplayHeight - rect.top; + } + return 0; + } + + private void setSafeInset(int gravity, int inset) { + if (gravity == LEFT) { + mSafeInsetLeft = inset; + } else if (gravity == TOP) { + mSafeInsetTop = inset; + } else if (gravity == RIGHT) { + mSafeInsetRight = inset; + } else if (gravity == BOTTOM) { + mSafeInsetBottom = inset; + } + } + + private int getSafeInset(int gravity) { + if (gravity == LEFT) { + return mSafeInsetLeft; + } else if (gravity == TOP) { + return mSafeInsetTop; + } else if (gravity == RIGHT) { + return mSafeInsetRight; + } else if (gravity == BOTTOM) { + return mSafeInsetBottom; + } + return 0; + } + + @NonNull + private Rect onSetEdgeCutout(boolean isStart, boolean isShortEdge, @NonNull Rect rect) { + final int gravity; + if (isShortEdge) { + gravity = decideWhichEdge(mIsShortEdgeOnTop, true, isStart); + } else { + if (mIsTouchShortEdgeStart && mIsTouchShortEdgeEnd) { + gravity = decideWhichEdge(mIsShortEdgeOnTop, false, isStart); + } else if (mIsTouchShortEdgeStart || mIsTouchShortEdgeEnd) { + gravity = decideWhichEdge(mIsShortEdgeOnTop, true, + mIsCloserToStartSide); + } else { + gravity = decideWhichEdge(mIsShortEdgeOnTop, isShortEdge, isStart); + } + } + + int oldSafeInset = getSafeInset(gravity); + int newSafeInset = computeSafeInsets(gravity, rect); + if (oldSafeInset < newSafeInset) { + setSafeInset(gravity, newSafeInset); + } + + return new Rect(rect); + } + + private void setEdgeCutout(@NonNull Path newPath) { + if (mBindRightCutout && mRightBound == null) { + mRightBound = onSetEdgeCutout(false, !mIsShortEdgeOnTop, mTmpRect); + } else if (mBindLeftCutout && mLeftBound == null) { + mLeftBound = onSetEdgeCutout(true, !mIsShortEdgeOnTop, mTmpRect); + } else if (mBindBottomCutout && mBottomBound == null) { + mBottomBound = onSetEdgeCutout(false, mIsShortEdgeOnTop, mTmpRect); + } else if (!(mBindBottomCutout || mBindLeftCutout || mBindRightCutout) + && mTopBound == null) { + mTopBound = onSetEdgeCutout(true, mIsShortEdgeOnTop, mTmpRect); + } else { + return; + } + + if (mPath != null) { + mPath.addPath(newPath); + } else { + mPath = newPath; + } + } + + private void parseSvgPathSpec(Region region, String spec) { + if (TextUtils.length(spec) < MINIMAL_ACCEPTABLE_PATH_LENGTH) { + Log.e(TAG, "According to SVG definition, it shouldn't happen"); + return; + } + spec.trim(); + translateMatrix(); + + final Path newPath = PathParser.createPathFromPathData(spec); + newPath.transform(mMatrix); + computeBoundsRectAndAddToRegion(newPath, region, mTmpRect); + + if (DEBUG) { + Log.d(TAG, String.format(Locale.ENGLISH, + "hasLeft = %b, hasRight = %b, hasBottom = %b, hasCenterVertical = %b", + mPositionFromLeft, mPositionFromRight, mPositionFromBottom, + mPositionFromCenterVertical)); + Log.d(TAG, "region = " + region); + Log.d(TAG, "spec = \"" + spec + "\" rect = " + mTmpRect + " newPath = " + newPath); + } + + if (mTmpRect.isEmpty()) { + return; + } + + if (mIsShortEdgeOnTop) { + mIsTouchShortEdgeStart = mTmpRect.top <= 0; + mIsTouchShortEdgeEnd = mTmpRect.bottom >= mDisplayHeight; + mIsCloserToStartSide = mTmpRect.centerY() < mDisplayHeight / 2; + } else { + mIsTouchShortEdgeStart = mTmpRect.left <= 0; + mIsTouchShortEdgeEnd = mTmpRect.right >= mDisplayWidth; + mIsCloserToStartSide = mTmpRect.centerX() < mDisplayWidth / 2; + } + + setEdgeCutout(newPath); + } + + private void parseSpecWithoutDp(@NonNull String specWithoutDp) { + Region region = Region.obtain(); + StringBuilder sb = null; + int currentIndex = 0; + int lastIndex = 0; + while ((currentIndex = specWithoutDp.indexOf(MARKER_START_CHAR, lastIndex)) != -1) { + if (sb == null) { + sb = new StringBuilder(specWithoutDp.length()); + } + sb.append(specWithoutDp, lastIndex, currentIndex); + + if (specWithoutDp.startsWith(LEFT_MARKER, currentIndex)) { + if (!mPositionFromRight) { + mPositionFromLeft = true; + } + currentIndex += LEFT_MARKER.length(); + } else if (specWithoutDp.startsWith(RIGHT_MARKER, currentIndex)) { + if (!mPositionFromLeft) { + mPositionFromRight = true; + } + currentIndex += RIGHT_MARKER.length(); + } else if (specWithoutDp.startsWith(BOTTOM_MARKER, currentIndex)) { + if (!mPositionFromCenterVertical) { + parseSvgPathSpec(region, sb.toString()); + } + currentIndex += BOTTOM_MARKER.length(); + + /* prepare to parse the rest path */ + resetStatus(sb); + mBindBottomCutout = true; + mPositionFromBottom = true; + } else if (specWithoutDp.startsWith(CENTER_VERTICAL_MARKER, currentIndex)) { + if (!mPositionFromBottom) { + parseSvgPathSpec(region, sb.toString()); + } + currentIndex += CENTER_VERTICAL_MARKER.length(); + + /* prepare to parse the rest path */ + resetStatus(sb); + mPositionFromCenterVertical = true; + } else if (specWithoutDp.startsWith(CUTOUT_MARKER, currentIndex)) { + parseSvgPathSpec(region, sb.toString()); + currentIndex += CUTOUT_MARKER.length(); + + /* prepare to parse the rest path */ + resetStatus(sb); + } else if (specWithoutDp.startsWith(BIND_LEFT_CUTOUT_MARKER, currentIndex)) { + if (!mBindBottomCutout && !mBindRightCutout) { + mBindLeftCutout = true; + } + currentIndex += BIND_LEFT_CUTOUT_MARKER.length(); + } else if (specWithoutDp.startsWith(BIND_RIGHT_CUTOUT_MARKER, currentIndex)) { + if (!mBindBottomCutout && !mBindLeftCutout) { + mBindRightCutout = true; + } + currentIndex += BIND_RIGHT_CUTOUT_MARKER.length(); + } else { + currentIndex += 1; + } + + lastIndex = currentIndex; + } + + if (sb == null) { + parseSvgPathSpec(region, specWithoutDp); + } else { + sb.append(specWithoutDp, lastIndex, specWithoutDp.length()); + parseSvgPathSpec(region, sb.toString()); + } + + region.recycle(); + } + + /** + * To parse specification string as the CutoutSpecification. + * + * @param originalSpec the specification string + * @return the CutoutSpecification instance + */ + @VisibleForTesting(visibility = PACKAGE) + public CutoutSpecification parse(@NonNull String originalSpec) { + Objects.requireNonNull(originalSpec); + + int dpIndex = originalSpec.lastIndexOf(DP_MARKER); + mInDp = (dpIndex != -1); + final String spec; + if (dpIndex != -1) { + spec = originalSpec.substring(0, dpIndex) + + originalSpec.substring(dpIndex + DP_MARKER.length()); + } else { + spec = originalSpec; + } + + parseSpecWithoutDp(spec); + + mInsets = Insets.of(mSafeInsetLeft, mSafeInsetTop, mSafeInsetRight, mSafeInsetBottom); + return new CutoutSpecification(this); + } + } +} diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index d433591f2b3c..31fc16188814 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -31,18 +31,12 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Resources; import android.graphics.Insets; -import android.graphics.Matrix; import android.graphics.Path; import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Region; -import android.graphics.Region.Op; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import android.util.Log; import android.util.Pair; -import android.util.PathParser; import android.util.proto.ProtoOutputStream; import com.android.internal.R; @@ -63,10 +57,6 @@ import java.util.List; public final class DisplayCutout { private static final String TAG = "DisplayCutout"; - private static final String BOTTOM_MARKER = "@bottom"; - private static final String DP_MARKER = "@dp"; - private static final String RIGHT_MARKER = "@right"; - private static final String LEFT_MARKER = "@left"; /** * Category for overlays that allow emulating a display cutout on devices that don't have @@ -703,77 +693,16 @@ public final class DisplayCutout { } } - Path p = null; - Rect boundTop = null; - Rect boundBottom = null; - Rect safeInset = new Rect(); - String bottomSpec = null; - if (!TextUtils.isEmpty(spec)) { - spec = spec.trim(); - final float offsetX; - if (spec.endsWith(RIGHT_MARKER)) { - offsetX = displayWidth; - spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim(); - } else if (spec.endsWith(LEFT_MARKER)) { - offsetX = 0; - spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim(); - } else { - offsetX = displayWidth / 2f; - } - final boolean inDp = spec.endsWith(DP_MARKER); - if (inDp) { - spec = spec.substring(0, spec.length() - DP_MARKER.length()); - } - - if (spec.contains(BOTTOM_MARKER)) { - String[] splits = spec.split(BOTTOM_MARKER, 2); - spec = splits[0].trim(); - bottomSpec = splits[1].trim(); - } + spec = spec.trim(); - final Matrix m = new Matrix(); - final Region r = Region.obtain(); - if (!spec.isEmpty()) { - try { - p = PathParser.createPathFromPathData(spec); - } catch (Throwable e) { - Log.wtf(TAG, "Could not inflate cutout: ", e); - } - - if (p != null) { - if (inDp) { - m.postScale(density, density); - } - m.postTranslate(offsetX, 0); - p.transform(m); + CutoutSpecification cutoutSpec = new CutoutSpecification.Parser(density, + displayWidth, displayHeight).parse(spec); + Rect safeInset = cutoutSpec.getSafeInset(); + final Rect boundLeft = cutoutSpec.getLeftBound(); + final Rect boundTop = cutoutSpec.getTopBound(); + final Rect boundRight = cutoutSpec.getRightBound(); + final Rect boundBottom = cutoutSpec.getBottomBound(); - boundTop = new Rect(); - toRectAndAddToRegion(p, r, boundTop); - safeInset.top = boundTop.bottom; - } - } - - if (bottomSpec != null) { - int bottomInset = 0; - Path bottomPath = null; - try { - bottomPath = PathParser.createPathFromPathData(bottomSpec); - } catch (Throwable e) { - Log.wtf(TAG, "Could not inflate bottom cutout: ", e); - } - - if (bottomPath != null) { - // Keep top transform - m.postTranslate(0, displayHeight); - bottomPath.transform(m); - p.addPath(bottomPath); - boundBottom = new Rect(); - toRectAndAddToRegion(bottomPath, r, boundBottom); - bottomInset = displayHeight - boundBottom.top; - } - safeInset.bottom = bottomInset; - } - } if (!waterfallInsets.equals(Insets.NONE)) { safeInset.set( @@ -784,9 +713,9 @@ public final class DisplayCutout { } final DisplayCutout cutout = new DisplayCutout( - safeInset, waterfallInsets, null /* boundLeft */, boundTop, - null /* boundRight */, boundBottom, false /* copyArguments */); - final Pair<Path, DisplayCutout> result = new Pair<>(p, cutout); + safeInset, waterfallInsets, boundLeft, boundTop, + boundRight, boundBottom, false /* copyArguments */); + final Pair<Path, DisplayCutout> result = new Pair<>(cutoutSpec.getPath(), cutout); synchronized (CACHE_LOCK) { sCachedSpec = spec; sCachedDisplayWidth = displayWidth; @@ -798,14 +727,6 @@ public final class DisplayCutout { return result; } - private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) { - final RectF rectF = new RectF(); - p.computeBounds(rectF, false /* unused */); - rectF.round(inoutRect); - inoutRegion.op(inoutRect, Op.UNION); - } - - private static Insets loadWaterfallInset(Resources res) { return Insets.of( res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size), diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 22d6f37dbd95..54de1bb3739d 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -190,9 +190,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation onAnimationFinish(); } }); - setStartingAnimation(true); mAnimator.start(); - setStartingAnimation(false); } @Override @@ -203,9 +201,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } - protected void setStartingAnimation(boolean startingAnimation) { - } - protected void onAnimationFinish() { mController.finish(mShow); } @@ -239,16 +234,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final @AnimationType int type; } - private class DefaultAnimationControlListener extends InternalAnimationControlListener { - DefaultAnimationControlListener(boolean show) { - super(show); - } - - @Override - protected void setStartingAnimation(boolean startingAnimation) { - mStartingAnimation = startingAnimation; - } - } /** * Represents a control request that we had to defer because we are waiting for the IME to * process our show request. @@ -822,7 +807,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return; } - final DefaultAnimationControlListener listener = new DefaultAnimationControlListener(show); + final InternalAnimationControlListener listener = + new InternalAnimationControlListener(show); // Show/hide animations always need to be relative to the display frame, in order that shown // and hidden state insets are correct. controlAnimationUnchecked( @@ -878,7 +864,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return true; } mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds); + mStartingAnimation = true; listener.onReady(controller, types); + mStartingAnimation = false; return true; } }); diff --git a/core/java/android/os/StatsDimensionsValue.aidl b/core/java/android/view/SurfaceControlViewHost.aidl index 81a14a4b67d9..3b31ab834a51 100644 --- a/core/java/android/os/StatsDimensionsValue.aidl +++ b/core/java/android/view/SurfaceControlViewHost.aidl @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018, The Android Open Source Project + * 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. @@ -14,7 +14,6 @@ * limitations under the License. */ -package android.os; +package android.view; -/** @hide */ -parcelable StatsDimensionsValue cpp_header "android/os/StatsDimensionsValue.h";
\ No newline at end of file +parcelable SurfaceControlViewHost.SurfacePackage;
\ No newline at end of file diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 75d5538faedd..b1c354f6f717 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1570,7 +1570,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void reparentSurfacePackage(SurfaceControl.Transaction t, SurfaceControlViewHost.SurfacePackage p) { // TODO: Link accessibility IDs here. - t.reparent(p.getSurfaceControl(), mSurfaceControl); + final SurfaceControl sc = p.getSurfaceControl(); + t.reparent(sc, mSurfaceControl).show(sc); } /** diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a407bd8f001e..a6f8fad817e1 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -12729,12 +12729,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return findViewInsideOutShouldExist(root, mNextFocusForwardId); case FOCUS_BACKWARD: { if (mID == View.NO_ID) return null; - return root.findViewByPredicateInsideOut(this, new Predicate<View>() { - @Override - public boolean test(View t) { - return t.findViewById(t.mNextFocusForwardId) == View.this; - } - }); + final View rootView = root; + final View startView = this; + // Since we have forward links but no backward links, we need to find the view that + // forward links to this view. We can't just find the view with the specified ID + // because view IDs need not be unique throughout the tree. + return root.findViewByPredicateInsideOut(startView, + t -> findViewInsideOutShouldExist(rootView, t, t.mNextFocusForwardId) + == startView); } } return null; @@ -12764,11 +12766,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } private View findViewInsideOutShouldExist(View root, int id) { + return findViewInsideOutShouldExist(root, this, id); + } + + private View findViewInsideOutShouldExist(View root, View start, int id) { if (mMatchIdPredicate == null) { mMatchIdPredicate = new MatchIdPredicate(); } mMatchIdPredicate.mId = id; - View result = root.findViewByPredicateInsideOut(this, mMatchIdPredicate); + View result = root.findViewByPredicateInsideOut(start, mMatchIdPredicate); if (result == null) { Log.w(VIEW_LOG_TAG, "couldn't find view with id " + id); } 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/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 4365d1f5194a..56683dd9a5d1 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -277,7 +277,7 @@ public final class WindowManagerImpl implements WindowManager { .setStableInsets(Insets.of(stableInsets)) .setDisplayCutout(displayCutout.get()).build(); } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } - return null; } } 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/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index c80a1ae55228..7f90d572ce89 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -33,11 +33,11 @@ import android.util.Printer; import android.view.View; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; +import java.util.Objects; /** * An EditorInfo describes several attributes of a text editing object @@ -558,7 +558,7 @@ public class EditorInfo implements InputType, Parcelable { * editor wants to trim out the first 10 chars, subTextStart should be 10. */ public void setInitialSurroundingSubText(@NonNull CharSequence subText, int subTextStart) { - Preconditions.checkNotNull(subText); + Objects.requireNonNull(subText); // Swap selection start and end if necessary. final int subTextSelStart = initialSelStart > initialSelEnd @@ -585,25 +585,35 @@ public class EditorInfo implements InputType, Parcelable { return; } - // The input text is too long. Let's try to trim it reasonably. Fundamental rules are: - // 1. Text before the cursor is the most important information to IMEs. - // 2. Text after the cursor is the second important information to IMEs. - // 3. Selected text is the least important information but it shall NEVER be truncated. - // When it is too long, just drop it. - // - // Source: <TextBeforeCursor><Selection><TextAfterCursor> - // Possible results: - // 1. <(maybeTrimmedAtHead)TextBeforeCursor><Selection><TextAfterCursor(maybeTrimmedAtTail)> - // 2. <(maybeTrimmedAtHead)TextBeforeCursor><TextAfterCursor(maybeTrimmedAtTail)> - // - final int sourceSelLength = subTextSelEnd - subTextSelStart; + trimLongSurroundingText(subText, subTextSelStart, subTextSelEnd); + } + + /** + * Trims the initial surrounding text when it is over sized. Fundamental trimming rules are: + * - The text before the cursor is the most important information to IMEs. + * - The text after the cursor is the second important information to IMEs. + * - The selected text is the least important information but it shall NEVER be truncated. When + * it is too long, just drop it. + *<p><pre> + * For example, the subText can be viewed as + * TextBeforeCursor + Selection + TextAfterCursor + * The result could be + * 1. (maybeTrimmedAtHead)TextBeforeCursor + Selection + TextAfterCursor(maybeTrimmedAtTail) + * 2. (maybeTrimmedAtHead)TextBeforeCursor + TextAfterCursor(maybeTrimmedAtTail)</pre> + * + * @param subText The long text that needs to be trimmed. + * @param selStart The text offset of the start of the selection. + * @param selEnd The text offset of the end of the selection + */ + private void trimLongSurroundingText(CharSequence subText, int selStart, int selEnd) { + final int sourceSelLength = selEnd - selStart; // When the selected text is too long, drop it. final int newSelLength = (sourceSelLength > MAX_INITIAL_SELECTION_LENGTH) ? 0 : sourceSelLength; // Distribute rest of length quota to TextBeforeCursor and TextAfterCursor in 4:1 ratio. - final int subTextBeforeCursorLength = subTextSelStart; - final int subTextAfterCursorLength = subTextLength - subTextSelEnd; + final int subTextBeforeCursorLength = selStart; + final int subTextAfterCursorLength = subText.length() - selEnd; final int maxLengthMinusSelection = MEMORY_EFFICIENT_TEXT_LENGTH - newSelLength; final int possibleMaxBeforeCursorLength = Math.min(subTextBeforeCursorLength, (int) (0.8 * maxLengthMinusSelection)); @@ -617,24 +627,23 @@ public class EditorInfo implements InputType, Parcelable { // We don't want to cut surrogate pairs in the middle. Exam that at the new head and tail. if (isCutOnSurrogate(subText, - subTextSelStart - newBeforeCursorLength, TrimPolicy.HEAD)) { + selStart - newBeforeCursorLength, TrimPolicy.HEAD)) { newBeforeCursorHead = newBeforeCursorHead + 1; newBeforeCursorLength = newBeforeCursorLength - 1; } if (isCutOnSurrogate(subText, - subTextSelEnd + newAfterCursorLength - 1, TrimPolicy.TAIL)) { + selEnd + newAfterCursorLength - 1, TrimPolicy.TAIL)) { newAfterCursorLength = newAfterCursorLength - 1; } // Now we know where to trim, compose the initialSurroundingText. final int newTextLength = newBeforeCursorLength + newSelLength + newAfterCursorLength; - CharSequence newInitialSurroundingText; + final CharSequence newInitialSurroundingText; if (newSelLength != sourceSelLength) { final CharSequence beforeCursor = subText.subSequence(newBeforeCursorHead, newBeforeCursorHead + newBeforeCursorLength); - - final CharSequence afterCursor = subText.subSequence(subTextSelEnd, - subTextSelEnd + newAfterCursorLength); + final CharSequence afterCursor = subText.subSequence(selEnd, + selEnd + newAfterCursorLength); newInitialSurroundingText = TextUtils.concat(beforeCursor, afterCursor); } else { @@ -651,15 +660,16 @@ public class EditorInfo implements InputType, Parcelable { } /** - * Get <var>n</var> characters of text before the current cursor position. May be {@code null} - * when the protocol is not supported. + * Get <var>length</var> characters of text before the current cursor position. May be + * {@code null} when the protocol is not supported. * * @param length The expected length of the text. * @param flags Supplies additional options controlling how the text is returned. May be * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}. * @return the text before the cursor position; the length of the returned text might be less - * than <var>n</var>. When there is no text before the cursor, an empty string will be returned. - * It could also be {@code null} when the editor or system could not support this protocol. + * than <var>length</var>. When there is no text before the cursor, an empty string will be + * returned. It could also be {@code null} when the editor or system could not support this + * protocol. */ @Nullable public CharSequence getInitialTextBeforeCursor(int length, int flags) { @@ -667,8 +677,8 @@ public class EditorInfo implements InputType, Parcelable { } /** - * Gets the selected text, if any. May be {@code null} when no text is selected or the selected - * text is way too long. + * Gets the selected text, if any. May be {@code null} when the protocol is not supported or the + * selected text is way too long. * * @param flags Supplies additional options controlling how the text is returned. May be * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}. @@ -693,15 +703,16 @@ public class EditorInfo implements InputType, Parcelable { } /** - * Get <var>n</var> characters of text after the current cursor position. May be {@code null} - * when the protocol is not supported. + * Get <var>length</var> characters of text after the current cursor position. May be + * {@code null} when the protocol is not supported. * * @param length The expected length of the text. * @param flags Supplies additional options controlling how the text is returned. May be * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}. * @return the text after the cursor position; the length of the returned text might be less - * than <var>n</var>. When there is no text after the cursor, an empty string will be returned. - * It could also be {@code null} when the editor or system could not support this protocol. + * than <var>length</var>. When there is no text after the cursor, an empty string will be + * returned. It could also be {@code null} when the editor or system could not support this + * protocol. */ @Nullable public CharSequence getInitialTextAfterCursor(int length, int flags) { @@ -863,7 +874,6 @@ public class EditorInfo implements InputType, Parcelable { return 0; } - // TODO(b/148035211): Unit tests for this class static final class InitialSurroundingText implements Parcelable { @Nullable final CharSequence mSurroundingText; final int mSelectionHead; 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/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index f0c16aadf12f..dbab81b129a9 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -664,8 +664,6 @@ public final class InputMethodManager { */ @Override public void setCurrentRootView(ViewRootImpl rootView) { - // If the mCurRootView is losing window focus, release the strong reference to it - // so as not to prevent it from being garbage-collected. if (mWindowFocusGainFuture != null) { mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */); mWindowFocusGainFuture = null; diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java index ed69513b7f69..3d5ac58e7704 100644 --- a/core/java/android/view/textclassifier/TextClassificationConstants.java +++ b/core/java/android/view/textclassifier/TextClassificationConstants.java @@ -17,6 +17,7 @@ package android.view.textclassifier; import android.annotation.Nullable; +import android.content.Context; import android.provider.DeviceConfig; import com.android.internal.annotations.VisibleForTesting; @@ -167,6 +168,16 @@ public final class TextClassificationConstants { static final String TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE = "textclassifier_service_package_override"; + /** + * Whether to use the default system text classifier as the default text classifier + * implementation. The local text classifier is used if it is {@code false}. + * + * @see android.service.textclassifier.TextClassifierService#getDefaultTextClassifierImplementation(Context) + */ + // TODO: Once the system health experiment is done, remove this together with local TC. + private static final String USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL = + "use_default_system_text_classifier_as_default_impl"; + private static final String DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE = null; private static final boolean LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT = true; private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT = true; @@ -209,7 +220,8 @@ public final class TextClassificationConstants { private static final boolean TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT = true; private static final boolean TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT = true; private static final boolean DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT = true; - private static final float[] LANG_ID_CONTEXT_SETTINGS_DEFAULT = new float[] {20f, 1.0f, 0.4f}; + private static final float[] LANG_ID_CONTEXT_SETTINGS_DEFAULT = new float[]{20f, 1.0f, 0.4f}; + private static final boolean USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL_DEFAULT = true; @Nullable public String getTextClassifierServicePackageOverride() { @@ -331,6 +343,13 @@ public final class TextClassificationConstants { LANG_ID_CONTEXT_SETTINGS, LANG_ID_CONTEXT_SETTINGS_DEFAULT); } + public boolean getUseDefaultTextClassifierAsDefaultImplementation() { + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL, + USE_DEFAULT_SYSTEM_TEXT_CLASSIFIER_AS_DEFAULT_IMPL_DEFAULT); + } + void dump(IndentingPrintWriter pw) { pw.println("TextClassificationConstants:"); pw.increaseIndent(); @@ -378,6 +397,8 @@ public final class TextClassificationConstants { .println(); pw.printPair("textclassifier_service_package_override", getTextClassifierServicePackageOverride()).println(); + pw.printPair("use_default_system_text_classifier_as_default_impl", + getUseDefaultTextClassifierAsDefaultImplementation()).println(); pw.decreaseIndent(); } diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java index a6c83a1cfa76..dfbec9b67027 100644 --- a/core/java/android/view/textclassifier/TextClassificationManager.java +++ b/core/java/android/view/textclassifier/TextClassificationManager.java @@ -25,7 +25,6 @@ import android.content.Context; import android.os.ServiceManager; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; -import android.util.SparseArray; import android.view.textclassifier.TextClassifier.TextClassifierType; import com.android.internal.annotations.GuardedBy; @@ -62,8 +61,6 @@ public final class TextClassificationManager { @Nullable private TextClassifier mLocalTextClassifier; @GuardedBy("mLock") - private SparseArray<TextClassifier> mSystemTextClassifiers = new SparseArray<>(); - @GuardedBy("mLock") private TextClassificationSessionFactory mSessionFactory; @GuardedBy("mLock") private TextClassificationConstants mSettings; @@ -207,23 +204,17 @@ public final class TextClassificationManager { /** @hide */ private TextClassifier getSystemTextClassifier(@TextClassifierType int type) { synchronized (mLock) { - if (mSystemTextClassifiers.get(type) == null - && getSettings().isSystemTextClassifierEnabled()) { + if (getSettings().isSystemTextClassifierEnabled()) { try { - mSystemTextClassifiers.put( - type, - new SystemTextClassifier( - mContext, - getSettings(), - /* useDefault= */ type == TextClassifier.DEFAULT_SERVICE)); - Log.d(LOG_TAG, "Initialized SystemTextClassifier, type = " + type); + Log.d(LOG_TAG, "Initializing SystemTextClassifier, type = " + type); + return new SystemTextClassifier( + mContext, + getSettings(), + /* useDefault= */ type == TextClassifier.DEFAULT_SERVICE); } catch (ServiceManager.ServiceNotFoundException e) { Log.e(LOG_TAG, "Could not initialize SystemTextClassifier", e); } } - if (mSystemTextClassifiers.get(type) != null) { - return mSystemTextClassifiers.get(type); - } return TextClassifier.NO_OP; } } @@ -263,7 +254,6 @@ public final class TextClassificationManager { private void invalidateTextClassifiers() { synchronized (mLock) { mLocalTextClassifier = null; - mSystemTextClassifiers.clear(); } } 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/AbsListView.java b/core/java/android/widget/AbsListView.java index 4752eadfe385..9f03d956f22c 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -756,9 +756,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te */ private ListItemAccessibilityDelegate mAccessibilityDelegate; - private int mLastAccessibilityScrollEventFromIndex; - private int mLastAccessibilityScrollEventToIndex; - /** * Track the item count from the last time we handled a data change. */ @@ -1520,25 +1517,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these. } - /** @hide */ - @Override - public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { - // Since this class calls onScrollChanged even if the mFirstPosition and the - // child count have not changed we will avoid sending duplicate accessibility - // events. - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { - final int firstVisiblePosition = getFirstVisiblePosition(); - final int lastVisiblePosition = getLastVisiblePosition(); - if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition - && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) { - return; - } else { - mLastAccessibilityScrollEventFromIndex = firstVisiblePosition; - mLastAccessibilityScrollEventToIndex = lastVisiblePosition; - } - } - super.sendAccessibilityEventUnchecked(event); - } + /** + * A TYPE_VIEW_SCROLLED event should be sent whenever a scroll happens, even if the + * mFirstPosition and the child count have not changed. + */ @Override public CharSequence getAccessibilityClassName() { diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 7fd41508dfc3..4e4a9837b32c 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -454,15 +454,17 @@ public class Editor { aspectRatio = 5.5f; } - final Paint.FontMetrics fontMetrics = mTextView.getPaint().getFontMetrics(); - final float sourceHeight = fontMetrics.descent - fontMetrics.ascent; + final Layout layout = mTextView.getLayout(); + final int line = layout.getLineForOffset(mTextView.getSelectionStart()); + final int sourceHeight = + layout.getLineBottomWithoutSpacing(line) - layout.getLineTop(line); // Slightly increase the height to avoid tooLargeTextForMagnifier() returns true. int height = (int)(sourceHeight * zoom) + 2; int width = (int)(aspectRatio * height); params.setFishEyeStyle() .setSize(width, height) - .setSourceSize(width, Math.round(sourceHeight)) + .setSourceSize(width, sourceHeight) .setElevation(0) .setInitialZoom(zoom) .setClippingEnabled(false); @@ -5041,7 +5043,7 @@ public class Editor { // Vertically snap to middle of current line. showPosInView.y = ((mTextView.getLayout().getLineTop(lineNumber) - + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f + + mTextView.getLayout().getLineBottomWithoutSpacing(lineNumber)) / 2.0f + mTextView.getTotalPaddingTop() - mTextView.getScrollY()) * mTextViewScaleY; return true; } diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 79ec680f4c52..3c3daa3c7001 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -3255,6 +3255,9 @@ public class ListView extends AbsListView { */ @UnsupportedAppUsage private void scrollListItemsBy(int amount) { + int oldX = mScrollX; + int oldY = mScrollY; + offsetChildrenTopAndBottom(amount); final int listBottom = getHeight() - mListPadding.bottom; @@ -3327,6 +3330,7 @@ public class ListView extends AbsListView { recycleBin.fullyDetachScrapViews(); removeUnusedFixedViews(mHeaderViewInfos); removeUnusedFixedViews(mFooterViewInfos); + onScrollChanged(mScrollX, mScrollY, oldX, oldY); } private View addViewAbove(View theView, int position) { 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/android/widget/Toast.java b/core/java/android/widget/Toast.java index 78d4e61ea953..8c52d1fadc52 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -148,6 +148,9 @@ public class Toast { @Nullable private CharSequence mText; + // TODO(b/144152069): Remove this after assessing impact on dogfood. + private boolean mIsCustomToast; + /** * Construct an empty Toast object. You must call {@link #setView} before you * can call {@link #show}. @@ -214,7 +217,8 @@ public class Toast { service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback); } } else { - service.enqueueToast(pkg, mToken, tn, mDuration, displayId); + service.enqueueTextOrCustomToast(pkg, mToken, tn, mDuration, displayId, + mIsCustomToast); } } catch (RemoteException e) { // Empty @@ -252,12 +256,22 @@ public class Toast { */ @Deprecated public void setView(View view) { + mIsCustomToast = true; mNextView = view; } /** * Return the view. * + * <p>Toasts constructed with {@link #Toast(Context)} that haven't called {@link #setView(View)} + * with a non-{@code null} view will return {@code null} here. + * + * <p>Starting from Android {@link Build.VERSION_CODES#R}, in apps targeting API level {@link + * Build.VERSION_CODES#R} or higher, toasts constructed with {@link #makeText(Context, + * CharSequence, int)} or its variants will also return {@code null} here unless they had called + * {@link #setView(View)} with a non-{@code null} view. If you want to be notified when the + * toast is shown or hidden, use {@link #addCallback(Callback)}. + * * @see #setView * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the * {@link #makeText(Context, CharSequence, int)} method, or use a @@ -266,7 +280,8 @@ public class Toast { * targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background * will not have custom toast views displayed. */ - public View getView() { + @Deprecated + @Nullable public View getView() { return mNextView; } @@ -292,6 +307,10 @@ public class Toast { /** * Set the margins of the view. * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher, this method is a no-op when + * called on text toasts. + * * @param horizontalMargin The horizontal margin, in percentage of the * container width, between the container's edges and the * notification @@ -300,30 +319,59 @@ public class Toast { * notification */ public void setMargin(float horizontalMargin, float verticalMargin) { + if (isSystemRenderedTextToast()) { + Log.e(TAG, "setMargin() shouldn't be called on text toasts, the values won't be used"); + } mTN.mHorizontalMargin = horizontalMargin; mTN.mVerticalMargin = verticalMargin; } /** * Return the horizontal margin. + * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called + * on text toasts as its return value may not reflect actual value since text toasts are not + * rendered by the app anymore. */ public float getHorizontalMargin() { + if (isSystemRenderedTextToast()) { + Log.e(TAG, "getHorizontalMargin() shouldn't be called on text toasts, the result may " + + "not reflect actual values."); + } return mTN.mHorizontalMargin; } /** * Return the vertical margin. + * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called + * on text toasts as its return value may not reflect actual value since text toasts are not + * rendered by the app anymore. */ public float getVerticalMargin() { + if (isSystemRenderedTextToast()) { + Log.e(TAG, "getVerticalMargin() shouldn't be called on text toasts, the result may not" + + " reflect actual values."); + } return mTN.mVerticalMargin; } /** * Set the location at which the notification should appear on the screen. + * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher, this method is a no-op when + * called on text toasts. + * * @see android.view.Gravity * @see #getGravity */ public void setGravity(int gravity, int xOffset, int yOffset) { + if (isSystemRenderedTextToast()) { + Log.e(TAG, "setGravity() shouldn't be called on text toasts, the values won't be used"); + } mTN.mGravity = gravity; mTN.mX = xOffset; mTN.mY = yOffset; @@ -331,27 +379,59 @@ public class Toast { /** * Get the location at which the notification should appear on the screen. + * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called + * on text toasts as its return value may not reflect actual value since text toasts are not + * rendered by the app anymore. + * * @see android.view.Gravity * @see #getGravity */ public int getGravity() { + if (isSystemRenderedTextToast()) { + Log.e(TAG, "getGravity() shouldn't be called on text toasts, the result may not reflect" + + " actual values."); + } return mTN.mGravity; } /** * Return the X offset in pixels to apply to the gravity's location. + * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called + * on text toasts as its return value may not reflect actual value since text toasts are not + * rendered by the app anymore. */ public int getXOffset() { + if (isSystemRenderedTextToast()) { + Log.e(TAG, "getXOffset() shouldn't be called on text toasts, the result may not reflect" + + " actual values."); + } return mTN.mX; } /** * Return the Y offset in pixels to apply to the gravity's location. + * + * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps + * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called + * on text toasts as its return value may not reflect actual value since text toasts are not + * rendered by the app anymore. */ public int getYOffset() { + if (isSystemRenderedTextToast()) { + Log.e(TAG, "getYOffset() shouldn't be called on text toasts, the result may not reflect" + + " actual values."); + } return mTN.mY; } + private boolean isSystemRenderedTextToast() { + return Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM) && mNextView == null; + } + /** * Adds a callback to be notified when the toast is shown or hidden. * @@ -385,7 +465,7 @@ public class Toast { } /** - * Make a standard toast that just contains a text view. + * Make a standard toast that just contains text. * * @param context The context to use. Usually your {@link android.app.Application} * or {@link android.app.Activity} object. @@ -428,7 +508,7 @@ public class Toast { } /** - * Make a standard toast that just contains a text view with the text from a resource. + * Make a standard toast that just contains text from a resource. * * @param context The context to use. Usually your {@link android.app.Application} * or {@link android.app.Activity} object. 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/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index a43e4fe118a9..65cad834d5be 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -2424,6 +2424,10 @@ public class ChooserActivity extends ResolverActivity implements offset += findViewById(R.id.content_preview_container).getHeight(); } + if (hasWorkProfile() && ENABLE_TABBED_VIEW) { + offset += findViewById(R.id.tabs).getHeight(); + } + int directShareHeight = 0; rowsToShow = Math.min(4, rowsToShow); for (int i = 0, childCount = recyclerView.getChildCount(); 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..0ccc45e000e6 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -25,6 +25,7 @@ import static android.system.OsConstants.S_IRWXU; import static android.system.OsConstants.S_IXGRP; import static android.system.OsConstants.S_IXOTH; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; @@ -32,7 +33,12 @@ import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.parsing.AndroidPackage; import android.os.Build; +import android.os.IBinder; import android.os.SELinux; +import android.os.ServiceManager; +import android.os.incremental.IIncrementalService; +import android.os.incremental.IncrementalManager; +import android.os.incremental.IncrementalStorage; import android.system.ErrnoException; import android.system.Os; import android.util.Slog; @@ -44,6 +50,7 @@ import java.io.Closeable; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; +import java.nio.file.Path; import java.util.List; /** @@ -73,6 +80,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 +111,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 +126,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 +137,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 +325,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 +389,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 +400,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 +424,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 +481,61 @@ 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; + private static int incrementalConfigureNativeBinariesForSupportedAbi(Handle handle, + File libSubDir, String abi) { + final String[] apkPaths = handle.apkPaths; + if (apkPaths == null || apkPaths.length == 0) { + Slog.e(TAG, "No apks to extract native libraries from."); + return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; } - // 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; + final IBinder incrementalService = ServiceManager.getService(Context.INCREMENTAL_SERVICE); + if (incrementalService == null) { + //TODO(b/133435829): add incremental specific error codes + return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + } + final IncrementalManager incrementalManager = new IncrementalManager( + IIncrementalService.Stub.asInterface(incrementalService)); + final File apkParent = new File(apkPaths[0]).getParentFile(); + IncrementalStorage incrementalStorage = + incrementalManager.openStorage(apkParent.getAbsolutePath()); + if (incrementalStorage == null) { + Slog.e(TAG, "Failed to find incremental storage"); + return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; } - return abi; + String libRelativeDir = getRelativePath(apkParent, libSubDir); + if (libRelativeDir == null) { + return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + } + + for (int i = 0; i < apkPaths.length; i++) { + if (!incrementalStorage.configureNativeBinaries(apkPaths[i], libRelativeDir, abi)) { + return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + } + } + return PackageManager.INSTALL_SUCCEEDED; + } + + private static String getRelativePath(File base, File target) { + try { + final Path basePath = base.toPath(); + final Path targetPath = target.toPath(); + final Path relativePath = basePath.relativize(targetPath); + if (relativePath.toString().isEmpty()) { + return ""; + } + return relativePath.toString(); + } catch (IllegalArgumentException ex) { + Slog.e(TAG, "Failed to find relative path between: " + base.getAbsolutePath() + + " and: " + target.getAbsolutePath()); + return null; + } } // We don't care about the other return values for now. diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java index 48d2bc2ae58d..67ffd4d93404 100644 --- a/core/java/com/android/internal/logging/UiEventLogger.java +++ b/core/java/com/android/internal/logging/UiEventLogger.java @@ -56,8 +56,8 @@ public interface UiEventLogger { * @param event an enum implementing UiEventEnum interface. * @param uid the uid of the relevant app, if known (0 otherwise). * @param packageName the package name of the relevant app, if known (null otherwise). - * @param instance An identifier obtained from an InstanceIdSequence. + * @param instance An identifier obtained from an InstanceIdSequence. If null, reduces to log(). */ void logWithInstanceId(@NonNull UiEventEnum event, int uid, @Nullable String packageName, - @NonNull InstanceId instance); + @Nullable InstanceId instance); } diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java index 785b2edf2e1b..4d171ec8a3a8 100644 --- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java +++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java @@ -41,9 +41,11 @@ public class UiEventLoggerImpl implements UiEventLogger { public void logWithInstanceId(UiEventEnum event, int uid, String packageName, InstanceId instance) { final int eventID = event.getId(); - if (eventID > 0) { + if ((eventID > 0) && (instance != null)) { FrameworkStatsLog.write(FrameworkStatsLog.UI_EVENT_REPORTED, eventID, uid, packageName, instance.getId()); + } else { + log(event, uid, packageName); } } } diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index f5a19fe73a86..6d2d7356a2e8 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -52,6 +52,7 @@ public class VpnConfig implements Parcelable { public static final String DIALOGS_PACKAGE = "com.android.vpndialogs"; + // TODO: Rename this to something that encompasses Settings-based Platform VPNs as well. public static final String LEGACY_VPN = "[Legacy VPN]"; public static Intent getIntentForConfirmation() { diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index c6ed624d73ac..518911e652f6 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -323,7 +323,7 @@ public class RuntimeInit { result.append(System.getProperty("java.vm.version")); // such as 1.1.0 result.append(" (Linux; U; Android "); - String version = Build.VERSION.RELEASE; // "1.0" or "3.4b5" + String version = Build.VERSION.RELEASE_OR_CODENAME; // "1.0" or "3.4b5" result.append(version.length() > 0 ? version : "1.0"); // add the model for the release build diff --git a/core/java/com/android/internal/policy/TaskResizingAlgorithm.java b/core/java/com/android/internal/policy/TaskResizingAlgorithm.java new file mode 100644 index 000000000000..1fce098fb93e --- /dev/null +++ b/core/java/com/android/internal/policy/TaskResizingAlgorithm.java @@ -0,0 +1,179 @@ +/* + * 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.internal.policy; + +import android.annotation.IntDef; +import android.graphics.Point; +import android.graphics.Rect; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Given a move coordinate (x, y), the original taks bounds and relevant details, calculate the new + * bounds. + * + * @hide + */ +public class TaskResizingAlgorithm { + + @IntDef(flag = true, + value = { + CTRL_NONE, + CTRL_LEFT, + CTRL_RIGHT, + CTRL_TOP, + CTRL_BOTTOM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CtrlType {} + + public static final int CTRL_NONE = 0x0; + public static final int CTRL_LEFT = 0x1; + public static final int CTRL_RIGHT = 0x2; + public static final int CTRL_TOP = 0x4; + public static final int CTRL_BOTTOM = 0x8; + + // The minimal aspect ratio which needs to be met to count as landscape (or 1/.. for portrait). + // Note: We do not use the 1.33 from the CDD here since the user is allowed to use what ever + // aspect he desires. + @VisibleForTesting + public static final float MIN_ASPECT = 1.2f; + + /** + * Given a (x, y) point and its original starting down point and its original bounds, calculate + * and return a new resized bound. + * @param x the new moved X point. + * @param y the new moved Y point. + * @param startDragX the original starting X point. + * @param startDragY the original starting Y point. + * @param originalBounds the original bound before resize. + * @param ctrlType The type of resize operation. + * @param minVisibleWidth The minimal width required for the new size. + * @param minVisibleHeight The minimal height required for the new size. + * @param maxVisibleSize The maximum size allowed. + * @param preserveOrientation + * @param startOrientationWasLandscape + * @return + */ + public static Rect resizeDrag(float x, float y, float startDragX, float startDragY, + Rect originalBounds, int ctrlType, int minVisibleWidth, int minVisibleHeight, + Point maxVisibleSize, boolean preserveOrientation, + boolean startOrientationWasLandscape) { + // This is a resizing operation. + // We need to keep various constraints: + // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y] + // 2. The orientation is kept - if required. + final int deltaX = Math.round(x - startDragX); + final int deltaY = Math.round(y - startDragY); + int left = originalBounds.left; + int top = originalBounds.top; + int right = originalBounds.right; + int bottom = originalBounds.bottom; + + // Calculate the resulting width and height of the drag operation. + int width = right - left; + int height = bottom - top; + if ((ctrlType & CTRL_LEFT) != 0) { + width = Math.max(minVisibleWidth, width - deltaX); + } else if ((ctrlType & CTRL_RIGHT) != 0) { + width = Math.max(minVisibleWidth, width + deltaX); + } + if ((ctrlType & CTRL_TOP) != 0) { + height = Math.max(minVisibleHeight, height - deltaY); + } else if ((ctrlType & CTRL_BOTTOM) != 0) { + height = Math.max(minVisibleHeight, height + deltaY); + } + + // If we have to preserve the orientation - check that we are doing so. + final float aspect = (float) width / (float) height; + if (preserveOrientation && ((startOrientationWasLandscape && aspect < MIN_ASPECT) + || (!startOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) { + // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major + // drag axis. What ever is producing the bigger rectangle will be chosen. + int width1; + int width2; + int height1; + int height2; + if (startOrientationWasLandscape) { + // Assuming that the width is our target we calculate the height. + width1 = Math.max(minVisibleWidth, Math.min(maxVisibleSize.x, width)); + height1 = Math.min(height, Math.round((float) width1 / MIN_ASPECT)); + if (height1 < minVisibleHeight) { + // If the resulting height is too small we adjust to the minimal size. + height1 = minVisibleHeight; + width1 = Math.max(minVisibleWidth, + Math.min(maxVisibleSize.x, Math.round((float) height1 * MIN_ASPECT))); + } + // Assuming that the height is our target we calculate the width. + height2 = Math.max(minVisibleHeight, Math.min(maxVisibleSize.y, height)); + width2 = Math.max(width, Math.round((float) height2 * MIN_ASPECT)); + if (width2 < minVisibleWidth) { + // If the resulting width is too small we adjust to the minimal size. + width2 = minVisibleWidth; + height2 = Math.max(minVisibleHeight, + Math.min(maxVisibleSize.y, Math.round((float) width2 / MIN_ASPECT))); + } + } else { + // Assuming that the width is our target we calculate the height. + width1 = Math.max(minVisibleWidth, Math.min(maxVisibleSize.x, width)); + height1 = Math.max(height, Math.round((float) width1 * MIN_ASPECT)); + if (height1 < minVisibleHeight) { + // If the resulting height is too small we adjust to the minimal size. + height1 = minVisibleHeight; + width1 = Math.max(minVisibleWidth, + Math.min(maxVisibleSize.x, Math.round((float) height1 / MIN_ASPECT))); + } + // Assuming that the height is our target we calculate the width. + height2 = Math.max(minVisibleHeight, Math.min(maxVisibleSize.y, height)); + width2 = Math.min(width, Math.round((float) height2 / MIN_ASPECT)); + if (width2 < minVisibleWidth) { + // If the resulting width is too small we adjust to the minimal size. + width2 = minVisibleWidth; + height2 = Math.max(minVisibleHeight, + Math.min(maxVisibleSize.y, Math.round((float) width2 * MIN_ASPECT))); + } + } + + // Use the bigger of the two rectangles if the major change was positive, otherwise + // do the opposite. + final boolean grows = width > (right - left) || height > (bottom - top); + if (grows == (width1 * height1 > width2 * height2)) { + width = width1; + height = height1; + } else { + width = width2; + height = height2; + } + } + + // Generate the final bounds by keeping the opposite drag edge constant. + if ((ctrlType & CTRL_LEFT) != 0) { + left = right - width; + } else { // Note: The right might have changed - if we pulled at the right or not. + right = left + width; + } + if ((ctrlType & CTRL_TOP) != 0) { + top = bottom - height; + } else { // Note: The height might have changed - if we pulled at the bottom or not. + bottom = top + height; + } + return new Rect(left, top, right, bottom); + } +} diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 74b481c938c3..2fcc1de776fb 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; @@ -902,7 +903,6 @@ public class SystemConfig { } break; case "component-override": { readComponentOverrides(parser, permFile); - XmlUtils.skipCurrentTag(parser); } break; case "backup-transport-whitelisted-service": { if (allowFeatures) { @@ -1156,6 +1156,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); } @@ -1398,8 +1402,7 @@ public class SystemConfig { final int depth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, depth)) { - String name = parser.getName(); - if ("component".equals(name)) { + if ("component".equals(parser.getName())) { String clsname = parser.getAttributeValue(null, "class"); String enabled = parser.getAttributeValue(null, "enabled"); if (clsname == null) { @@ -1427,8 +1430,6 @@ public class SystemConfig { } componentEnabledStates.put(clsname, !"false".equals(enabled)); - } else { - XmlUtils.skipCurrentTag(parser); } } } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index cec68df3c949..8959d6fb845e 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -35,8 +35,13 @@ cc_library_shared { "android_animation_PropertyValuesHolder.cpp", "android_os_SystemClock.cpp", "android_os_SystemProperties.cpp", + "android_os_Trace.cpp", + "android_text_AndroidCharacter.cpp", "android_util_EventLog.cpp", "android_util_Log.cpp", + "android_util_StringBlock.cpp", + "android_util_XmlBlock.cpp", + "android_view_RenderNodeAnimator.cpp", "com_android_internal_util_VirtualRefBasePtr.cpp", "com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp", ], @@ -114,14 +119,12 @@ cc_library_shared { "android_view_KeyEvent.cpp", "android_view_MotionEvent.cpp", "android_view_PointerIcon.cpp", - "android_view_RenderNodeAnimator.cpp", "android_view_Surface.cpp", "android_view_SurfaceControl.cpp", "android_graphics_BLASTBufferQueue.cpp", "android_view_SurfaceSession.cpp", "android_view_TextureView.cpp", "android_view_VelocityTracker.cpp", - "android_text_AndroidCharacter.cpp", "android_text_Hyphenator.cpp", "android_os_Debug.cpp", "android_os_GraphicsEnvironment.cpp", @@ -138,7 +141,6 @@ cc_library_shared { "android_os_SELinux.cpp", "android_os_SharedMemory.cpp", "android_os_storage_StorageManager.cpp", - "android_os_Trace.cpp", "android_os_UEventObserver.cpp", "android_os_VintfObject.cpp", "android_os_VintfRuntimeInfo.cpp", @@ -150,10 +152,8 @@ cc_library_shared { "android_util_Binder.cpp", "android_util_MemoryIntArray.cpp", "android_util_Process.cpp", - "android_util_StringBlock.cpp", - "android_util_XmlBlock.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", @@ -182,6 +182,7 @@ cc_library_shared { "android_hardware_UsbRequest.cpp", "android_hardware_location_ActivityRecognitionHardware.cpp", "android_util_FileObserver.cpp", + "android/graphics/GraphicsStatsService.cpp", "android/graphics/SurfaceTexture.cpp", "android/opengl/poly_clip.cpp", // TODO: .arm "android/opengl/util.cpp", @@ -208,6 +209,7 @@ cc_library_shared { static_libs: [ "libasync_safe", + "libbinderthreadstateutils", "libdmabufinfo", "libgif", "libseccomp_policy", @@ -272,6 +274,7 @@ cc_library_shared { "libstats_jni", "libstatslog", "server_configurable_flags", + "libstatspull", ], export_shared_lib_headers: [ // AndroidRuntime.h depends on nativehelper/jni.h @@ -311,11 +314,8 @@ cc_library_shared { srcs: [ "android_content_res_ApkAssets.cpp", "android_os_MessageQueue.cpp", - "android_os_Trace.cpp", "android_util_AssetManager.cpp", "android_util_FileObserver.cpp", - "android_util_StringBlock.cpp", - "android_util_XmlBlock.cpp", ], }, }, @@ -360,7 +360,6 @@ cc_library_static { "android_view_RenderNode.cpp", "android_util_PathParser.cpp", - "android/graphics/AnimatedImageDrawable.cpp", "android/graphics/Bitmap.cpp", "android/graphics/BitmapFactory.cpp", "android/graphics/ByteBufferStreamAdaptor.cpp", @@ -436,6 +435,7 @@ cc_library_static { "android_view_TextureLayer.cpp", "android_view_ThreadedRenderer.cpp", + "android/graphics/AnimatedImageDrawable.cpp", "android/graphics/BitmapRegionDecoder.cpp", "android/graphics/GIFMovie.cpp", "android/graphics/Movie.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 481be241cc99..b47b7e39d1e8 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); @@ -209,9 +209,11 @@ static const char* kNoGenerationalCCRuntimeOption = "-Xgc:nogenerational_cc"; static const char* PROFILE_BOOT_CLASS_PATH = "profilebootclasspath"; // Feature flag name for running the JIT in Zygote experiment, b/119800099. -static const char* ENABLE_APEX_IMAGE = "enable_apex_image"; -// Flag to pass to the runtime when using the apex image. -static const char* kApexImageOption = "-Ximage:/system/framework/apex.art"; +// TODO: Rename the server-level flag or remove. +static const char* ENABLE_JITZYGOTE_IMAGE = "enable_apex_image"; +// Flag to pass to the runtime when using the JIT Zygote image. +static const char* kJitZygoteImageOption = + "-Ximage:boot.art:/nonx/boot-framework.art!/system/etc/boot-image.prof"; // Feature flag name for disabling lock profiling. static const char* DISABLE_LOCK_PROFILING = "disable_lock_profiling"; @@ -631,11 +633,11 @@ 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]; - char dex2oatXmsFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX]; - char dex2oatXmxFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX]; char dex2oatCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX]; char dex2oatImageCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX]; char dex2oatThreadsBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX]; @@ -689,16 +691,16 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p addOption("-Xjitsaveprofilinginfo"); } - std::string use_apex_image_flag = - server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE, - ENABLE_APEX_IMAGE, - /*default_value=*/ ""); + std::string use_jitzygote_image_flag = + server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE, + ENABLE_JITZYGOTE_IMAGE, + /*default_value=*/""); // Use the APEX boot image for boot class path profiling to get JIT samples on BCP methods. // Also use the APEX boot image if it's explicitly enabled via configuration flag. - const bool use_apex_image = profile_boot_class_path || (use_apex_image_flag == "true"); + const bool use_apex_image = profile_boot_class_path || (use_jitzygote_image_flag == "true"); if (use_apex_image) { - addOption(kApexImageOption); - ALOGI("Using Apex boot image: '%s'\n", kApexImageOption); + ALOGI("Using JIT Zygote image: '%s'\n", kJitZygoteImageOption); + addOption(kJitZygoteImageOption); } else if (parseRuntimeOption("dalvik.vm.boot-image", bootImageBuf, "-Ximage:")) { ALOGI("Using dalvik.vm.boot-image: '%s'\n", bootImageBuf); } else { @@ -786,7 +788,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. */ @@ -885,88 +895,45 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p bool skip_compilation = ((strcmp(voldDecryptBuf, "trigger_restart_min_framework") == 0) || (strcmp(voldDecryptBuf, "1") == 0)); - // Extra options for boot.art/boot.oat image generation. - parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xms", dex2oatXmsImageFlagsBuf, - "-Xms", "-Ximage-compiler-option"); - parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xmx", dex2oatXmxImageFlagsBuf, - "-Xmx", "-Ximage-compiler-option"); - if (skip_compilation) { - addOption("-Ximage-compiler-option"); - addOption("--compiler-filter=assume-verified"); - } else { - parseCompilerOption("dalvik.vm.image-dex2oat-filter", dex2oatImageCompilerFilterBuf, - "--compiler-filter=", "-Ximage-compiler-option"); - } - - // If there is a boot profile, it takes precedence over the image and preloaded classes. - if (hasFile("/system/etc/boot-image.prof")) { - addOption("-Ximage-compiler-option"); - addOption("--profile-file=/system/etc/boot-image.prof"); - addOption("-Ximage-compiler-option"); - addOption("--compiler-filter=speed-profile"); - } else { - ALOGE("Missing boot-image.prof file, /system/etc/boot-image.prof not found: %s\n", - strerror(errno)); - return -1; - } - - - // If there is a dirty-image-objects file, push it. - if (hasFile("/system/etc/dirty-image-objects")) { - addOption("-Ximage-compiler-option"); - addOption("--dirty-image-objects=/system/etc/dirty-image-objects"); - } - - property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, ""); - parseExtraOpts(dex2oatImageFlagsBuf, "-Ximage-compiler-option"); - - // Extra options for DexClassLoader. - parseCompilerRuntimeOption("dalvik.vm.dex2oat-Xms", dex2oatXmsFlagsBuf, - "-Xms", "-Xcompiler-option"); - parseCompilerRuntimeOption("dalvik.vm.dex2oat-Xmx", dex2oatXmxFlagsBuf, - "-Xmx", "-Xcompiler-option"); + // Extra options for JIT. if (skip_compilation) { addOption("-Xcompiler-option"); addOption("--compiler-filter=assume-verified"); - - // We skip compilation when a minimal runtime is brought up for decryption. In that case - // /data is temporarily backed by a tmpfs, which is usually small. - // If the system image contains prebuilts, they will be relocated into the tmpfs. In this - // specific situation it is acceptable to *not* relocate and run out of the prebuilts - // directly instead. - addOption("--runtime-arg"); - addOption("-Xnorelocate"); } else { parseCompilerOption("dalvik.vm.dex2oat-filter", dex2oatCompilerFilterBuf, "--compiler-filter=", "-Xcompiler-option"); } parseCompilerOption("dalvik.vm.dex2oat-threads", dex2oatThreadsBuf, "-j", "-Xcompiler-option"); - parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j", - "-Ximage-compiler-option"); parseCompilerOption("dalvik.vm.dex2oat-cpu-set", dex2oatCpuSetBuf, "--cpu-set=", "-Xcompiler-option"); - parseCompilerOption("dalvik.vm.image-dex2oat-cpu-set", dex2oatCpuSetImageBuf, "--cpu-set=", - "-Ximage-compiler-option"); - - // The runtime will compile a boot image, when necessary, not using installd. Thus, we need to - // pass the instruction-set-features/variant as an image-compiler-option. - // Note: it is OK to reuse the buffer, as the values are exactly the same between - // * compiler-option, used for runtime compilation (DexClassLoader) - // * image-compiler-option, used for boot-image compilation on device // Copy the variant. sprintf(dex2oat_isa_variant_key, "dalvik.vm.isa.%s.variant", ABI_STRING); parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant, - "--instruction-set-variant=", "-Ximage-compiler-option"); - parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant, "--instruction-set-variant=", "-Xcompiler-option"); // Copy the features. sprintf(dex2oat_isa_features_key, "dalvik.vm.isa.%s.features", ABI_STRING); parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features, - "--instruction-set-features=", "-Ximage-compiler-option"); - parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features, "--instruction-set-features=", "-Xcompiler-option"); + /* + * When running with debug.generate-debug-info, add --generate-debug-info to + * the compiler options so that both JITted code and the boot image extension, + * if it is compiled on device, will include native debugging information. + */ + property_get("debug.generate-debug-info", propBuf, ""); + bool generate_debug_info = (strcmp(propBuf, "true") == 0); + if (generate_debug_info) { + addOption("-Xcompiler-option"); + addOption("--generate-debug-info"); + } + + // The mini-debug-info makes it possible to backtrace through compiled code. + bool generate_mini_debug_info = property_get_bool("dalvik.vm.minidebuginfo", 0); + if (generate_mini_debug_info) { + addOption("-Xcompiler-option"); + addOption("--generate-mini-debug-info"); + } property_get("dalvik.vm.dex2oat-flags", dex2oatFlagsBuf, ""); parseExtraOpts(dex2oatFlagsBuf, "-Xcompiler-option"); @@ -975,6 +942,53 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p property_get("dalvik.vm.extra-opts", extraOptsBuf, ""); parseExtraOpts(extraOptsBuf, NULL); + // Extra options for boot image extension generation. + if (skip_compilation) { + addOption("-Xnoimage-dex2oat"); + } else { + parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xms", dex2oatXmsImageFlagsBuf, + "-Xms", "-Ximage-compiler-option"); + parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xmx", dex2oatXmxImageFlagsBuf, + "-Xmx", "-Ximage-compiler-option"); + + parseCompilerOption("dalvik.vm.image-dex2oat-filter", dex2oatImageCompilerFilterBuf, + "--compiler-filter=", "-Ximage-compiler-option"); + + // If there is a dirty-image-objects file, push it. + if (hasFile("/system/etc/dirty-image-objects")) { + addOption("-Ximage-compiler-option"); + addOption("--dirty-image-objects=/system/etc/dirty-image-objects"); + } + + parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j", + "-Ximage-compiler-option"); + parseCompilerOption("dalvik.vm.image-dex2oat-cpu-set", dex2oatCpuSetImageBuf, "--cpu-set=", + "-Ximage-compiler-option"); + + // The runtime may compile a boot image extension, when necessary, not using installd. + // Thus, we need to pass the instruction-set-features/variant as an image-compiler-option. + // Note: it is OK to reuse the buffer, as the values are exactly the same between + // * compiler-option, used for runtime compilation (DexClassLoader) + // * image-compiler-option, used for boot-image compilation on device + parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant, + "--instruction-set-variant=", "-Ximage-compiler-option"); + parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features, + "--instruction-set-features=", "-Ximage-compiler-option"); + + if (generate_debug_info) { + addOption("-Ximage-compiler-option"); + addOption("--generate-debug-info"); + } + + if (generate_mini_debug_info) { + addOption("-Ximage-compiler-option"); + addOption("--generate-mini-debug-info"); + } + + property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, ""); + parseExtraOpts(dex2oatImageFlagsBuf, "-Ximage-compiler-option"); + } + /* Set the properties for locale */ { strcpy(localeOption, "-Duser.locale="); @@ -1032,25 +1046,6 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p parseRuntimeOption("dalvik.vm.zygote.max-boot-retry", cachePruneBuf, "-Xzygote-max-boot-retry="); - /* - * When running with debug.generate-debug-info, add --generate-debug-info to - * the compiler options so that the boot image, if it is compiled on device, - * will include native debugging information. - */ - property_get("debug.generate-debug-info", propBuf, ""); - if (strcmp(propBuf, "true") == 0) { - addOption("-Xcompiler-option"); - addOption("--generate-debug-info"); - addOption("-Ximage-compiler-option"); - addOption("--generate-debug-info"); - } - - // The mini-debug-info makes it possible to backtrace through JIT code. - if (property_get_bool("dalvik.vm.minidebuginfo", 0)) { - addOption("-Xcompiler-option"); - addOption("--generate-mini-debug-info"); - } - // If set, the property below can be used to enable core platform API violation reporting. property_get("persist.debug.dalvik.vm.core_platform_api_policy", propBuf, ""); if (propBuf[0] != '\0') { @@ -1441,140 +1436,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/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp index 6c0680fe6707..571a33879411 100644 --- a/core/jni/LayoutlibLoader.cpp +++ b/core/jni/LayoutlibLoader.cpp @@ -75,10 +75,12 @@ extern int register_android_os_MessageQueue(JNIEnv* env); extern int register_android_os_SystemClock(JNIEnv* env); extern int register_android_os_SystemProperties(JNIEnv* env); extern int register_android_os_Trace(JNIEnv* env); +extern int register_android_text_AndroidCharacter(JNIEnv* env); extern int register_android_util_EventLog(JNIEnv* env); extern int register_android_util_Log(JNIEnv* env); extern int register_android_util_PathParser(JNIEnv* env); extern int register_android_view_RenderNode(JNIEnv* env); +extern int register_android_view_RenderNodeAnimator(JNIEnv* env); extern int register_android_view_DisplayListCanvas(JNIEnv* env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); extern int register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper(JNIEnv *env); @@ -90,58 +92,66 @@ struct RegJNIRec { // Map of all possible class names to register to their corresponding JNI registration function pointer // The actual list of registered classes will be determined at runtime via the 'native_classes' System property -static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { - {"android.animation.PropertyValuesHolder", REG_JNI(register_android_animation_PropertyValuesHolder)}, +static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { + {"android.animation.PropertyValuesHolder", + REG_JNI(register_android_animation_PropertyValuesHolder)}, #ifdef __linux__ - {"android.content.AssetManager", REG_JNI(register_android_content_AssetManager)}, - {"android.content.StringBlock", REG_JNI(register_android_content_StringBlock)}, - {"android.content.XmlBlock", REG_JNI(register_android_content_XmlBlock)}, - {"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)}, + {"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)}, + {"android.content.res.AssetManager", REG_JNI(register_android_content_AssetManager)}, #endif - {"android.graphics.Bitmap", REG_JNI(register_android_graphics_Bitmap)}, - {"android.graphics.BitmapFactory", REG_JNI(register_android_graphics_BitmapFactory)}, - {"android.graphics.ByteBufferStreamAdaptor", REG_JNI(register_android_graphics_ByteBufferStreamAdaptor)}, - {"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)}, - {"android.graphics.RenderNode", REG_JNI(register_android_view_RenderNode)}, - {"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)}, - {"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)}, - {"android.graphics.CreateJavaOutputStreamAdaptor", REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor)}, - {"android.graphics.DrawFilter", REG_JNI(register_android_graphics_DrawFilter)}, - {"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)}, - {"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)}, - {"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)}, - {"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)}, - {"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)}, - {"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)}, - {"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)}, - {"android.graphics.Path", REG_JNI(register_android_graphics_Path)}, - {"android.graphics.PathEffect", REG_JNI(register_android_graphics_PathEffect)}, - {"android.graphics.PathMeasure", REG_JNI(register_android_graphics_PathMeasure)}, - {"android.graphics.Picture", REG_JNI(register_android_graphics_Picture)}, - {"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)}, - {"android.graphics.Region", REG_JNI(register_android_graphics_Region)}, - {"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)}, - {"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)}, - {"android.graphics.drawable.AnimatedVectorDrawable", REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)}, - {"android.graphics.drawable.VectorDrawable", REG_JNI(register_android_graphics_drawable_VectorDrawable)}, - {"android.graphics.fonts.Font", REG_JNI(register_android_graphics_fonts_Font)}, - {"android.graphics.fonts.FontFamily", REG_JNI(register_android_graphics_fonts_FontFamily)}, - {"android.graphics.text.LineBreaker", REG_JNI(register_android_graphics_text_LineBreaker)}, - {"android.graphics.text.MeasuredText", REG_JNI(register_android_graphics_text_MeasuredText)}, + {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)}, + {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)}, + {"android.graphics.Bitmap", REG_JNI(register_android_graphics_Bitmap)}, + {"android.graphics.BitmapFactory", REG_JNI(register_android_graphics_BitmapFactory)}, + {"android.graphics.ByteBufferStreamAdaptor", + REG_JNI(register_android_graphics_ByteBufferStreamAdaptor)}, + {"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)}, + {"android.graphics.RenderNode", REG_JNI(register_android_view_RenderNode)}, + {"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)}, + {"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)}, + {"android.graphics.CreateJavaOutputStreamAdaptor", + REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor)}, + {"android.graphics.DrawFilter", REG_JNI(register_android_graphics_DrawFilter)}, + {"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)}, + {"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)}, + {"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)}, + {"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)}, + {"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)}, + {"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)}, + {"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)}, + {"android.graphics.Path", REG_JNI(register_android_graphics_Path)}, + {"android.graphics.PathEffect", REG_JNI(register_android_graphics_PathEffect)}, + {"android.graphics.PathMeasure", REG_JNI(register_android_graphics_PathMeasure)}, + {"android.graphics.Picture", REG_JNI(register_android_graphics_Picture)}, + {"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)}, + {"android.graphics.Region", REG_JNI(register_android_graphics_Region)}, + {"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)}, + {"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)}, + {"android.graphics.drawable.AnimatedVectorDrawable", + REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)}, + {"android.graphics.drawable.VectorDrawable", + REG_JNI(register_android_graphics_drawable_VectorDrawable)}, + {"android.graphics.fonts.Font", REG_JNI(register_android_graphics_fonts_Font)}, + {"android.graphics.fonts.FontFamily", REG_JNI(register_android_graphics_fonts_FontFamily)}, + {"android.graphics.text.LineBreaker", REG_JNI(register_android_graphics_text_LineBreaker)}, + {"android.graphics.text.MeasuredText", + REG_JNI(register_android_graphics_text_MeasuredText)}, #ifdef __linux__ - {"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)}, - {"android.os.MessageQueue", REG_JNI(register_android_os_MessageQueue)}, + {"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)}, + {"android.os.MessageQueue", REG_JNI(register_android_os_MessageQueue)}, #endif - {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)}, - {"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)}, -#ifdef __linux__ - {"android.os.Trace", REG_JNI(register_android_os_Trace)}, -#endif - {"android.util.EventLog", REG_JNI(register_android_util_EventLog)}, - {"android.util.Log", REG_JNI(register_android_util_Log)}, - {"android.util.PathParser", REG_JNI(register_android_util_PathParser)}, - {"com.android.internal.util.VirtualRefBasePtr", REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)}, - {"com.android.internal.view.animation.NativeInterpolatorFactoryHelper", REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper)}, + {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)}, + {"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)}, + {"android.os.Trace", REG_JNI(register_android_os_Trace)}, + {"android.text.AndroidCharacter", REG_JNI(register_android_text_AndroidCharacter)}, + {"android.util.EventLog", REG_JNI(register_android_util_EventLog)}, + {"android.util.Log", REG_JNI(register_android_util_Log)}, + {"android.util.PathParser", REG_JNI(register_android_util_PathParser)}, + {"android.view.RenderNodeAnimator", REG_JNI(register_android_view_RenderNodeAnimator)}, + {"com.android.internal.util.VirtualRefBasePtr", + REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)}, + {"com.android.internal.view.animation.NativeInterpolatorFactoryHelper", + REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper)}, }; // Vector to store the names of classes that need delegates of their native methods static vector<string> classesToDelegate; @@ -159,7 +169,6 @@ static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { - string classNameString = string(className); if (find(classesToDelegate.begin(), classesToDelegate.end(), classNameString) != classesToDelegate.end()) { 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/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index aa209cb3899e..38fb8bdc1f7a 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -569,8 +569,8 @@ bool RecyclingClippingPixelAllocator::allocPixelRef(SkBitmap* bitmap) { // mRecycledBitmap specifies the width and height of the bitmap that we // want to reuse. Neither can be changed. We will try to find a way // to reuse the memory. - const int maxWidth = SkTMax(bitmap->width(), mRecycledBitmap->info().width()); - const int maxHeight = SkTMax(bitmap->height(), mRecycledBitmap->info().height()); + const int maxWidth = std::max(bitmap->width(), mRecycledBitmap->info().width()); + const int maxHeight = std::max(bitmap->height(), mRecycledBitmap->info().height()); const SkImageInfo maxInfo = bitmap->info().makeWH(maxWidth, maxHeight); const size_t rowBytes = maxInfo.minRowBytes(); const size_t bytesNeeded = maxInfo.computeByteSize(rowBytes); diff --git a/core/jni/android/graphics/GraphicsStatsService.cpp b/core/jni/android/graphics/GraphicsStatsService.cpp new file mode 100644 index 000000000000..ef0aacc4d9ec --- /dev/null +++ b/core/jni/android/graphics/GraphicsStatsService.cpp @@ -0,0 +1,195 @@ +/* + * 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. + */ + +#define LOG_TAG "GraphicsStatsService" + +#include <JankTracker.h> +#include <jni.h> +#include <log/log.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedPrimitiveArray.h> +#include <nativehelper/ScopedUtfChars.h> +#include <service/GraphicsStatsService.h> +#include <stats_event.h> +#include <stats_pull_atom_callback.h> +#include <statslog.h> +#include "core_jni_helpers.h" + +namespace android { + +using namespace android::uirenderer; + +static jint getAshmemSize(JNIEnv*, jobject) { + return sizeof(ProfileData); +} + +static jlong createDump(JNIEnv*, jobject, jint fd, jboolean isProto) { + GraphicsStatsService::Dump* dump = + GraphicsStatsService::createDump(fd, + isProto ? GraphicsStatsService::DumpType::Protobuf + : GraphicsStatsService::DumpType::Text); + return reinterpret_cast<jlong>(dump); +} + +static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage, + jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { + std::string path; + const ProfileData* data = nullptr; + LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null"); + ScopedByteArrayRO buffer{env}; + if (jdata != nullptr) { + buffer.reset(jdata); + LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData), + "Buffer size %zu doesn't match expected %zu!", buffer.size(), + sizeof(ProfileData)); + data = reinterpret_cast<const ProfileData*>(buffer.get()); + } + if (jpath != nullptr) { + ScopedUtfChars pathChars(env, jpath); + LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), + "Failed to get path chars"); + path.assign(pathChars.c_str(), pathChars.size()); + } + ScopedUtfChars packageChars(env, jpackage); + LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), + "Failed to get path chars"); + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer"); + + const std::string package(packageChars.c_str(), packageChars.size()); + GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data); +} + +static void addFileToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath) { + ScopedUtfChars pathChars(env, jpath); + LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); + const std::string path(pathChars.c_str(), pathChars.size()); + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + GraphicsStatsService::addToDump(dump, path); +} + +static void finishDump(JNIEnv*, jobject, jlong dumpPtr) { + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + GraphicsStatsService::finishDump(dump); +} + +static void finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr, jlong pulledData, + jboolean lastFullDay) { + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + AStatsEventList* data = reinterpret_cast<AStatsEventList*>(pulledData); + GraphicsStatsService::finishDumpInMemory(dump, data, lastFullDay == JNI_TRUE); +} + +static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage, + jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { + ScopedByteArrayRO buffer(env, jdata); + LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData), + "Buffer size %zu doesn't match expected %zu!", buffer.size(), + sizeof(ProfileData)); + ScopedUtfChars pathChars(env, jpath); + LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); + ScopedUtfChars packageChars(env, jpackage); + LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), + "Failed to get path chars"); + + const std::string path(pathChars.c_str(), pathChars.size()); + const std::string package(packageChars.c_str(), packageChars.size()); + const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer.get()); + GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data); +} + +static jobject gGraphicsStatsServiceObject = nullptr; +static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID; + +static JNIEnv* getJNIEnv() { + JavaVM* vm = AndroidRuntime::getJavaVM(); + JNIEnv* env = nullptr; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!"); + } + } + return env; +} + +// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom. +static AStatsManager_PullAtomCallbackReturn graphicsStatsPullCallback(int32_t atom_tag, + AStatsEventList* data, + void* cookie) { + JNIEnv* env = getJNIEnv(); + if (!env) { + return false; + } + if (gGraphicsStatsServiceObject == nullptr) { + ALOGE("Failed to get graphicsstats service"); + return AStatsManager_PULL_SKIP; + } + + for (bool lastFullDay : {true, false}) { + env->CallVoidMethod(gGraphicsStatsServiceObject, + gGraphicsStatsService_pullGraphicsStatsMethodID, + (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE), + reinterpret_cast<jlong>(data)); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + ALOGE("Failed to invoke graphicsstats service"); + return AStatsManager_PULL_SKIP; + } + } + return AStatsManager_PULL_SUCCESS; +} + +// Register a puller for GRAPHICS_STATS atom with the statsd service. +static void nativeInit(JNIEnv* env, jobject javaObject) { + gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject); + AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain(); + AStatsManager_PullAtomMetadata_setCoolDownNs(metadata, 10 * 1000000); // 10 milliseconds + AStatsManager_PullAtomMetadata_setTimeoutNs(metadata, 2 * NS_PER_SEC); // 2 seconds + + AStatsManager_registerPullAtomCallback(android::util::GRAPHICS_STATS, + &graphicsStatsPullCallback, metadata, nullptr); + + AStatsManager_PullAtomMetadata_release(metadata); +} + +static void nativeDestructor(JNIEnv* env, jobject javaObject) { + AStatsManager_unregisterPullAtomCallback(android::util::GRAPHICS_STATS); + env->DeleteGlobalRef(gGraphicsStatsServiceObject); + gGraphicsStatsServiceObject = nullptr; +} + +static const JNINativeMethod sMethods[] = + {{"nGetAshmemSize", "()I", (void*)getAshmemSize}, + {"nCreateDump", "(IZ)J", (void*)createDump}, + {"nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)addToDump}, + {"nAddToDump", "(JLjava/lang/String;)V", (void*)addFileToDump}, + {"nFinishDump", "(J)V", (void*)finishDump}, + {"nFinishDumpInMemory", "(JJZ)V", (void*)finishDumpInMemory}, + {"nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)saveBuffer}, + {"nativeInit", "()V", (void*)nativeInit}, + {"nativeDestructor", "()V", (void*)nativeDestructor}}; + +int register_android_graphics_GraphicsStatsService(JNIEnv* env) { + jclass graphicsStatsService_class = + FindClassOrDie(env, "android/graphics/GraphicsStatsService"); + gGraphicsStatsService_pullGraphicsStatsMethodID = + GetMethodIDOrDie(env, graphicsStatsService_class, "pullGraphicsStats", "(ZJ)V"); + return jniRegisterNativeMethods(env, "android/graphics/GraphicsStatsService", sMethods, + NELEM(sMethods)); +} + +} // namespace android 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/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index c269d1cf4295..14d74878b2ea 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -37,6 +37,7 @@ #include <binder/Parcel.h> #include <binder/ProcessState.h> #include <binder/Stability.h> +#include <binderthreadstate/CallerUtils.h> #include <cutils/atomic.h> #include <log/log.h> #include <utils/KeyedVector.h> @@ -950,7 +951,7 @@ static jint android_os_Binder_getCallingUid() static jboolean android_os_Binder_isHandlingTransaction() { - return IPCThreadState::self()->isServingCall(); + return getCurrentServingCall() == BinderCallType::BINDER; } static jlong android_os_Binder_clearCallingIdentity() diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 69ca17c08257..5a8225c1f21a 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -147,10 +147,12 @@ static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, job } static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject clazz, - jboolean translucent, jlong rootRenderNodePtr) { + jboolean translucent, jboolean isWideGamut, jlong rootRenderNodePtr) { RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr); ContextFactoryImpl factory(rootRenderNode); - return (jlong) new RenderProxy(translucent, rootRenderNode, &factory); + RenderProxy* proxy = new RenderProxy(translucent, rootRenderNode, &factory); + proxy->setWideGamut(isWideGamut); + return (jlong) proxy; } static void android_view_ThreadedRenderer_deleteProxy(JNIEnv* env, jobject clazz, @@ -627,7 +629,7 @@ static const JNINativeMethod gMethods[] = { { "nSetProcessStatsBuffer", "(I)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer }, { "nGetRenderThreadTid", "(J)I", (void*) android_view_ThreadedRenderer_getRenderThreadTid }, { "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode }, - { "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy }, + { "nCreateProxy", "(ZZJ)J", (void*) android_view_ThreadedRenderer_createProxy }, { "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy }, { "nLoadSystemProperties", "(J)Z", (void*) android_view_ThreadedRenderer_loadSystemProperties }, { "nSetName", "(JLjava/lang/String;)V", (void*) android_view_ThreadedRenderer_setName }, 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 a6e08d2cecc1..53aa2bbd57ea 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -708,6 +708,22 @@ enum Action { // ACTION: Deny "Access all files" for an app APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_DENY = 1731; + + // ACTION: Battery feature usage + ACTION_BATTERY_OPTION_FEATURE_USAGE = 1732; + + // ACTION: Battery feature runtime event + ACTION_BATTERY_OPTION_RUNTIME_EVENT = 1733; + + // ACTION: Settings > Developer Options > Toggle on Wireless debugging + // CATEGORY: SETTINGS + // OS: R + ACTION_ADB_WIRELESS_ON = 1734; + + // ACTION: Settings > Developer Options > Toggle off Wireless debugging + // CATEGORY: SETTINGS + // OS: R + ACTION_ADB_WIRELESS_OFF = 1735; } /** @@ -2562,7 +2578,7 @@ enum PageId { // CATEGORY: SETTINGS // OS: R OPEN_SUPPORTED_LINKS = 1824; - + // OPEN: Settings > Display > Dark theme > Set start time dialog DIALOG_DARK_THEME_SET_START_TIME = 1825; @@ -2573,4 +2589,32 @@ enum PageId { // CATEGORY: SETTINGS // OS: R VIBRATE_FOR_CALLS = 1827; + + // OPEN: Settings > Connected devices > Connection preferences > NFC + // CATEGORY: SETTINGS + // OS: R + CONNECTION_DEVICE_ADVANCED_NFC = 1828; + + // OPEN: Settings -> Apps & Notifications -> Special App Access + INTERACT_ACROSS_PROFILES = 1829; + + // OPEN: Settings > Notifications > (app or conversations) > conversation + NOTIFICATION_CONVERSATION_SETTINGS = 1830; + + // OPEN: Settings > Developer Options > Wireless debugging + // CATEGORY: SETTINGS + // OS: R + SETTINGS_ADB_WIRELESS = 1831; + + // OPEN: Settings > Developer Options > Wireless debugging + // > Pair device with pairing code > Pairing code dialog + // CATEGORY: SETTINGS + // OS: R + ADB_WIRELESS_DEVICE_PAIRING_DIALOG = 1832; + + // OPEN: Settings > Developer Options > Wireless debugging + // > Pair device with QR code > Scan QR code > Pairing device dialog + // CATEGORY: SETTINGS + // OS: R + ADB_WIRELESS_DEVICE_QR_PAIRING_DIALOG = 1833; } diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 8adcc9ed905d..bf4cdee72b2d 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -52,6 +52,7 @@ import "frameworks/base/core/proto/android/service/package.proto"; import "frameworks/base/core/proto/android/service/print.proto"; import "frameworks/base/core/proto/android/service/procstats.proto"; import "frameworks/base/core/proto/android/service/restricted_image.proto"; +import "frameworks/base/core/proto/android/service/sensor_service.proto"; import "frameworks/base/core/proto/android/service/usb.proto"; import "frameworks/base/core/proto/android/util/event_log_tags.proto"; import "frameworks/base/core/proto/android/util/log.proto"; @@ -492,6 +493,11 @@ message IncidentProto { (section).args = "contexthub --proto" ]; + optional android.service.SensorServiceProto sensor_service = 3053 [ + (section).type = SECTION_DUMPSYS, + (section).args = "sensorservice --proto" + ]; + // Reserved for OEMs. extensions 50000 to 100000; } diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index 0a2fd7093a49..2d2ead455a4d 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -27,7 +27,6 @@ import "frameworks/base/core/proto/android/content/component_name.proto"; import "frameworks/base/core/proto/android/content/configuration.proto"; import "frameworks/base/core/proto/android/content/intent.proto"; import "frameworks/base/core/proto/android/content/package_item_info.proto"; -import "frameworks/base/core/proto/android/graphics/rect.proto"; import "frameworks/base/core/proto/android/internal/processstats.proto"; import "frameworks/base/core/proto/android/os/bundle.proto"; import "frameworks/base/core/proto/android/os/looper.proto"; 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/proto/android/server/notificationhistory.proto b/core/proto/android/server/notificationhistory.proto index 1e6ee3f1a3a8..6749719be124 100644 --- a/core/proto/android/server/notificationhistory.proto +++ b/core/proto/android/server/notificationhistory.proto @@ -17,8 +17,6 @@ syntax = "proto2"; package com.android.server.notification; -import "frameworks/base/core/proto/android/server/enums.proto"; - option java_multiple_files = true; // On disk data store for historical notifications diff --git a/core/proto/android/service/OWNERS b/core/proto/android/service/OWNERS new file mode 100644 index 000000000000..70cb50f75362 --- /dev/null +++ b/core/proto/android/service/OWNERS @@ -0,0 +1 @@ +per-file sensor_service.proto = arthuri@google.com, bduddie@google.com, stange@google.com diff --git a/core/proto/android/service/sensor_service.proto b/core/proto/android/service/sensor_service.proto new file mode 100644 index 000000000000..48f6670b59fe --- /dev/null +++ b/core/proto/android/service/sensor_service.proto @@ -0,0 +1,248 @@ +/* + * 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. + */ + +syntax = "proto2"; +package android.service; + +import "frameworks/base/core/proto/android/privacy.proto"; + +option java_multiple_files = true; + +/* + * Notes: + * 1. When using ProtoOutputStream to write this proto message, must call + * token = ProtoOutputStream#start(fieldId) before and ProtoOutputStream#end(token) after + * writing a nested message. + * 2. Never reuse a proto field number. When removing a field, mark it as reserved. + */ + +// Proto dump of android::SensorService. dumpsys sensorservice --proto +message SensorServiceProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + enum OperatingModeEnum { + OP_MODE_UNKNOWN = 0; + OP_MODE_NORMAL = 1; + OP_MODE_RESTRICTED = 2; + OP_MODE_DATA_INJECTION = 3; + } + optional sint32 init_status = 16; + optional int64 current_time_ms = 1; + optional SensorDeviceProto sensor_device = 2; + optional SensorListProto sensors = 3; + optional SensorFusionProto fusion_state = 4; + optional SensorEventsProto sensor_events = 5; + repeated ActiveSensorProto active_sensors = 6; + optional int32 socket_buffer_size = 7; + optional int32 socket_buffer_size_in_events = 8; + optional bool wake_lock_acquired = 9; + optional OperatingModeEnum operating_mode = 10; + // Non-empty only if operating_mode is RESTRICTED or DATA_INJECTION. + optional string whitelisted_package = 11; + optional bool sensor_privacy = 12; + repeated SensorEventConnectionProto active_connections = 13; + repeated SensorDirectConnectionProto direct_connections = 14; + repeated SensorRegistrationInfoProto previous_registrations = 15; + + // Next tag: 17 +} + +// Proto dump of android::SensorDevice +message SensorDeviceProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional bool initialized = 1; + optional int32 total_sensors = 2; + optional int32 active_sensors = 3; + + message SensorProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 handle = 1; + optional int32 active_count = 2; + repeated float sampling_period_ms = 3; + optional float sampling_period_selected = 4; + repeated float batching_period_ms = 5; + optional float batching_period_selected = 6; + } + repeated SensorProto sensors = 4; +} + +// Proto dump of android::SensorServiceUtil::SensorList +message SensorListProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + enum ReportingModeEnum { + RM_UNKNOWN = 0; + RM_CONTINUOUS = 1; + RM_ON_CHANGE = 2; + RM_ONE_SHOT = 3; + RM_SPECIAL_TRIGGER = 4; + } + + message SensorProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 handle = 1; + optional string name = 2; + optional string vendor = 3; + optional int32 version = 4; + optional string string_type = 5; + optional int32 type = 6; + optional string required_permission = 7; + optional int32 flags = 8; + optional ReportingModeEnum reporting_mode = 9; + optional int32 max_delay_us = 10; + optional int32 min_delay_us = 11; + optional int32 fifo_max_event_count = 12; + optional int32 fifo_reserved_event_count = 13; + optional bool is_wakeup = 14; + optional bool data_injection_supported = 15; + optional bool is_dynamic = 16; + optional bool has_additional_info = 17; + optional int32 highest_rate_level = 18; + optional bool ashmem = 19; + optional bool gralloc = 20; + optional float min_value = 21; + optional float max_value = 22; + optional float resolution = 23; + optional float power_usage = 24; + } + repeated SensorProto sensors = 1; +} + + +// Proto dump of android::SensorFusion +message SensorFusionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + message FusionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional bool enabled = 1; + optional int32 num_clients = 2; + optional float estimated_gyro_rate = 3; + optional float attitude_x = 4; + optional float attitude_y = 5; + optional float attitude_z = 6; + optional float attitude_w = 7; + optional float attitude_length = 8; + optional float bias_x = 9; + optional float bias_y = 10; + optional float bias_z = 11; + } + optional FusionProto fusion_9axis = 1; + optional FusionProto fusion_nomag = 2; + optional FusionProto fusion_nogyro = 3; +} + +// Proto dump of android::SensorServiceUtil::RecentEventLogger +message SensorEventsProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + message Event { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional float timestamp_sec = 1; + optional int64 wall_timestamp_ms = 2; + optional bool masked = 3; + optional int64 int64_data = 4; + repeated float float_array = 5; + } + + message RecentEventsLog { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string name = 1; + optional int32 recent_events_count = 2; + repeated Event events = 3; + } + repeated RecentEventsLog recent_events_logs = 1; +} + +message ActiveSensorProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string name = 1; + optional int32 handle = 2; + optional int32 num_connections = 3; +} + +// Proto dump of android::SensorService::SensorDirectConnection +message SensorDirectConnectionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + message SensorProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 sensor = 1; + optional int32 rate = 2; + } + + optional string package_name = 1; + optional int32 hal_channel_handle = 2; + optional int32 num_sensor_activated = 3; + repeated SensorProto sensors = 4; +} + +// Proto dump of android::SensorService::SensorEventConnection +message SensorEventConnectionProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + message FlushInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional string sensor_name = 1; + optional int32 sensor_handle = 2; + optional bool first_flush_pending = 3; + optional int32 pending_flush_events_to_send = 4; + } + + enum OperatingModeEnum { + OP_MODE_UNKNOWN = 0; + OP_MODE_NORMAL = 1; + OP_MODE_RESTRICTED = 2; + OP_MODE_DATA_INJECTION = 3; + } + + optional OperatingModeEnum operating_mode = 1; + optional string package_name = 2; + optional int32 wake_lock_ref_count = 3; + optional int32 uid = 4; + optional int32 cache_size = 5; + optional int32 max_cache_size = 6; + repeated FlushInfoProto flush_infos = 7; + optional int32 events_received = 8; + optional int32 events_sent = 9; + optional int32 events_cache = 10; + optional int32 events_dropped = 11; + optional int32 total_acks_needed = 12; + optional int32 total_acks_received = 13; +} + +// Proto dump of android::SensorService::SensorRegistrationInfo +message SensorRegistrationInfoProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int64 timestamp_sec = 1; + optional int32 sensor_handle = 2; + optional string package_name = 3; + optional int32 pid = 4; + optional int32 uid = 5; + optional int64 sampling_rate_us = 6; + optional int64 max_report_latency_us = 7; + optional bool activated = 8; +} diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto index 0f03e69e6c93..d1392a5e0f31 100644 --- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto +++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto @@ -157,4 +157,6 @@ enum EventId { SET_FACTORY_RESET_PROTECTION = 130; SET_COMMON_CRITERIA_MODE = 131; ALLOW_MODIFICATION_OF_ADMIN_CONFIGURED_NETWORKS = 132; + SET_TIME = 133; + SET_TIME_ZONE = 134; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7a302566a872..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" /> @@ -2605,7 +2605,7 @@ <p>Not for use by third-party applications. @hide --> - <permission android:name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE" + <permission android:name="android.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE" android:protectionLevel="signature|telephony" /> <!-- Allows applications like settings to suggest the user's manually chosen time / time zone. @@ -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" /> @@ -3448,6 +3448,14 @@ <permission android:name="android.permission.TUNER_RESOURCE_ACCESS" android:protectionLevel="signature|privileged" /> + <!-- This permission is required by Media Resource Manager Service when + accessing its overridePid Api. + <p>Protection level: signature + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" + android:protectionLevel="signature" /> + <!-- Must be required by a {@link android.media.routing.MediaRouteService} to ensure that only the system can interact with it. @hide --> @@ -4057,6 +4065,11 @@ <permission android:name="android.permission.STATSCOMPANION" android:protectionLevel="signature" /> + <!--@SystemApi @hide Allows an application to register stats pull atom callbacks. + <p>Not for use by third-party applications.--> + <permission android:name="android.permission.REGISTER_STATS_PULL_ATOM" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to control the backup and restore process. <p>Not for use by third-party applications. @hide pending API council --> @@ -4865,6 +4878,19 @@ <permission android:name="android.permission.ACCESS_SHARED_LIBRARIES" android:protectionLevel="signature|installer" /> + <!-- Allows an app to log compat change usage. + @hide <p>Not for use by third-party applications.</p> --> + <permission android:name="android.permission.LOG_COMPAT_CHANGE" + android:protectionLevel="signature|privileged" /> + <!-- Allows an app to read compat change config. + @hide <p>Not for use by third-party applications.</p> --> + <permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" + android:protectionLevel="signature|privileged" /> + <!-- Allows an app to override compat change config. + @hide <p>Not for use by third-party applications.</p> --> + <permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG" + android:protectionLevel="signature|privileged" /> + <!-- Allows input events to be monitored. Very dangerous! @hide --> <permission android:name="android.permission.MONITOR_INPUT" android:protectionLevel="signature" /> @@ -5302,6 +5328,10 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> + <service android:name="com.android.server.blob.BlobStoreIdleJobService" + android:permission="android.permission.BIND_JOB_SERVICE"> + </service> + <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"> <intent-filter> <action android:name="android.intent.action.LOAD_DATA" /> diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml index 46e8f64753ac..b01eb3944b50 100644 --- a/core/res/res/drawable-nodpi/platlogo.xml +++ b/core/res/res/drawable-nodpi/platlogo.xml @@ -1,31 +1,50 @@ -<!-- -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="240dp" - android:height="240dp" - android:viewportWidth="24" - android:viewportHeight="24"> + android:width="512dp" + android:height="512dp" + android:viewportWidth="512" + android:viewportHeight="512"> <path - android:fillColor="#000" - android:pathData="M16 4c-2.2 0-4 1.8-4 4v4H4V8c0-2.2 1.8-4 4-4h8z"/> + android:fillColor="#F86734" + android:pathData="M416.23 236.62h-10.67c-1.46 0-2.65-1.19-2.65-2.65v-9.85c0-1.47 1.19-2.65 2.65-2.65h23.37c1.47 0 2.66 1.19 2.66 2.65v66.9c0 1.46-1.2 2.65-2.66 2.65H418.9c-1.47 0-2.66-1.19-2.66-2.65v-54.4z"/> <path - android:fillColor="#000" - android:pathData="M8 20c2.2 0 4-1.8 4-4v-4H4v8h4z"/> + android:fillColor="#F86734" + android:pathData="M455.51 236.62h-10.67c-1.47 0-2.65-1.19-2.65-2.65v-9.85c0-1.47 1.18-2.65 2.65-2.65h23.37c1.47 0 2.66 1.19 2.66 2.65v66.9c0 1.46-1.2 2.65-2.66 2.65h-10.05c-1.46 0-2.65-1.19-2.65-2.65v-54.4z"/> <path - android:fillColor="#80000000" - android:pathData="M16 12c2.2 0 4-1.8 4-4V4h-8v8h4z"/> + android:fillColor="#D6F0FF" + android:pathData="M364.12 400.25a4.34 4.34 0 1 0 0 8.68a4.34 4.34 0 1 0 0-8.68z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M275.46 433.53a4.84 4.84 0 1 0 0 9.68a4.84 4.84 0 1 0 0-9.68z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M184.52 418.83a5.36 5.36 0 1 0 0 10.72a5.36 5.36 0 1 0 0-10.72z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M110.42 359.19a5.89 5.89 0 1 0 0 11.78a5.89 5.89 0 1 0 0-11.78z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M75.94 270.17a6.43 6.43 0 1 0 0 12.86a6.43 6.43 0 1 0 0-12.86z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M89.48 178.57a6.98 6.98 0 1 0 0 13.96a6.98 6.98 0 1 0 0-13.96z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M147.97 103.54a7.54 7.54 0 1 0 0 15.08a7.54 7.54 0 1 0 0-15.08z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M236.63 66.7a8.1 8.1 0 1 0 0 16.2a8.1 8.1 0 1 0 0-16.2z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M327.09 78.3a8.66 8.66 0 1 0 0 17.32a8.66 8.66 0 1 0 0-17.32z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M401.05 136.97a9.22 9.22 0 1 0 0 18.44a9.22 9.22 0 1 0 0-18.44z"/> + <group> + <path + android:fillColor="#3DDB85" + android:pathData="M255.45 129.46a128.11 128.11 0 1 0 0 256.22a128.11 128.11 0 1 0 0-256.22z"/> + <path + android:fillColor="#FFF" + android:pathData="M339.23 236.09a21.48 21.48 0 1 0 0 42.96a21.48 21.48 0 1 0 0-42.96z"/> + </group> </vector> - diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml index 0e9aab2ab59d..700781b1d0b1 100644 --- a/core/res/res/drawable-nodpi/stat_sys_adb.xml +++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml @@ -1,17 +1,17 @@ -<!-- -Copyright (C) 2020 The Android Open Source Project +<?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 + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + 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" @@ -19,12 +19,22 @@ Copyright (C) 2020 The Android Open Source Project android:viewportWidth="24" android:viewportHeight="24"> <path - android:fillColor="#000" - android:pathData="M16 4c-2.2 0-4 1.8-4 4v4H4V8c0-2.2 1.8-4 4-4h8z"/> - <path - android:fillColor="#000" - android:pathData="M8 20c2.2 0 4-1.8 4-4v-4H4v8h4z"/> - <path - android:fillColor="#80000000" - android:pathData="M16 12c2.2 0 4-1.8 4-4V4h-8v8h4z"/> + android:fillColor="#FFFFFF" + android:pathData=" +M 12,1 +A 11 11 0 0 0 1,12 +A 11 11 0 1 0 12,1 +Z + +M 12.5,8 +a 3,3 0 0 1 6,0 +a 3,3 0 0 1 -6,0 +Z + +M 5.5,8 +a 3,3 0 0 1 6,0 +l 0,8 +a 3,3 0 0 1 -6,0 +Z +"/> </vector> 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/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-af/strings.xml b/core/res/res/values-af/strings.xml index d6c5a1379a58..5e895336d7f3 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Skakel werkprofiel aan?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Jou werkprogramme, kennisgewings, data en ander werkprofielkenmerke sal aangeskakel word"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Skakel aan"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Program is nie beskikbaar nie"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is nie op die oomblik beskikbaar nie."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Hierdie program is vir \'n ouer weergawe van Android gebou en sal dalk nie behoorlik werk nie. Probeer kyk vir opdaterings, of kontak die ontwikkelaar."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kyk vir opdatering"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Jy het nuwe boodskappe"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index e9409fbf92ed..b637a637c3fe 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"የስራ መገለጫ ይብራ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"የእርስዎ የስራ መተግበሪያዎች፣ ማሳወቂያዎች፣ ውሂብ እና ሌሎች የስራ መገለጫ ባህሪያት ይበራሉ"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"አብራ"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"መተግበሪያ አይገኝም"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> አሁን አይገኝም።"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ይህ መተግበሪያ ለቆየ የAndroid ስሪት ነው የተገነባው፣ እና በአግባቡ ላይሰራ ይችላል። ዝማኔዎች ካሉ ለመመልከት ይሞክሩ፣ ወይም ደግሞ ገንቢውን ያነጋግሩ።"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ዝማኔ ካለ አረጋግጥ"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"አዲስ መልዕክቶች አለዎት"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 23ffd7b6594a..002d61e0ff80 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -915,7 +915,7 @@ <string name="factorytest_no_action" msgid="339252838115675515">"لم يتم العثور على أي حزمة توفر إجراء FACTORY_TEST."</string> <string name="factorytest_reboot" msgid="2050147445567257365">"إعادة تشغيل"</string> <string name="js_dialog_title" msgid="7464775045615023241">"تعرض الصفحة في \"<xliff:g id="TITLE">%s</xliff:g>\":"</string> - <string name="js_dialog_title_default" msgid="3769524569903332476">"جافا سكريبت"</string> + <string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</string> <string name="js_dialog_before_unload_title" msgid="7012587995876771246">"تأكيد الانتقال"</string> <string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"مغادرة هذه الصفحة"</string> <string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"البقاء في هذه الصفحة"</string> @@ -1395,7 +1395,7 @@ <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"يمكنك إجراء إعادة ضبط على الإعدادات الأصلية لإيقاف وضع \"مفعِّل اختبار\"."</string> <string name="console_running_notification_title" msgid="6087888939261635904">"وحدة التحكّم التسلسلية مفعّلة"</string> <string name="console_running_notification_message" msgid="7892751888125174039">"الأداء متأثر. لإيقاف وحدة التحكّم، تحقّق من برنامج الإقلاع."</string> - <string name="usb_contaminant_detected_title" msgid="4359048603069159678">"السوائل والشوائب في منفذ USB"</string> + <string name="usb_contaminant_detected_title" msgid="4359048603069159678">"السوائل أو الشوائب في منفذ USB"</string> <string name="usb_contaminant_detected_message" msgid="7346100585390795743">"تمّ إيقاف منفذ USB تلقائيًا. انقُر لمعرفة المزيد من المعلومات."</string> <string name="usb_contaminant_not_detected_title" msgid="2651167729563264053">"مسموح باستخدام منفذ USB"</string> <string name="usb_contaminant_not_detected_message" msgid="892863190942660462">"لم يَعُد الهاتف يكتشف سوائل أو شوائب."</string> @@ -1981,6 +1981,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"تفعيل الملف الشخصي للعمل؟"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"سيتم تفعيل تطبيقات العمل التي تستخدمها والإشعارات والبيانات وغيرها من ميزات الملف الشخصي للعمل"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"تشغيل"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"التطبيق غير متاح"</string> + <string name="app_blocked_message" msgid="542972921087873023">"تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> غير متاح الآن."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"تمّ إنشاء هذا التطبيق لإصدار قديم من Android وقد لا يعمل بشكل صحيح. جرِّب البحث عن تحديثات أو الاتصال بمطوّر البرامج."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"البحث عن تحديث"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"لديك رسائل جديدة"</string> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index a09c377d45b9..bdf8fcf18718 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"কৰ্মস্থানৰ প্ৰ\'ফাইল অন কৰিবনে?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"আপোনাৰ কৰ্মস্থানৰ এপসমূহ, জাননীসমূহ, ডেটা আৰু কৰ্মস্থানৰ প্ৰ\'ফাইলৰ অইন সুবিধাসমূহ অন কৰা হ\'ব"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"অন কৰক"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"এপ্টো উপলব্ধ নহয়"</string> + <string name="app_blocked_message" msgid="542972921087873023">"এই মুহূৰ্তত <xliff:g id="APP_NAME">%1$s</xliff:g> উপলব্ধ নহয়।"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"এই এপটো Androidৰ এটা পুৰণা সংস্কৰণৰ বাবে প্ৰস্তুত কৰা হৈছিল, আৰু ই বিচৰাধৰণে কাম নকৰিবও পাৰে। ইয়াৰ আপডে’ট আছে নেকি চাওক, বা বিকাশকৰ্তাৰ সৈতে যোগাযোগ কৰক।"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"আপডে’ট আছে নেকি চাওক"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"আপুনি নতুন বার্তা লাভ কৰিছে"</string> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index b1209b100633..6f13024ada2e 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"İş profili aktiv edilsin?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"İş tətbiqləri, bildirişləri, data və digər iş profili funksiyaları aktiv ediləcək"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivləşdirin"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Tətbiq əlçatan deyil"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> hazırda əlçatan deyil."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Bu tətbiq köhnə Android versiyası üçün hazırlanıb və düzgün işləməyə bilər. Güncəlləməni yoxlayın və ya developer ilə əlaqə saxlayın."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Güncəlləməni yoxlayın"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Yeni mesajlarınız var"</string> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index 5600882ca16f..862495a6f9bf 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -1885,6 +1885,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Da uključimo profil za Work?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Uključiće se poslovne aplikacije, obaveštenja, podaci i druge funkcije profila za Work"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Uključi"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno nije dostupna."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova aplikacija je napravljena za stariju verziju Android-a, pa možda neće raditi ispravno. Potražite ažuriranja ili kontaktirajte programera."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Potraži ažuriranje"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index fb80e4b043b9..8a02da2f3794 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Уключыць працоўны профіль?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Будуць уключаны вашы рабочыя праграмы, апавяшчэнні, даныя і іншыя функцыі працоўнага профілю"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Уключыць"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Праграма недаступная"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" цяпер недаступная."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Гэта праграма была створана для больш старой версіі Android і можа не працаваць належным чынам. Праверце наяўнасць абнаўленняў або звярніцеся да распрацоўшчыка."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Праверыць на наяўнасць абнаўленняў"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"У вас ёсць новыя паведамленні"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 2892853f0923..f7830c738dcc 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Вкл. на служ. потр. профил?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Служебните ви приложения, известия и данни, както и другите функции на служебния потребителски профил ще бъдат включени"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Включване"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Приложението не е достъпно"</string> + <string name="app_blocked_message" msgid="542972921087873023">"В момента няма достъп до <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Това приложение бе създадено за по-стара версия на Android и може да не работи правилно. Опитайте да проверите за актуализации или се свържете с програмиста."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Проверка за актуализация"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Имате нови съобщения"</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index 4c95081cc15d..af50b3357cee 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -1297,7 +1297,7 @@ <string name="no_permissions" msgid="5729199278862516390">"কোনো অনুমতির প্রয়োজন নেই"</string> <string name="perm_costs_money" msgid="749054595022779685">"এর জন্য অর্থপ্রদান করতে হতে পারে"</string> <string name="dlg_ok" msgid="5103447663504839312">"ঠিক আছে"</string> - <string name="usb_charging_notification_title" msgid="1674124518282666955">"এই ডিভাইসটি USB এর মাধ্যমে চার্জ করুন"</string> + <string name="usb_charging_notification_title" msgid="1674124518282666955">"এই ডিভাইসটি USB দিয়ে চার্জ করা হচ্ছে"</string> <string name="usb_supplying_notification_title" msgid="5378546632408101811">"সংযুক্ত ডিভাইসটি USB এর মাধ্যমে চার্জ করা হচ্ছে"</string> <string name="usb_mtp_notification_title" msgid="1065989144124499810">"USB ফাইল ট্রান্সফার চালু করা হয়েছে"</string> <string name="usb_ptp_notification_title" msgid="5043437571863443281">"USB এর মাধ্যমে PTP চালু করা হয়েছে"</string> @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"কাজের প্রোফাইল চালু করবেন?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"আপনার কাজের অ্যাপ, বিজ্ঞপ্তি, ডেটা এবং কাজের প্রোফাইলের অন্যান্য বৈশিষ্ট্য চালু করা হবে"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"চালু করুন"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"অ্যাপ পাওয়া যাচ্ছে না"</string> + <string name="app_blocked_message" msgid="542972921087873023">"এই মুহূর্তে <xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপ পাওয়া যাচ্ছে না।"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"এই অ্যাপটি Android এর একটি পুরনো ভার্সনের জন্য তৈরি করা হয়েছিল, তাই এখানে সেটি ঠিকমতো কাজ নাও করতে পারে। আপডেট পাওয়া যাচ্ছে কিনা দেখুন বা ডেভেলপারের সাথে যোগাযোগ করুন।"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"আপডেট পাওয়া যাচ্ছে কিনা দেখুন"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"আপনার নতুন মেসেজ আছে"</string> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index eaa212dccf2f..d436c5fbc87d 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -1887,6 +1887,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Uključiti radni profil?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Uključit će se poslovne aplikacije, obavještenja, podaci i druge funkcije radnog profila"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Uključi"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno nije dostupna."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova aplikacija je pravljena za stariju verziju Androida i možda neće ispravno raditi. Provjerite jesu li dostupna ažuriranja ili kontaktirajte programera."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Provjeri je li dostupno ažuriranje"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 0b58caea91b8..f3b416cee191 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -182,11 +182,11 @@ <item quantity="one">Autoritat de certificació instal·lada</item> </plurals> <string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"Per un tercer desconegut"</string> - <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"Per l\'administrador del teu perfil professional"</string> + <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"Per l\'administrador del teu perfil de treball"</string> <string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"Per <xliff:g id="MANAGING_DOMAIN">%s</xliff:g>"</string> - <string name="work_profile_deleted" msgid="5891181538182009328">"S\'ha suprimit el perfil professional"</string> - <string name="work_profile_deleted_details" msgid="3773706828364418016">"Falta l\'aplicació d\'administració del perfil professional o està malmesa. Com a conseqüència, s\'han suprimit el teu perfil professional i les dades relacionades. Contacta amb l\'administrador per obtenir ajuda."</string> - <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"El teu perfil professional ja no està disponible en aquest dispositiu"</string> + <string name="work_profile_deleted" msgid="5891181538182009328">"S\'ha suprimit el perfil de treball"</string> + <string name="work_profile_deleted_details" msgid="3773706828364418016">"Falta l\'aplicació d\'administració del perfil de treball o està malmesa. Com a conseqüència, s\'han suprimit el teu perfil de treball i les dades relacionades. Contacta amb l\'administrador per obtenir ajuda."</string> + <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"El teu perfil de treball ja no està disponible en aquest dispositiu"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Has intentat introduir la contrasenya massa vegades"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"L\'administrador ha cedit el dispositiu per a ús personal"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"El dispositiu està gestionat"</string> @@ -280,7 +280,7 @@ <string name="safeMode" msgid="8974401416068943888">"Mode segur"</string> <string name="android_system_label" msgid="5974767339591067210">"Sistema Android"</string> <string name="user_owner_label" msgid="8628726904184471211">"Canvia al perfil personal"</string> - <string name="managed_profile_label" msgid="7316778766973512382">"Canvia al perfil professional"</string> + <string name="managed_profile_label" msgid="7316778766973512382">"Canvia al perfil de treball"</string> <string name="permgrouplab_contacts" msgid="4254143639307316920">"Contactes"</string> <string name="permgroupdesc_contacts" msgid="9163927941244182567">"accedir als contactes"</string> <string name="permgrouplab_location" msgid="1858277002233964394">"Ubicació"</string> @@ -1406,8 +1406,8 @@ <string name="deny" msgid="6632259981847676572">"Denega"</string> <string name="permission_request_notification_title" msgid="1810025922441048273">"Permís sol·licitat"</string> <string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"S\'ha sol·licitat permís\nper al compte <xliff:g id="ACCOUNT">%s</xliff:g>."</string> - <string name="forward_intent_to_owner" msgid="4620359037192871015">"Estàs utilitzant aquesta aplicació fora del perfil professional."</string> - <string name="forward_intent_to_work" msgid="3620262405636021151">"Estàs utilitzant l\'aplicació al perfil professional."</string> + <string name="forward_intent_to_owner" msgid="4620359037192871015">"Estàs utilitzant aquesta aplicació fora del perfil de treball."</string> + <string name="forward_intent_to_work" msgid="3620262405636021151">"Estàs utilitzant l\'aplicació al perfil de treball."</string> <string name="input_method_binding_label" msgid="1166731601721983656">"Mètode d\'introducció de text"</string> <string name="sync_binding_label" msgid="469249309424662147">"Sincronització"</string> <string name="accessibility_binding_label" msgid="1974602776545801715">"Accessibilitat"</string> @@ -1817,7 +1817,7 @@ <string name="stk_cc_ss_to_dial_video" msgid="1324194624384312664">"La sol·licitud SS s\'ha canviat per una videotrucada"</string> <string name="stk_cc_ss_to_ussd" msgid="8417905193112944760">"La sol·licitud SS s\'ha canviat per una sol·licitud USSD"</string> <string name="stk_cc_ss_to_ss" msgid="132040645206514450">"S\'ha canviat a una nova sol·licitud SS"</string> - <string name="notification_work_profile_content_description" msgid="5296477955677725799">"Perfil professional"</string> + <string name="notification_work_profile_content_description" msgid="5296477955677725799">"Perfil de treball"</string> <string name="notification_alerted_content_description" msgid="6139691253611265992">"S\'ha enviat una alerta"</string> <string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"Desplega"</string> <string name="expand_button_content_description_expanded" msgid="7484217944948667489">"Replega"</string> @@ -1850,15 +1850,17 @@ <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> no està disponible en aquests moments. Aquesta opció es gestiona a <xliff:g id="APP_NAME_1">%2$s</xliff:g>."</string> <string name="app_suspended_more_details" msgid="211260942831587014">"Més informació"</string> <string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"Reactiva l\'aplicació"</string> - <string name="work_mode_off_title" msgid="5503291976647976560">"Activar el perfil professional?"</string> - <string name="work_mode_off_message" msgid="8417484421098563803">"S\'activaran les teves aplicacions de treball, les notificacions, les dades i altres funcions del perfil professional"</string> + <string name="work_mode_off_title" msgid="5503291976647976560">"Activar el perfil de treball?"</string> + <string name="work_mode_off_message" msgid="8417484421098563803">"S\'activaran les teves aplicacions de treball, les notificacions, les dades i altres funcions del perfil de treball"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Activa"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"L\'aplicació no està disponible"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Ara mateix, <xliff:g id="APP_NAME">%1$s</xliff:g> no està disponible."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aquesta aplicació es va crear per a una versió antiga d\'Android i pot ser que no funcioni correctament. Prova de cercar actualitzacions o contacta amb el desenvolupador."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Cerca actualitzacions"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Tens missatges nous"</string> <string name="new_sms_notification_content" msgid="3197949934153460639">"Obre l\'aplicació d\'SMS per veure\'ls"</string> <string name="profile_encrypted_title" msgid="9001208667521266472">"Algunes funcions poden ser limitades"</string> - <string name="profile_encrypted_detail" msgid="5279730442756849055">"Perfil professional bloquejat"</string> + <string name="profile_encrypted_detail" msgid="5279730442756849055">"Perfil de treball bloquejat"</string> <string name="profile_encrypted_message" msgid="1128512616293157802">"Toca per desbloquejar el perfil"</string> <string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"S\'ha connectat a <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string> <string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Toca per veure els fitxers"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 5874c2493041..754a02016f8b 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Zapnout pracovní profil?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Vaše pracovní aplikace, oznámení, data a ostatní funkce pracovního účtu budou zapnuty"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Zapnout"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikace není k dispozici"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> v tuto chvíli není k dispozici."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Tato aplikace byla vytvořena pro starší verzi systému Android a nemusí fungovat správně. Zkuste vyhledat aktualizace, případně kontaktujte vývojáře."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Zkontrolovat aktualizace"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Máte nové zprávy"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index eb7ad1b9e48f..733c7f0e76a1 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Skal arbejdsprofilen slås til?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Dine arbejdsapps, notifikationer, data og andre funktioner til din arbejdsprofil deaktiveres"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Slå til"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Appen er ikke tilgængelig"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ikke tilgængelig lige nu."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Denne app er lavet til en ældre version af Android og fungerer muligvis ikke korrekt. Prøv at søge efter opdateringer, eller kontakt udvikleren."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Søg efter opdatering"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Du har nye beskeder"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 6b01af1f3b08..9a4c606f76b3 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Arbeitsprofil aktivieren?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Deine geschäftlichen Apps, Benachrichtigungen, Daten und andere Funktionen des Arbeitsprofils werden aktiviert"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivieren"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"App ist nicht verfügbar"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ist derzeit nicht verfügbar."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Diese App wurde für eine ältere Android-Version entwickelt und funktioniert möglicherweise nicht mehr richtig. Prüfe, ob Updates verfügbar sind oder kontaktiere den Entwickler."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Auf Updates prüfen"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Du hast neue Nachrichten"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 61ca692aae16..30a8d61d7b22 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Ενεργοποίηση προφίλ εργασίας;"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Οι εφαρμογές, οι ειδοποιήσεις και τα δεδομένα εργασίας σας, καθώς και άλλες λειτουργίες του προφίλ εργασίας, θα ενεργοποιηθούν"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Ενεργοποίηση"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Η εφαρμογή δεν είναι διαθέσιμη"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> δεν είναι διαθέσιμη αυτήν τη στιγμή."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Αυτή η εφαρμογή δημιουργήθηκε για παλαιότερη έκδοση του Android και μπορεί να μην λειτουργεί σωστά. Δοκιμάστε να ελέγξετε εάν υπάρχουν ενημερώσεις ή επικοινωνήστε με τον προγραμματιστή."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Έλεγχος για ενημέρωση"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Έχετε νέα μηνύματα"</string> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 15a059f449f1..d15fc5a3c61e 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string> diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index f3ab25afdf1e..bec2e349ec67 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 15a059f449f1..d15fc5a3c61e 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index 15a059f449f1..d15fc5a3c61e 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string> diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index 871e528c8e94..42ec9321019e 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data, and other work profile features will be turned on"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 1cb9fde2956a..ac555d58a5e8 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"¿Activar el perfil de trabajo?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Se activaran las apps de trabajo, los datos, las notificaciones y otras funciones del perfil de trabajo"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Activar"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"La app no está disponible"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> no está disponible en este momento."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta app se creó para una versión anterior de Android y es posible que no funcione correctamente. Busca actualizaciones o comunícate con el programador."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualización"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Tienes mensajes nuevos"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index d8509a331453..190664b4fd54 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -802,7 +802,7 @@ <string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"Introduce el código PIN para desbloquear."</string> <string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"Código PIN incorrecto"</string> <string name="keyguard_label_text" msgid="3841953694564168384">"Para desbloquear el teléfono, pulsa la tecla de menú y, a continuación, pulsa 0."</string> - <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Número de emergencia"</string> + <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Llamada de emergencia"</string> <string name="lockscreen_carrier_default" msgid="6192313772955399160">"Sin servicio"</string> <string name="lockscreen_screen_locked" msgid="7364905540516041817">"Pantalla bloqueada"</string> <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Pulsa la tecla de menú para desbloquear el teléfono o realizar una llamada de emergencia."</string> @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"¿Activar el perfil de trabajo?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Tus aplicaciones, notificaciones, datos y otras funciones del perfil de trabajo se activarán"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Activar"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"La aplicación no está disponible"</string> + <string name="app_blocked_message" msgid="542972921087873023">"En estos momentos, <xliff:g id="APP_NAME">%1$s</xliff:g> no está disponible."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicación se ha diseñado para una versión anterior de Android y es posible que no funcione correctamente. Busca actualizaciones o ponte en contacto con el desarrollador."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualizaciones"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Tienes mensajes nuevos"</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 2148c09f08ad..0c302ee6b5fa 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Kas lülitada tööprofiil sisse?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Teie töörakendused, märguanded, andmed ja muud tööprofiili funktsioonid lülitatakse sisse"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Lülita sisse"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Rakendus ei ole saadaval"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei ole praegu saadaval."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"See rakendus on loodud Androidi vanema versiooni jaoks ega pruugi õigesti töötada. Otsige värskendusi või võtke ühendust arendajaga."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Otsi värskendust"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Teile on uusi sõnumeid"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 07bde5ee0648..e7aedd557225 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -517,7 +517,7 @@ <string name="permdesc_imagesWrite" msgid="5195054463269193317">"Argazki-bilduma aldatzeko baimena ematen die aplikazioei."</string> <string name="permlab_mediaLocation" msgid="7368098373378598066">"multimedia-edukien bildumako kokapena irakurri"</string> <string name="permdesc_mediaLocation" msgid="597912899423578138">"Multimedia-edukien bildumako kokapena irakurtzeko baimena ematen die aplikazioei."</string> - <string name="biometric_dialog_default_title" msgid="5284880398508155088">"Egiaztatu zu zarela"</string> + <string name="biometric_dialog_default_title" msgid="5284880398508155088">"Egiaztatu zeu zarela"</string> <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrikoa ez dago erabilgarri"</string> <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Utzi da autentifikazioa"</string> <string name="biometric_not_recognized" msgid="5106687642694635888">"Ez da ezagutu"</string> @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Laneko profila aktibatu?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Laneko aplikazioak, jakinarazpenak, datuak eta laneko profileko bestelako eginbideak aktibatuko dira"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktibatu"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikazioa ez dago erabilgarri"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ez dago erabilgarri une honetan."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aplikazioa Android-en bertsio zaharrago baterako sortu zenez, baliteke behar bezala ez funtzionatzea. Bilatu eguneratzerik baden, edo jarri garatzailearekin harremanetan."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Bilatu eguneratzeak"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Mezu berriak dituzu"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index d65357b73ba0..9d9a13c5d75c 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"نمایه کاری روشن شود؟"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"برنامهها، اعلانها، دادهها و سایر قابلیتهای نمایه کاری شما روشن خواهد شد"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"روشن کردن"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"برنامه در دسترس نیست"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> درحالحاضر در دسترس نیست."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"این برنامه برای نسخه قدیمیتری از Android ساخته شده است و ممکن است درست کار نکند. وجود بهروزرسانی را بررسی کنید یا با برنامهنویس تماس بگیرید."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"بررسی وجود بهروزرسانی"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"پیامهای جدیدی دارید"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 17b3d33b4cf8..35cc8b9a8cb8 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Otetaanko työprofiili käyttöön?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Työsovellukset, ‑ilmoitukset, ‑tiedot ja muut työprofiiliominaisuudet otetaan käyttöön"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Ota käyttöön"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Sovellus ei ole käytettävissä"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei ole nyt käytettävissä."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Tämä sovellus on suunniteltu vanhemmalle Android-versiolle eikä välttämättä toimi oikein. Kokeile tarkistaa päivitykset tai ottaa yhteyttä kehittäjään."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Tarkista päivitykset"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Sinulle on uusia viestejä"</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index 45b8613a1e6f..caec9fa7615e 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Activer le profil professionnel?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Vos applications professionnelles, vos notifications, vos données et les autres fonctionnalités de profil professionnel seront activées"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Activer"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"L\'application n\'est pas accessible"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> n\'est pas accessible pour le moment."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Cette application a été conçue pour une ancienne version d\'Android et pourrait ne pas fonctionner correctement. Essayez de vérifier les mises à jour ou communiquez avec son concepteur."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Vérifier la présence de mises à jour"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Vous avez de nouveaux messages"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index c646aeb8d82d..601fe431a292 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Activer profil professionnel ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Vos applications professionnelles, notifications, données et d\'autres fonctionnalités de votre profil professionnel seront activées"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Activer"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Application non disponible"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> n\'est pas disponible pour le moment."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Cette application a été conçue pour une ancienne version d\'Android et risque de ne pas fonctionner correctement. Recherchez des mises à jour ou contactez le développeur."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Rechercher une mise à jour"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Vous avez de nouveaux messages"</string> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 2a2d7c4712b9..0aa627716191 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Activar o perfil de traballo?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Activaranse as túas aplicacións de traballo, as notificacións, os datos e outras funcións do perfil de traballo"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Activar"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"A aplicación non está dispoñible"</string> + <string name="app_blocked_message" msgid="542972921087873023">"A aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> non está dispoñible neste momento."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicación deseñouse para unha versión anterior de Android e quizais non funcione correctamente. Proba a buscar actualizacións ou contacta co programador."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualización"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Tes mensaxes novas"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index aab09f751076..85bf30ed51f8 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"કાર્યાલયની પ્રોફાઇલ ચાલુ કરીએ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"તમારી કાર્યાલયની ઍપ, નોટિફિકેશન, ડેટા અને અન્ય કાર્યાલયની પ્રોફાઇલ સુવિધાઓ ચાલુ કરવામાં આવશે"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ચાલુ કરો"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ઍપ ઉપલબ્ધ નથી"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> હાલમાં ઉપલબ્ધ નથી."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"આ ઍપ Androidના જૂના વર્ઝન માટે બનાવવામાં આવ્યું હતું અને તે કદાચ તે યોગ્ય રીતે કાર્ય કરી શકશે નહીં. અપડેટ માટે તપાસવાનો પ્રયાસ કરો અથવા ડેવલપરનો સંપર્ક કરો."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"અપડેટ માટે તપાસો"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"તમારી પાસે નવા સંદેશા છે"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index e1bb060fbc76..19e154ef2046 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"वर्क प्रोफ़ाइल चालू करें?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"आपके काम से जुड़े ऐप्लिकेशन, सूचनाएं, डेटा और वर्क प्रोफ़ाइल से जुड़ी दूसरी सुविधाएं चालू हो जाएंगी"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"चालू करें"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ऐप्लिकेशन उपलब्ध नहीं है"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> इस समय उपलब्ध नहीं है."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"यह ऐप्लिकेशन Android के पुराने वर्शन के लिए बनाया गया था, इसलिए हो सकता है कि यह सही से काम न करे. देखें कि अपडेट मौजूद हैं या नहीं, या फिर डेवलपर से संपर्क करें."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"देखें कि अपडेट मौजूद है या नहीं"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"आपके पास नए संदेश हैं"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 383943429400..9a9357f188e2 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1885,6 +1885,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Želite uključiti radni profil?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Uključit će se vaše radne aplikacije, obavijesti, podaci i druge značajke radnog profila"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Uključi"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutačno nije dostupna."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova je aplikacija razvijena za stariju verziju Androida i možda neće funkcionirati pravilno. Potražite ažuriranja ili se obratite razvojnom programeru."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Provjeri ažuriranja"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index fcea1ec4975a..d4ac7e2b6a61 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Bekapcsolja a munkaprofilt?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"A munkahelyi alkalmazások, értesítések, adatok és a munkaprofilhoz tartozó egyéb funkciók be lesznek kapcsolva"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Bekapcsolás"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Az alkalmazás nem hozzáférhető"</string> + <string name="app_blocked_message" msgid="542972921087873023">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> jelenleg nem hozzáférhető."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ez az alkalmazás az Android egyik korábbi verziójához készült, így elképzelhető, hogy nem működik majd megfelelően ezen a rendszeren. Keressen frissítéseket, vagy vegye fel a kapcsolatot a fejlesztővel."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Frissítés keresése"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Új üzenetei érkeztek"</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index cbd754f15e29..5cd8e8eeb8e5 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Միացնե՞լ աշխատանքային պրոֆիլը"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Ձեր աշխատանքային հավելվածները, ծանուցումները, տվյալները և աշխատանքային պրոֆիլի մյուս գործառույթները կմիանան"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Միացնել"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Հավելվածը հասանելի չէ"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածն այս պահին հասանելի չէ։"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Այս հավելվածը ստեղծվել է Android-ի ավելի հին տարբերակի համար և կարող է պատշաճ չաշխատել: Ստուգեք թարմացումների առկայությունը կամ դիմեք մշակողին:"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Ստուգել նոր տարբերակի առկայությունը"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Դուք ունեք նոր հաղորդագրություններ"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index f1a94ff0149e..3a1571df64bd 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Aktifkan profil kerja?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Aplikasi kerja, notifikasi, data, dan fitur profil kerja lainnya akan diaktifkan"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktifkan"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikasi tidak tersedia"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak tersedia saat ini."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aplikasi ini dibuat untuk Android versi lama dan mungkin tidak berfungsi sebagaimana mestinya. Coba periksa apakah ada update, atau hubungi developer."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Periksa apakah ada update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Ada pesan baru"</string> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index b4cb76d367b5..eddb21455101 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Kveikja á vinnusniði?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Kveikt verður á vinnuforritum, tilkynningum, gögnum og öðrum eiginleikum vinnusniðsins"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Kveikja"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Forrit er ekki tiltækt"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ekki tiltækt núna."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Þetta forrit var hannað fyrir eldri útgáfu af Android og ekki er víst að það virki eðlilega. Athugaðu hvort uppfærslur séu í boði eða hafðu samband við þróunaraðilann."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Leita að uppfærslu"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Þú ert með ný skilaboð"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 23a62a8a48f8..135a226e0cf5 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Attivare il profilo di lavoro?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Le tue app di lavoro, le notifiche, i dati e altri elementi del profilo di lavoro saranno attivati."</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Attiva"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"L\'app non è disponibile"</string> + <string name="app_blocked_message" msgid="542972921087873023">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> non è al momento disponibile."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Questa app è stata realizzata per una versione precedente di Android e potrebbe non funzionare correttamente. Prova a verificare la disponibilità di aggiornamenti o contatta lo sviluppatore."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Verifica la presenza di aggiornamenti"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Hai nuovi messaggi"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 4319aa8644f0..65b738461e56 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"להפעיל את פרופיל העבודה?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"אפליקציות העבודה, התראות, נתונים ותכונות נוספות של פרופיל העבודה יופעלו"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"הפעל"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"האפליקציה לא זמינה"</string> + <string name="app_blocked_message" msgid="542972921087873023">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> לא זמינה בשלב זה."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"האפליקציה הזו עוצבה לגרסה ישנה יותר של Android וייתכן שלא תפעל כראוי. ניתן לבדוק אם יש עדכונים או ליצור קשר עם המפתח."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"האם יש עדכון חדש?"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"יש לך הודעות חדשות"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 481af76de9a7..bda206442c6e 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"仕事用プロファイルの有効化"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"仕事用のアプリ、通知、データなど、仕事用プロファイルの機能が ON になります"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ON にする"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"アプリの利用不可"</string> + <string name="app_blocked_message" msgid="542972921087873023">"現在 <xliff:g id="APP_NAME">%1$s</xliff:g> はご利用になれません。"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"このアプリは以前のバージョンの Android 用に作成されており、正常に動作しない可能性があります。アップデートを確認するか、デベロッパーにお問い合わせください。"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"アップデートを確認"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"新着メッセージがあります"</string> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index c2c28e515593..c47b71048163 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"ჩაირთოს სამსახურის პროფილი?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"თქვენი სამსახურის აპები, შეტყობინებები, მონაცემები და სამსახურის პროფილის ყველა სხვა ფუნქცია ჩაირთვება"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ჩართვა"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"აპი მიუწვდომელია"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ამჟამად მიუწვდომელია."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ეს აპი Android-ის ძველი ვერსიისთვის შეიქმნა და შესაძლოა სათანადოდ არ მუშაობდეს. გადაამოწმეთ განახლებები ან დაუკავშირდით დეველოპერს."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"განახლების შემოწმება"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"თქვენ ახალი შეტყობინებები გაქვთ"</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 7cb05129495d..241cf08bf594 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Жұмыс профилі қосылсын ба?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Жұмыс қолданбалары, хабарландырулар, деректер және басқа да жұмыс профильдерінің мүмкіндіктері қосылады"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Қосу"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Қолданба қолжетімді емес"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> қазір қолжетімді емес."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Қолданба Android жүйесінің ескі нұсқасына арналған және дұрыс жұмыс істемеуі мүмкін. Жаңартылған нұсқаны тексеріңіз немесе әзірлеушіге хабарласыңыз."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Жаңартылған нұсқаны тексеру"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Сізде жаңа хабарлар бар"</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index e0267d363721..483e236c13c7 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -1855,6 +1855,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"បើកកម្រងព័ត៌មានការងារ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"កម្មវិធីការងារ ការជូនដំណឹង ទិន្នន័យ និងមុខងារកម្រងព័ត៌មានការងារផ្សេងទៀតរបស់អ្នកនឹងត្រូវបានបើក"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"បើក"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"មិនអាចប្រើកម្មវិធីនេះបានទេ"</string> + <string name="app_blocked_message" msgid="542972921087873023">"មិនអាចប្រើ <xliff:g id="APP_NAME">%1$s</xliff:g> នៅពេលនេះបានទេ។"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"កម្មវិធីនេះត្រូវបានបង្កើតឡើងសម្រាប់កំណែប្រព័ន្ធប្រតិបត្តិការ Android ចាស់ ហើយវាអាចដំណើរការខុសប្រក្រតី។ សូមសាកល្បងពិនិត្យមើលកំណែថ្មី ឬទាក់ទងទៅអ្នកអភិវឌ្ឍន៍។"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"រកមើលកំណែថ្មី"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"អ្នកមានសារថ្មី"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 4a2bc72d27d8..7133e89a8436 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ ಆನ್ ಮಾಡುವುದೇ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"ನಿಮ್ಮ ಕೆಲಸದ ಅಪ್ಲಿಕೇಶನ್ಗಳು, ಅಧಿಸೂಚನೆಗಳು, ಡೇಟಾ ಮತ್ತು ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ನ ಇತರ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಆನ್ ಮಾಡಲಾಗುತ್ತದೆ"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ಆನ್ ಮಾಡಿ"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ಆ್ಯಪ್ ಲಭ್ಯವಿಲ್ಲ"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಇದೀಗ ಲಭ್ಯವಿಲ್ಲ."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು Android ನ ಹಳೆಯ ಆವೃತ್ತಿಗೆ ರಚಿಸಲಾಗಿದೆ ಮತ್ತು ಸರಿಯಾಗಿ ಕೆಲಸ ಮಾಡದಿರಬಹುದು. ಅಪ್ಡೇಟ್ಗಳನ್ನು ಪರಿಶೀಲಿಸಲು ಪ್ರಯತ್ನಿಸಿ ಅಥವಾ ಡೆವಲಪರ್ ಅನ್ನು ಸಂಪರ್ಕಿಸಿ."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ಅಪ್ಡೇಟ್ಗಾಗಿ ಪರಿಶೀಲಿಸಿ"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"ನೀವು ಹೊಸ ಸಂದೇಶಗಳನ್ನು ಹೊಂದಿರುವಿರಿ"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 0ac7a1447f7c..12c05baedbd7 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"직장 프로필을 사용 설정하시겠어요?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"업무용 앱, 알림, 데이터 및 기타 직장 프로필 기능이 사용 설정됩니다."</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"사용 설정"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"앱을 사용할 수 없습니다"</string> + <string name="app_blocked_message" msgid="542972921087873023">"현재 <xliff:g id="APP_NAME">%1$s</xliff:g> 앱을 사용할 수 없습니다."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"이 앱은 Android 이전 버전에 맞게 개발되었기 때문에 제대로 작동하지 않을 수 있습니다. 업데이트를 확인하거나 개발자에게 문의하세요."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"업데이트 확인"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"새 메시지 있음"</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index 88e36ff0b533..ccbf845ea1e0 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -190,7 +190,7 @@ <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Өтө көп жолу сырсөздү киргизүү аракети жасалды"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Админ түзмөктөн жеке колдонуу үчүн баш тартты"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Түзмөктү ишкана башкарат"</string> - <string name="network_logging_notification_text" msgid="1327373071132562512">"Ишканаңыз бул түзмөктү башкарат жана тармак трафигин көзөмөлдөшү мүмкүн. Чоо-жайын көрүү үчүн таптап коюңуз."</string> + <string name="network_logging_notification_text" msgid="1327373071132562512">"Ишканаңыз бул түзмөктү башкарат жана тармак трафигин көзөмөлдөшү мүмкүн. Чоо-жайын билгиңиз келсе, таптап коюңуз."</string> <string name="factory_reset_warning" msgid="6858705527798047809">"Түзмөгүңүз тазаланат"</string> <string name="factory_reset_message" msgid="2657049595153992213">"Түзмөктү башкаруучу колдонмо жараксыз. Түзмөгүңүз азыр тазаланат.\n\nСуроолоруңуз болсо, ишканаңыздын администраторуна кайрылыңыз."</string> <string name="printing_disabled_by" msgid="3517499806528864633">"Басып чыгаруу <xliff:g id="OWNER_APP">%s</xliff:g> тарабынан өчүрүлдү."</string> @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Жумуш профили күйгүзүлсүнбү?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Жумуш колдонмолоруңуз, эскертмелериңиз, дайын-даректериңиз жана жумуш профилинин башка функциялары күйгүзүлөт."</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Күйгүзүү"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Колдонмо учурда жеткиликсиз"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> учурда жеткиликсиз"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Бул колдонмо Android\'дин эски версиясы үчүн иштеп чыгарылган, андыктан туура эмес иштеши мүмкүн. Жаңыртууларды издеп көрүңүз же иштеп чыгуучуга кайрылыңыз."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Жаңыртууну издөө"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Сизге жаңы билдирүүлөр келди"</string> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index 63b87ac54e3e..e8bda532f31b 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"ເປີດໃຊ້ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກບໍ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"ແອັບວຽກຂອງທ່ານ, ການແຈ້ງເຕືອນ, ຂໍ້ມູນ ແລະ ຄຸນສົມບັດໂປຣໄຟລ໌ວຽກຈະຖືກເປີດໃຊ້"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ເປີດ"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ແອັບບໍ່ສາມາດໃຊ້ໄດ້"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ່ສາມາດໃຊ້ໄດ້ໃນຕອນນີ້."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ແອັບນີ້ຖືກສ້າງຂຶ້ນສຳລັບ Android ເວີຊັນທີ່ເກົ່າກວ່າ ແລະ ອາດເຮັດວຽກໄດ້ບໍ່ປົກກະຕິ. ໃຫ້ລອງກວດສອບເບິ່ງອັບເດດ ຫຼື ຕິດຕໍ່ຜູ້ພັດທະນາ."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ກວດເບິ່ງອັບເດດ"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"ທ່ານມີຂໍ້ຄວາມໃໝ່"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index b8d848e91929..ad90ab3412e2 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Įjungti darbo profilį?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Darbo programos, pranešimai, duomenys ir kitos darbo profilio funkcijos bus išjungtos"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Įjungti"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Programa nepasiekiama."</string> + <string name="app_blocked_message" msgid="542972921087873023">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ šiuo metu nepasiekiama."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ši programa sukurta naudoti senesnės versijos sistemoje „Android“ ir gali tinkamai neveikti. Pabandykite patikrinti, ar yra naujinių, arba susisiekite su kūrėju."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Tikrinti, ar yra naujinių"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Turite naujų pranešimų"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index f168bb4121c8..6ffe39af644f 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1885,6 +1885,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Vai ieslēgt darba profilu?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Tiks ieslēgtas jūsu darba lietotnes, paziņojumi, dati un citas darba profila funkcijas."</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Ieslēgt"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Lietotne nav pieejama"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> pašlaik nav pieejama."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Šī lietotne tika izstrādāta vecākai Android versijai un var nedarboties pareizi. Meklējiet atjauninājumus vai sazinieties ar izstrādātāju."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Meklēt atjauninājumu"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Jums ir jaunas īsziņas."</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index f94d191fba28..bb1d9c6ca23e 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -1855,6 +1855,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Да се вклучи работниот профил?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Вашите работни апликации, известувања, податоци и други функции на работниот профил ќе бидат вклучени"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Вклучи"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Апликацијата не е достапна"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> не е достапна во моментов."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Апликацијава е создадена за постара верзија на Android и може да не функционира правилно. Проверете за ажурирања или контактирајте со програмерот."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Проверка за ажурирање"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Имате нови пораки"</string> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index 1efd21132ebc..31edd8cc324d 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"ഔദ്യോഗിക പ്രൊഫൈൽ ഓണാക്കണോ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"നിങ്ങളുടെ ഔദ്യോഗിക ആപ്പുകൾ, അറിയിപ്പുകൾ, ഡാറ്റ, മറ്റ് ഔദ്യോഗിക പ്രൊഫൈൽ ഫീച്ചറുകൾ എന്നിവ ഓണാക്കും"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ഓണാക്കുക"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ആപ്പ് ലഭ്യമല്ല"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ഇപ്പോൾ ലഭ്യമല്ല."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ഈ ആപ്പ് Android-ന്റെ പഴയ പതിപ്പിനായി നിർമ്മിച്ചിരിക്കുന്നതിനാൽ ശരിയായി പ്രവർത്തിച്ചേക്കില്ല. അപ്ഡേറ്റിനായി പരിശോധിക്കുക, അല്ലെങ്കിൽ ഡെവലപ്പറുമായി ബന്ധപ്പെടുക."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"അപ്ഡേറ്റിനായി പരിശോധിക്കുക"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"നിങ്ങൾക്ക് പുതിയ സന്ദേശങ്ങൾ ഉണ്ട്"</string> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index cc031754b2b7..eb61a46d876a 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Ажлын профайлыг асаах уу?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Таны ажлын апп, мэдэгдэл, өгөгдөл болон бусад ажлын профайлын онцлогийг асаана"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Асаах"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Апп боломжгүй байна"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> яг одоо боломжгүй байна."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Энэ аппыг Андройдын хуучин хувилбарт зориулсан бөгөөд буруу ажиллаж болзошгүй. Шинэчлэлтийг шалгаж эсвэл хөгжүүлэгчтэй холбогдоно уу."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Шинэчлэлтийг шалгах"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Танд шинэ зурвасууд байна"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index cd9b6fc5ee5a..0a8f4afa79ac 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"कार्य प्रोफाइल चालू ठेवायची?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"तुमची कार्य अॅप्स, सूचना, डेटा आणि अन्य कार्य प्रोफाइल वैशिष्ट्ये चालू केली जातील"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"चालू करा"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ॲप उपलब्ध नाही"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> आता उपलब्ध नाही."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"हे अॅप Android च्या जुन्या आवृत्ती साठी तयार करण्यात आले होते आणि योग्यरितीने कार्य करू शकणार नाही. अपडेट आहेत का ते तपासून पाहा, किंवा डेव्हलपरशी संपर्क साधण्याचा प्रयत्न करा."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"अपडेट आहे का ते तपासा"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"आपल्याकडे नवीन मेसेज आहेत"</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index 0a4073519b59..59c7d65afb32 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Hidupkan profil kerja?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Apl kerja, pemberitahuan, data dan ciri profil kerja anda yang lain akan dihidupkan"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Hidupkan"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Apl tidak tersedia"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak tersedia sekarang."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Apl ini dibina untuk versi Android yang lebih lama dan mungkin tidak berfungsi dengan betul. Cuba semak kemas kini atau hubungi pembangun."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Semak kemas kini"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Anda mempunyai mesej baharu"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 9e048411b5c7..b3efcb180d90 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"အလုပ်ပရိုဖိုင် ဖွင့်လိုသလား။"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"သင်၏ အလုပ်အက်ပ်၊ အကြောင်းကြားချက်၊ ဒေတာနှင့် အခြားအလုပ်ပရိုဖိုင် ဝန်ဆောင်မှုများကို ဖွင့်လိုက်ပါမည်"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ဖွင့်ပါ"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"အက်ပ်ကို မရနိုင်ပါ"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ကို ယခု မရနိုင်ပါ။"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ဤအက်ပ်ကို Android ဗားရှင်းဟောင်းအတွက် ပြုလုပ်ထားခြင်းဖြစ်ပြီး ပုံမှန်အလုပ်မလုပ်နိုင်ပါ။ အပ်ဒိတ်များအတွက် ရှာကြည့်ပါ သို့မဟုတ် ဆော့ဖ်ဝဲအင်ဂျင်နီယာကို ဆက်သွယ်ပါ။"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"အပ်ဒိတ်စစ်ရန်"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"သင့်ထံတွင် စာအသစ်များရောက်နေသည်"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 45ab3e552c17..5a3a5886eb24 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Vil du slå på jobbprofilen?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Jobbappene dine samt varsler, data og andre funksjoner i jobbprofilen din blir slått på"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Slå på"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Appen er ikke tilgjengelig"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ikke tilgjengelig for øyeblikket."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Denne appen er utviklet for en eldre versjon av Android og fungerer kanskje ikke som den skal. Prøv å se etter oppdateringer, eller kontakt utvikleren."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Se etter oppdateringer"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Du har nye meldinger"</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index 11033a740aa1..e95866ba1a7c 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -1859,6 +1859,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"कार्य प्रोफाइल सक्रिय गर्ने?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"तपाईंका कार्यसम्बन्धी अनुप्रयोग, सूचना, डेटा र कार्य प्रोफाइलका अन्य सुविधाहरू सक्रिय गरिने छन्"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"सक्रिय गर्नुहोस्"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"अनुप्रयोग उपलब्ध छैन"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> अहिले उपलब्ध छैन।"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"यो अनुप्रयोग Android को पुरानो संस्करणका लागि बनाइएको हुनाले यसले सही ढङ्गले काम नगर्न सक्छ। अद्यावधिकहरू उपलब्ध छन् वा छैनन् भनी जाँच गरी हेर्नुहोस् वा यसको विकासकर्तालाई सम्पर्क गर्नुहोस्।"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"अद्यावधिक उपलब्ध छ वा छैन भनी जाँच गर्नुहोस्"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"तपाईंलाई नयाँ सन्देश आएको छ"</string> 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-nl/strings.xml b/core/res/res/values-nl/strings.xml index 13f6725c82b7..e524c1b71d85 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Werkprofiel inschakelen?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Je werk-apps, meldingen, gegevens en andere functies van je werkprofiel worden uitgeschakeld"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Inschakelen"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"App is niet beschikbaar"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is momenteel niet beschikbaar."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Deze app is ontwikkeld voor een oudere versie van Android en werkt mogelijk niet op de juiste manier. Controleer op updates of neem contact op met de ontwikkelaar."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Controleren op update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Je hebt nieuwe berichten"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index 4424af7dcab0..246b4d9b86f5 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"ୱର୍କ ପ୍ରୋଫାଇଲ୍କୁ ଚାଲୁ କରିବେ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"ଆପଣଙ୍କର କାର୍ଯ୍ୟକାରୀ ଆପ୍, ବିଜ୍ଞପ୍ତି, ଡାଟା ଓ ଅନ୍ୟ ୱର୍କ ପ୍ରୋଫାଇଲ୍ଗୁଡ଼ିକ ଚାଲୁ ହୋଇଯିବ"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ଅନ୍ କରନ୍ତୁ"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ଆପ୍ ଉପଲବ୍ଧ ନାହିଁ"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବର୍ତ୍ତମାନ ଉପଲବ୍ଧ ନାହିଁ।"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ଏହି ଆପ୍କୁ Androidର ପୁରୁଣା ଭର୍ସନ୍ ପାଇଁ ନିର୍ମାଣ କରାଯାଇଥିଲା ଏବଂ ଠିକ୍ ଭାବେ କାମ କରିନପାରେ। ଏହାପାଇଁ ଅପଡେଟ୍ ଅଛି କି ନାହିଁ ଯାଞ୍ଚ କରନ୍ତୁ କିମ୍ବା ଡେଭେଲପର୍ଙ୍କ ସହିତ ସମ୍ପର୍କ କରନ୍ତୁ।"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ଅପଡେଟ୍ ପାଇଁ ଯାଞ୍ଚ କରନ୍ତୁ"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"ଆପଣଙ୍କ ପାଖରେ ନୂଆ ମେସେଜ୍ ରହିଛି"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 8c87581f9a61..3d688325fc6e 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"ਕੀ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਚਾਲੂ ਕਰਨੀ ਹੈ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"ਤੁਹਾਡੀਆਂ ਕਾਰਜ-ਸਥਾਨ ਐਪਾਂ, ਸੂਚਨਾਵਾਂ, ਡਾਟਾ ਅਤੇ ਹੋਰ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਚਾਲੂ ਕੀਤੀਆਂ ਜਾਣਗੀਆਂ"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ਚਾਲੂ ਕਰੋ"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ਐਪ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਐਪ ਇਸ ਵੇਲੇ ਉਪਲਬਧ ਨਹੀਂ ਹੈ।"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ਇਹ ਐਪ Android ਦੇ ਕਿਸੇ ਵਧੇਰੇ ਪੁਰਾਣੇ ਵਰਜਨ ਲਈ ਬਣਾਈ ਗਈ ਸੀ ਅਤੇ ਸ਼ਾਇਦ ਸਹੀ ਢੰਗ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ। ਅੱਪਡੇਟਾਂ ਲਈ ਜਾਂਚ ਕਰੋ ਜਾਂ ਵਿਕਾਸਕਾਰ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ਅੱਪਡੇਟ ਦੀ ਜਾਂਚ ਕਰੋ"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"ਤੁਹਾਨੂੰ ਨਵੇਂ ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਹੋਏ ਹਨ"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 5efbfbe36830..2fb9ac20ba23 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Włączyć profil służbowy?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Aplikacje do pracy, powiadomienia, dane i inne funkcje profilu do pracy zostaną włączone"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Włącz"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacja jest niedostępna"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> jest obecnie niedostępna."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ta aplikacja jest na starszą wersję Androida i może nie działać prawidłowo. Sprawdź dostępność aktualizacji lub skontaktuj się z programistą."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Sprawdź dostępność aktualizacji"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Masz nowe wiadomości"</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index db4844163ba9..1ae0c360bf11 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -509,8 +509,8 @@ <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Permite que o app execute métodos para adicionar e excluir modelos de impressão digital para uso."</string> <string name="permlab_useFingerprint" msgid="1001421069766751922">"usar hardware de impressão digital"</string> <string name="permdesc_useFingerprint" msgid="412463055059323742">"Permite que o app use hardware de impressão digital para autenticação."</string> - <string name="permlab_audioWrite" msgid="8501705294265669405">"modificar sua coleção de músicas"</string> - <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite que o app modifique sua coleção de músicas."</string> + <string name="permlab_audioWrite" msgid="8501705294265669405">"modificar sua biblioteca de música"</string> + <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite que o app modifique sua biblioteca de música."</string> <string name="permlab_videoWrite" msgid="5940738769586451318">"modificar sua coleção de vídeos"</string> <string name="permdesc_videoWrite" msgid="6124731210613317051">"Permite que o app modifique sua coleção de vídeos."</string> <string name="permlab_imagesWrite" msgid="1774555086984985578">"modificar sua coleção de fotos"</string> @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Ativar o perfil de trabalho?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Seus apps, notificações, dados e outros recursos do perfil de trabalho serão ativados"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Ativar"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"O app não está disponível"</string> + <string name="app_blocked_message" msgid="542972921087873023">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível no momento."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Este app foi criado para uma versão mais antiga do Android e pode não funcionar corretamente. Tente verificar se há atualizações ou entre em contato com o desenvolvedor."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Procurar atualizações"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Você tem mensagens novas"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 04296fe10378..c381709f61db 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Ativar o perfil de trabalho?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"As aplicações de trabalho, as notificações, os dados e outras funcionalidades do perfil de trabalho serão desativados"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Ativar"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"A app não está disponível"</string> + <string name="app_blocked_message" msgid="542972921087873023">"De momento, a app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicação foi concebida para uma versão mais antiga do Android e pode não funcionar corretamente. Experimente verificar se existem atualizações ou contacte o programador."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Verificar se existem atualizações"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Tem mensagens novas"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index db4844163ba9..1ae0c360bf11 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -509,8 +509,8 @@ <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Permite que o app execute métodos para adicionar e excluir modelos de impressão digital para uso."</string> <string name="permlab_useFingerprint" msgid="1001421069766751922">"usar hardware de impressão digital"</string> <string name="permdesc_useFingerprint" msgid="412463055059323742">"Permite que o app use hardware de impressão digital para autenticação."</string> - <string name="permlab_audioWrite" msgid="8501705294265669405">"modificar sua coleção de músicas"</string> - <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite que o app modifique sua coleção de músicas."</string> + <string name="permlab_audioWrite" msgid="8501705294265669405">"modificar sua biblioteca de música"</string> + <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite que o app modifique sua biblioteca de música."</string> <string name="permlab_videoWrite" msgid="5940738769586451318">"modificar sua coleção de vídeos"</string> <string name="permdesc_videoWrite" msgid="6124731210613317051">"Permite que o app modifique sua coleção de vídeos."</string> <string name="permlab_imagesWrite" msgid="1774555086984985578">"modificar sua coleção de fotos"</string> @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Ativar o perfil de trabalho?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Seus apps, notificações, dados e outros recursos do perfil de trabalho serão ativados"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Ativar"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"O app não está disponível"</string> + <string name="app_blocked_message" msgid="542972921087873023">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível no momento."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Este app foi criado para uma versão mais antiga do Android e pode não funcionar corretamente. Tente verificar se há atualizações ou entre em contato com o desenvolvedor."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Procurar atualizações"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Você tem mensagens novas"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 030e76a2f6cf..7c5b63efdb02 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -1885,6 +1885,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Activați profilul de serviciu?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Se vor activa aplicațiile dvs. de serviciu, notificările, datele și alte funcții ale profilului de serviciu"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Activați"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplicația nu este disponibilă"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> nu este disponibilă momentan."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Această aplicație a fost creată pentru o versiune Android mai veche și este posibil să nu funcționeze corect. Încercați să căutați actualizări sau contactați dezvoltatorul."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Căutați actualizări"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Aveți mesaje noi"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 94029f51c0c0..fa2c9f5697ab 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1177,7 +1177,7 @@ <string name="whichEditApplication" msgid="6191568491456092812">"Редактировать с помощью приложения:"</string> <string name="whichEditApplicationNamed" msgid="8096494987978521514">"Редактировать с помощью приложения \"%1$s\""</string> <string name="whichEditApplicationLabel" msgid="1463288652070140285">"Изменить"</string> - <string name="whichSendApplication" msgid="4143847974460792029">"Отправка данных"</string> + <string name="whichSendApplication" msgid="4143847974460792029">"Поделиться"</string> <string name="whichSendApplicationNamed" msgid="4470386782693183461">"Поделиться через %1$s"</string> <string name="whichSendApplicationLabel" msgid="7467813004769188515">"Поделиться"</string> <string name="whichSendToApplication" msgid="77101541959464018">"Выберите приложение"</string> @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Включить рабочий профиль?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Будут включены корпоративные приложения, уведомления, данные и другие функции рабочего профиля."</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Включить"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Приложение недоступно"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" сейчас недоступно."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Это приложение было создано для более ранней версии Android и может работать со сбоями. Проверьте наличие обновлений или свяжитесь с разработчиком."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Проверить обновления"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Новые сообщения"</string> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index 73cf01890108..dbe1f2203c84 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -1855,6 +1855,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"කාර්යාල පැතිකඩ ක්රියාත්මක කරන්නද?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"ඔබගේ වැඩ යෙදුම්, දැනුම්දීම්, දත්ත සහ වෙනත් කාර්යාල පැතිකඩ විශේෂාංග ක්රියාත්මක කරනු ඇත"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ක්රියාත්මක කරන්න"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"යෙදුම ලබා ගත නොහැකිය"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> මේ දැන් ලබා ගත නොහැකිය."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"මෙම යෙදුම Android හි පැරණි අනුවාදයක් සඳහා තනා ඇති අතර නිසියාකාරව ක්රියා නොකරනු ඇත. යාවත්කාලීන සඳහා පරික්ෂා කිරීම උත්සාහ කරන්න, නැතහොත් සංවර්ධක අමතන්න."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"යාවත්කාලීන සඳහා පරික්ෂා කරන්න"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"ඔබට නව පණිවිඩ තිබේ"</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index b86fc4696a01..5483efdb4d51 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Zapnúť pracovný profil?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Pracovné aplikácie, upozornenia, dáta a ďalšie funkcie pracovného profilu sa zapnú"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Zapnúť"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikácia nie je dostupná"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> nie je teraz dostupná."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Táto aplikácia bola zostavená pre staršiu verziu Androidu a nemusí správne fungovať. Skúste skontrolovať dostupnosť aktualizácií alebo kontaktovať vývojára."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Skontrolovať dostupnosť aktualizácie"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Máte nové správy."</string> @@ -1944,7 +1946,7 @@ <string name="app_category_social" msgid="2278269325488344054">"Sociálne siete a komunikácia"</string> <string name="app_category_news" msgid="1172762719574964544">"Noviny a časopisy"</string> <string name="app_category_maps" msgid="6395725487922533156">"Mapy a navigácia"</string> - <string name="app_category_productivity" msgid="1844422703029557883">"Produktivita"</string> + <string name="app_category_productivity" msgid="1844422703029557883">"Kancelárske"</string> <string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"Úložisko zariadenia"</string> <string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"Ladenie cez USB"</string> <string name="time_picker_hour_label" msgid="4208590187662336864">"hodina"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index bc2bc3acb7db..fed39e8343f4 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Želite vklopiti delovni profil?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Vklopili boste svoje delovne aplikacije, obvestila, podatke in druge funkcije delovnega profila"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Vklop"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija ni na voljo"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno ni na voljo."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ta aplikacija je bila zasnovana za starejšo različico Androida in morda ne bo delovala pravilno. Preverite, ali so na voljo posodobitve, ali pa se obrnite na razvijalca."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Preveri, ali je na voljo posodobitev"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nova sporočila."</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index a35328879f29..ec61fb966b5e 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Të aktivizohet profili i punës?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Aplikacionet e punës, njoftimet, të dhënat e tua dhe funksionet e tjera të profilit të punës do të aktivizohen"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivizo"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacioni nuk ofrohet"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> nuk ofrohet për momentin."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ky aplikacion është ndërtuar për një version më të vjetër të Android dhe mund të mos funksionojë mirë. Provo të kontrollosh për përditësime ose kontakto me zhvilluesin."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kontrollo për përditësim"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Ke mesazhe të reja"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 8ee644ad2e4f..e1c4e1822e5b 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -1885,6 +1885,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Да укључимо профил за Work?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Укључиће се пословне апликације, обавештења, подаци и друге функције профила за Work"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Укључи"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Апликација није доступна"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> тренутно није доступна."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ова апликација је направљена за старију верзију Android-а, па можда неће радити исправно. Потражите ажурирања или контактирајте програмера."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Потражи ажурирање"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Имате нове поруке"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 577933e6a296..0737b22c4aea 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Vill du aktivera jobbprofilen?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Jobbappar, aviseringar, data och andra funktioner i jobbprofilen aktiveras"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivera"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Appen är inte tillgänglig"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> är inte tillgängligt just nu."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Appen har utvecklats för en äldre version av Android och kanske inte fungerar som den ska. Testa att söka efter uppdateringar eller kontakta utvecklaren."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Sök efter uppdateringar"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Du har nya meddelanden"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index a6ee3667b3c1..3c6b0fad22c8 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Ungependa kuwasha wasifu wa kazini?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Hatua hii itawasha data, arifa, programu za kazini, arifa na vipengele vingine vya wasifu wa kazini"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Washa"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Programu haipatikani"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> haipatikani hivi sasa."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Programu hii iliundwa kwa ajili ya toleo la zamani la Android na huenda isifanye kazi vizuri. Jaribu kuangalia masasisho au uwasiliane na msanidi programu."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Angalia masasisho"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Una ujumbe mpya"</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index a56465fcd8ac..e80f37becd91 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"பணிச் சுயவிவரத்தை ஆன் செய்யவா?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"பணி ஆப்ஸ், அறிவிப்புகள், தரவு மற்றும் பிற பணிச் சுயவிவர அம்சங்கள் ஆன் செய்யப்படும்"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"இயக்கு"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"இந்த ஆப்ஸ் இப்போது கிடைப்பதில்லை"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் இப்போது கிடைப்பதில்லை."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"இந்த ஆப்ஸ் Android இன் பழைய பதிப்புக்காக உருவாக்கப்பட்டதால், சரியாக வேலை செய்யாமல் போகலாம். புதுப்பிப்புகள் ஏதேனும் உள்ளதா எனப் பார்க்கவும் அல்லது டெவெலப்பரைத் தொடர்புகொள்ளவும்."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"புதுப்பிப்பு உள்ளதா எனப் பார்"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"புதிய செய்திகள் வந்துள்ளன"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 7a0aece84500..81d4b4fd0220 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"కార్యాలయ ప్రొఫైల్ని ఆన్ చేయాలా?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"మీ కార్యాలయ యాప్లు, నోటిఫికేషన్లు, డేటా మరియు ఇతర కార్యాలయ ప్రొఫైల్ ఫీచర్లు ఆన్ చేయబడతాయి"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ఆన్ చేయి"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"యాప్ అందుబాటులో లేదు"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ప్రస్తుతం అందుబాటులో లేదు."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ఈ యాప్ పాత వెర్షన్ Android కోసం రూపొందించబడింది మరియు అది సరిగ్గా పని చేయకపోవచ్చు. అప్డేట్ల కోసం తనిఖీ చేయడానికి ప్రయత్నించండి లేదా డెవలపర్ని సంప్రదించండి."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"అప్డేట్ కోసం తనిఖీ చేయండి"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"మీకు కొత్త సందేశాలు ఉన్నాయి"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 98f2d8880b8a..46530ca33912 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"เปิดโปรไฟล์งานไหม"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"ระบบจะเปิดแอปงาน การแจ้งเตือน ข้อมูล และฟีเจอร์อื่นๆ ในโปรไฟล์งาน"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"เปิด"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"แอปไม่พร้อมใช้งาน"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ไม่พร้อมใช้งานในขณะนี้"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"แอปนี้สร้างขึ้นเพื่อ Android เวอร์ชันเก่าและอาจทำงานผิดปกติ โปรดลองตรวจหาการอัปเดตหรือติดต่อนักพัฒนาซอฟต์แวร์"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ตรวจสอบอัปเดต"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"คุณมีข้อความใหม่"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index c38a6d54cdbd..b9f1f293475b 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"I-on ang profile sa trabaho?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Mao-on ang iyong mga app sa trabaho, notification, data, at iba pang feature sa profile sa trabaho"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"I-on"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Hindi available ang app"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Hindi available sa ngayon ang <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ang app na ito ay ginawa para sa mas lumang bersyon ng Android at maaaring hindi gumana nang maayos. Subukang tingnan kung may mga update, o makipag-ugnayan sa developer."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Tingnan kung may update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Mayroon kang mga bagong mensahe"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 5e0930947afb..81fb07933f0c 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"İş profili açılsın mı?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"İş uygulamalarınız, bildirimleriniz, verileriniz ve diğer iş profili özellikleriniz açılacak"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Aç"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Uygulama kullanılamıyor"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulaması şu anda kullanılamıyor."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Bu uygulama Android\'in daha eski bir sürümü için oluşturuldu ve düzgün çalışmayabilir. Güncellemeleri kontrol etmeyi deneyin veya geliştiriciyle iletişime geçin."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Güncellemeleri denetle"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Yeni mesajlarınız var"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 1096f97e3c98..c71ea74ccaad 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Увімкнути робочий профіль?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Додатки, сповіщення, дані й інші функції робочого профілю буде ввімкнено"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Увімкнути"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Додаток недоступний"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> зараз недоступний."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Цей додаток створений для старішої версії Android і може працювати неналежним чином. Спробуйте знайти оновлення або зв’яжіться з розробником."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Шукати оновлення"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"У вас є нові повідомлення"</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 4c331916bb57..8a0425bdcd34 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"دفتری پروفائل آن کریں؟"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"آپ کی دفتری ایپس، اطلاعات، ڈیٹا اور دفتری پروفائل کی دیگر خصوصیات آن کر دی جائیں گی"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"آن کریں"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ایپ دستیاب نہیں ہے"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ابھی دستیاب نہیں ہے۔"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"یہ ایپ Android کے پرانے ورژن کے لئے بنائی گئی ہے اور ہو سکتا ہے صحیح طور پر کام نہ کرے۔ اپ ڈیٹس چیک کر کے آزمائیں یا ڈیولپر سے رابطہ کریں۔"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"اپ ڈیٹ چیک کریں"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"آپ کے پاس نئے پیغامات ہیں"</string> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index 23873b5f2b4f..b5c31c2f50dd 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Ishchi profil yoqilsinmi?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Ishchi ilovalar, bildirishnomalar, ma’lumotlar va boshqa ishchi profil imkoniyatlari yoqiladi"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Yoqish"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Ilova ishlamayapti"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Ayni vaqtda <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi ishlamayapti."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Bu ilova eskiroq Android versiyalariga chiqarilgan va xato ishlashi mumkin. Yangilanishlarini tekshiring yoki dasturchi bilan bog‘laning."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Yangilanish borligini tekshirish"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Sizga yangi SMS keldi"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 1ed30d7db57e..970d41ff9203 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Bạn muốn bật hồ sơ công việc?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Ứng dụng công việc, thông báo, dữ liệu và các tính năng khác của hồ sơ công việc sẽ được bật"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Bật"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Ứng dụng này không dùng được"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> hiện không dùng được."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ứng dụng này được xây dựng cho một phiên bản Android cũ hơn và có thể hoạt động không bình thường. Hãy thử kiểm tra các bản cập nhật hoặc liên hệ với nhà phát triển."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kiểm tra bản cập nhật"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Bạn có tin nhắn mới"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 7671063821d0..c9bba5902788 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"要开启工作资料吗?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"您的工作应用、通知、数据及其他工作资料功能将会开启"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"开启"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"应用无法使用"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g>目前无法使用。"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"此应用专为旧版 Android 打造,因此可能无法正常运行。请尝试检查更新或与开发者联系。"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"检查更新"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"您有新消息"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index 89bea07ae1a7..fc652e3b59e5 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"要開啟工作設定檔嗎?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"系統將開啟您的工作應用程式、通知、資料和其他工作設定檔功能"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"開啟"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"無法使用應用程式"</string> + <string name="app_blocked_message" msgid="542972921087873023">"目前無法使用「<xliff:g id="APP_NAME">%1$s</xliff:g>」。"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"此應用程式專為舊版 Android 打造,因此可能無法正常運作。請嘗試檢查更新,或與開發人員聯絡。"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"檢查更新"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"您有新的訊息"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index c95ae37602a0..75d05a29f2c9 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"要開啟工作資料夾嗎?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"系統將開啟你的辦公應用程式、通知、資料和其他工作資料夾功能"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"開啟"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"應用程式無法使用"</string> + <string name="app_blocked_message" msgid="542972921087873023">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」目前無法使用。"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"這個應用程式是專為舊版 Android 所打造,因此可能無法正常運作。請嘗試檢查更新,或是與開發人員聯絡。"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"檢查更新"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"你有新訊息"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 0cd669bb07a7..0d2abc0c4aa7 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Vula iphrofayela yomsebenzi?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Izinhlelo zakho zokusebenza zomsebenzi, izaziso, idatha, nezinye izici zephrofayela yomsebenzi kuzovulwa"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Vula"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Uhlelo lokusebenza alutholakali"</string> + <string name="app_blocked_message" msgid="542972921087873023">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> ayitholakali khona manje."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Lolu hlelo lokusebenza belakhelwe inguqulo endala ye-Android futhi kungenzeka lungasebenzi kahle. Zama ukuhlolela izibuyekezo, noma uxhumane nonjiniyela."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Hlola izibuyekezo"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Unemilayezo emisha"</string> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index b22e1867f257..c66261bb6630 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2056,6 +2056,9 @@ <attr name="name" /> </declare-styleable> <declare-styleable name="AndroidManifestQueriesIntent" parent="AndroidManifestQueries" /> + <declare-styleable name="AndroidManifestQueriesProvider" parent="AndroidManifestQueries" > + <attr name="authorities" /> + </declare-styleable> <!-- The <code>static-library</code> tag declares that this apk is providing itself diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b2d08a9730fc..f17cd45cd880 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3402,8 +3402,8 @@ <!-- True if assistant app should be pinned via Pinner Service --> <bool name="config_pinnerAssistantApp">false</bool> - <!-- List of files pinned by the Pinner Service with the apex boot image b/119800099 --> - <string-array translatable="false" name="config_apexBootImagePinnerServiceFiles"> + <!-- List of files pinned by the Pinner Service with the JIT Zygote boot image b/119800099 --> + <string-array translatable="false" name="config_jitzygoteBootImagePinnerServiceFiles"> </string-array> <!-- Number of days preloaded file cache should be preserved on a device before it can be @@ -4343,4 +4343,7 @@ Determines whether the specified key groups can be used to wake up the device. --> <bool name="config_wakeOnDpadKeyPress">true</bool> <bool name="config_wakeOnAssistKeyPress">true</bool> + + <!-- Whether to default to an expanded list of users on the lock screen user switcher. --> + <bool name="config_expandLockScreenUserSwitcher">false</bool> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c59d25f10b0b..2dd62c176e98 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3037,7 +3037,7 @@ <java-symbol type="bool" name="config_pinnerCameraApp" /> <java-symbol type="bool" name="config_pinnerHomeApp" /> <java-symbol type="bool" name="config_pinnerAssistantApp" /> - <java-symbol type="array" name="config_apexBootImagePinnerServiceFiles" /> + <java-symbol type="array" name="config_jitzygoteBootImagePinnerServiceFiles" /> <java-symbol type="string" name="config_doubleTouchGestureEnableFile" /> @@ -3863,4 +3863,7 @@ <!-- Toast message for background started foreground service while-in-use permission restriction feature --> <java-symbol type="string" name="allow_while_in_use_permission_in_fgs" /> + + <!-- Whether to expand the lock screen user switcher by default --> + <java-symbol type="bool" name="config_expandLockScreenUserSwitcher" /> </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/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 0d7ac089ac4a..70917e76f8b4 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -34,7 +34,7 @@ http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ --> <!-- Arab Emirates --> - <shortcode country="ae" pattern="\\d{1,5}" free="3214|1017" /> + <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214" /> <!-- Albania: 5 digits, known short codes listed --> <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" /> @@ -184,7 +184,7 @@ <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" /> <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed --> - <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="46645|5050|26259|50025|50052|9963|76551" /> + <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963" /> <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf --> <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288" /> @@ -199,7 +199,7 @@ <shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" free="2171" /> <!-- New Zealand: 3-4 digits, known premium codes listed --> - <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" /> + <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" /> <!-- Peru: 4-5 digits (not confirmed), known premium codes listed --> <shortcode country="pe" pattern="\\d{4,5}" free="9963" /> @@ -267,4 +267,7 @@ <!-- Mayotte (French Territory): 1-5 digits (not confirmed) --> <shortcode country="yt" pattern="\\d{1,5}" free="38600,36300,36303,959" /> + <!-- South Africa --> + <shortcode country="za" pattern="\d{1,5}" free="44136" /> + </shortcodes> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 38454ecb6111..e04d3de622d8 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -38,6 +38,7 @@ android_test { "mockito-target-minus-junit4", "ub-uiautomator", "platform-test-annotations", + "platform-compat-test-rules", "truth-prebuilt", "print-test-util-lib", "testng", @@ -51,9 +52,14 @@ android_test { "android.test.base", "android.test.mock", "framework-atb-backward-compatibility", + "framework-all", + "icing-java-proto-lite", + "ext", + "framework-res", ], platform_apis: true, + sdk_version: "core_platform", test_suites: ["device-tests"], certificate: "platform", diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index ee93b397bedf..718ca46a4f18 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -111,6 +111,10 @@ <uses-permission android:name="android.permission.MOVE_PACKAGE" /> <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" /> + <!-- gating and logging permissions --> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" /> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> + <!-- os storage test permissions --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.ASEC_ACCESS" /> @@ -1326,6 +1330,12 @@ android:process=":FakeProvider"> </provider> + <provider + android:name="android.content.SlowProvider" + android:authorities="android.content.SlowProvider" + android:process=":SlowProvider"> + </provider> + <!-- Application components used for os tests --> <service android:name="android.os.MessengerService" diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java index 0a21875fd77d..d1608d055604 100644 --- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java +++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java @@ -234,6 +234,40 @@ public class NotificationHistoryTest { } @Test + public void testRemoveNotificationFromWrite() { + NotificationHistory history = new NotificationHistory(); + + List<HistoricalNotification> postRemoveExpectedEntries = new ArrayList<>(); + List<String> postRemoveExpectedStrings = new ArrayList<>(); + for (int i = 1; i <= 10; i++) { + HistoricalNotification n = getHistoricalNotification("pkg", i); + + if (987654323 != n.getPostedTimeMs()) { + postRemoveExpectedStrings.add(n.getPackage()); + postRemoveExpectedStrings.add(n.getChannelName()); + postRemoveExpectedStrings.add(n.getChannelId()); + postRemoveExpectedEntries.add(n); + } + + history.addNotificationToWrite(n); + } + + history.poolStringsFromNotifications(); + + assertThat(history.getNotificationsToWrite().size()).isEqualTo(10); + // 1 package name and 10 unique channel names and ids + assertThat(history.getPooledStringsToWrite().length).isEqualTo(21); + + history.removeNotificationFromWrite("pkg", 987654323); + + + // 1 package names and 9 * 2 unique channel names and ids + assertThat(history.getPooledStringsToWrite().length).isEqualTo(19); + assertThat(history.getNotificationsToWrite()) + .containsExactlyElementsIn(postRemoveExpectedEntries); + } + + @Test public void testParceling() { NotificationHistory history = new NotificationHistory(); diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java index 6c5d5485664e..02be557c01bd 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java @@ -24,6 +24,9 @@ import android.app.ActivityManager.TaskDescription; import android.content.Context; import android.content.pm.ConfigurationInfo; import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.os.Parcel; +import android.os.Parcelable; import android.test.AndroidTestCase; import androidx.test.filters.SmallTest; @@ -137,21 +140,7 @@ public class ActivityManagerTest extends AndroidTestCase { // Must overwrite all the fields td2.copyFrom(td1); - assertEquals(td1.getLabel(), td2.getLabel()); - assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon()); - assertEquals(td1.getIconFilename(), td2.getIconFilename()); - assertEquals(td1.getIconResource(), td2.getIconResource()); - assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor()); - assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor()); - assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor()); - assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor()); - assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(), - td2.getEnsureStatusBarContrastWhenTransparent()); - assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(), - td2.getEnsureNavigationBarContrastWhenTransparent()); - assertEquals(td1.getResizeMode(), td2.getResizeMode()); - assertEquals(td1.getMinWidth(), td2.getMinWidth()); - assertEquals(td1.getMinHeight(), td2.getMinHeight()); + assertTaskDescriptionEqual(td1, td2, true, true); } @SmallTest @@ -191,44 +180,101 @@ public class ActivityManagerTest extends AndroidTestCase { // Must overwrite all public and hidden fields, since other has all fields set. td2.copyFromPreserveHiddenFields(td1); - assertEquals(td1.getLabel(), td2.getLabel()); - assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon()); - assertEquals(td1.getIconFilename(), td2.getIconFilename()); - assertEquals(td1.getIconResource(), td2.getIconResource()); - assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor()); - assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor()); - assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor()); - assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor()); - assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(), - td2.getEnsureStatusBarContrastWhenTransparent()); - assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(), - td2.getEnsureNavigationBarContrastWhenTransparent()); - assertEquals(td1.getResizeMode(), td2.getResizeMode()); - assertEquals(td1.getMinWidth(), td2.getMinWidth()); - assertEquals(td1.getMinHeight(), td2.getMinHeight()); + assertTaskDescriptionEqual(td1, td2, true, true); TaskDescription td3 = new TaskDescription(); // Must overwrite only public fields, and preserve hidden fields. td2.copyFromPreserveHiddenFields(td3); - // Overwritten fields - assertEquals(td3.getLabel(), td2.getLabel()); - assertEquals(td3.getInMemoryIcon(), td2.getInMemoryIcon()); - assertEquals(td3.getIconFilename(), td2.getIconFilename()); - assertEquals(td3.getIconResource(), td2.getIconResource()); - assertEquals(td3.getPrimaryColor(), td2.getPrimaryColor()); - assertEquals(td3.getEnsureStatusBarContrastWhenTransparent(), - td2.getEnsureStatusBarContrastWhenTransparent()); - assertEquals(td3.getEnsureNavigationBarContrastWhenTransparent(), - td2.getEnsureNavigationBarContrastWhenTransparent()); - - // Preserved fields - assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor()); - assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor()); - assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor()); - assertEquals(td1.getResizeMode(), td2.getResizeMode()); - assertEquals(td1.getMinWidth(), td2.getMinWidth()); - assertEquals(td1.getMinHeight(), td2.getMinHeight()); + assertTaskDescriptionEqual(td3, td2, true, false); + assertTaskDescriptionEqual(td1, td2, false, true); + } + + @SmallTest + public void testTaskDescriptionParceling() throws Exception { + TaskDescription tdBitmapNull = new TaskDescription( + "test label", // label + null, // bitmap + 21, // iconRes + "dummy file", // iconFilename + 0x111111, // colorPrimary + 0x222222, // colorBackground + 0x333333, // statusBarColor + 0x444444, // navigationBarColor + false, // ensureStatusBarContrastWhenTransparent + false, // ensureNavigationBarContrastWhenTransparent + RESIZE_MODE_UNRESIZEABLE, // resizeMode + 10, // minWidth + 20 // minHeight + ); + + // Normal parceling should keep everything the same. + TaskDescription tdParcelled = new TaskDescription(parcelingRoundTrip(tdBitmapNull)); + assertTaskDescriptionEqual(tdBitmapNull, tdParcelled, true, true); + + Bitmap recycledBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); + recycledBitmap.recycle(); + assertTrue(recycledBitmap.isRecycled()); + TaskDescription tdBitmapRecycled = new TaskDescription( + "test label", // label + recycledBitmap, // bitmap + 21, // iconRes + "dummy file", // iconFilename + 0x111111, // colorPrimary + 0x222222, // colorBackground + 0x333333, // statusBarColor + 0x444444, // navigationBarColor + false, // ensureStatusBarContrastWhenTransparent + false, // ensureNavigationBarContrastWhenTransparent + RESIZE_MODE_UNRESIZEABLE, // resizeMode + 10, // minWidth + 20 // minHeight + ); + // Recycled bitmap will be ignored while parceling. + tdParcelled = new TaskDescription(parcelingRoundTrip(tdBitmapRecycled)); + assertTaskDescriptionEqual(tdBitmapNull, tdParcelled, true, true); + + } + + private void assertTaskDescriptionEqual(TaskDescription td1, TaskDescription td2, + boolean checkOverwrittenFields, boolean checkPreservedFields) { + if (checkOverwrittenFields) { + assertEquals(td1.getLabel(), td2.getLabel()); + assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon()); + assertEquals(td1.getIconFilename(), td2.getIconFilename()); + assertEquals(td1.getIconResource(), td2.getIconResource()); + assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor()); + assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(), + td2.getEnsureStatusBarContrastWhenTransparent()); + assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(), + td2.getEnsureNavigationBarContrastWhenTransparent()); + } + if (checkPreservedFields) { + assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor()); + assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor()); + assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor()); + assertEquals(td1.getResizeMode(), td2.getResizeMode()); + assertEquals(td1.getMinWidth(), td2.getMinWidth()); + assertEquals(td1.getMinHeight(), td2.getMinHeight()); + } + } + + private <T extends Parcelable> T parcelingRoundTrip(final T in) throws Exception { + final Parcel p = Parcel.obtain(); + in.writeToParcel(p, /* flags */ 0); + p.setDataPosition(0); + final byte[] marshalledData = p.marshall(); + p.recycle(); + + final Parcel q = Parcel.obtain(); + q.unmarshall(marshalledData, 0, marshalledData.length); + q.setDataPosition(0); + + final Parcelable.Creator<T> creator = (Parcelable.Creator<T>) + in.getClass().getField("CREATOR").get(null); // static object, so null receiver + final T unmarshalled = (T) creator.createFromParcel(q); + q.recycle(); + return unmarshalled; } // If any entries in appear in the list, sanity check them against all running applications diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index c986db8b2a83..c328d720426d 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -504,15 +504,17 @@ public class ActivityThreadTest { } @Override - public void onPictureInPictureRequested() { + public boolean onPictureInPictureRequested() { mPipRequested = true; if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_ENTER, false)) { enterPictureInPictureMode(new PictureInPictureParams.Builder().build()); mPipEntered = true; + return true; } else if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_SKIP, false)) { mPipEnterSkipped = true; + return false; } - super.onPictureInPictureRequested(); + return super.onPictureInPictureRequested(); } boolean pipRequested() { diff --git a/core/tests/coretests/src/android/app/compat/CompatChangesTest.java b/core/tests/coretests/src/android/app/compat/CompatChangesTest.java new file mode 100644 index 000000000000..fbd02eddbcf8 --- /dev/null +++ b/core/tests/coretests/src/android/app/compat/CompatChangesTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.compat; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Instrumentation; +import android.compat.testing.PlatformCompatChangeRule; +import android.os.Process; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +/** + * {@link CompatChanges} tests. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class CompatChangesTest { + static final long CHANGE_ID = 1L; + + private Instrumentation mInstrumentation; + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + @Before + public void setup() { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + } + + + private String getPackageName() { + return mInstrumentation.getTargetContext().getPackageName(); + } + + @Test + @EnableCompatChanges(CHANGE_ID) + public void testEnabledChange() { + assertThat(CompatChanges.isChangeEnabled(CHANGE_ID)).isTrue(); + assertThat(CompatChanges.isChangeEnabled(CHANGE_ID, Process.myUid())).isTrue(); + assertThat(CompatChanges.isChangeEnabled(CHANGE_ID, getPackageName(), + UserHandle.of(UserHandle.myUserId()))).isTrue(); + } + + @Test + @DisableCompatChanges(CHANGE_ID) + public void testDisabledChange() { + assertThat(CompatChanges.isChangeEnabled(CHANGE_ID)).isFalse(); + assertThat(CompatChanges.isChangeEnabled(CHANGE_ID, Process.myUid())).isFalse(); + assertThat(CompatChanges.isChangeEnabled(CHANGE_ID, getPackageName(), + UserHandle.of(UserHandle.myUserId()))).isFalse(); + } +} diff --git a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java index d17b63597a21..4b64dfc84fb7 100644 --- a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java +++ b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java @@ -26,44 +26,45 @@ import android.os.TimestampedValue; import org.junit.Test; -public class PhoneTimeSuggestionTest { +public class TelephonyTimeSuggestionTest { private static final int SLOT_INDEX = 99999; @Test public void testEquals() { - PhoneTimeSuggestion.Builder builder1 = new PhoneTimeSuggestion.Builder(SLOT_INDEX); + TelephonyTimeSuggestion.Builder builder1 = new TelephonyTimeSuggestion.Builder(SLOT_INDEX); { - PhoneTimeSuggestion one = builder1.build(); + TelephonyTimeSuggestion one = builder1.build(); assertEquals(one, one); } - PhoneTimeSuggestion.Builder builder2 = new PhoneTimeSuggestion.Builder(SLOT_INDEX); + TelephonyTimeSuggestion.Builder builder2 = new TelephonyTimeSuggestion.Builder(SLOT_INDEX); { - PhoneTimeSuggestion one = builder1.build(); - PhoneTimeSuggestion two = builder2.build(); + TelephonyTimeSuggestion one = builder1.build(); + TelephonyTimeSuggestion two = builder2.build(); assertEquals(one, two); assertEquals(two, one); } builder1.setUtcTime(new TimestampedValue<>(1111L, 2222L)); { - PhoneTimeSuggestion one = builder1.build(); + TelephonyTimeSuggestion one = builder1.build(); assertEquals(one, one); } builder2.setUtcTime(new TimestampedValue<>(1111L, 2222L)); { - PhoneTimeSuggestion one = builder1.build(); - PhoneTimeSuggestion two = builder2.build(); + TelephonyTimeSuggestion one = builder1.build(); + TelephonyTimeSuggestion two = builder2.build(); assertEquals(one, two); assertEquals(two, one); } - PhoneTimeSuggestion.Builder builder3 = new PhoneTimeSuggestion.Builder(SLOT_INDEX + 1); + TelephonyTimeSuggestion.Builder builder3 = + new TelephonyTimeSuggestion.Builder(SLOT_INDEX + 1); builder3.setUtcTime(new TimestampedValue<>(1111L, 2222L)); { - PhoneTimeSuggestion one = builder1.build(); - PhoneTimeSuggestion three = builder3.build(); + TelephonyTimeSuggestion one = builder1.build(); + TelephonyTimeSuggestion three = builder3.build(); assertNotEquals(one, three); assertNotEquals(three, one); } @@ -72,15 +73,15 @@ public class PhoneTimeSuggestionTest { builder1.addDebugInfo("Debug info 1"); builder2.addDebugInfo("Debug info 2"); { - PhoneTimeSuggestion one = builder1.build(); - PhoneTimeSuggestion two = builder2.build(); + TelephonyTimeSuggestion one = builder1.build(); + TelephonyTimeSuggestion two = builder2.build(); assertEquals(one, two); } } @Test public void testParcelable() { - PhoneTimeSuggestion.Builder builder = new PhoneTimeSuggestion.Builder(SLOT_INDEX); + TelephonyTimeSuggestion.Builder builder = new TelephonyTimeSuggestion.Builder(SLOT_INDEX); assertRoundTripParcelable(builder.build()); builder.setUtcTime(new TimestampedValue<>(1111L, 2222L)); @@ -88,9 +89,9 @@ public class PhoneTimeSuggestionTest { // DebugInfo should also be stored (but is not checked by equals() { - PhoneTimeSuggestion suggestion1 = builder.build(); + TelephonyTimeSuggestion suggestion1 = builder.build(); builder.addDebugInfo("This is debug info"); - PhoneTimeSuggestion rtSuggestion1 = roundTripParcelable(suggestion1); + TelephonyTimeSuggestion rtSuggestion1 = roundTripParcelable(suggestion1); assertEquals(suggestion1.getDebugInfo(), rtSuggestion1.getDebugInfo()); } } diff --git a/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java deleted file mode 100644 index 384dbf9fab07..000000000000 --- a/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app.timezonedetector; - -import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable; -import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -public class PhoneTimeZoneSuggestionTest { - private static final int SLOT_INDEX = 99999; - - @Test - public void testEquals() { - PhoneTimeZoneSuggestion.Builder builder1 = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); - { - PhoneTimeZoneSuggestion one = builder1.build(); - assertEquals(one, one); - } - - PhoneTimeZoneSuggestion.Builder builder2 = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertEquals(one, two); - assertEquals(two, one); - } - - PhoneTimeZoneSuggestion.Builder builder3 = - new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX + 1); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion three = builder3.build(); - assertNotEquals(one, three); - assertNotEquals(three, one); - } - - builder1.setZoneId("Europe/London"); - builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); - builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertNotEquals(one, two); - } - - builder2.setZoneId("Europe/Paris"); - builder2.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); - builder2.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertNotEquals(one, two); - } - - builder1.setZoneId("Europe/Paris"); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertEquals(one, two); - } - - builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID); - builder2.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertNotEquals(one, two); - } - - builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertEquals(one, two); - } - - builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE); - builder2.setQuality(PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertNotEquals(one, two); - } - - builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS); - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - assertEquals(one, two); - } - - // DebugInfo must not be considered in equals(). - { - PhoneTimeZoneSuggestion one = builder1.build(); - PhoneTimeZoneSuggestion two = builder2.build(); - one.addDebugInfo("Debug info 1"); - two.addDebugInfo("Debug info 2"); - assertEquals(one, two); - } - } - - @Test(expected = RuntimeException.class) - public void testBuilderValidates_emptyZone_badMatchType() { - PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); - // No zone ID, so match type should be left unset. - builder.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET); - builder.build(); - } - - @Test(expected = RuntimeException.class) - public void testBuilderValidates_zoneSet_badMatchType() { - PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); - builder.setZoneId("Europe/London"); - builder.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE); - builder.build(); - } - - @Test - public void testParcelable() { - PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(SLOT_INDEX); - assertRoundTripParcelable(builder.build()); - - builder.setZoneId("Europe/London"); - builder.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID); - builder.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE); - PhoneTimeZoneSuggestion suggestion1 = builder.build(); - assertRoundTripParcelable(suggestion1); - - // DebugInfo should also be stored (but is not checked by equals() - String debugString = "This is debug info"; - suggestion1.addDebugInfo(debugString); - PhoneTimeZoneSuggestion suggestion1_2 = roundTripParcelable(suggestion1); - assertEquals(suggestion1, suggestion1_2); - assertTrue(suggestion1_2.getDebugInfo().contains(debugString)); - } -} diff --git a/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java new file mode 100644 index 000000000000..59d55b79157c --- /dev/null +++ b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java @@ -0,0 +1,162 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.timezonedetector; + +import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable; +import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class TelephonyTimeZoneSuggestionTest { + private static final int SLOT_INDEX = 99999; + + @Test + public void testEquals() { + TelephonyTimeZoneSuggestion.Builder builder1 = + new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + assertEquals(one, one); + } + + TelephonyTimeZoneSuggestion.Builder builder2 = + new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertEquals(one, two); + assertEquals(two, one); + } + + TelephonyTimeZoneSuggestion.Builder builder3 = + new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX + 1); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion three = builder3.build(); + assertNotEquals(one, three); + assertNotEquals(three, one); + } + + builder1.setZoneId("Europe/London"); + builder1.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); + builder1.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertNotEquals(one, two); + } + + builder2.setZoneId("Europe/Paris"); + builder2.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); + builder2.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertNotEquals(one, two); + } + + builder1.setZoneId("Europe/Paris"); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertEquals(one, two); + } + + builder1.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID); + builder2.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertNotEquals(one, two); + } + + builder1.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertEquals(one, two); + } + + builder1.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE); + builder2.setQuality( + TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertNotEquals(one, two); + } + + builder1.setQuality( + TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS); + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + assertEquals(one, two); + } + + // DebugInfo must not be considered in equals(). + { + TelephonyTimeZoneSuggestion one = builder1.build(); + TelephonyTimeZoneSuggestion two = builder2.build(); + one.addDebugInfo("Debug info 1"); + two.addDebugInfo("Debug info 2"); + assertEquals(one, two); + } + } + + @Test(expected = RuntimeException.class) + public void testBuilderValidates_emptyZone_badMatchType() { + TelephonyTimeZoneSuggestion.Builder builder = + new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX); + // No zone ID, so match type should be left unset. + builder.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET); + builder.build(); + } + + @Test(expected = RuntimeException.class) + public void testBuilderValidates_zoneSet_badMatchType() { + TelephonyTimeZoneSuggestion.Builder builder = + new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX); + builder.setZoneId("Europe/London"); + builder.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE); + builder.build(); + } + + @Test + public void testParcelable() { + TelephonyTimeZoneSuggestion.Builder builder = + new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX); + assertRoundTripParcelable(builder.build()); + + builder.setZoneId("Europe/London"); + builder.setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID); + builder.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE); + TelephonyTimeZoneSuggestion suggestion1 = builder.build(); + assertRoundTripParcelable(suggestion1); + + // DebugInfo should also be stored (but is not checked by equals() + String debugString = "This is debug info"; + suggestion1.addDebugInfo(debugString); + TelephonyTimeZoneSuggestion suggestion1_2 = roundTripParcelable(suggestion1); + assertEquals(suggestion1, suggestion1_2); + assertTrue(suggestion1_2.getDebugInfo().contains(debugString)); + } +} diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java index 9dcce1e51e0b..6dc7392945d8 100644 --- a/core/tests/coretests/src/android/content/ContentResolverTest.java +++ b/core/tests/coretests/src/android/content/ContentResolverTest.java @@ -209,4 +209,13 @@ public class ContentResolverTest { String type = mResolver.getType(Uri.parse("content://android.content.FakeProviderRemote")); assertEquals("fake/remote", type); } + + + @Test + public void testGetType_slowProvider() { + // This provider is running in a different process and is intentionally slow to start. + // We are trying to confirm that it does not cause an ANR + String type = mResolver.getType(Uri.parse("content://android.content.SlowProvider")); + assertEquals("slow", type); + } } diff --git a/core/tests/coretests/src/android/content/SlowProvider.java b/core/tests/coretests/src/android/content/SlowProvider.java new file mode 100644 index 000000000000..aba32e836e80 --- /dev/null +++ b/core/tests/coretests/src/android/content/SlowProvider.java @@ -0,0 +1,65 @@ +/* + * 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.content; + +import android.database.Cursor; +import android.net.Uri; + +/** + * A dummy content provider for tests. This provider runs in a different process from the test and + * is intentionally slow. + */ +public class SlowProvider extends ContentProvider { + + private static final int ON_CREATE_LATENCY_MILLIS = 3000; + + @Override + public boolean onCreate() { + try { + Thread.sleep(ON_CREATE_LATENCY_MILLIS); + } catch (InterruptedException e) { + // Ignore + } + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + return null; + } + + @Override + public String getType(Uri uri) { + return "slow"; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } +} diff --git a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java index bf782034c8f1..ea6917682965 100644 --- a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java @@ -29,6 +29,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.Arrays; +import java.util.Collections; + @RunWith(JUnit4.class) public class AtomicFormulaTest { @@ -90,18 +93,18 @@ public class AtomicFormulaTest { } @Test - public void testValidAtomicFormula_stringValue_appCertificateAutoHashed() { + public void testValidAtomicFormula_stringValue_appCertificateIsNotAutoHashed() { String appCert = "cert"; StringAtomicFormula stringAtomicFormula = new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE, appCert); assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.APP_CERTIFICATE); - assertThat(stringAtomicFormula.getValue()).doesNotMatch(appCert); + assertThat(stringAtomicFormula.getValue()).matches(appCert); assertThat(stringAtomicFormula.getIsHashedValue()).isTrue(); } @Test - public void testValidAtomicFormula_stringValue_installerCertificateAutoHashed() { + public void testValidAtomicFormula_stringValue_installerCertificateIsNotAutoHashed() { String installerCert = "cert"; StringAtomicFormula stringAtomicFormula = new StringAtomicFormula(AtomicFormula.INSTALLER_CERTIFICATE, @@ -109,7 +112,7 @@ public class AtomicFormulaTest { assertThat(stringAtomicFormula.getKey()).isEqualTo( AtomicFormula.INSTALLER_CERTIFICATE); - assertThat(stringAtomicFormula.getValue()).doesNotMatch(installerCert); + assertThat(stringAtomicFormula.getValue()).matches(installerCert); assertThat(stringAtomicFormula.getIsHashedValue()).isTrue(); } @@ -230,7 +233,7 @@ public class AtomicFormulaTest { } @Test - public void testFormulaMatches_string_true() { + public void testFormulaMatches_string_packageNameFormula_true() { StringAtomicFormula stringAtomicFormula = new StringAtomicFormula( AtomicFormula.PACKAGE_NAME, "com.test.app", /* isHashedValue= */ @@ -242,7 +245,7 @@ public class AtomicFormulaTest { } @Test - public void testFormulaMatches_string_false() { + public void testFormulaMatches_string_packageNameFormula_false() { StringAtomicFormula stringAtomicFormula = new StringAtomicFormula( AtomicFormula.PACKAGE_NAME, "com.test.app", /* isHashedValue= */ @@ -253,6 +256,63 @@ public class AtomicFormulaTest { assertThat(stringAtomicFormula.matches(appInstallMetadata)).isFalse(); } + @Test + public void testFormulaMatches_string_multipleAppCertificates_true() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, "cert", /* isHashedValue= */ true); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder() + .setPackageName("com.test.app") + .setAppCertificates(Arrays.asList("test-cert", "cert")) + .build(); + + assertThat(stringAtomicFormula.matches(appInstallMetadata)).isTrue(); + } + + @Test + public void testFormulaMatches_string_multipleAppCertificates_false() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, "cert", /* isHashedValue= */ true); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder() + .setPackageName("com.test.app") + .setAppCertificates(Arrays.asList("test-cert", "another-cert")) + .build(); + + assertThat(stringAtomicFormula.matches(appInstallMetadata)).isFalse(); + } + + @Test + public void testFormulaMatches_string_multipleInstallerCertificates_true() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.INSTALLER_CERTIFICATE, "cert", /* isHashedValue= */ true); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder() + .setPackageName("com.test.app") + .setAppCertificates(Collections.singletonList("abc")) + .setInstallerCertificates(Arrays.asList("test-cert", "cert")) + .build(); + + assertThat(stringAtomicFormula.matches(appInstallMetadata)).isTrue(); + } + + @Test + public void testFormulaMatches_string_multipleInstallerCertificates_false() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.INSTALLER_CERTIFICATE, "cert", /* isHashedValue= */ true); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder() + .setPackageName("com.test.app") + .setAppCertificates(Collections.singletonList("abc")) + .setInstallerCertificates(Arrays.asList("test-cert", "another-cert")) + .build(); + + assertThat(stringAtomicFormula.matches(appInstallMetadata)).isFalse(); + } @Test public void testIsAppCertificateFormula_string_true() { @@ -430,8 +490,8 @@ public class AtomicFormulaTest { private AppInstallMetadata.Builder getAppInstallMetadataBuilder() { return new AppInstallMetadata.Builder() .setPackageName("abc") - .setAppCertificate("abc") - .setInstallerCertificate("abc") + .setAppCertificates(Collections.singletonList("abc")) + .setInstallerCertificates(Collections.singletonList("abc")) .setInstallerName("abc") .setVersionCode(-1) .setIsPreInstalled(true); diff --git a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java index f47dfdd191fb..abc5fed181de 100644 --- a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java @@ -286,8 +286,8 @@ public class CompoundFormulaTest { private AppInstallMetadata.Builder getAppInstallMetadataBuilder() { return new AppInstallMetadata.Builder() .setPackageName("abc") - .setAppCertificate("abc") - .setInstallerCertificate("abc") + .setAppCertificates(Collections.singletonList("abc")) + .setInstallerCertificates(Collections.singletonList("abc")) .setInstallerName("abc") .setVersionCode(-1) .setIsPreInstalled(true); diff --git a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java index c1806028f75b..75ef1f22b819 100644 --- a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java @@ -40,7 +40,7 @@ public class IntegrityFormulaTest { assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.PACKAGE_NAME); assertThat(stringAtomicFormula.getValue()).isEqualTo(packageName); - assertThat(stringAtomicFormula.getIsHashedValue()).isEqualTo(false); + assertThat(stringAtomicFormula.getIsHashedValue()).isFalse(); } @Test @@ -53,8 +53,8 @@ public class IntegrityFormulaTest { (AtomicFormula.StringAtomicFormula) formula; assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.APP_CERTIFICATE); - assertThat(stringAtomicFormula.getValue()).doesNotMatch(appCertificate); - assertThat(stringAtomicFormula.getIsHashedValue()).isEqualTo(true); + assertThat(stringAtomicFormula.getValue()).matches(appCertificate); + assertThat(stringAtomicFormula.getIsHashedValue()).isTrue(); } @Test @@ -68,7 +68,7 @@ public class IntegrityFormulaTest { assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.INSTALLER_NAME); assertThat(stringAtomicFormula.getValue()).isEqualTo(installerName); - assertThat(stringAtomicFormula.getIsHashedValue()).isEqualTo(false); + assertThat(stringAtomicFormula.getIsHashedValue()).isFalse(); } @Test @@ -81,8 +81,8 @@ public class IntegrityFormulaTest { (AtomicFormula.StringAtomicFormula) formula; assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.INSTALLER_CERTIFICATE); - assertThat(stringAtomicFormula.getValue()).doesNotMatch(installerCertificate); - assertThat(stringAtomicFormula.getIsHashedValue()).isEqualTo(true); + assertThat(stringAtomicFormula.getValue()).matches(installerCertificate); + assertThat(stringAtomicFormula.getIsHashedValue()).isTrue(); } @Test diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java index decc76869a53..2295eb989108 100644 --- a/core/tests/coretests/src/android/os/BuildTest.java +++ b/core/tests/coretests/src/android/os/BuildTest.java @@ -60,7 +60,7 @@ public class BuildTest extends TestCase { assertNotEmpty("BRAND", Build.BRAND); assertNotEmpty("MODEL", Build.MODEL); assertNotEmpty("VERSION.INCREMENTAL", Build.VERSION.INCREMENTAL); - assertNotEmpty("VERSION.RELEASE", Build.VERSION.RELEASE); + assertNotEmpty("VERSION.RELEASE", Build.VERSION.RELEASE_OR_CODENAME); assertNotEmpty("TYPE", Build.TYPE); Assert.assertNotNull("TAGS", Build.TAGS); // TAGS is allowed to be empty. assertNotEmpty("FINGERPRINT", Build.FINGERPRINT); diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java index 84c42dbc14e7..d649b945492b 100644 --- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java +++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java @@ -376,6 +376,24 @@ public class DeviceConfigTest { } @Test + public void resetToDefault_makeDefault() { + DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, true); + assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE); + + DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, NAMESPACE); + assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE); + } + + @Test + public void resetToDefault_doNotMakeDefault() { + DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false); + assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE); + + DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, NAMESPACE); + assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isNull(); + } + + @Test public void getProperties_fullNamespace() { Properties properties = DeviceConfig.getProperties(NAMESPACE); assertThat(properties.getKeyset()).isEmpty(); diff --git a/core/tests/coretests/src/android/view/CutoutSpecificationTest.java b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java new file mode 100644 index 000000000000..1f831bb2f9a8 --- /dev/null +++ b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java @@ -0,0 +1,262 @@ +/* + * 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; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import android.graphics.Rect; + +import org.junit.Before; +import org.junit.Test; + +public class CutoutSpecificationTest { + private static final String WITHOUT_BIND_CUTOUT_SPECIFICATION = "M 0,0\n" + + "h 48\n" + + "v 48\n" + + "h -48\n" + + "z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "h 48\n" + + "v 48\n" + + "h -48\n" + + "z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "h -48\n" + + "v 48\n" + + "h 48\n" + + "z\n" + + "@right\n" + + "@dp"; + private static final String WITH_BIND_CUTOUT_SPECIFICATION = "M 0,0\n" + + "h 48\n" + + "v 48\n" + + "h -48\n" + + "z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "h 48\n" + + "v 48\n" + + "h -48\n" + + "z\n" + + "@left\n" + + "@bind_left_cutout\n" + + "@center_vertical\n" + + "M 0,0\n" + + "h -48\n" + + "v 48\n" + + "h 48\n" + + "z\n" + + "@right\n" + + "@bind_right_cutout\n" + + "@dp"; + private static final String CORNER_CUTOUT_SPECIFICATION = "M 0,0\n" + + "h 1\n" + + "v 1\n" + + "h -1\n" + + "z\n" + + "@left\n" + + "@cutout\n" + + "M 0, 0\n" + + "h -2\n" + + "v 2\n" + + "h 2\n" + + "z\n" + + "@right\n" + + "@bind_right_cutout\n" + + "@cutout\n" + + "M 0, 200\n" + + "h 3\n" + + "v -3\n" + + "h -3\n" + + "z\n" + + "@left\n" + + "@bind_left_cutout\n" + + "@bottom\n" + + "M 0, 0\n" + + "h -4\n" + + "v -4\n" + + "h 4\n" + + "z\n" + + "@right\n" + + "@dp"; + + private CutoutSpecification.Parser mParser; + + /** + * Setup the necessary member field used by test methods. + */ + @Before + public void setUp() { + mParser = new CutoutSpecification.Parser(3.5f, 1080, 1920); + } + + @Test + public void parse_nullString_shouldTriggerException() { + assertThrows(NullPointerException.class, () -> mParser.parse(null)); + } + + @Test + public void parse_emptyString_pathShouldBeNull() { + CutoutSpecification cutoutSpecification = mParser.parse(""); + assertThat(cutoutSpecification.getPath()).isNull(); + } + + @Test + public void parse_withoutBindMarker_shouldHaveNoLeftBound() { + CutoutSpecification cutoutSpecification = mParser.parse(WITHOUT_BIND_CUTOUT_SPECIFICATION); + assertThat(cutoutSpecification.getLeftBound()).isNull(); + } + + @Test + public void parse_withoutBindMarker_shouldHaveNoRightBound() { + CutoutSpecification cutoutSpecification = mParser.parse(WITHOUT_BIND_CUTOUT_SPECIFICATION); + assertThat(cutoutSpecification.getRightBound()).isNull(); + } + + @Test + public void parse_withBindMarker_shouldHaveLeftBound() { + CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION); + assertThat(cutoutSpecification.getLeftBound()).isEqualTo(new Rect(0, 960, 168, 1128)); + } + + @Test + public void parse_withBindMarker_shouldHaveRightBound() { + CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION); + assertThat(cutoutSpecification.getRightBound()).isEqualTo(new Rect(912, 960, 1080, 1128)); + } + + @Test + public void parse_tallCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -48, 0\n" + + "L -44.3940446283, 36.0595537175\n" + + "C -43.5582133885, 44.4178661152 -39.6, 48.0 -31.2, 48.0\n" + + "L 31.2, 48.0\n" + + "C 39.6, 48.0 43.5582133885, 44.4178661152 44.3940446283, 36.0595537175\n" + + "L 48, 0\n" + + "Z\n" + + "@dp"); + + assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(168); + } + + @Test + public void parse_wideCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, 20.0595537175\n" + + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n" + + "L 56.8, 32.0\n" + + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@dp"); + + assertThat(cutoutSpecification.getTopBound().width()).isEqualTo(504); + } + + @Test + public void parse_narrowCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -24, 0\n" + + "L -21.9940446283, 20.0595537175\n" + + "C -21.1582133885, 28.4178661152 -17.2, 32.0 -8.8, 32.0\n" + + "L 8.8, 32.0\n" + + "C 17.2, 32.0 21.1582133885, 28.4178661152 21.9940446283, 20.0595537175\n" + + "L 24, 0\n" + + "Z\n" + + "@dp"); + + assertThat(cutoutSpecification.getTopBound().width()).isEqualTo(168); + } + + @Test + public void parse_doubleCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, 20.0595537175\n" + + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n" + + "L 56.8, 32.0\n" + + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@bottom\n" + + "M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, -20.0595537175\n" + + "C -69.1582133885, -28.4178661152 -65.2, -32.0 -56.8, -32.0\n" + + "L 56.8, -32.0\n" + + "C 65.2, -32.0 69.1582133885, -28.4178661152 69.9940446283, -20" + + ".0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@dp"); + + assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(112); + } + + @Test + public void parse_cornerCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -48, 0\n" + + "C -48,48 -48,48 0,48\n" + + "Z\n" + + "@dp\n" + + "@right"); + + assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(168); + } + + @Test + public void parse_holeCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 20.0,20.0\n" + + "h 136\n" + + "v 136\n" + + "h -136\n" + + "Z\n" + + "@left"); + + assertThat(cutoutSpecification.getSafeInset()).isEqualTo(new Rect(0, 156, 0, 0)); + } + + @Test + public void getSafeInset_shortEdgeIsTopBottom_shouldMatchExpectedInset() { + CutoutSpecification cutoutSpecification = + new CutoutSpecification.Parser(2f, 200, 400) + .parse(CORNER_CUTOUT_SPECIFICATION); + + assertThat(cutoutSpecification.getSafeInset()) + .isEqualTo(new Rect(0, 4, 0, 8)); + } + + @Test + public void getSafeInset_shortEdgeIsLeftRight_shouldMatchExpectedInset() { + CutoutSpecification cutoutSpecification = + new CutoutSpecification.Parser(2f, 400, 200) + .parse(CORNER_CUTOUT_SPECIFICATION); + + assertThat(cutoutSpecification.getSafeInset()) + .isEqualTo(new Rect(6, 0, 8, 0)); + } +} 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/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java index 12c057f5a91a..02ffc00dcba5 100644 --- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java +++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java @@ -20,11 +20,11 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyInt; import android.annotation.Nullable; import android.os.Parcel; import android.os.UserHandle; -import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextUtils; @@ -41,6 +41,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class EditorInfoTest { private static final int TEST_USER_ID = 42; + private static final int LONG_EXP_TEXT_LENGTH = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH * 2; /** * Makes sure that {@code null} {@link EditorInfo#targetInputMethodUser} can be copied via @@ -79,8 +80,8 @@ public class EditorInfoTest { } @Test - public void testNullTextInputComposeInitialSurroundingText() { - final Spannable testText = null; + public void setInitialText_nullInputText_throwsException() { + final CharSequence testText = null; final EditorInfo editorInfo = new EditorInfo(); try { @@ -92,56 +93,75 @@ public class EditorInfoTest { } @Test - public void testNonNullTextInputComposeInitialSurroundingText() { - final Spannable testText = createTestText(/* prependLength= */ 0, - EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); - final EditorInfo editorInfo = new EditorInfo(); + public void setInitialText_cursorAtHead_dividesByCursorPosition() { + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); - // Cursor at position 0. - int selectionLength = 0; + final EditorInfo editorInfo = new EditorInfo(); + final int selectionLength = 0; editorInfo.initialSelStart = 0; editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; - int expectedTextBeforeCursorLength = 0; - int expectedTextAfterCursorLength = testText.length(); + final int expectedTextBeforeCursorLength = 0; + final int expectedTextAfterCursorLength = testText.length(); editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, expectedTextAfterCursorLength); + } - // Cursor at the end. + @Test + public void setInitialText_cursorAtTail_dividesByCursorPosition() { + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); + final EditorInfo editorInfo = new EditorInfo(); + final int selectionLength = 0; editorInfo.initialSelStart = testText.length() - selectionLength; editorInfo.initialSelEnd = testText.length(); - expectedTextBeforeCursorLength = testText.length(); - expectedTextAfterCursorLength = 0; + final int expectedTextBeforeCursorLength = testText.length(); + final int expectedTextAfterCursorLength = 0; editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, expectedTextAfterCursorLength); + } - // Cursor at the middle. - selectionLength = 2; + @Test + public void setInitialText_cursorAtMiddle_dividesByCursorPosition() { + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); + final EditorInfo editorInfo = new EditorInfo(); + final int selectionLength = 2; editorInfo.initialSelStart = testText.length() / 2; editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; - expectedTextBeforeCursorLength = editorInfo.initialSelStart; - expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd; + final int expectedTextBeforeCursorLength = editorInfo.initialSelStart; + final int expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd; editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, expectedTextAfterCursorLength); + } - // Accidentally swap selection start and end. + @Test + public void setInitialText_incorrectCursorOrder_correctsThenDivide() { + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); + final EditorInfo editorInfo = new EditorInfo(); + final int selectionLength = 2; editorInfo.initialSelEnd = testText.length() / 2; editorInfo.initialSelStart = editorInfo.initialSelEnd + selectionLength; + final int expectedTextBeforeCursorLength = testText.length() / 2; + final int expectedTextAfterCursorLength = testText.length() - testText.length() / 2 + - selectionLength; editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, expectedTextAfterCursorLength); + } - // Invalid cursor position. + @Test + public void setInitialText_invalidCursorPosition_returnsNull() { + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); + final EditorInfo editorInfo = new EditorInfo(); editorInfo.initialSelStart = -1; editorInfo.setInitialSurroundingText(testText); @@ -153,64 +173,33 @@ public class EditorInfoTest { } @Test - public void testTooLongTextInputComposeInitialSurroundingText() { - final Spannable testText = createTestText(/* prependLength= */ 0, - EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + 2); + public void setOverSizeInitialText_cursorAtMiddle_dividesProportionately() { + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + 2); final EditorInfo editorInfo = new EditorInfo(); - - // Cursor at position 0. - int selectionLength = 0; - editorInfo.initialSelStart = 0; - editorInfo.initialSelEnd = 0 + selectionLength; - int expectedTextBeforeCursorLength = 0; - int expectedTextAfterCursorLength = editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH; - - editorInfo.setInitialSurroundingText(testText); - - assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, - expectedTextAfterCursorLength); - - // Cursor at the end. - editorInfo.initialSelStart = testText.length() - selectionLength; - editorInfo.initialSelEnd = testText.length(); - expectedTextBeforeCursorLength = editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH; - expectedTextAfterCursorLength = 0; - - editorInfo.setInitialSurroundingText(testText); - - assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, - expectedTextAfterCursorLength); - - // Cursor at the middle. - selectionLength = 2; + final int selectionLength = 2; editorInfo.initialSelStart = testText.length() / 2; editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; - expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart, + final int expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart, (int) (0.8 * (EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH - selectionLength))); - expectedTextAfterCursorLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + final int expectedTextAfterCursorLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH - expectedTextBeforeCursorLength - selectionLength; editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, expectedTextAfterCursorLength); + } - // Accidentally swap selection start and end. - editorInfo.initialSelEnd = testText.length() / 2; - editorInfo.initialSelStart = editorInfo.initialSelEnd + selectionLength; - - editorInfo.setInitialSurroundingText(testText); - - assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, - expectedTextAfterCursorLength); - - // Selection too long, selected text should be dropped. - selectionLength = EditorInfo.MAX_INITIAL_SELECTION_LENGTH + 1; + @Test + public void setOverSizeInitialText_overSizeSelection_dropsSelection() { + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + 2); + final EditorInfo editorInfo = new EditorInfo(); + final int selectionLength = EditorInfo.MAX_INITIAL_SELECTION_LENGTH + 1; editorInfo.initialSelStart = testText.length() / 2; editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; - expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart, + final int expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart, (int) (0.8 * EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH)); - expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd; + final int expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd; editorInfo.setInitialSurroundingText(testText); @@ -219,34 +208,59 @@ public class EditorInfoTest { } @Test - public void testTooLongSubTextInputComposeInitialSurroundingText() { - final int prependLength = 5; - final int subTextLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH; - final Spannable fullText = createTestText(prependLength, subTextLength); + public void setInitialSubText_trimmedSubText_dividesByOriginalCursorPosition() { + final String prefixString = "prefix"; + final CharSequence subText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); + final CharSequence originalText = TextUtils.concat(prefixString, subText); final EditorInfo editorInfo = new EditorInfo(); - // Cursor at the middle. - final int selectionLength = 2; - editorInfo.initialSelStart = fullText.length() / 2; - editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; - // #prependLength characters will be trimmed out. - final Spannable expectedTextBeforeCursor = createExpectedText(/* startNumber= */0, - editorInfo.initialSelStart - prependLength); - final Spannable expectedSelectedText = createExpectedText( - editorInfo.initialSelStart - prependLength, selectionLength); - final Spannable expectedTextAfterCursor = createExpectedText( - editorInfo.initialSelEnd - prependLength, - fullText.length() - editorInfo.initialSelEnd); - - editorInfo.setInitialSurroundingSubText(fullText.subSequence(prependLength, - fullText.length()), prependLength); + final int selLength = 2; + editorInfo.initialSelStart = originalText.length() / 2; + editorInfo.initialSelEnd = editorInfo.initialSelStart + selLength; + final CharSequence expectedTextBeforeCursor = createExpectedText(/* startNumber= */0, + editorInfo.initialSelStart - prefixString.length()); + final CharSequence expectedSelectedText = createExpectedText( + editorInfo.initialSelStart - prefixString.length(), selLength); + final CharSequence expectedTextAfterCursor = createExpectedText( + editorInfo.initialSelEnd - prefixString.length(), + originalText.length() - editorInfo.initialSelEnd); + + editorInfo.setInitialSurroundingSubText(subText, prefixString.length()); assertTrue(TextUtils.equals(expectedTextBeforeCursor, - editorInfo.getInitialTextBeforeCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH, - InputConnection.GET_TEXT_WITH_STYLES))); + editorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH, anyInt()))); assertTrue(TextUtils.equals(expectedSelectedText, - editorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES))); + editorInfo.getInitialSelectedText(anyInt()))); assertTrue(TextUtils.equals(expectedTextAfterCursor, - editorInfo.getInitialTextAfterCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH, + editorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH, anyInt()))); + } + + @Test + public void initialSurroundingText_wrapIntoParcel_staysIntact() { + // EditorInfo.InitialSurroundingText is not visible to test class. But all its key elements + // must stay intact for its getter methods to return correct value and it will be wrapped + // into its outer class for parcel transfer, therefore we can verify its parcel + // wrapping/unwrapping logic through its outer class. + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); + final EditorInfo sourceEditorInfo = new EditorInfo(); + final int selectionLength = 2; + sourceEditorInfo.initialSelStart = testText.length() / 2; + sourceEditorInfo.initialSelEnd = sourceEditorInfo.initialSelStart + selectionLength; + sourceEditorInfo.setInitialSurroundingText(testText); + + final EditorInfo targetEditorInfo = cloneViaParcel(sourceEditorInfo); + + assertTrue(TextUtils.equals( + sourceEditorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH, + InputConnection.GET_TEXT_WITH_STYLES), + targetEditorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH, + InputConnection.GET_TEXT_WITH_STYLES))); + assertTrue(TextUtils.equals( + sourceEditorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES), + targetEditorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES))); + assertTrue(TextUtils.equals( + sourceEditorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH, + InputConnection.GET_TEXT_WITH_STYLES), + targetEditorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH, InputConnection.GET_TEXT_WITH_STYLES))); } @@ -254,12 +268,12 @@ public class EditorInfoTest { @Nullable Integer expectBeforeCursorLength, @Nullable Integer expectSelectionLength, @Nullable Integer expectAfterCursorLength) { final CharSequence textBeforeCursor = - editorInfo.getInitialTextBeforeCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH, + editorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH, InputConnection.GET_TEXT_WITH_STYLES); final CharSequence selectedText = editorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES); final CharSequence textAfterCursor = - editorInfo.getInitialTextAfterCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH, + editorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH, InputConnection.GET_TEXT_WITH_STYLES); if (expectBeforeCursorLength == null) { @@ -281,19 +295,15 @@ public class EditorInfoTest { } } - private static Spannable createTestText(int prependLength, int surroundingLength) { + private static CharSequence createTestText(int surroundingLength) { final SpannableStringBuilder builder = new SpannableStringBuilder(); - for (int i = 0; i < prependLength; i++) { - builder.append("a"); - } - for (int i = 0; i < surroundingLength; i++) { builder.append(Integer.toString(i % 10)); } return builder; } - private static Spannable createExpectedText(int startNumber, int length) { + private static CharSequence createExpectedText(int startNumber, int length) { final SpannableStringBuilder builder = new SpannableStringBuilder(); for (int i = startNumber; i < startNumber + length; i++) { builder.append(Integer.toString(i % 10)); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index df6b9066ea5c..ce71bebfc455 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -38,6 +38,7 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -355,6 +356,7 @@ public class ChooserActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); Intent sendIntent = createSendTextIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(2); @@ -1209,17 +1211,24 @@ public class ChooserActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; int personalProfileTargets = 3; + int otherProfileTargets = 1; List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(personalProfileTargets); + createResolvedComponentsForTestWithOtherProfile( + personalProfileTargets + otherProfileTargets, /* userID */ 10); int workProfileTargets = 4; List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest( workProfileTargets); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); Intent sendIntent = createSendTextIntent(); sendIntent.setType("TestType"); markWorkProfileUserAvailable(); @@ -1229,8 +1238,6 @@ public class ChooserActivityTest { waitForIdle(); assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0)); - // The work list adapter must only be filled when we open the work tab - assertThat(activity.getWorkListAdapter().getCount(), is(0)); onView(withText(R.string.resolver_work_tab)).perform(click()); assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); assertThat(activity.getPersonalListAdapter().getCount(), is(personalProfileTargets)); @@ -1243,11 +1250,22 @@ public class ChooserActivityTest { ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); int workProfileTargets = 4; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); Intent sendIntent = createSendTextIntent(); sendIntent.setType("TestType"); @@ -1357,6 +1375,20 @@ public class ChooserActivityTest { return infoList; } + private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( + int numberOfResults, int userId) { + List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); + for (int i = 0; i < numberOfResults; i++) { + if (i == 0) { + infoList.add( + ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId)); + } else { + infoList.add(ResolverDataProvider.createResolvedComponentInfo(i)); + } + } + return infoList; + } + private List<ResolvedComponentInfo> createResolvedComponentsForTestWithUserId( int numberOfResults, int userId) { List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index eee62bb791bf..a68b59086d42 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -16,10 +16,15 @@ package com.android.internal.app; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.annotation.Nullable; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionManager; +import android.app.prediction.AppPredictor; import android.app.usage.UsageStatsManager; import android.content.ContentResolver; import android.content.Context; @@ -40,6 +45,8 @@ import com.android.internal.app.chooser.TargetInfo; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import org.mockito.Mockito; + import java.util.function.Function; public class ChooserWrapperActivity extends ChooserActivity { @@ -173,6 +180,12 @@ public class ChooserWrapperActivity extends ChooserActivity { return mMultiProfilePagerAdapter.getCurrentUserHandle(); } + @Override + public Context createContextAsUser(UserHandle user, int flags) { + // return the current context as a work profile doesn't really exist in these tests + return getApplicationContext(); + } + /** * We cannot directly mock the activity created since instrumentation creates it. * <p> diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index 911490f30799..5f4194aa51e3 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -225,6 +225,7 @@ public class ResolverActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); Intent sendIntent = createSendImageIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(2); @@ -246,7 +247,6 @@ public class ResolverActivityTest { chosen[0] = targetInfo.getResolveInfo(); return true; }; - // Make a stable copy of the components as the original list may be modified List<ResolvedComponentInfo> stableCopy = createResolvedComponentsForTestWithOtherProfile(2); @@ -443,7 +443,7 @@ public class ResolverActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), @@ -451,6 +451,11 @@ public class ResolverActivityTest { when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); Intent sendIntent = createSendImageIntent(); markWorkProfileUserAvailable(); @@ -478,17 +483,20 @@ public class ResolverActivityTest { Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), - eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + eq(sOverrides.workProfileUserHandle))) + .thenReturn(new ArrayList<>(workResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), - eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + eq(sOverrides.workProfileUserHandle))) + .thenReturn(new ArrayList<>(workResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), @@ -502,7 +510,7 @@ public class ResolverActivityTest { onView(withText(R.string.resolver_work_tab)).perform(click()); assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); - assertThat(activity.getPersonalListAdapter().getCount(), is(3)); + assertThat(activity.getPersonalListAdapter().getCount(), is(2)); } @Test @@ -511,14 +519,20 @@ public class ResolverActivityTest { ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); Intent sendIntent = createSendImageIntent(); final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); @@ -536,14 +550,20 @@ public class ResolverActivityTest { ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); Intent sendIntent = createSendImageIntent(); ResolveInfo[] chosen = new ResolveInfo[1]; sOverrides.onSafelyStartCallback = targetInfo -> { @@ -587,17 +607,20 @@ public class ResolverActivityTest { Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), - eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + eq(sOverrides.workProfileUserHandle))) + .thenReturn(new ArrayList<>(workResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), - eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + eq(sOverrides.workProfileUserHandle))) + .thenReturn(new ArrayList<>(workResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), @@ -678,6 +701,20 @@ public class ResolverActivityTest { return infoList; } + private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( + int numberOfResults, int userId) { + List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); + for (int i = 0; i < numberOfResults; i++) { + if (i == 0) { + infoList.add( + ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId)); + } else { + infoList.add(ResolverDataProvider.createResolvedComponentInfo(i)); + } + } + return infoList; + } + private void waitForIdle() { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml index a200a510cf12..fe1182ecad4f 100644 --- a/data/etc/com.android.settings.xml +++ b/data/etc/com.android.settings.xml @@ -26,6 +26,7 @@ <permission name="android.permission.DELETE_PACKAGES"/> <permission name="android.permission.FORCE_STOP_PACKAGES"/> <permission name="android.permission.LOCAL_MAC_ADDRESS"/> + <permission name="android.permission.LOG_COMPAT_CHANGE" /> <permission name="android.permission.MANAGE_DEBUGGING"/> <permission name="android.permission.MANAGE_DEVICE_ADMINS"/> <permission name="android.permission.MANAGE_FINGERPRINT"/> @@ -37,8 +38,10 @@ <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <permission name="android.permission.MOVE_PACKAGE"/> + <permission name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG" /> <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/> <permission name="android.permission.PACKAGE_USAGE_STATS"/> + <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> <permission name="android.permission.READ_SEARCH_INDEXABLES"/> <permission name="android.permission.REBOOT"/> diff --git a/data/etc/platform.xml b/data/etc/platform.xml index da505505628e..6929d0d1879d 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -72,6 +72,11 @@ <group gid="net_admin" /> </permission> + <permission name="android.permission.MAINLINE_NETWORK_STACK" > + <group gid="net_admin" /> + <group gid="net_raw" /> + </permission> + <!-- The group that /cache belongs to, linked to the permission set on the applications that can access /cache --> <permission name="android.permission.ACCESS_CACHE_FILESYSTEM" > diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index b5eba090691f..f83fb3f312e9 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -169,7 +169,7 @@ applications that come with the platform <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/> <permission name="android.permission.STATUS_BAR"/> <permission name="android.permission.STOP_APP_SWITCHES"/> - <permission name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE"/> + <permission name="android.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE"/> <permission name="android.permission.UPDATE_APP_OPS_STATS"/> <permission name="android.permission.UPDATE_DEVICE_STATS"/> <permission name="android.permission.UPDATE_LOCK"/> @@ -366,6 +366,10 @@ applications that come with the platform <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/> <!-- Permission required for Tethering CTS tests. --> <permission name="android.permission.TETHER_PRIVILEGED"/> + <!-- Permissions required for ganting and logging --> + <permission name="android.permission.LOG_COMPAT_CHANGE" /> + <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> + <permission name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG" /> <!-- Permissions required to test ambient display. --> <permission name="android.permission.READ_DREAM_STATE" /> <permission name="android.permission.WRITE_DREAM_STATE" /> diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/graphics/java/android/graphics/GraphicsStatsService.java index 5179fa7a6eb5..8dfd6ee92a9a 100644 --- a/services/core/java/com/android/server/GraphicsStatsService.java +++ b/graphics/java/android/graphics/GraphicsStatsService.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.server; +package android.graphics; +import android.annotation.SystemApi; import android.app.AlarmManager; import android.app.AppOpsManager; import android.content.Context; @@ -26,13 +27,14 @@ import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; -import android.os.MemoryFile; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; +import android.os.SharedMemory; import android.os.Trace; import android.os.UserHandle; +import android.system.ErrnoException; import android.util.Log; import android.view.IGraphicsStats; import android.view.IGraphicsStatsCallback; @@ -45,6 +47,7 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -84,8 +87,8 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { // This isn't static because we need this to happen after registerNativeMethods, however // the class is loaded (and thus static ctor happens) before that occurs. - private final int ASHMEM_SIZE = nGetAshmemSize(); - private final byte[] ZERO_DATA = new byte[ASHMEM_SIZE]; + private final int mAshmemSize = nGetAshmemSize(); + private final byte[] mZeroData = new byte[mAshmemSize]; private final Context mContext; private final AppOpsManager mAppOps; @@ -97,6 +100,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { private Handler mWriteOutHandler; private boolean mRotateIsScheduled = false; + @SystemApi public GraphicsStatsService(Context context) { mContext = context; mAppOps = context.getSystemService(AppOpsManager.class); @@ -108,7 +112,8 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { throw new IllegalStateException("Graphics stats directory does not exist: " + mGraphicsStatsDir.getAbsolutePath()); } - HandlerThread bgthread = new HandlerThread("GraphicsStats-disk", Process.THREAD_PRIORITY_BACKGROUND); + HandlerThread bgthread = new HandlerThread("GraphicsStats-disk", + Process.THREAD_PRIORITY_BACKGROUND); bgthread.start(); mWriteOutHandler = new Handler(bgthread.getLooper(), new Handler.Callback() { @@ -159,7 +164,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { active.mCallback.onRotateGraphicsStatsBuffer(); } catch (RemoteException e) { Log.w(TAG, String.format("Failed to notify '%s' (pid=%d) to rotate buffers", - active.mInfo.packageName, active.mPid), e); + active.mInfo.mPackageName, active.mPid), e); } } // Give a few seconds for everyone to rotate before doing the cleanup @@ -167,8 +172,8 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { } @Override - public ParcelFileDescriptor requestBufferForProcess(String packageName, IGraphicsStatsCallback token) - throws RemoteException { + public ParcelFileDescriptor requestBufferForProcess(String packageName, + IGraphicsStatsCallback token) throws RemoteException { int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); ParcelFileDescriptor pfd = null; @@ -196,7 +201,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { // current day. // This method is invoked from native code only. @SuppressWarnings({"UnusedDeclaration"}) - private long pullGraphicsStats(boolean lastFullDay) throws RemoteException { + private void pullGraphicsStats(boolean lastFullDay, long pulledData) throws RemoteException { int uid = Binder.getCallingUid(); // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method. @@ -213,13 +218,13 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { long callingIdentity = Binder.clearCallingIdentity(); try { - return pullGraphicsStatsImpl(lastFullDay); + pullGraphicsStatsImpl(lastFullDay, pulledData); } finally { Binder.restoreCallingIdentity(callingIdentity); } } - private long pullGraphicsStatsImpl(boolean lastFullDay) { + private void pullGraphicsStatsImpl(boolean lastFullDay, long pulledData) { long targetDay; if (lastFullDay) { // Get stats from yesterday. Stats stay constant, because the day is over. @@ -235,7 +240,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { buffers = new ArrayList<>(mActive.size()); for (int i = 0; i < mActive.size(); i++) { ActiveBuffer buffer = mActive.get(i); - if (buffer.mInfo.startTime == targetDay) { + if (buffer.mInfo.mStartTime == targetDay) { try { buffers.add(new HistoricalBuffer(buffer)); } catch (IOException ex) { @@ -267,18 +272,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { } } } finally { - return nFinishDumpInMemory(dump); - } - } - - private ParcelFileDescriptor getPfd(MemoryFile file) { - try { - if (!file.getFileDescriptor().valid()) { - throw new IllegalStateException("Invalid file descriptor"); - } - return ParcelFileDescriptor.dup(file.getFileDescriptor()); - } catch (IOException ex) { - throw new IllegalStateException("Failed to get PFD from memory file", ex); + nFinishDumpInMemory(dump, pulledData, lastFullDay); } } @@ -286,7 +280,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { int uid, int pid, String packageName, long versionCode) throws RemoteException { ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName, versionCode); scheduleRotateLocked(); - return getPfd(buffer.mProcessBuffer); + return buffer.getPfd(); } private Calendar normalizeDate(long timestamp) { @@ -301,13 +295,15 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { private File pathForApp(BufferInfo info) { String subPath = String.format("%d/%s/%d/total", - normalizeDate(info.startTime).getTimeInMillis(), info.packageName, info.versionCode); + normalizeDate(info.mStartTime).getTimeInMillis(), info.mPackageName, + info.mVersionCode); return new File(mGraphicsStatsDir, subPath); } private void saveBuffer(HistoricalBuffer buffer) { if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { - Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "saving graphicsstats for " + buffer.mInfo.packageName); + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, + "saving graphicsstats for " + buffer.mInfo.mPackageName); } synchronized (mFileAccessLock) { File path = pathForApp(buffer.mInfo); @@ -317,8 +313,9 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { Log.w(TAG, "Unable to create path: '" + parent.getAbsolutePath() + "'"); return; } - nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.packageName, buffer.mInfo.versionCode, - buffer.mInfo.startTime, buffer.mInfo.endTime, buffer.mData); + nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.mPackageName, + buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime, + buffer.mData); } Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } @@ -365,7 +362,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { HistoricalBuffer data = new HistoricalBuffer(buffer); Message.obtain(mWriteOutHandler, SAVE_BUFFER, data).sendToTarget(); } catch (IOException e) { - Log.w(TAG, "Failed to copy graphicsstats from " + buffer.mInfo.packageName, e); + Log.w(TAG, "Failed to copy graphicsstats from " + buffer.mInfo.mPackageName, e); } buffer.closeAllBuffers(); } @@ -386,7 +383,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { if (buffer.mPid == pid && buffer.mUid == uid) { // If the buffer is too old we remove it and return a new one - if (buffer.mInfo.startTime < today) { + if (buffer.mInfo.mStartTime < today) { buffer.binderDied(); break; } else { @@ -410,8 +407,8 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { HistoricalBuffer buffer = buffers.get(i); File path = pathForApp(buffer.mInfo); skipFiles.add(path); - nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.packageName, - buffer.mInfo.versionCode, buffer.mInfo.startTime, buffer.mInfo.endTime, + nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.mPackageName, + buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime, buffer.mData); } return skipFiles; @@ -478,20 +475,20 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { long versionCode, long startTime, long endTime, byte[] data); private static native void nAddToDump(long dump, String path); private static native void nFinishDump(long dump); - private static native long nFinishDumpInMemory(long dump); + private static native void nFinishDumpInMemory(long dump, long pulledData, boolean lastFullDay); private static native void nSaveBuffer(String path, String packageName, long versionCode, long startTime, long endTime, byte[] data); private final class BufferInfo { - final String packageName; - final long versionCode; - long startTime; - long endTime; + final String mPackageName; + final long mVersionCode; + long mStartTime; + long mEndTime; BufferInfo(String packageName, long versionCode, long startTime) { - this.packageName = packageName; - this.versionCode = versionCode; - this.startTime = startTime; + this.mPackageName = packageName; + this.mVersionCode = versionCode; + this.mStartTime = startTime; } } @@ -501,7 +498,8 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { final int mPid; final IGraphicsStatsCallback mCallback; final IBinder mToken; - MemoryFile mProcessBuffer; + SharedMemory mProcessBuffer; + ByteBuffer mMapping; ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName, long versionCode) @@ -512,8 +510,14 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { mCallback = token; mToken = mCallback.asBinder(); mToken.linkToDeath(this, 0); - mProcessBuffer = new MemoryFile("GFXStats-" + pid, ASHMEM_SIZE); - mProcessBuffer.writeBytes(ZERO_DATA, 0, 0, ASHMEM_SIZE); + try { + mProcessBuffer = SharedMemory.create("GFXStats-" + pid, mAshmemSize); + mMapping = mProcessBuffer.mapReadWrite(); + } catch (ErrnoException ex) { + ex.rethrowAsIOException(); + } + mMapping.position(0); + mMapping.put(mZeroData, 0, mAshmemSize); } @Override @@ -523,20 +527,40 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { } void closeAllBuffers() { + if (mMapping != null) { + SharedMemory.unmap(mMapping); + mMapping = null; + } if (mProcessBuffer != null) { mProcessBuffer.close(); mProcessBuffer = null; } } + + ParcelFileDescriptor getPfd() { + try { + return mProcessBuffer.getFdDup(); + } catch (IOException ex) { + throw new IllegalStateException("Failed to get PFD from memory file", ex); + } + } + + void readBytes(byte[] buffer, int count) throws IOException { + if (mMapping == null) { + throw new IOException("SharedMemory has been deactivated"); + } + mMapping.position(0); + mMapping.get(buffer, 0, count); + } } private final class HistoricalBuffer { final BufferInfo mInfo; - final byte[] mData = new byte[ASHMEM_SIZE]; + final byte[] mData = new byte[mAshmemSize]; HistoricalBuffer(ActiveBuffer active) throws IOException { mInfo = active.mInfo; - mInfo.endTime = System.currentTimeMillis(); - active.mProcessBuffer.readBytes(mData, 0, 0, ASHMEM_SIZE); + mInfo.mEndTime = System.currentTimeMillis(); + active.readBytes(mData, mAshmemSize); } } } diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index 3b864139cf71..d08bfcf45a5c 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -157,7 +157,7 @@ public class HardwareRenderer { public HardwareRenderer() { mRootNode = RenderNode.adopt(nCreateRootRenderNode()); mRootNode.setClipToBounds(false); - mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode); + mNativeProxy = nCreateProxy(!mOpaque, mIsWideGamut, mRootNode.mNativeRenderNode); if (mNativeProxy == 0) { throw new OutOfMemoryError("Unable to create hardware renderer"); } @@ -1085,7 +1085,8 @@ public class HardwareRenderer { private static native long nCreateRootRenderNode(); - private static native long nCreateProxy(boolean translucent, long rootRenderNode); + private static native long nCreateProxy(boolean translucent, boolean isWideGamut, + long rootRenderNode); private static native void nDeleteProxy(long nativeProxy); diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java index dcc6b95aec02..129063361b35 100644 --- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java +++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java @@ -38,6 +38,10 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore { ICredentialStoreFactory storeFactory = ICredentialStoreFactory.Stub.asInterface( ServiceManager.getService("android.security.identity")); + if (storeFactory == null) { + // This can happen if credstore is not running or not installed. + return null; + } ICredentialStore credStore = null; try { diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 51270f5bcebd..debb38b2c1b0 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -92,9 +92,12 @@ cc_defaults { "libandroidfw", "libcrypto", "libsync", + "libstatspull", + "libstatssocket", ], static_libs: [ "libEGL_blobCache", + "libprotoutil", ], }, host: { @@ -168,7 +171,6 @@ cc_defaults { "renderthread/RenderTask.cpp", "renderthread/TimeLord.cpp", "hwui/AnimatedImageDrawable.cpp", - "hwui/AnimatedImageThread.cpp", "hwui/Bitmap.cpp", "hwui/Canvas.cpp", "hwui/ImageDecoder.cpp", @@ -210,6 +212,7 @@ cc_defaults { android: { srcs: [ + "hwui/AnimatedImageThread.cpp", "pipeline/skia/ATraceMemoryDump.cpp", "pipeline/skia/GLFunctorDrawable.cpp", "pipeline/skia/LayerDrawable.cpp", diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index 3681c69e912b..a3d552faeb0a 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -187,7 +187,9 @@ private: EGLSyncKHR fence = mUploadThread->queue().runSync([&]() -> EGLSyncKHR { AutoSkiaGlTexture glTexture; glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image); - GL_CHECKPOINT(MODERATE); + if (GLUtils::dumpGLErrors()) { + return EGL_NO_SYNC_KHR; + } // glTexSubImage2D is synchronous in sense that it memcpy() from pointer that we // provide. @@ -195,19 +197,26 @@ private: // when we first use it in drawing glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(), format.format, format.type, bitmap.getPixels()); - GL_CHECKPOINT(MODERATE); + if (GLUtils::dumpGLErrors()) { + return EGL_NO_SYNC_KHR; + } EGLSyncKHR uploadFence = eglCreateSyncKHR(eglGetCurrentDisplay(), EGL_SYNC_FENCE_KHR, NULL); - LOG_ALWAYS_FATAL_IF(uploadFence == EGL_NO_SYNC_KHR, - "Could not create sync fence %#x", eglGetError()); + if (uploadFence == EGL_NO_SYNC_KHR) { + ALOGW("Could not create sync fence %#x", eglGetError()); + }; glFlush(); + GLUtils::dumpGLErrors(); return uploadFence; }); + if (fence == EGL_NO_SYNC_KHR) { + return false; + } EGLint waitStatus = eglClientWaitSyncKHR(display, fence, 0, FENCE_TIMEOUT); - LOG_ALWAYS_FATAL_IF(waitStatus != EGL_CONDITION_SATISFIED_KHR, - "Failed to wait for the fence %#x", eglGetError()); + ALOGE_IF(waitStatus != EGL_CONDITION_SATISFIED_KHR, + "Failed to wait for the fence %#x", eglGetError()); eglDestroySyncKHR(display, fence); } 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/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp index 4544beae5df8..638de850a6c5 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.cpp +++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp @@ -15,7 +15,9 @@ */ #include "AnimatedImageDrawable.h" +#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread #include "AnimatedImageThread.h" +#endif #include "utils/TraceUtils.h" @@ -160,8 +162,10 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { } else if (starting) { // The image has animated, and now is being reset. Queue up the first // frame, but keep showing the current frame until the first is ready. +#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread auto& thread = uirenderer::AnimatedImageThread::getInstance(); mNextSnapshot = thread.reset(sk_ref_sp(this)); +#endif } bool finalFrame = false; @@ -187,8 +191,10 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { } if (mRunning && !mNextSnapshot.valid()) { +#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread auto& thread = uirenderer::AnimatedImageThread::getInstance(); mNextSnapshot = thread.decodeNextFrame(sk_ref_sp(this)); +#endif } if (!drawDirectly) { 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/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index e7efe2ff798b..8d5acc631274 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -171,17 +171,15 @@ static void setBufferCount(ANativeWindow* window, uint32_t extraBuffers) { } bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior, - ColorMode colorMode, uint32_t extraBuffers) { + uint32_t extraBuffers) { if (mEglSurface != EGL_NO_SURFACE) { mEglManager.destroySurface(mEglSurface); mEglSurface = EGL_NO_SURFACE; } - setSurfaceColorProperties(colorMode); - if (surface) { mRenderThread.requireGlContext(); - auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorSpace); + auto newSurface = mEglManager.createSurface(surface, mColorMode, mSurfaceColorSpace); if (!newSurface) { return false; } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index 3fe0f92b1924..e482cad6c953 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -44,7 +44,7 @@ public: FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior, - renderthread::ColorMode colorMode, uint32_t extraBuffers) override; + uint32_t extraBuffers) override; void onStop() override; bool isSurfaceReady() override; bool isContextReady() override; diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 6f4af3d26b3a..29b4dd7f32e7 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -44,6 +44,7 @@ namespace uirenderer { namespace skiapipeline { SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) { + setSurfaceColorProperties(mColorMode); } SkiaPipeline::~SkiaPipeline() { @@ -584,6 +585,7 @@ void SkiaPipeline::dumpResourceCacheUsage() const { } void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { + mColorMode = colorMode; if (colorMode == ColorMode::SRGB) { mSurfaceColorType = SkColorType::kN32_SkColorType; mSurfaceColorSpace = SkColorSpace::MakeSRGB(); diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index af8414de4bc1..8341164edc19 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -50,6 +50,7 @@ public: bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, ErrorHandler* errorHandler) override; + void setSurfaceColorProperties(renderthread::ColorMode colorMode) override; SkColorType getSurfaceColorType() const override { return mSurfaceColorType; } sk_sp<SkColorSpace> getSurfaceColorSpace() override { return mSurfaceColorSpace; } @@ -72,9 +73,10 @@ public: protected: void dumpResourceCacheUsage() const; - void setSurfaceColorProperties(renderthread::ColorMode colorMode); renderthread::RenderThread& mRenderThread; + + renderthread::ColorMode mColorMode = renderthread::ColorMode::SRGB; SkColorType mSurfaceColorType; sk_sp<SkColorSpace> mSurfaceColorSpace; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index ad7c70614239..535a19956e03 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -117,17 +117,16 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { void SkiaVulkanPipeline::onStop() {} bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior, - ColorMode colorMode, uint32_t extraBuffers) { + uint32_t extraBuffers) { if (mVkSurface) { mVkManager.destroySurface(mVkSurface); mVkSurface = nullptr; } - setSurfaceColorProperties(colorMode); if (surface) { mRenderThread.requireVkContext(); mVkSurface = - mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace, mSurfaceColorType, + mVkManager.createSurface(surface, mColorMode, mSurfaceColorSpace, mSurfaceColorType, mRenderThread.getGrContext(), extraBuffers); } diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index 31734783de7f..c8bf233d8e1c 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -43,7 +43,7 @@ public: FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior, - renderthread::ColorMode colorMode, uint32_t extraBuffers) override; + uint32_t extraBuffers) override; void onStop() override; bool isSurfaceReady() override; bool isContextReady() override; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index c1435d1ea2d5..91f9447a3d59 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -161,9 +161,8 @@ void CanvasContext::setSurface(sp<Surface>&& surface, bool enableTimeout) { mRenderAheadCapacity = mRenderAheadDepth; } - ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::SRGB; bool hasSurface = mRenderPipeline->setSurface( - mNativeSurface ? mNativeSurface->getNativeWindow() : nullptr, mSwapBehavior, colorMode, + mNativeSurface ? mNativeSurface->getNativeWindow() : nullptr, mSwapBehavior, mRenderAheadCapacity); mFrameNumber = -1; @@ -174,7 +173,7 @@ void CanvasContext::setSurface(sp<Surface>&& surface, bool enableTimeout) { // Enable frame stats after the surface has been bound to the appropriate graphics API. // Order is important when new and old surfaces are the same, because old surface has // its frame stats disabled automatically. - mNativeSurface->enableFrameTimestamps(true); + native_window_enable_frame_timestamps(mNativeSurface->getNativeWindow(), true); } else { mRenderThread.removeFrameCallback(this); mGenerationID++; @@ -225,7 +224,8 @@ void CanvasContext::setOpaque(bool opaque) { } void CanvasContext::setWideGamut(bool wideGamut) { - mWideColorGamut = wideGamut; + ColorMode colorMode = wideGamut ? ColorMode::WideColorGamut : ColorMode::SRGB; + mRenderPipeline->setSurfaceColorProperties(colorMode); } bool CanvasContext::makeCurrent() { @@ -429,7 +429,8 @@ void CanvasContext::setPresentTime() { if (renderAhead) { presentTime = mCurrentFrameInfo->get(FrameInfoIndex::Vsync) + - (frameIntervalNanos * (renderAhead + 1)); + (frameIntervalNanos * (renderAhead + 1)) - DeviceInfo::get()->getAppOffset() + + (frameIntervalNanos / 2); } native_window_set_buffers_timestamp(mNativeSurface->getNativeWindow(), presentTime); } @@ -555,8 +556,9 @@ void CanvasContext::draw() { FrameInfo* forthBehind = mLast4FrameInfos.front().first; int64_t composedFrameId = mLast4FrameInfos.front().second; nsecs_t acquireTime = -1; - mNativeSurface->getFrameTimestamps(composedFrameId, nullptr, &acquireTime, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr); + native_window_get_frame_timestamps(mNativeSurface->getNativeWindow(), composedFrameId, + nullptr, &acquireTime, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr); // Ignore default -1, NATIVE_WINDOW_TIMESTAMP_INVALID and NATIVE_WINDOW_TIMESTAMP_PENDING forthBehind->set(FrameInfoIndex::GpuCompleted) = acquireTime > 0 ? acquireTime : -1; mJankTracker.finishGpuDraw(*forthBehind); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 0967b20e44ee..629c741e8757 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -251,7 +251,6 @@ private: nsecs_t mLastDropVsync = 0; bool mOpaque; - bool mWideColorGamut = false; bool mUseForceDark = false; LightInfo mLightInfo; LightGeometry mLightGeometry = {{0, 0, 0}, 0}; diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index ef0aa98d4220..ba0d64c5492d 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -66,7 +66,7 @@ public: virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) = 0; virtual DeferredLayerUpdater* createTextureLayer() = 0; - virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode, + virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, uint32_t extraBuffers) = 0; virtual void onStop() = 0; virtual bool isSurfaceReady() = 0; @@ -80,6 +80,8 @@ public: virtual bool pinImages(std::vector<SkImage*>& mutableImages) = 0; virtual bool pinImages(LsaVector<sk_sp<Bitmap>>& images) = 0; virtual void unpinImages() = 0; + + virtual void setSurfaceColorProperties(ColorMode colorMode) = 0; virtual SkColorType getSurfaceColorType() const = 0; virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0; virtual GrSurfaceOrigin getSurfaceOrigin() = 0; diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h index da5097ce33f0..e3cd8c019a23 100644 --- a/libs/hwui/renderthread/ReliableSurface.h +++ b/libs/hwui/renderthread/ReliableSurface.h @@ -49,21 +49,6 @@ public: return ret; } - status_t getFrameTimestamps(uint64_t frameNumber, - nsecs_t* outRequestedPresentTime, nsecs_t* outAcquireTime, - nsecs_t* outLatchTime, nsecs_t* outFirstRefreshStartTime, - nsecs_t* outLastRefreshStartTime, nsecs_t* outGlCompositionDoneTime, - nsecs_t* outDisplayPresentTime, nsecs_t* outDequeueReadyTime, - nsecs_t* outReleaseTime) { - return mSurface->getFrameTimestamps(frameNumber, outRequestedPresentTime, outAcquireTime, - outLatchTime, outFirstRefreshStartTime, outLastRefreshStartTime, - outGlCompositionDoneTime, outDisplayPresentTime, outDequeueReadyTime, outReleaseTime); - } - - void enableFrameTimestamps(bool enable) { - return mSurface->enableFrameTimestamps(enable); - } - private: sp<Surface> mSurface; 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/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp index c4186174b637..644d5fbd5bf9 100644 --- a/libs/hwui/service/GraphicsStatsService.cpp +++ b/libs/hwui/service/GraphicsStatsService.cpp @@ -26,9 +26,9 @@ #include <sys/types.h> #include <unistd.h> -#include <algorithm> -#include <map> -#include <vector> +#include <android/util/ProtoOutputStream.h> +#include <stats_event.h> +#include <statslog.h> #include "JankTracker.h" #include "protos/graphicsstats.pb.h" @@ -61,7 +61,7 @@ public: } } bool valid() { return mFd != -1; } - operator int() { return mFd; } // NOLINT(google-explicit-constructor) + operator int() { return mFd; } // NOLINT(google-explicit-constructor) private: int mFd; @@ -485,79 +485,82 @@ void GraphicsStatsService::finishDump(Dump* dump) { delete dump; } -class MemOutputStreamLite : public io::ZeroCopyOutputStream { -public: - explicit MemOutputStreamLite() : mCopyAdapter(), mImpl(&mCopyAdapter) {} - virtual ~MemOutputStreamLite() {} - - virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); } - - virtual void BackUp(int count) override { mImpl.BackUp(count); } - - virtual int64 ByteCount() const override { return mImpl.ByteCount(); } - - bool Flush() { return mImpl.Flush(); } - - void copyData(const DumpMemoryFn& reader, void* param1, void* param2) { - int bufferOffset = 0; - int totalSize = mCopyAdapter.mBuffersSize - mCopyAdapter.mCurrentBufferUnusedSize; - int totalDataLeft = totalSize; - for (auto& it : mCopyAdapter.mBuffers) { - int bufferSize = std::min(totalDataLeft, (int)it.size()); // last buffer is not full - reader(it.data(), bufferOffset, bufferSize, totalSize, param1, param2); - bufferOffset += bufferSize; - totalDataLeft -= bufferSize; - } - } - -private: - struct MemAdapter : public io::CopyingOutputStream { - // Data is stored in an array of buffers. - // JNI SetByteArrayRegion assembles data in one continuous Java byte[] buffer. - std::vector<std::vector<unsigned char>> mBuffers; - int mBuffersSize = 0; // total bytes allocated in mBuffers - int mCurrentBufferUnusedSize = 0; // unused bytes in the last buffer mBuffers.back() - unsigned char* mCurrentBuffer = nullptr; // pointer to next free byte in mBuffers.back() +using namespace google::protobuf; - explicit MemAdapter() {} - virtual ~MemAdapter() {} +// Field ids taken from FrameTimingHistogram message in atoms.proto +#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1 +#define FRAME_COUNTS_FIELD_NUMBER 2 + +static void writeCpuHistogram(AStatsEvent* event, + const uirenderer::protos::GraphicsStatsProto& stat) { + util::ProtoOutputStream proto; + for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { + auto& bucket = stat.histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | + TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, + (int)bucket.render_millis()); + } + for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { + auto& bucket = stat.histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | + FRAME_COUNTS_FIELD_NUMBER /* field id */, + (long long)bucket.frame_count()); + } + std::vector<uint8_t> outVector; + proto.serializeToVector(&outVector); + AStatsEvent_writeByteArray(event, outVector.data(), outVector.size()); +} - virtual bool Write(const void* buffer, int size) override { - while (size > 0) { - if (0 == mCurrentBufferUnusedSize) { - mCurrentBufferUnusedSize = - std::max(size, mBuffersSize ? 2 * mBuffersSize : 10000); - mBuffers.emplace_back(); - mBuffers.back().resize(mCurrentBufferUnusedSize); - mCurrentBuffer = mBuffers.back().data(); - mBuffersSize += mCurrentBufferUnusedSize; - } - int dataMoved = std::min(mCurrentBufferUnusedSize, size); - memcpy(mCurrentBuffer, buffer, dataMoved); - mCurrentBufferUnusedSize -= dataMoved; - mCurrentBuffer += dataMoved; - buffer = reinterpret_cast<const unsigned char*>(buffer) + dataMoved; - size -= dataMoved; - } - return true; - } - }; +static void writeGpuHistogram(AStatsEvent* event, + const uirenderer::protos::GraphicsStatsProto& stat) { + util::ProtoOutputStream proto; + for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { + auto& bucket = stat.gpu_histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | + TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, + (int)bucket.render_millis()); + } + for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { + auto& bucket = stat.gpu_histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | + FRAME_COUNTS_FIELD_NUMBER /* field id */, + (long long)bucket.frame_count()); + } + std::vector<uint8_t> outVector; + proto.serializeToVector(&outVector); + AStatsEvent_writeByteArray(event, outVector.data(), outVector.size()); +} - MemOutputStreamLite::MemAdapter mCopyAdapter; - io::CopyingOutputStreamAdaptor mImpl; -}; -void GraphicsStatsService::finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1, - void* param2) { - MemOutputStreamLite stream; +void GraphicsStatsService::finishDumpInMemory(Dump* dump, AStatsEventList* data, + bool lastFullDay) { dump->updateProto(); - bool success = dump->proto().SerializeToZeroCopyStream(&stream) && stream.Flush(); - delete dump; - if (!success) { - return; + auto& serviceDump = dump->proto(); + for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) { + auto& stat = serviceDump.stats(stat_index); + AStatsEvent* event = AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId(event, android::util::GRAPHICS_STATS); + AStatsEvent_writeString(event, stat.package_name().c_str()); + AStatsEvent_writeInt64(event, (int64_t)stat.version_code()); + AStatsEvent_writeInt64(event, (int64_t)stat.stats_start()); + AStatsEvent_writeInt64(event, (int64_t)stat.stats_end()); + AStatsEvent_writeInt32(event, (int32_t)stat.pipeline()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().total_frames()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_vsync_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().high_input_latency_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_ui_thread_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_bitmap_upload_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_draw_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_deadline_count()); + writeCpuHistogram(event, stat); + writeGpuHistogram(event, stat); + // TODO: fill in UI mainline module version, when the feature is available. + AStatsEvent_writeInt64(event, (int64_t)0); + AStatsEvent_writeBool(event, !lastFullDay); + AStatsEvent_build(event); } - stream.copyData(reader, param1, param2); } + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h index 4bed96330a52..59e21d039c9d 100644 --- a/libs/hwui/service/GraphicsStatsService.h +++ b/libs/hwui/service/GraphicsStatsService.h @@ -20,6 +20,7 @@ #include "JankTracker.h" #include "utils/Macros.h" +#include <stats_pull_atom_callback.h> namespace android { namespace uirenderer { @@ -27,9 +28,6 @@ namespace protos { class GraphicsStatsProto; } -typedef void (*DumpMemoryFn)(void* buffer, int bufferOffset, int bufferSize, int totalSize, - void* param1, void* param2); - /* * The exported entry points used by GraphicsStatsService.java in f/b/services/core * @@ -56,8 +54,8 @@ public: int64_t startTime, int64_t endTime, const ProfileData* data); ANDROID_API static void addToDump(Dump* dump, const std::string& path); ANDROID_API static void finishDump(Dump* dump); - ANDROID_API static void finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1, - void* param2); + ANDROID_API static void finishDumpInMemory(Dump* dump, AStatsEventList* data, + bool lastFullDay); // Visible for testing static bool parseFromFile(const std::string& path, protos::GraphicsStatsProto* output); 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/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 307d13606cb8..90bcd1c0e370 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -398,7 +398,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) { auto surface = context.surface(); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); EXPECT_FALSE(pipeline->isSurfaceReady()); - EXPECT_TRUE(pipeline->setSurface(surface.get(), SwapBehavior::kSwap_default, ColorMode::SRGB, 0)); + EXPECT_TRUE(pipeline->setSurface(surface.get(), SwapBehavior::kSwap_default, 0)); EXPECT_TRUE(pipeline->isSurfaceReady()); renderThread.destroyRenderingContext(); EXPECT_FALSE(pipeline->isSurfaceReady()); 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/libs/incident/Android.bp b/libs/incident/Android.bp index 150f6dcde5d0..512b8c439dcf 100644 --- a/libs/incident/Android.bp +++ b/libs/incident/Android.bp @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -cc_library_shared { - name: "libincident", + +cc_defaults { + name: "libincidentpriv_defaults", cflags: [ "-Wall", @@ -50,6 +51,70 @@ cc_library_shared { ":libincident_aidl", "src/IncidentReportArgs.cpp", ], +} + +cc_library_shared { + name: "libincidentpriv", + defaults: ["libincidentpriv_defaults"], + export_include_dirs: ["include_priv"], +} + +cc_library_shared { + name: "libincident", + + cflags: [ + "-Wall", + "-Werror", + "-Wno-missing-field-initializers", + "-Wno-unused-variable", + "-Wunused-parameter", + ], + + shared_libs: [ + "libbinder", + "liblog", + "libutils", + "libincidentpriv", + ], + + srcs: [ + "src/incident_report.cpp", + ], export_include_dirs: ["include"], + + stubs: { + symbol_file: "libincident.map.txt", + versions: [ + "30", + ], + }, } + +cc_test { + name: "libincident_test", + defaults: ["libincidentpriv_defaults"], + test_suites: ["device-tests"], + + include_dirs: [ + "frameworks/base/libs/incident/include", + "frameworks/base/libs/incident/include_priv", + ], + + srcs: [ + "tests/IncidentReportArgs_test.cpp", + "tests/IncidentReportRequest_test.cpp", + "tests/c_api_compile_test.c", + ], + + shared_libs: [ + "libincident", + ], + + static_libs: [ + "libgmock", + ], +} + + + diff --git a/libs/incident/include/incident/incident_report.h b/libs/incident/include/incident/incident_report.h new file mode 100644 index 000000000000..49fe5b9b73b4 --- /dev/null +++ b/libs/incident/include/incident/incident_report.h @@ -0,0 +1,192 @@ +/** + * 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. + */ + +/** + * @file incident_report.h + */ + +#ifndef ANDROID_INCIDENT_INCIDENT_REPORT_H +#define ANDROID_INCIDENT_INCIDENT_REPORT_H + +#include <stdbool.h> + +#if __cplusplus +#include <set> +#include <string> +#include <vector> + +extern "C" { +#endif // __cplusplus + +struct AIncidentReportArgs; +/** + * Opaque class to represent the arguments to an incident report request. + * Incident reports contain debugging data about the device at runtime. + * For more information see the android.os.IncidentManager java class. + */ +typedef struct AIncidentReportArgs AIncidentReportArgs; + +// Privacy policy enum value, sync with frameworks/base/core/proto/android/privacy.proto, +// IncidentReportArgs.h and IncidentReportArgs.java. +enum { + /** + * Flag marking fields and incident reports than can be taken + * off the device only via adb. + */ + INCIDENT_REPORT_PRIVACY_POLICY_LOCAL = 0, + + /** + * Flag marking fields and incident reports than can be taken + * off the device with contemporary consent. + */ + INCIDENT_REPORT_PRIVACY_POLICY_EXPLICIT = 100, + + /** + * Flag marking fields and incident reports than can be taken + * off the device with prior consent. + */ + INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC = 200, + + /** + * Flag to indicate that a given field has not been marked + * with a privacy policy. + */ + INCIDENT_REPORT_PRIVACY_POLICY_UNSET = 255 +}; + +/** + * Allocate and initialize an AIncidentReportArgs object. + */ +AIncidentReportArgs* AIncidentReportArgs_init(); + +/** + * Duplicate an existing AIncidentReportArgs object. + */ +AIncidentReportArgs* AIncidentReportArgs_clone(AIncidentReportArgs* that); + +/** + * Clean up and delete an AIncidentReportArgs object. + */ +void AIncidentReportArgs_delete(AIncidentReportArgs* args); + +/** + * Set this incident report to include all sections. + */ +void AIncidentReportArgs_setAll(AIncidentReportArgs* args, bool all); + +/** + * Set this incident report privacy policy spec. + */ +void AIncidentReportArgs_setPrivacyPolicy(AIncidentReportArgs* args, int privacyPolicy); + +/** + * Add this section to the incident report. The section IDs are the field numbers + * from the android.os.IncidentProto protobuf message. + */ +void AIncidentReportArgs_addSection(AIncidentReportArgs* args, int section); + +/** + * Set the apk package name that will be sent a broadcast when the incident + * report completes. Must be called in conjunction with AIncidentReportArgs_setReceiverClass. + */ +void AIncidentReportArgs_setReceiverPackage(AIncidentReportArgs* args, char const* pkg); + +/** + * Set the fully qualified class name of the java BroadcastReceiver class that will be + * sent a broadcast when the report completes. Must be called in conjunction with + * AIncidentReportArgs_setReceiverPackage. + */ +void AIncidentReportArgs_setReceiverClass(AIncidentReportArgs* args, char const* cls); + +/** + * Add protobuf data as a header to the incident report. The buffer should be a serialized + * android.os.IncidentHeaderProto object. + */ +void AIncidentReportArgs_addHeader(AIncidentReportArgs* args, uint8_t const* buf, size_t size); + +/** + * Initiate taking the report described in the args object. Returns 0 on success, + * and non-zero otherwise. + */ +int AIncidentReportArgs_takeReport(AIncidentReportArgs* args); + +#if __cplusplus +} // extern "C" + +namespace android { +namespace os { + +class IncidentReportRequest { +public: + inline IncidentReportRequest() { + mImpl = AIncidentReportArgs_init(); + } + + inline IncidentReportRequest(const IncidentReportRequest& that) { + mImpl = AIncidentReportArgs_clone(that.mImpl); + } + + inline ~IncidentReportRequest() { + AIncidentReportArgs_delete(mImpl); + } + + inline AIncidentReportArgs* getImpl() { + return mImpl; + } + + inline void setAll(bool all) { + AIncidentReportArgs_setAll(mImpl, all); + } + + inline void setPrivacyPolicy(int privacyPolicy) { + AIncidentReportArgs_setPrivacyPolicy(mImpl, privacyPolicy); + } + + inline void addSection(int section) { + AIncidentReportArgs_addSection(mImpl, section); + } + + inline void setReceiverPackage(const std::string& pkg) { + AIncidentReportArgs_setReceiverPackage(mImpl, pkg.c_str()); + }; + + inline void setReceiverClass(const std::string& cls) { + AIncidentReportArgs_setReceiverClass(mImpl, cls.c_str()); + }; + + inline void addHeader(const std::vector<uint8_t>& headerProto) { + AIncidentReportArgs_addHeader(mImpl, headerProto.data(), headerProto.size()); + }; + + inline void addHeader(const uint8_t* buf, size_t size) { + AIncidentReportArgs_addHeader(mImpl, buf, size); + }; + + // returns a status_t + inline int takeReport() { + return AIncidentReportArgs_takeReport(mImpl); + } + +private: + AIncidentReportArgs* mImpl; +}; + +} // namespace os +} // namespace android + +#endif // __cplusplus + +#endif // ANDROID_INCIDENT_INCIDENT_REPORT_H diff --git a/libs/incident/include/android/os/IncidentReportArgs.h b/libs/incident/include_priv/android/os/IncidentReportArgs.h index 94b4ad6eae31..0e6159032e45 100644 --- a/libs/incident/include/android/os/IncidentReportArgs.h +++ b/libs/incident/include_priv/android/os/IncidentReportArgs.h @@ -14,9 +14,10 @@ * limitations under the License. */ -#ifndef ANDROID_OS_DUMPSTATE_ARGS_H_ -#define ANDROID_OS_DUMPSTATE_ARGS_H_ +#ifndef ANDROID_OS_INCIDENT_REPORT_ARGS_H +#define ANDROID_OS_INCIDENT_REPORT_ARGS_H +#include <binder/IServiceManager.h> #include <binder/Parcel.h> #include <binder/Parcelable.h> #include <utils/String16.h> @@ -29,7 +30,8 @@ namespace os { using namespace std; -// DESTINATION enum value, sync with frameworks/base/core/proto/android/privacy.proto +// DESTINATION enum value, sync with frameworks/base/core/proto/android/privacy.proto, +// incident/incident_report.h and IncidentReportArgs.java const uint8_t PRIVACY_POLICY_LOCAL = 0; const uint8_t PRIVACY_POLICY_EXPLICIT = 100; const uint8_t PRIVACY_POLICY_AUTOMATIC = 200; @@ -74,4 +76,4 @@ private: } } -#endif // ANDROID_OS_DUMPSTATE_ARGS_H_ +#endif // ANDROID_OS_INCIDENT_REPORT_ARGS_H diff --git a/libs/incident/libincident.map.txt b/libs/incident/libincident.map.txt new file mode 100644 index 000000000000..f157763f1a03 --- /dev/null +++ b/libs/incident/libincident.map.txt @@ -0,0 +1,15 @@ +LIBINCIDENT { + global: + AIncidentReportArgs_init; # apex # introduced=30 + AIncidentReportArgs_clone; # apex # introduced=30 + AIncidentReportArgs_delete; # apex # introduced=30 + AIncidentReportArgs_setAll; # apex # introduced=30 + AIncidentReportArgs_setPrivacyPolicy; # apex # introduced=30 + AIncidentReportArgs_addSection; # apex # introduced=30 + AIncidentReportArgs_setReceiverPackage; # apex # introduced=30 + AIncidentReportArgs_setReceiverClass; # apex # introduced=30 + AIncidentReportArgs_addHeader; # apex # introduced=30 + AIncidentReportArgs_takeReport; # apex # introduced=30 + local: + *; +}; diff --git a/libs/incident/src/incident_report.cpp b/libs/incident/src/incident_report.cpp new file mode 100644 index 000000000000..7897ddf6d251 --- /dev/null +++ b/libs/incident/src/incident_report.cpp @@ -0,0 +1,83 @@ +/** + * 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. + */ + +#define LOG_TAG "libincident" + +#include <incident/incident_report.h> + +#include <android/os/IIncidentManager.h> +#include <android/os/IncidentReportArgs.h> +#include <binder/IServiceManager.h> +#include <binder/Status.h> +#include <log/log.h> + +using android::sp; +using android::binder::Status; +using android::os::IncidentReportArgs; +using android::os::IIncidentManager; +using std::string; +using std::vector; + +AIncidentReportArgs* AIncidentReportArgs_init() { + return reinterpret_cast<AIncidentReportArgs*>(new IncidentReportArgs()); +} + +AIncidentReportArgs* AIncidentReportArgs_clone(AIncidentReportArgs* that) { + return reinterpret_cast<AIncidentReportArgs*>( + new IncidentReportArgs(*reinterpret_cast<IncidentReportArgs*>(that))); +} + +void AIncidentReportArgs_delete(AIncidentReportArgs* args) { + delete reinterpret_cast<IncidentReportArgs*>(args); +} + +void AIncidentReportArgs_setAll(AIncidentReportArgs* args, bool all) { + reinterpret_cast<IncidentReportArgs*>(args)->setAll(all); +} + +void AIncidentReportArgs_setPrivacyPolicy(AIncidentReportArgs* args, int privacyPolicy) { + reinterpret_cast<IncidentReportArgs*>(args)->setPrivacyPolicy(privacyPolicy); +} + +void AIncidentReportArgs_addSection(AIncidentReportArgs* args, int section) { + reinterpret_cast<IncidentReportArgs*>(args)->addSection(section); +} + +void AIncidentReportArgs_setReceiverPackage(AIncidentReportArgs* args, char const* pkg) { + reinterpret_cast<IncidentReportArgs*>(args)->setReceiverPkg(string(pkg)); +} + +void AIncidentReportArgs_setReceiverClass(AIncidentReportArgs* args, char const* cls) { + reinterpret_cast<IncidentReportArgs*>(args)->setReceiverCls(string(cls)); +} + +void AIncidentReportArgs_addHeader(AIncidentReportArgs* args, uint8_t const* buf, size_t size) { + vector<uint8_t> vec(buf, buf+size); + reinterpret_cast<IncidentReportArgs*>(args)->addHeader(vec); +} + +int AIncidentReportArgs_takeReport(AIncidentReportArgs* argp) { + IncidentReportArgs* args = reinterpret_cast<IncidentReportArgs*>(argp); + + sp<IIncidentManager> service = android::interface_cast<IIncidentManager>( + android::defaultServiceManager()->getService(android::String16("incident"))); + if (service == nullptr) { + ALOGW("Failed to fetch incident service."); + return false; + } + Status s = service->reportIncident(*args); + return s.transactionError(); +} diff --git a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp b/libs/incident/tests/IncidentReportArgs_test.cpp index 38bc19452afa..224b343c554a 100644 --- a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp +++ b/libs/incident/tests/IncidentReportArgs_test.cpp @@ -20,6 +20,8 @@ namespace android { namespace os { namespace statsd { +// Checks that all of the inline methods on IncidentReportRequest and the real C functions +// result in a working IncidentReportArgs. TEST(IncidentReportArgsTest, testSerialization) { IncidentReportArgs args; args.setAll(0); diff --git a/libs/incident/tests/IncidentReportRequest_test.cpp b/libs/incident/tests/IncidentReportRequest_test.cpp new file mode 100644 index 000000000000..6d218b6682a3 --- /dev/null +++ b/libs/incident/tests/IncidentReportRequest_test.cpp @@ -0,0 +1,65 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <android/os/IncidentReportArgs.h> +#include <incident/incident_report.h> + +#include <gtest/gtest.h> + +namespace android { +namespace os { +namespace statsd { + +TEST(IncidentReportRequestTest, testWrite) { + IncidentReportRequest request; + request.setAll(0); + request.addSection(1000); + request.addSection(1001); + + vector<uint8_t> header1; + header1.push_back(0x1); + header1.push_back(0x2); + vector<uint8_t> header2; + header1.push_back(0x22); + header1.push_back(0x33); + + request.addHeader(header1); + request.addHeader(header2); + + request.setPrivacyPolicy(1); + + request.setReceiverPackage("com.android.os"); + request.setReceiverClass("com.android.os.Receiver"); + + IncidentReportArgs* args = reinterpret_cast<IncidentReportArgs*>(request.getImpl()); + + EXPECT_EQ(0, args->all()); + set<int> sections; + sections.insert(1000); + sections.insert(1001); + EXPECT_EQ(sections, args->sections()); + EXPECT_EQ(1, args->getPrivacyPolicy()); + + EXPECT_EQ(string("com.android.os"), args->receiverPkg()); + EXPECT_EQ(string("com.android.os.Receiver"), args->receiverCls()); + + vector<vector<uint8_t>> headers; + headers.push_back(header1); + headers.push_back(header2); + EXPECT_EQ(headers, args->headers()); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/libs/incident/tests/c_api_compile_test.c b/libs/incident/tests/c_api_compile_test.c new file mode 100644 index 000000000000..e1620dfe3280 --- /dev/null +++ b/libs/incident/tests/c_api_compile_test.c @@ -0,0 +1,11 @@ +#include <stdio.h> +#include <incident/incident_report.h> + +/* + * This file ensures that incident/incident_report.h actually compiles with C, + * since there is no other place in the tree that actually uses it from C. + */ +int not_called() { + return 0; +} + diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index e4348f2a9b21..3b494e9129db 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -251,19 +251,24 @@ void PointerController::unfade(Transition transition) { void PointerController::setPresentation(Presentation presentation) { AutoMutex _l(mLock); - if (presentation == PRESENTATION_POINTER && mLocked.additionalMouseResources.empty()) { - mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, - &mLocked.animationResources, mLocked.viewport.displayId); + if (mLocked.presentation == presentation) { + return; } - if (mLocked.presentation != presentation) { - mLocked.presentation = presentation; - mLocked.presentationChanged = true; + mLocked.presentation = presentation; + mLocked.presentationChanged = true; - if (presentation != PRESENTATION_SPOT) { - fadeOutAndReleaseAllSpotsLocked(); - } + if (!mLocked.viewport.isValid()) { + return; + } + if (presentation == PRESENTATION_POINTER) { + if (mLocked.additionalMouseResources.empty()) { + mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, + &mLocked.animationResources, + mLocked.viewport.displayId); + } + fadeOutAndReleaseAllSpotsLocked(); updatePointerLocked(); } } @@ -285,6 +290,9 @@ void PointerController::setSpots(const PointerCoords* spotCoords, #endif AutoMutex _l(mLock); + if (!mLocked.viewport.isValid()) { + return; + } std::vector<Spot*> newSpots; std::map<int32_t, std::vector<Spot*>>::const_iterator iter = @@ -331,6 +339,9 @@ void PointerController::clearSpots() { #endif AutoMutex _l(mLock); + if (!mLocked.viewport.isValid()) { + return; + } fadeOutAndReleaseAllSpotsLocked(); } @@ -752,6 +763,10 @@ void PointerController::fadeOutAndReleaseAllSpotsLocked() { } void PointerController::loadResourcesLocked() REQUIRES(mLock) { + if (!mLocked.viewport.isValid()) { + return; + } + mPolicy->loadPointerResources(&mResources, mLocked.viewport.displayId); mPolicy->loadPointerIcon(&mLocked.pointerIcon, mLocked.viewport.displayId); diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index b36406d6a703..a15742671dc7 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -39,8 +39,8 @@ enum TestCursorType { using ::testing::AllOf; using ::testing::Field; -using ::testing::NiceMock; using ::testing::Mock; +using ::testing::NiceMock; using ::testing::Return; using ::testing::Test; @@ -57,12 +57,20 @@ public: virtual int32_t getDefaultPointerIconId() override; virtual int32_t getCustomPointerIconId() override; + bool allResourcesAreLoaded(); + bool noResourcesAreLoaded(); + private: void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType); + + bool pointerIconLoaded{false}; + bool pointerResourcesLoaded{false}; + bool additionalMouseResourcesLoaded{false}; }; void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) { loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT); + pointerIconLoaded = true; } void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources, @@ -70,6 +78,7 @@ void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER); loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH); loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR); + pointerResourcesLoaded = true; } void MockPointerControllerPolicyInterface::loadAdditionalMouseResources( @@ -91,6 +100,8 @@ void MockPointerControllerPolicyInterface::loadAdditionalMouseResources( anim.durationPerFrame = 10; (*outResources)[cursorType] = icon; (*outAnimationResources)[cursorType] = anim; + + additionalMouseResourcesLoaded = true; } int32_t MockPointerControllerPolicyInterface::getDefaultPointerIconId() { @@ -101,18 +112,27 @@ int32_t MockPointerControllerPolicyInterface::getCustomPointerIconId() { return CURSOR_TYPE_CUSTOM; } +bool MockPointerControllerPolicyInterface::allResourcesAreLoaded() { + return pointerIconLoaded && pointerResourcesLoaded && additionalMouseResourcesLoaded; +} + +bool MockPointerControllerPolicyInterface::noResourcesAreLoaded() { + return !(pointerIconLoaded || pointerResourcesLoaded || additionalMouseResourcesLoaded); +} + void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* icon, int32_t type) { icon->style = type; std::pair<float, float> hotSpot = getHotSpotCoordinatesForType(type); icon->hotSpotX = hotSpot.first; icon->hotSpotY = hotSpot.second; } - class PointerControllerTest : public Test { protected: PointerControllerTest(); ~PointerControllerTest(); + void ensureDisplayViewportIsSet(); + sp<MockSprite> mPointerSprite; sp<MockPointerControllerPolicyInterface> mPolicy; sp<MockSpriteController> mSpriteController; @@ -141,7 +161,14 @@ PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<Moc .WillOnce(Return(mPointerSprite)); mPointerController = new PointerController(mPolicy, mLooper, mSpriteController); +} + +PointerControllerTest::~PointerControllerTest() { + mRunning.store(false, std::memory_order_relaxed); + mThread.join(); +} +void PointerControllerTest::ensureDisplayViewportIsSet() { DisplayViewport viewport; viewport.displayId = ADISPLAY_ID_DEFAULT; viewport.logicalRight = 1600; @@ -151,11 +178,9 @@ PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<Moc viewport.deviceWidth = 400; viewport.deviceHeight = 300; mPointerController->setDisplayViewport(viewport); -} -PointerControllerTest::~PointerControllerTest() { - mRunning.store(false, std::memory_order_relaxed); - mThread.join(); + // The first call to setDisplayViewport should trigger the loading of the necessary resources. + EXPECT_TRUE(mPolicy->allResourcesAreLoaded()); } void PointerControllerTest::loopThread() { @@ -167,6 +192,7 @@ void PointerControllerTest::loopThread() { } TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) { + ensureDisplayViewportIsSet(); mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); std::pair<float, float> hotspot = getHotSpotCoordinatesForType(CURSOR_TYPE_DEFAULT); @@ -181,6 +207,7 @@ TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) { } TEST_F(PointerControllerTest, updatePointerIcon) { + ensureDisplayViewportIsSet(); mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); int32_t type = CURSOR_TYPE_ADDITIONAL; @@ -196,6 +223,7 @@ TEST_F(PointerControllerTest, updatePointerIcon) { } TEST_F(PointerControllerTest, setCustomPointerIcon) { + ensureDisplayViewportIsSet(); mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); int32_t style = CURSOR_TYPE_CUSTOM; @@ -217,4 +245,18 @@ TEST_F(PointerControllerTest, setCustomPointerIcon) { mPointerController->setCustomPointerIcon(icon); } +TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) { + mPointerController->setPresentation(PointerController::PRESENTATION_POINTER); + mPointerController->setSpots(nullptr, nullptr, BitSet32(), -1); + mPointerController->clearSpots(); + mPointerController->setPosition(1.0f, 1.0f); + mPointerController->move(1.0f, 1.0f); + mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); + mPointerController->fade(PointerController::TRANSITION_IMMEDIATE); + + EXPECT_TRUE(mPolicy->noResourcesAreLoaded()); + + ensureDisplayViewportIsSet(); +} + } // namespace android diff --git a/libs/services/Android.bp b/libs/services/Android.bp index 9b047ca22d19..1e621079b532 100644 --- a/libs/services/Android.bp +++ b/libs/services/Android.bp @@ -20,7 +20,6 @@ cc_library_shared { ":IDropBoxManagerService.aidl", "src/content/ComponentName.cpp", "src/os/DropBoxManager.cpp", - "src/os/StatsDimensionsValue.cpp", ], shared_libs: [ diff --git a/libs/services/include/android/os/StatsDimensionsValue.h b/libs/services/include/android/os/StatsDimensionsValue.h deleted file mode 100644 index cc0b05644f2c..000000000000 --- a/libs/services/include/android/os/StatsDimensionsValue.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef STATS_DIMENSIONS_VALUE_H -#define STATS_DIMENSIONS_VALUE_H - -#include <binder/Parcel.h> -#include <binder/Parcelable.h> -#include <binder/Status.h> -#include <utils/String16.h> -#include <vector> - -namespace android { -namespace os { - -// Represents a parcelable object. Used to send data from statsd to StatsCompanionService.java. -class StatsDimensionsValue : public android::Parcelable { -public: - StatsDimensionsValue(); - - StatsDimensionsValue(int32_t field, String16 value); - StatsDimensionsValue(int32_t field, int32_t value); - StatsDimensionsValue(int32_t field, int64_t value); - StatsDimensionsValue(int32_t field, bool value); - StatsDimensionsValue(int32_t field, float value); - StatsDimensionsValue(int32_t field, std::vector<StatsDimensionsValue> value); - - virtual ~StatsDimensionsValue(); - - virtual android::status_t writeToParcel(android::Parcel* out) const override; - virtual android::status_t readFromParcel(const android::Parcel* in) override; - -private: - // Keep constants in sync with android/os/StatsDimensionsValue.java - // and stats_log.proto's DimensionValue. - static const int kStrValueType = 2; - static const int kIntValueType = 3; - static const int kLongValueType = 4; - static const int kBoolValueType = 5; - static const int kFloatValueType = 6; - static const int kTupleValueType = 7; - - int32_t mField; - int32_t mValueType; - - // This isn't very clever, but it isn't used for long-term storage, so it'll do. - String16 mStrValue; - int32_t mIntValue; - int64_t mLongValue; - bool mBoolValue; - float mFloatValue; - std::vector<StatsDimensionsValue> mTupleValue; -}; - -} // namespace os -} // namespace android - -#endif // STATS_DIMENSIONS_VALUE_H diff --git a/libs/services/src/os/StatsDimensionsValue.cpp b/libs/services/src/os/StatsDimensionsValue.cpp deleted file mode 100644 index 0052e0baa905..000000000000 --- a/libs/services/src/os/StatsDimensionsValue.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "StatsDimensionsValue" - -#include "android/os/StatsDimensionsValue.h" - -#include <cutils/log.h> - -using android::Parcel; -using android::Parcelable; -using android::status_t; -using std::vector; - -namespace android { -namespace os { - -StatsDimensionsValue::StatsDimensionsValue() {}; - -StatsDimensionsValue::StatsDimensionsValue(int32_t field, String16 value) : - mField(field), - mValueType(kStrValueType), - mStrValue(value) { -} -StatsDimensionsValue::StatsDimensionsValue(int32_t field, int32_t value) : - mField(field), - mValueType(kIntValueType), - mIntValue(value) { -} -StatsDimensionsValue::StatsDimensionsValue(int32_t field, int64_t value) : - mField(field), - mValueType(kLongValueType), - mLongValue(value) { -} -StatsDimensionsValue::StatsDimensionsValue(int32_t field, bool value) : - mField(field), - mValueType(kBoolValueType), - mBoolValue(value) { -} -StatsDimensionsValue::StatsDimensionsValue(int32_t field, float value) : - mField(field), - mValueType(kFloatValueType), - mFloatValue(value) { -} -StatsDimensionsValue::StatsDimensionsValue(int32_t field, vector<StatsDimensionsValue> value) : - mField(field), - mValueType(kTupleValueType), - mTupleValue(value) { -} - -StatsDimensionsValue::~StatsDimensionsValue() {} - -status_t -StatsDimensionsValue::writeToParcel(Parcel* out) const { - status_t err ; - - err = out->writeInt32(mField); - if (err != NO_ERROR) { - return err; - } - err = out->writeInt32(mValueType); - if (err != NO_ERROR) { - return err; - } - switch (mValueType) { - case kStrValueType: - err = out->writeString16(mStrValue); - break; - case kIntValueType: - err = out->writeInt32(mIntValue); - break; - case kLongValueType: - err = out->writeInt64(mLongValue); - break; - case kBoolValueType: - err = out->writeBool(mBoolValue); - break; - case kFloatValueType: - err = out->writeFloat(mFloatValue); - break; - case kTupleValueType: - { - int sz = mTupleValue.size(); - err = out->writeInt32(sz); - if (err != NO_ERROR) { - return err; - } - for (int i = 0; i < sz; ++i) { - err = mTupleValue[i].writeToParcel(out); - if (err != NO_ERROR) { - return err; - } - } - } - break; - default: - err = UNKNOWN_ERROR; - break; - } - return err; -} - -status_t -StatsDimensionsValue::readFromParcel(const Parcel* in) -{ - // Implement me if desired. We don't currently use this. - ALOGE("Cannot do c++ StatsDimensionsValue.readFromParcel(); it is not implemented."); - (void)in; // To prevent compile error of unused parameter 'in' - return UNKNOWN_ERROR; -} - -} // namespace os -} // namespace android diff --git a/location/java/android/location/GnssRequest.aidl b/location/java/android/location/GnssRequest.aidl new file mode 100644 index 000000000000..581abcce6651 --- /dev/null +++ b/location/java/android/location/GnssRequest.aidl @@ -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. + */ + +package android.location; + +/** + * @hide + */ +parcelable GnssRequest; diff --git a/location/java/android/location/GnssRequest.java b/location/java/android/location/GnssRequest.java new file mode 100644 index 000000000000..2afb265f638c --- /dev/null +++ b/location/java/android/location/GnssRequest.java @@ -0,0 +1,147 @@ +/* + * 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.location; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class contains extra parameters to pass to a GNSS provider implementation. + * @hide + */ +@SystemApi +public final class GnssRequest implements Parcelable { + private final boolean mFullTracking; + + /** + * Creates a {@link GnssRequest} with a full list of parameters. + */ + private GnssRequest(boolean fullTracking) { + mFullTracking = fullTracking; + } + + /** + * Represents whether to enable full GNSS tracking. + * + * <p>If true, GNSS chipset switches off duty cycling. In such a mode, no clock + * discontinuities are expected, and when supported, carrier phase should be continuous in + * good signal conditions. All non-blacklisted, healthy constellations, satellites and + * frequency bands that the chipset supports must be reported in this mode. The GNSS chipset + * is allowed to consume more power in this mode. If false, GNSS chipset optimizes power via + * duty cycling, constellations and frequency limits, etc. + */ + public boolean isFullTracking() { + return mFullTracking; + } + + @NonNull + public static final Creator<GnssRequest> CREATOR = + new Creator<GnssRequest>() { + @Override + @NonNull + public GnssRequest createFromParcel(@NonNull Parcel parcel) { + return new GnssRequest(parcel.readBoolean()); + } + + @Override + public GnssRequest[] newArray(int i) { + return new GnssRequest[i]; + } + }; + + @NonNull + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("GnssRequest["); + s.append("FullTracking=").append(mFullTracking); + s.append(']'); + return s.toString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof GnssRequest)) return false; + + GnssRequest other = (GnssRequest) obj; + if (mFullTracking != other.mFullTracking) return false; + + return true; + } + + @Override + public int hashCode() { + return mFullTracking ? 1 : 0; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeBoolean(mFullTracking); + } + + /** Builder for {@link GnssRequest} */ + public static final class Builder { + private boolean mFullTracking; + + /** + * Constructs a {@link Builder} instance. + */ + public Builder() { + } + + /** + * Constructs a {@link Builder} instance by copying a {@link GnssRequest}. + */ + public Builder(@NonNull GnssRequest request) { + mFullTracking = request.isFullTracking(); + } + + /** + * Set the value of whether to enable full GNSS tracking, which is false by default. + * + * <p>If true, GNSS chipset switches off duty cycling. In such a mode, no clock + * discontinuities are expected, and when supported, carrier phase should be continuous in + * good signal conditions. All non-blacklisted, healthy constellations, satellites and + * frequency bands that the chipset supports must be reported in this mode. The GNSS chipset + * is allowed to consume more power in this mode. If false, GNSS chipset optimizes power via + * duty cycling, constellations and frequency limits, etc. + * + * <p>Full tracking requests always override non-full tracking requests. If any full + * tracking request occurs, all listeners on the device will receive full tracking GNSS + * measurements. + */ + @NonNull public Builder setFullTracking(boolean value) { + mFullTracking = value; + return this; + } + + /** Builds a {@link GnssRequest} instance as specified by this builder. */ + @NonNull + public GnssRequest build() { + return new GnssRequest(mFullTracking); + } + } +} diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 197787e5b6e6..7e6486cc933e 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -33,6 +33,9 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.app.AlarmManager; import android.app.PendingIntent; +import android.compat.Compatibility; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; @@ -50,7 +53,6 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.util.ArrayMap; -import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.location.ProviderProperties; @@ -82,6 +84,36 @@ public class LocationManager { private static final String TAG = "LocationManager"; /** + * For apps targeting Android K and above, supplied {@link PendingIntent}s must be targeted to a + * specific package. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.JELLY_BEAN) + public static final long TARGETED_PENDING_INTENT = 148963590L; + + /** + * For apps targeting Android K and above, incomplete locations may not be passed to + * {@link #setTestProviderLocation}. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.JELLY_BEAN) + private static final long INCOMPLETE_LOCATION = 148964793L; + + /** + * For apps targeting Android S and above, all {@link GpsStatus} API usage must be replaced with + * {@link GnssStatus} APIs. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + private static final long GPS_STATUS_USAGE = 144027538L; + + /** * Name of the network location provider. * * <p>This provider determines location based on nearby of cell tower and WiFi access points. @@ -771,7 +803,6 @@ public class LocationManager { public void requestSingleUpdate(@NonNull String provider, @NonNull PendingIntent pendingIntent) { Preconditions.checkArgument(provider != null, "invalid null provider"); - checkPendingIntent(pendingIntent); LocationRequest request = LocationRequest.createFromDeprecatedProvider( provider, 0, 0, true); @@ -800,7 +831,6 @@ public class LocationManager { public void requestSingleUpdate(@NonNull Criteria criteria, @NonNull PendingIntent pendingIntent) { Preconditions.checkArgument(criteria != null, "invalid null criteria"); - checkPendingIntent(pendingIntent); LocationRequest request = LocationRequest.createFromDeprecatedCriteria( criteria, 0, 0, true); @@ -1021,7 +1051,6 @@ public class LocationManager { public void requestLocationUpdates(@NonNull String provider, long minTimeMs, float minDistanceM, @NonNull PendingIntent pendingIntent) { Preconditions.checkArgument(provider != null, "invalid null provider"); - checkPendingIntent(pendingIntent); LocationRequest request = LocationRequest.createFromDeprecatedProvider( provider, minTimeMs, minDistanceM, false); @@ -1048,7 +1077,6 @@ public class LocationManager { public void requestLocationUpdates(long minTimeMs, float minDistanceM, @NonNull Criteria criteria, @NonNull PendingIntent pendingIntent) { Preconditions.checkArgument(criteria != null, "invalid null criteria"); - checkPendingIntent(pendingIntent); LocationRequest request = LocationRequest.createFromDeprecatedCriteria( criteria, minTimeMs, minDistanceM, false); @@ -1164,9 +1192,9 @@ public class LocationManager { @NonNull PendingIntent pendingIntent) { Preconditions.checkArgument(locationRequest != null, "invalid null location request"); Preconditions.checkArgument(pendingIntent != null, "invalid null pending intent"); - if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN) { + if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) { Preconditions.checkArgument(pendingIntent.isTargetedToPackage(), - "pending intent must be targeted to package"); + "pending intent must be targeted to a package"); } try { @@ -1198,15 +1226,9 @@ public class LocationManager { */ @RequiresPermission(allOf = {LOCATION_HARDWARE, ACCESS_FINE_LOCATION}) public boolean injectLocation(@NonNull Location location) { - if (location == null) { - IllegalArgumentException e = new IllegalArgumentException("invalid null location"); - if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R) { - throw e; - } else { - Log.w(TAG, e); - return false; - } - } + Preconditions.checkArgument(location != null, "invalid null location"); + Preconditions.checkArgument(location.isComplete(), + "incomplete location object, missing timestamp or accuracy?"); try { return mService.injectLocation(location); @@ -1487,15 +1509,11 @@ public class LocationManager { Preconditions.checkArgument(provider != null, "invalid null provider"); Preconditions.checkArgument(location != null, "invalid null location"); - if (!location.isComplete()) { - IllegalArgumentException e = new IllegalArgumentException( - "Incomplete location object, missing timestamp or accuracy? " + location); - if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN) { - Log.w(TAG, e); - location.makeComplete(); - } else { - throw e; - } + if (Compatibility.isChangeEnabled(INCOMPLETE_LOCATION)) { + Preconditions.checkArgument(location.isComplete(), + "incomplete location object, missing timestamp or accuracy?"); + } else { + location.makeComplete(); } try { @@ -1629,7 +1647,11 @@ public class LocationManager { @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) public void addProximityAlert(double latitude, double longitude, float radius, long expiration, @NonNull PendingIntent intent) { - checkPendingIntent(intent); + Preconditions.checkArgument(intent != null, "invalid null pending intent"); + if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) { + Preconditions.checkArgument(intent.isTargetedToPackage(), + "pending intent must be targeted to a package"); + } if (expiration < 0) expiration = Long.MAX_VALUE; Geofence fence = Geofence.createCircle(latitude, longitude, radius); @@ -1659,7 +1681,11 @@ public class LocationManager { * permission is not present */ public void removeProximityAlert(@NonNull PendingIntent intent) { - checkPendingIntent(intent); + Preconditions.checkArgument(intent != null, "invalid null pending intent"); + if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) { + Preconditions.checkArgument(intent.isTargetedToPackage(), + "pending intent must be targeted to a package"); + } try { mService.removeGeofence(null, intent, mContext.getPackageName()); @@ -1709,8 +1735,13 @@ public class LocationManager { @NonNull LocationRequest request, @NonNull Geofence fence, @NonNull PendingIntent intent) { - checkPendingIntent(intent); + Preconditions.checkArgument(request != null, "invalid null location request"); Preconditions.checkArgument(fence != null, "invalid null geofence"); + Preconditions.checkArgument(intent != null, "invalid null pending intent"); + if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) { + Preconditions.checkArgument(intent.isTargetedToPackage(), + "pending intent must be targeted to a package"); + } try { mService.requestGeofence(request, fence, intent, mContext.getPackageName(), @@ -1737,8 +1768,12 @@ public class LocationManager { * @hide */ public void removeGeofence(@NonNull Geofence fence, @NonNull PendingIntent intent) { - checkPendingIntent(intent); Preconditions.checkArgument(fence != null, "invalid null geofence"); + Preconditions.checkArgument(intent != null, "invalid null pending intent"); + if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) { + Preconditions.checkArgument(intent.isTargetedToPackage(), + "pending intent must be targeted to a package"); + } try { mService.removeGeofence(fence, intent, mContext.getPackageName()); @@ -1759,7 +1794,11 @@ public class LocationManager { * @hide */ public void removeAllGeofences(@NonNull PendingIntent intent) { - checkPendingIntent(intent); + Preconditions.checkArgument(intent != null, "invalid null pending intent"); + if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) { + Preconditions.checkArgument(intent.isTargetedToPackage(), + "pending intent must be targeted to a package"); + } try { mService.removeGeofence(null, intent, mContext.getPackageName()); @@ -1833,14 +1872,15 @@ public class LocationManager { * @param status object containing GPS status details, or null. * @return status object containing updated GPS status. * - * @deprecated GpsStatus APIs are deprecated, use {@link GnssStatus} APIs instead. + * @deprecated GpsStatus APIs are deprecated, use {@link GnssStatus} APIs instead. No longer + * supported in apps targeting S and above. */ @Deprecated @RequiresPermission(ACCESS_FINE_LOCATION) public @Nullable GpsStatus getGpsStatus(@Nullable GpsStatus status) { - if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.R) { + if (Compatibility.isChangeEnabled(GPS_STATUS_USAGE)) { throw new UnsupportedOperationException( - "GpsStatus APIs not supported in S and above, use GnssStatus APIs instead"); + "GpsStatus APIs not supported, please use GnssStatus APIs instead"); } GnssStatus gnssStatus = mGnssStatusListenerManager.getGnssStatus(); @@ -1863,17 +1903,14 @@ public class LocationManager { * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present * * @deprecated use {@link #registerGnssStatusCallback(GnssStatus.Callback)} instead. No longer - * supported in apps targeting R and above. + * supported in apps targeting S and above. */ @Deprecated @RequiresPermission(ACCESS_FINE_LOCATION) public boolean addGpsStatusListener(GpsStatus.Listener listener) { - UnsupportedOperationException ex = new UnsupportedOperationException( - "GpsStatus APIs not supported in S and above, use GnssStatus APIs instead"); - if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.R) { - throw ex; - } else { - Log.w(TAG, ex); + if (Compatibility.isChangeEnabled(GPS_STATUS_USAGE)) { + throw new UnsupportedOperationException( + "GpsStatus APIs not supported, please use GnssStatus APIs instead"); } try { @@ -1889,16 +1926,13 @@ public class LocationManager { * @param listener GPS status listener object to remove * * @deprecated use {@link #unregisterGnssStatusCallback(GnssStatus.Callback)} instead. No longer - * supported in apps targeting R and above. + * supported in apps targeting S and above. */ @Deprecated public void removeGpsStatusListener(GpsStatus.Listener listener) { - UnsupportedOperationException ex = new UnsupportedOperationException( - "GpsStatus APIs not supported in S and above, use GnssStatus APIs instead"); - if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.R) { - throw ex; - } else { - Log.w(TAG, ex); + if (Compatibility.isChangeEnabled(GPS_STATUS_USAGE)) { + throw new UnsupportedOperationException( + "GpsStatus APIs not supported, please use GnssStatus APIs instead"); } try { @@ -2162,6 +2196,30 @@ public class LocationManager { } /** + * Registers a GNSS Measurement callback. + * + * @param request extra parameters to pass to GNSS measurement provider. For example, if {@link + * GnssRequest#isFullTrackingEnabled()} is true, GNSS chipset switches off duty + * cycling. + * @param executor the executor that the callback runs on. + * @param callback a {@link GnssMeasurementsEvent.Callback} object to register. + * @return {@code true} if the callback was added successfully, {@code false} otherwise. + * @throws IllegalArgumentException if request is null + * @throws IllegalArgumentException if executor is null + * @throws IllegalArgumentException if callback is null + * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present + * @hide + */ + @SystemApi + @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, LOCATION_HARDWARE}) + public boolean registerGnssMeasurementsCallback( + @NonNull GnssRequest request, + @NonNull @CallbackExecutor Executor executor, + @NonNull GnssMeasurementsEvent.Callback callback) { + throw new RuntimeException(); + } + + /** * Injects GNSS measurement corrections into the GNSS chipset. * * @param measurementCorrections a {@link GnssMeasurementCorrections} object with the GNSS @@ -2397,19 +2455,6 @@ public class LocationManager { } } - private void checkPendingIntent(PendingIntent pendingIntent) { - Preconditions.checkArgument(pendingIntent != null, "invalid null pending intent"); - if (!pendingIntent.isTargetedToPackage()) { - IllegalArgumentException e = new IllegalArgumentException( - "invalid pending intent - must be targeted to package"); - if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN) { - throw e; - } else { - Log.w(TAG, e); - } - } - } - private static class GetCurrentLocationTransport extends ILocationListener.Stub implements AlarmManager.OnAlarmListener { 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/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java index f3613d3665ca..42841d178401 100644 --- a/media/java/android/media/AudioRecordingConfiguration.java +++ b/media/java/android/media/AudioRecordingConfiguration.java @@ -18,6 +18,7 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; @@ -229,13 +230,19 @@ public final class AudioRecordingConfiguration implements Parcelable { * <p>This information is only available if the caller has the * {@link android.Manifest.permission.MODIFY_AUDIO_ROUTING} * permission. - * <br>The result is -1 without the permission. * @return the user id + * @throws SecurityException Thrown if the caller is missing the MODIFY_AUDIO_ROUTING permission * * @hide */ @SystemApi - public int getClientUid() { return mClientUid; } + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public int getClientUid() { + if (mClientUid == -1) { + throw new SecurityException("MODIFY_AUDIO_ROUTING permission is missing"); + } + return mClientUid; + } /** * Returns information about the audio input device used for this recording. 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/ExifInterface.java b/media/java/android/media/ExifInterface.java index 767b67b7e885..d2379757f226 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -1457,6 +1457,9 @@ public class ExifInterface { private int mRw2JpgFromRawOffset; private boolean mIsSupportedFile; private boolean mModified; + // XMP data can be contained as either part of the EXIF data (tag number 700), or as a + // separate data marker (a separate MARKER_APP1). + private boolean mXmpIsFromSeparateMarker; // Pattern to check non zero timestamp private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*"); @@ -2837,10 +2840,12 @@ public class ExifInterface { final long offset = start + IDENTIFIER_XMP_APP1.length; final byte[] value = Arrays.copyOfRange(bytes, IDENTIFIER_XMP_APP1.length, bytes.length); - + // TODO: check if ignoring separate XMP data when tag 700 already exists is + // valid. if (getAttribute(TAG_XMP) == null) { mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, new ExifAttribute( IFD_FORMAT_BYTE, value.length, offset, value)); + mXmpIsFromSeparateMarker = true; } } break; @@ -3445,11 +3450,24 @@ public class ExifInterface { } dataOutputStream.writeByte(MARKER_SOI); + // Remove XMP data if it is from a separate marker (IDENTIFIER_XMP_APP1, not + // IDENTIFIER_EXIF_APP1) + // Will re-add it later after the rest of the file is written + ExifAttribute xmpAttribute = null; + if (getAttribute(TAG_XMP) != null && mXmpIsFromSeparateMarker) { + xmpAttribute = (ExifAttribute) mAttributes[IFD_TYPE_PRIMARY].remove(TAG_XMP); + } + // Write EXIF APP1 segment dataOutputStream.writeByte(MARKER); dataOutputStream.writeByte(MARKER_APP1); writeExifSegment(dataOutputStream); + // Re-add previously removed XMP data. + if (xmpAttribute != null) { + mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, xmpAttribute); + } + byte[] bytes = new byte[4096]; while (true) { 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/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl index 194669c19018..9131f3bc960d 100644 --- a/media/java/android/media/IMediaRoute2Provider.aidl +++ b/media/java/android/media/IMediaRoute2Provider.aidl @@ -37,5 +37,4 @@ oneway interface IMediaRoute2Provider { void notifyControlRequestSent(String id, in Intent request); void requestSetVolume(String id, int volume); - void requestUpdateVolume(String id, int delta); } diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index dac0fba876be..6fef46889742 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -52,7 +52,6 @@ interface IMediaRouterService { void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route, in Intent request); void requestSetVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int volume); - void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction); void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route, int requestId, in @nullable Bundle sessionHints); @@ -70,8 +69,6 @@ interface IMediaRouterService { void requestSetVolume2Manager(IMediaRouter2Manager manager, in MediaRoute2Info route, int volume); - void requestUpdateVolume2Manager(IMediaRouter2Manager manager, - in MediaRoute2Info route, int direction); List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager); void selectClientRoute(IMediaRouter2Manager manager, diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index d0b5d486d19c..20a59bba54c6 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -46,13 +46,20 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * Base class for media route provider services. * <p> + * Media route provider services are used to publish {@link MediaRoute2Info media routes} such as + * speakers, TVs, etc. The routes are published by calling {@link #notifyRoutes(Collection)}. + * Media apps which use {@link MediaRouter2} can request to play their media on the routes. + * </p><p> + * When {@link MediaRouter2 media router} wants to play media on a route, + * {@link #onCreateSession(String, String, long, Bundle)} will be called to handle the request. + * A session can be considered as a group of currently selected routes for each connection. + * Create and manage the sessions by yourself, and notify the {@link RoutingSessionInfo + * session infos} when there are any changes. + * </p><p> * The system media router service will bind to media route provider services when a * {@link RouteDiscoveryPreference discovery preference} is registered via - * a {@link MediaRouter2 media router} by an application. - * </p><p> - * To implement your own media route provider service, extend this class and - * override {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} to publish - * {@link MediaRoute2Info routes}. + * a {@link MediaRouter2 media router} by an application. See + * {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} for the details. * </p> */ public abstract class MediaRoute2ProviderService extends Service { @@ -118,22 +125,15 @@ public abstract class MediaRoute2ProviderService extends Service { public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request); /** - * Called when requestSetVolume is called on a route of the provider + * Called when requestSetVolume is called on a route of the provider. * * @param routeId the id of the route * @param volume the target volume + * @see MediaRoute2Info#getVolumeMax() */ public abstract void onSetVolume(@NonNull String routeId, int volume); /** - * Called when requestUpdateVolume is called on a route of the provider - * - * @param routeId id of the route - * @param delta the delta to add to the current volume - */ - public abstract void onUpdateVolume(@NonNull String routeId, int delta); - - /** * Gets information of the session with the given id. * * @param sessionId id of the session @@ -520,14 +520,5 @@ public abstract class MediaRoute2ProviderService extends Service { mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetVolume, MediaRoute2ProviderService.this, routeId, volume)); } - - @Override - public void requestUpdateVolume(String routeId, int delta) { - if (!checkCallerisSystem()) { - return; - } - mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onUpdateVolume, - MediaRoute2ProviderService.this, routeId, delta)); - } } } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 515cabe5a5c9..64186106d2c8 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -462,31 +462,6 @@ public class MediaRouter2 { } } - /** - * Requests an incremental volume update for the route asynchronously. - * <p> - * It may have no effect if the route is currently not selected. - * </p> - * - * @param delta The delta to add to the current volume. - * @hide - */ - public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) { - Objects.requireNonNull(route, "route must not be null"); - - Client2 client; - synchronized (sRouterLock) { - client = mClient; - } - if (client != null) { - try { - mMediaRouterService.requestUpdateVolume2(client, route, delta); - } catch (RemoteException ex) { - Log.e(TAG, "Unable to send control request.", ex); - } - } - } - void addRoutesOnHandler(List<MediaRoute2Info> routes) { // TODO: When onRoutesAdded is first called, // 1) clear mRoutes before adding the routes @@ -496,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); } } @@ -512,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); } } @@ -528,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); } } @@ -668,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/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 662eeb1418f1..2c1fdab9da01 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -247,13 +247,23 @@ public class MediaRouter2Manager { Objects.requireNonNull(packageName, "packageName must not be null"); Objects.requireNonNull(route, "route must not be null"); + boolean transferred = false; + //TODO: instead of release all controllers, add an API to specify controllers that + // should be released (or is the system controller). for (RoutingController controller : getRoutingControllers(packageName)) { - if (controller.getSessionInfo().getTransferrableRoutes().contains(route.getId())) { + if (!transferred && controller.getSessionInfo().getTransferrableRoutes() + .contains(route.getId())) { controller.transferToRoute(route); - return; + transferred = true; + } else if (!controller.getSessionInfo().isSystemSession()) { + controller.release(); } } + if (transferred) { + return; + } + Client client; synchronized (sLock) { client = mClient; @@ -294,30 +304,6 @@ public class MediaRouter2Manager { } } - /** - * Requests an incremental volume update for the route asynchronously. - * <p> - * It may have no effect if the route is currently not selected. - * </p> - * - * @param delta The delta to add to the current volume. - */ - public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) { - Objects.requireNonNull(route, "route must not be null"); - - Client client; - synchronized (sLock) { - client = mClient; - } - if (client != null) { - try { - mMediaRouterService.requestUpdateVolume2Manager(client, route, delta); - } catch (RemoteException ex) { - Log.e(TAG, "Unable to send control request.", ex); - } - } - } - void addRoutesOnHandler(List<MediaRoute2Info> routes) { synchronized (mRoutesLock) { for (MediaRoute2Info route : routes) { 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/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 870c1b4a3558..486c0c25a737 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -266,8 +266,12 @@ public final class MediaSession { * playback after the session has been stopped. If your app is started in * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via * the pending intent. + * <p> + * The pending intent is recommended to be explicit to follow the security recommendation of + * {@link PendingIntent#getActivity}. * * @param mbr The {@link PendingIntent} to send the media button event to. + * @see PendingIntent#getActivity */ public void setMediaButtonReceiver(@Nullable PendingIntent mbr) { try { 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/TvContract.java b/media/java/android/media/tv/TvContract.java index 09b755912c43..433c6227cd5f 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -1109,6 +1109,24 @@ public final class TvContract { * <p>Type: TEXT */ String COLUMN_SERIES_ID = "series_id"; + + /** + * The split ID of this TV program for multi-part content, as a URI. + * + * <p>A content may consist of multiple programs within the same channel or over several + * channels. For example, a film might be divided into two parts interrupted by a news in + * the middle or a longer sport event might be split into several parts over several + * channels. The split ID is used to identify all the programs in the same multi-part + * content. Suitable URIs include + * <ul> + * <li>{@code crid://<CRIDauthority>/<data>#<IMI>} from ETSI TS 102 323 + * </ul> + * + * <p>Can be empty. + * + * <p>Type: TEXT + */ + String COLUMN_SPLIT_ID = "split_id"; } /** @@ -1677,6 +1695,7 @@ public final class TvContract { TYPE_ATSC_T, TYPE_ATSC_C, TYPE_ATSC_M_H, + TYPE_ATSC3_T, TYPE_ISDB_T, TYPE_ISDB_TB, TYPE_ISDB_S, @@ -1801,6 +1820,13 @@ public final class TvContract { public static final String TYPE_ATSC_M_H = "TYPE_ATSC_M_H"; /** + * The channel type for ATSC3.0 (terrestrial). + * + * @see #COLUMN_TYPE + */ + public static final String TYPE_ATSC3_T = "TYPE_ATSC3_T"; + + /** * The channel type for ISDB-T (terrestrial). * * @see #COLUMN_TYPE @@ -2022,6 +2048,7 @@ public final class TvContract { * {@link #TYPE_ATSC_C}, * {@link #TYPE_ATSC_M_H}, * {@link #TYPE_ATSC_T}, + * {@link #TYPE_ATSC3_T}, * {@link #TYPE_CMMB}, * {@link #TYPE_DTMB}, * {@link #TYPE_DVB_C}, @@ -2407,6 +2434,22 @@ public final class TvContract { */ public static final String COLUMN_TRANSIENT = "transient"; + /** + * The global content ID of this TV channel, as a URI. + * + * <p>A globally unique URI that identifies this TV channel, if applicable. Suitable URIs + * include + * <ul> + * <li>{@code globalServiceId} from ATSC A/331. ex {@code https://doi.org/10.5239/7E4E-B472} + * <li>Other broadcast ID provider. ex {@code http://example.com/tv_channel/1234} + * </ul> + * + * <p>Can be empty. + * + * <p>Type: TEXT + */ + public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id"; + private Channels() {} /** @@ -2562,6 +2605,37 @@ public final class TvContract { */ public static final String COLUMN_RECORDING_PROHIBITED = "recording_prohibited"; + /** + * The event ID of this TV program. + * + * <p>It is used to identify the current TV program in the same channel, if applicable. + * Use the same coding for {@code event_id} in the underlying broadcast standard if it + * is defined there (e.g. ATSC A/65, ETSI EN 300 468 and ARIB STD-B10). + * + * <p>This is a required field only if the underlying broadcast standard defines the same + * name field. Otherwise, leave empty. + * + * <p>Type: INTEGER + */ + public static final String COLUMN_EVENT_ID = "event_id"; + + /** + * The global content ID of this TV program, as a URI. + * + * <p>A globally unique ID that identifies this TV program, if applicable. Suitable URIs + * include + * <ul> + * <li>{@code crid://<CRIDauthority>/<data>} from ETSI TS 102 323 + * <li>{@code globalContentId} from ATSC A/332 + * <li>Other broadcast ID provider. ex {@code http://example.com/tv_program/1234} + * </ul> + * + * <p>Can be empty. + * + * <p>Type: TEXT + */ + public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id"; + private Programs() {} /** Canonical genres for TV programs. */ 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/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java index c1b17d3615bb..63de03344219 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java @@ -41,8 +41,7 @@ public class FrontendStatus { FRONTEND_STATUS_TYPE_MODULATION, FRONTEND_STATUS_TYPE_SPECTRAL, FRONTEND_STATUS_TYPE_LNB_VOLTAGE, FRONTEND_STATUS_TYPE_PLP_ID, FRONTEND_STATUS_TYPE_EWBS, FRONTEND_STATUS_TYPE_AGC, FRONTEND_STATUS_TYPE_LNA, - FRONTEND_STATUS_TYPE_LAYER_ERROR, FRONTEND_STATUS_TYPE_VBER_CN, - FRONTEND_STATUS_TYPE_LBER_CN, FRONTEND_STATUS_TYPE_XER_CN, FRONTEND_STATUS_TYPE_MER, + FRONTEND_STATUS_TYPE_LAYER_ERROR, FRONTEND_STATUS_TYPE_MER, FRONTEND_STATUS_TYPE_FREQ_OFFSET, FRONTEND_STATUS_TYPE_HIERARCHY, FRONTEND_STATUS_TYPE_RF_LOCK, FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO}) @Retention(RetentionPolicy.SOURCE) @@ -125,18 +124,6 @@ public class FrontendStatus { public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR = Constants.FrontendStatusType.LAYER_ERROR; /** - * CN value by VBER. - */ - public static final int FRONTEND_STATUS_TYPE_VBER_CN = Constants.FrontendStatusType.VBER_CN; - /** - * CN value by LBER. - */ - public static final int FRONTEND_STATUS_TYPE_LBER_CN = Constants.FrontendStatusType.LBER_CN; - /** - * CN value by XER. - */ - public static final int FRONTEND_STATUS_TYPE_XER_CN = Constants.FrontendStatusType.XER_CN; - /** * Modulation Error Ratio. */ public static final int FRONTEND_STATUS_TYPE_MER = Constants.FrontendStatusType.MER; @@ -223,9 +210,6 @@ public class FrontendStatus { private Integer mAgc; private Boolean mIsLnaOn; private boolean[] mIsLayerErrors; - private Integer mVberCn; - private Integer mLberCn; - private Integer mXerCn; private Integer mMer; private Integer mFreqOffset; private Integer mHierarchy; @@ -403,33 +387,6 @@ public class FrontendStatus { return mIsLayerErrors; } /** - * Gets CN value by VBER in thousandths of a deciBel (0.001dB). - */ - public int getVberCn() { - if (mVberCn == null) { - throw new IllegalStateException(); - } - return mVberCn; - } - /** - * Gets CN value by LBER in thousandths of a deciBel (0.001dB). - */ - public int getLberCn() { - if (mLberCn == null) { - throw new IllegalStateException(); - } - return mLberCn; - } - /** - * Gets CN value by XER in thousandths of a deciBel (0.001dB). - */ - public int getXerCn() { - if (mXerCn == null) { - throw new IllegalStateException(); - } - return mXerCn; - } - /** * Gets Modulation Error Ratio in thousandths of a deciBel (0.001dB). */ public int getMer() { diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java index f90144bc97ef..8105c74cfb62 100644 --- a/media/java/android/media/tv/tuner/frontend/ScanCallback.java +++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java @@ -67,7 +67,7 @@ public interface ScanCallback { /** Frontend hierarchy. */ void onHierarchy(@DvbtFrontendSettings.Hierarchy int hierarchy); - /** Frontend hierarchy. */ + /** Frontend signal type. */ void onSignalType(@AnalogFrontendSettings.SignalType int signalType); } 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/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index 4316e42bb0e1..f10e5ebb5d3e 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -289,12 +289,13 @@ public class MediaRouterManagerTest { MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); int originalVolume = volRoute.getVolume(); - int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1); + int targetVolume = originalVolume == volRoute.getVolumeMax() + ? originalVolume - 1 : originalVolume + 1; awaitOnRouteChangedManager( - () -> mManager.requestUpdateVolume(volRoute, deltaVolume), + () -> mManager.requestSetVolume(volRoute, targetVolume), ROUTE_ID_VARIABLE_VOLUME, - (route -> route.getVolume() == originalVolume + deltaVolume)); + (route -> route.getVolume() == targetVolume)); awaitOnRouteChangedManager( () -> mManager.requestSetVolume(volRoute, originalVolume), diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java index f1a08f29e0cc..1a866cafff90 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java @@ -154,20 +154,6 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onUpdateVolume(String routeId, int delta) { - MediaRoute2Info route = mRoutes.get(routeId); - if (route == null) { - return; - } - int volume = route.getVolume() + delta; - volume = Math.min(volume, Math.max(0, route.getVolumeMax())); - mRoutes.put(routeId, new MediaRoute2Info.Builder(route) - .setVolume(volume) - .build()); - publishRoutes(); - } - - @Override public void onCreateSession(String packageName, String routeId, long requestId, @Nullable Bundle sessionHints) { MediaRoute2Info route = mRoutes.get(routeId); diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 392c9f6404ba..ba793e83f1fb 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -294,7 +294,7 @@ void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* aSurfaceTransaction, auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats; - for (const auto& [surfaceControl, acquireTime, previousReleaseFence, transformHint] : surfaceControlStats) { + for (const auto& [surfaceControl, latchTime, acquireTime, presentFence, previousReleaseFence, transformHint, frameEvents] : surfaceControlStats) { ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get()); aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime; aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence; 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/DynamicSystemInstallationService/res/values/strings.xml b/packages/DynamicSystemInstallationService/res/values/strings.xml index 9bd5be7b0dd7..7595d2b1eea3 100644 --- a/packages/DynamicSystemInstallationService/res/values/strings.xml +++ b/packages/DynamicSystemInstallationService/res/values/strings.xml @@ -35,4 +35,7 @@ <!-- Toast when we fail to launch into Dynamic System [CHAR LIMIT=64] --> <string name="toast_failed_to_reboot_to_dynsystem">Can\u2019t restart or load dynamic system</string> + <!-- URL of Dynamic System Key Revocation List [DO NOT TRANSLATE] --> + <string name="key_revocation_list_url" translatable="false">https://dl.google.com/developers/android/gsi/gsi-keyblacklist.json</string> + </resources> diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index 9ccb837cf613..9bae223a0a3e 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -46,6 +46,7 @@ import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.net.http.HttpResponseCache; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -60,6 +61,8 @@ import android.text.TextUtils; import android.util.Log; import android.widget.Toast; +import java.io.File; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -146,10 +149,26 @@ public class DynamicSystemInstallationService extends Service prepareNotification(); mDynSystem = (DynamicSystemManager) getSystemService(Context.DYNAMIC_SYSTEM_SERVICE); + + // Install an HttpResponseCache in the application cache directory so we can cache + // gsi key revocation list. The http(s) protocol handler uses this cache transparently. + // The cache size is chosen heuristically. Since we don't have too much traffic right now, + // a moderate size of 1MiB should be enough. + try { + File httpCacheDir = new File(getCacheDir(), "httpCache"); + long httpCacheSize = 1 * 1024 * 1024; // 1 MiB + HttpResponseCache.install(httpCacheDir, httpCacheSize); + } catch (IOException e) { + Log.d(TAG, "HttpResponseCache.install() failed: " + e); + } } @Override public void onDestroy() { + HttpResponseCache cache = HttpResponseCache.getInstalled(); + if (cache != null) { + cache.flush(); + } // Cancel the persistent notification. mNM.cancel(NOTIFICATION_ID); } diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java index 9aea0e713179..438c435ef0e4 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java @@ -25,6 +25,8 @@ import android.os.image.DynamicSystemManager; import android.util.Log; import android.webkit.URLUtil; +import org.json.JSONException; + import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; @@ -100,7 +102,9 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog private final Context mContext; private final DynamicSystemManager mDynSystem; private final ProgressListener mListener; + private final boolean mIsNetworkUrl; private DynamicSystemManager.Session mInstallationSession; + private KeyRevocationList mKeyRevocationList; private boolean mIsZip; private boolean mIsCompleted; @@ -123,6 +127,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog mContext = context; mDynSystem = dynSystem; mListener = listener; + mIsNetworkUrl = URLUtil.isNetworkUrl(mUrl); } @Override @@ -152,9 +157,11 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog return null; } + // TODO(yochiang): do post-install public key check (revocation list / boot-ramdisk) + mDynSystem.finishInstallation(); } catch (Exception e) { - e.printStackTrace(); + Log.e(TAG, e.toString(), e); mDynSystem.remove(); return e; } finally { @@ -220,7 +227,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog String.format(Locale.US, "Unsupported file format: %s", mUrl)); } - if (URLUtil.isNetworkUrl(mUrl)) { + if (mIsNetworkUrl) { mStream = new URL(mUrl).openStream(); } else if (URLUtil.isFileUrl(mUrl)) { if (mIsZip) { @@ -234,6 +241,25 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog throw new UnsupportedUrlException( String.format(Locale.US, "Unsupported URL: %s", mUrl)); } + + // TODO(yochiang): Bypass this check if device is unlocked + try { + String listUrl = mContext.getString(R.string.key_revocation_list_url); + mKeyRevocationList = KeyRevocationList.fromUrl(new URL(listUrl)); + } catch (IOException | JSONException e) { + Log.d(TAG, "Failed to fetch Dynamic System Key Revocation List"); + mKeyRevocationList = new KeyRevocationList(); + keyRevocationThrowOrWarning(e); + } + } + + private void keyRevocationThrowOrWarning(Exception e) throws Exception { + if (mIsNetworkUrl) { + throw e; + } else { + // If DSU is being installed from a local file URI, then be permissive + Log.w(TAG, e.toString()); + } } private void installUserdata() throws Exception { diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java new file mode 100644 index 000000000000..522bc547325b --- /dev/null +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dynsystem; + +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; + +class KeyRevocationList { + + private static final String TAG = "KeyRevocationList"; + + private static final String JSON_ENTRIES = "entries"; + private static final String JSON_PUBLIC_KEY = "public_key"; + private static final String JSON_STATUS = "status"; + private static final String JSON_REASON = "reason"; + + private static final String STATUS_REVOKED = "REVOKED"; + + @VisibleForTesting + HashMap<String, RevocationStatus> mEntries; + + static class RevocationStatus { + final String mStatus; + final String mReason; + + RevocationStatus(String status, String reason) { + mStatus = status; + mReason = reason; + } + } + + KeyRevocationList() { + mEntries = new HashMap<String, RevocationStatus>(); + } + + /** + * Returns the revocation status of a public key. + * + * @return a RevocationStatus for |publicKey|, null if |publicKey| doesn't exist. + */ + RevocationStatus getRevocationStatusForKey(String publicKey) { + return mEntries.get(publicKey); + } + + /** Test if a public key is revoked or not. */ + boolean isRevoked(String publicKey) { + RevocationStatus entry = getRevocationStatusForKey(publicKey); + return entry != null && TextUtils.equals(entry.mStatus, STATUS_REVOKED); + } + + @VisibleForTesting + void addEntry(String publicKey, String status, String reason) { + mEntries.put(publicKey, new RevocationStatus(status, reason)); + } + + /** + * Creates a KeyRevocationList from a JSON String. + * + * @param jsonString the revocation list, for example: + * <pre>{@code + * { + * "entries": [ + * { + * "public_key": "00fa2c6637c399afa893fe83d85f3569998707d5", + * "status": "REVOKED", + * "reason": "Revocation Reason" + * } + * ] + * } + * }</pre> + * + * @throws JSONException if |jsonString| is malformed. + */ + static KeyRevocationList fromJsonString(String jsonString) throws JSONException { + JSONObject jsonObject = new JSONObject(jsonString); + KeyRevocationList list = new KeyRevocationList(); + Log.d(TAG, "Begin of revocation list"); + if (jsonObject.has(JSON_ENTRIES)) { + JSONArray entries = jsonObject.getJSONArray(JSON_ENTRIES); + for (int i = 0; i < entries.length(); ++i) { + JSONObject entry = entries.getJSONObject(i); + String publicKey = entry.getString(JSON_PUBLIC_KEY); + String status = entry.getString(JSON_STATUS); + String reason = entry.has(JSON_REASON) ? entry.getString(JSON_REASON) : ""; + list.addEntry(publicKey, status, reason); + Log.d(TAG, "Revocation entry: " + entry.toString()); + } + } + Log.d(TAG, "End of revocation list"); + return list; + } + + /** + * Creates a KeyRevocationList from a URL. + * + * @throws IOException if |url| is inaccessible. + * @throws JSONException if fetched content is malformed. + */ + static KeyRevocationList fromUrl(URL url) throws IOException, JSONException { + Log.d(TAG, "Fetch from URL: " + url.toString()); + // Force "conditional GET" + // Force validate the cached result with server each time, and use the cached result + // only if it is validated by server, else fetch new data from server. + // Ref: https://developer.android.com/reference/android/net/http/HttpResponseCache#force-a-network-response + URLConnection connection = url.openConnection(); + connection.setUseCaches(true); + connection.addRequestProperty("Cache-Control", "max-age=0"); + try (InputStream stream = connection.getInputStream()) { + return fromJsonString(readFully(stream)); + } + } + + private static String readFully(InputStream in) throws IOException { + int n; + byte[] buffer = new byte[4096]; + StringBuilder builder = new StringBuilder(); + while ((n = in.read(buffer, 0, 4096)) > -1) { + builder.append(new String(buffer, 0, n)); + } + return builder.toString(); + } +} diff --git a/packages/DynamicSystemInstallationService/tests/Android.bp b/packages/DynamicSystemInstallationService/tests/Android.bp new file mode 100644 index 000000000000..3bdf82966889 --- /dev/null +++ b/packages/DynamicSystemInstallationService/tests/Android.bp @@ -0,0 +1,15 @@ +android_test { + name: "DynamicSystemInstallationServiceTests", + + srcs: ["src/**/*.java"], + static_libs: [ + "androidx.test.runner", + "androidx.test.rules", + "mockito-target-minus-junit4", + ], + + resource_dirs: ["res"], + platform_apis: true, + instrumentation_for: "DynamicSystemInstallationService", + certificate: "platform", +} diff --git a/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml b/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml new file mode 100644 index 000000000000..f5f0ae6adaba --- /dev/null +++ b/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.dynsystem.tests"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.dynsystem" + android:label="Tests for DynamicSystemInstallationService" /> + +</manifest> diff --git a/packages/DynamicSystemInstallationService/tests/res/values/strings.xml b/packages/DynamicSystemInstallationService/tests/res/values/strings.xml new file mode 100644 index 000000000000..fdb620bfe094 --- /dev/null +++ b/packages/DynamicSystemInstallationService/tests/res/values/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- testFromJsonString --> + <string name="blacklist_json_string" translatable="false"> + { + \"entries\":[ + { + \"public_key\":\"00fa2c6637c399afa893fe83d85f3569998707d5\", + \"status\":\"REVOKED\", + \"reason\":\"Key revocation test key\" + }, + { + \"public_key\":\"key2\", + \"status\":\"REVOKED\" + } + ] + } + </string> +</resources> diff --git a/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java b/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java new file mode 100644 index 000000000000..82ce542cf5de --- /dev/null +++ b/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dynsystem; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import android.content.Context; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.json.JSONException; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +/** + * A test for KeyRevocationList.java + */ +@RunWith(AndroidJUnit4.class) +public class KeyRevocationListTest { + + private static final String TAG = "KeyRevocationListTest"; + + private static Context sContext; + + private static String sBlacklistJsonString; + + @BeforeClass + public static void setUpClass() throws Exception { + sContext = InstrumentationRegistry.getInstrumentation().getContext(); + sBlacklistJsonString = + sContext.getString(com.android.dynsystem.tests.R.string.blacklist_json_string); + } + + @Test + @SmallTest + public void testFromJsonString() throws JSONException { + KeyRevocationList blacklist; + blacklist = KeyRevocationList.fromJsonString(sBlacklistJsonString); + Assert.assertNotNull(blacklist); + Assert.assertFalse(blacklist.mEntries.isEmpty()); + blacklist = KeyRevocationList.fromJsonString("{}"); + Assert.assertNotNull(blacklist); + Assert.assertTrue(blacklist.mEntries.isEmpty()); + } + + @Test + @SmallTest + public void testFromUrl() throws IOException, JSONException { + URLConnection mockConnection = mock(URLConnection.class); + doReturn(new ByteArrayInputStream(sBlacklistJsonString.getBytes())) + .when(mockConnection).getInputStream(); + URL mockUrl = new URL( + "http", // protocol + "foo.bar", // host + 80, // port + "baz", // file + new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL url) { + return mockConnection; + } + }); + URL mockBadUrl = new URL( + "http", // protocol + "foo.bar", // host + 80, // port + "baz", // file + new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL url) throws IOException { + throw new IOException(); + } + }); + + KeyRevocationList blacklist = KeyRevocationList.fromUrl(mockUrl); + Assert.assertNotNull(blacklist); + Assert.assertFalse(blacklist.mEntries.isEmpty()); + + blacklist = null; + try { + blacklist = KeyRevocationList.fromUrl(mockBadUrl); + // Up should throw, down should be unreachable + Assert.fail("Expected IOException not thrown"); + } catch (IOException e) { + // This is expected, do nothing + } + Assert.assertNull(blacklist); + } + + @Test + @SmallTest + public void testIsRevoked() { + KeyRevocationList blacklist = new KeyRevocationList(); + blacklist.addEntry("key1", "REVOKED", "reason for key1"); + + KeyRevocationList.RevocationStatus revocationStatus = + blacklist.getRevocationStatusForKey("key1"); + Assert.assertNotNull(revocationStatus); + Assert.assertEquals(revocationStatus.mReason, "reason for key1"); + + revocationStatus = blacklist.getRevocationStatusForKey("key2"); + Assert.assertNull(revocationStatus); + + Assert.assertTrue(blacklist.isRevoked("key1")); + Assert.assertFalse(blacklist.isRevoked("key2")); + } +} 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/Incremental/NativeAdbDataLoader/AndroidManifest.xml b/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml index a06dc546f85c..c4d8f35cec71 100644 --- a/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml +++ b/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml @@ -29,7 +29,7 @@ <service android:enabled="true" android:name="com.android.incremental.nativeadb.NativeAdbDataLoaderService" android:label="@string/app_name" - android:exported="true"> + android:exported="false"> <intent-filter> <action android:name="android.intent.action.LOAD_DATA" /> </intent-filter> diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 9c8345dafbc8..62124934f416 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -10,6 +10,7 @@ android_library { "androidx.appcompat_appcompat", "androidx.lifecycle_lifecycle-runtime", "androidx.mediarouter_mediarouter-nodeps", + "iconloader", "SettingsLibHelpUtils", "SettingsLibRestrictedLockUtils", 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-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index b31841d82d0e..1a015a697ee0 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Gekoppel via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Beskikbaar via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tik om aan te meld"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Geen internet nie"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Daar kan nie by private DNS-bediener ingegaan word nie"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Beperkte verbinding"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Geen internet nie"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rooi-groen)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (blou-geel)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Kleurregstelling"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Kleurregstelling help mense met kleurblindheid om akkurater kleure te sien"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Geneutraliseer deur <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> oor"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> tot battery gelaai is"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Laai"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"laai tans"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Laai nie"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Ingeprop; kan nie op die oomblik laai nie"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Vol"</string> diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index 4955ad8fda29..05f0e1bd94d9 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"በ <xliff:g id="NAME">%1$s</xliff:g> በኩል ተገናኝተዋል"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"በ%1$s በኩል የሚገኝ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ለመመዝገብ መታ ያድርጉ"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ምንም በይነመረብ የለም"</string> <string name="private_dns_broken" msgid="1984159464346556931">"የግል ዲኤንኤስ አገልጋይ ሊደረስበት አይችልም"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"የተገደበ ግንኙነት"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ምንም በይነመረብ የለም"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ፕሮታኖማሊ (ቀይ-አረንጓዴ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ትራይታኖማሊ (ሰማያዊ-ቢጫ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"የቀለም ማስተካከያ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ቀለም ማስተካከያ የቀለም ማየት የማይችሉ ሰዎች ተጨማሪ ትክክለኛ ቀለማትን እንዲመለከቱ ያስችላቸዋል"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"በ<xliff:g id="TITLE">%1$s</xliff:g> ተሽሯል"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ገደማ ቀርቷል"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ኃይል እስከሚሞላ ድረስ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ያልታወቀ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ኃይል በመሙላት ላይ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ኃይል በመሙላት ላይ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ባትሪ እየሞላ አይደለም"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ተሰክቷል፣ አሁን ኃይል መሙላት አይቻልም"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ሙሉነው"</string> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index 7b26be4f4c14..07befebde7ec 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"تم الاتصال عبر <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="available_via_passpoint" msgid="1716000261192603682">"متوفرة عبر %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"انقر للاشتراك."</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"لا يتوفر اتصال إنترنت."</string> <string name="private_dns_broken" msgid="1984159464346556931">"لا يمكن الوصول إلى خادم أسماء نظام نطاقات خاص"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"اتصال محدود"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"لا يتوفر اتصال إنترنت."</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"غطش الأحمر (الأحمر والأخضر)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"غمش الأزرق (الأزرق والأصفر)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"تصحيح الألوان"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"تساعد ميزة تصحيح الألوان المصابين بعمى الألوان على رؤية الألوان بدقة أكبر"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"تم الاستبدال بـ <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"يتبقى <xliff:g id="TIME_REMAINING">%1$s</xliff:g> تقريبًا"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> إلى أن يتم شحن الجهاز بالكامل"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"غير معروف"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"جارٍ الشحن"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"جارٍ الشحن"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"لا يتم الشحن"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"تم التوصيل، ولكن يتعذّر الشحن الآن"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ممتلئة"</string> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index dcebe5bd9b6c..4d0d8cc4ba01 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>ৰ জৰিয়তে সংযুক্ত"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$sৰ মাধ্যমেৰে উপলব্ধ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ছাইন আপ কৰিবলৈ টিপক"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ইণ্টাৰনেট সংযোগ নাই"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ব্যক্তিগত DNS ছাৰ্ভাৰ এক্সেছ কৰিব নোৱাৰি"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ইণ্টাৰনেট সংযোগ সীমিত"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ইণ্টাৰনেট সংযোগ নাই"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"প্ৰ’টানোমালি (ৰঙা-সেউজীয়া)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ট্ৰাইটান\'মেলী (নীলা-হালধীয়া)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ৰং শুধৰণী"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ৰং শুধৰণী কৰা কার্যই বর্ণান্ধলোকসকলক ৰংবোৰ অধিক সঠিককৈ দেখাত সহায় কৰে"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g>ৰ দ্বাৰা অগ্ৰাহ্য কৰা হৈছে"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"প্রায় <xliff:g id="TIME_REMAINING">%1$s</xliff:g> বাকী আছে"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> চাৰ্জ হ\'বলৈ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"অজ্ঞাত"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"চাৰ্জ কৰি থকা হৈছে"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"চ্চাৰ্জ হৈ আছে"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"চ্চাৰ্জ কৰা নাই"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"প্লাগ কৰি থোৱা হৈছে, এই মুহূৰ্তত চ্চাৰ্জ কৰিব নোৱাৰি"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"পূৰ্ণ"</string> diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index 7f3db37f2a25..73eb48a02324 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ilə qoşulub"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s vasitəsilə əlçatandır"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Qeydiyyatdan keçmək üçün klikləyin"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"İnternet yoxdur"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Özəl DNS serverinə giriş mümkün deyil"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Məhdud bağlantı"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"İnternet yoxdur"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaliya (qırmızı-yaşıl)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaliya (göy-sarı)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Rəng düzəlişi"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Rəng düzəlişi rəng korluğu olanların daha yaxşı görməsinə kömək edir"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tərəfindən qəbul edilmir"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Təxminən <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qalıb"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - Enerjinin dolmasına <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Naməlum"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Enerji doldurma"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"enerji yığır"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Doldurulmur"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Cihaz hazırda batareya yığa bilmir"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Tam"</string> diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index b789cb0dbe5a..dff657e34e69 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezano preko: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupna je preko pristupne tačke %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite da biste se registrovali"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nema interneta"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Pristup privatnom DNS serveru nije uspeo"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema interneta"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (crveno-zeleno)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (plavo-žuto)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekcija boja"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcija boja pomaže ljudima koji su daltonisti da preciznije vide boje"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamenjuje ga <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>–<xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Preostalo je oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – napuniće se za <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Puni se"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"puni se"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Priključeno je, ali punjenje trenutno nije moguće"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Puna"</string> diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index ce191eb59676..12c21a6d7ba4 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Падключана праз праграму \"<xliff:g id="NAME">%1$s</xliff:g>\""</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Даступна праз %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Націсніце, каб зарэгістравацца"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Не падключана да інтэрнэту"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Не ўдалося атрымаць доступ да прыватнага DNS-сервера"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Абмежаваныя магчымасці падключэння"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Не падключана да інтэрнэту"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Пратанамалія (чырвоны-зялёны)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Трытанамалія (сіні-жоўты)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Карэкцыя колеру"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Карэкцыя колеру дазваляе людзям з парушэннямі колеравага зроку лепш распазнаваць выявы на экране"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Перавызначаны <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Зараду хопіць прыблізна на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> да поўнай зарадкі"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Невядома"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарадка"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ідзе зарадка"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не зараджаецца"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Падключана да сеткі сілкавання, зарадзіць зараз немагчыма"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Акумулятар зараджаны"</string> diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index 3e6f77db29c1..6fda5b358a7c 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Установена е връзка през <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Мрежата е достъпна през „%1$s“"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Докоснете, за да се регистрирате"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Няма интернет"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Не може да се осъществи достъп до частния DNS сървър"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена връзка"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Няма връзка с интернет"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (червено – зелено)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (синьо – жълто)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекция на цветове"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Коригирането на цветовете помага на хората с цветна слепота да виждат по-точни цветове"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Заменено от „<xliff:g id="TITLE">%1$s</xliff:g>“"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Още около <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до пълно зареждане"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарежда се"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"зарежда се"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не се зарежда"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Включена в захранването, в момента не се зарежда"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Пълна"</string> diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index 7f6938ac8de2..be2d1e0482be 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>-এর মাধ্যমে কানেক্ট করা আছে"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s এর মাধ্যমে উপলব্ধ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"সাইন-আপ করতে ট্যাপ করুন"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ইন্টারনেট কানেকশন নেই"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ব্যক্তিগত ডিএনএস সার্ভার অ্যাক্সেস করা যাবে না"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"সীমিত কানেকশন"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ইন্টারনেট কানেকশন নেই"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"প্রোটানোম্যালি (লাল-সবুজ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ট্রিট্যানোম্যালি (নীল-হলুদ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"রঙ সংশোধন"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"রঙ অ্যাডজাস্ট করার সেটিংস, বর্ণান্ধতা আছে এমন ব্যক্তিদের আরও সঠিকভাবে রঙ চিনতে সাহায্য করে"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> এর দ্বারা ওভাররাইড করা হয়েছে"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"আর আনুমানিক <xliff:g id="TIME_REMAINING">%1$s</xliff:g> চলবে"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>-এ সম্পূর্ণ চার্জ হয়ে যাবে"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"অজানা"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"চার্জ হচ্ছে"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"চার্জ হচ্ছে"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"চার্জ হচ্ছে না"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"প্লাগ-ইন করা হয়েছে কিন্তু এখনই চার্জ করা যাবে না"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"পূর্ণ"</string> diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index 0563abd66311..c32df18d8f14 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezano preko <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupan preko %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite za prijavu"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nema internetske veze"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Nije moguće pristupiti privatnom DNS serveru"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema internetske veze"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (crveno-zeleno)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (plavo-žuto)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Ispravka boje"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ispravka boje pomaže daltonistima da preciznije vide boje"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamjenjuje <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Preostalo je još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – napunit će se za <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"punjenje"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Priključen, trenutno se ne može puniti"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Puna"</string> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 71b2c5d85f84..813fff4281d4 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connectat mitjançant <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible mitjançant %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca per registrar-te"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sense connexió a Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"No es pot accedir al servidor DNS privat"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexió limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sense connexió a Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermell-verd)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (blau-groc)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correcció del color"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correcció del color ajuda les persones daltòniques a veure colors més precisos"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"S\'ha substituït per <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>: <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant aproximat: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> per completar la càrrega"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconegut"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"S\'està carregant"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"s\'està carregant"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"No s\'està carregant"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"El dispositiu està endollat però en aquests moments no es pot carregar"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Completa"</string> diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index a381772970aa..f116733375af 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Připojeno přes <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupné prostřednictvím %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Klepnutím se zaregistrujete"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nejste připojeni k internetu"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Nelze získat přístup k soukromému serveru DNS"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Omezené připojení"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nejste připojeni k internetu"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomálie (červená a zelená)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomálie (modrá a žlutá)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekce barev"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekce barev pomáhá barvoslepým lidem vidět přesnější barvy"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Přepsáno nastavením <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do nabití"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznámé"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíjí se"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"nabíjení"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenabíjí se"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Zapojeno, ale nelze nabíjet"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Nabitá"</string> diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index f6e8576117d9..e3b96a4bbd03 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Forbundet via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tilgængelig via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tryk for at registrere dig"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Intet internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Der er ikke adgang til den private DNS-server"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Begrænset forbindelse"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Intet internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopi (rød-grøn)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanopi (blå-gul)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korriger farver"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Farvekorrigering gør det nemmere for farveblinde at se farver mere nøjagtigt"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tilsidesat af <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – opladet om <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukendt"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Oplader"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"oplader"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Oplader ikke"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Enheden er tilsluttet en strømkilde. Det er ikke muligt at oplade på nuværende tidspunkt."</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Fuldt"</string> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index 99a091093463..1ddabcaba02f 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Verbunden über <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Verfügbar über %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Zum Anmelden tippen"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Kein Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Auf den privaten DNS-Server kann nicht zugegriffen werden"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Eingeschränkte Verbindung"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Kein Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (Rot-Grün-Sehschwäche)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (Blau-Gelb-Sehschwäche)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Farbkorrektur"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Die Farbkorrektur hilft farbenblinden Menschen, Farben besser zu erkennen"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Außer Kraft gesetzt von \"<xliff:g id="TITLE">%1$s</xliff:g>\""</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noch etwa <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> bis zur Aufladung"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unbekannt"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Wird aufgeladen"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"wird aufgeladen..."</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Wird nicht geladen"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Angeschlossen, kann derzeit nicht geladen werden"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Voll"</string> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index d37ea4409124..3a97d5a43291 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Συνδέθηκε μέσω <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Διαθέσιμο μέσω %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Πατήστε για εγγραφή"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Χωρίς σύνδεση στο διαδίκτυο"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Δεν είναι δυνατή η πρόσβαση στον ιδιωτικό διακομιστή DNS."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Περιορισμένη σύνδεση"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Δεν υπάρχει σύνδεση στο διαδίκτυο"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Πρωτανοπία (κόκκινο-πράσινο)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Τριτανοπία (μπλε-κίτρινο)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Διόρθωση χρωμάτων"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Η διόρθωση χρωμάτων βοηθάει τους ανθρώπους με αχρωματοψία να βλέπουν τα χρώματα με μεγαλύτερη ακρίβεια"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Αντικαταστάθηκε από <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Απομένει/ουν περίπου <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> για την ολοκλήρωση της φόρτισης"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Άγνωστο"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Φόρτιση"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"φόρτιση"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Δεν φορτίζει"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Συνδέθηκε, δεν είναι δυνατή η φόρτιση αυτήν τη στιγμή"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Πλήρης"</string> diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index 429cd3edd60b..ec0a12994d71 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml index 429cd3edd60b..ec0a12994d71 100644 --- a/packages/SettingsLib/res/values-en-rCA/strings.xml +++ b/packages/SettingsLib/res/values-en-rCA/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index 429cd3edd60b..ec0a12994d71 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index 429cd3edd60b..ec0a12994d71 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml index 1aa6cdba32fb..3b286f7c0c9e 100644 --- a/packages/SettingsLib/res/values-en-rXC/strings.xml +++ b/packages/SettingsLib/res/values-en-rXC/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Color correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Color correction helps people with color blindness to see more accurate colors"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge right now"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 7d28a3255a7b..966f2f25e827 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conexión a través de <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible a través de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Presiona para registrarte"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sin Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"No se puede acceder al servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexión limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sin Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (rojo-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarillo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección de color"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La corrección de colores ayuda a las personas con daltonismo a ver colores más exactos"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Reemplazado por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tiempo restante: aproximadamente <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> para completar la carga"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"cargando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"No se está cargando."</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectado. No se puede cargar en este momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Cargado"</string> diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index b35696f54cdc..11987221bdab 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectado a través de <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible a través de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca para registrarte"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sin Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"No se ha podido acceder al servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexión limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sin Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (rojo-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarillo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección de color"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La corrección del color ayuda a las personas con daltonismo a ver los colores más reales"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>: <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tiempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> hasta que termine de cargarse)"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"cargando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"No se está cargando"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Se ha conectado, pero no se puede cargar en este momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Completa"</string> diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index b6c112b94da7..9be72f90ad06 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Ühendatud võrgu <xliff:g id="NAME">%1$s</xliff:g> kaudu"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Saadaval üksuse %1$s kaudu"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Puudutage registreerumiseks"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Interneti pole"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Privaatsele DNS-serverile ei pääse juurde"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Piiratud ühendus"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Interneti-ühendus puudub"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaalia (punane-roheline)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaalia (sinine-kollane)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Värvide korrigeerimine"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Värviparanduse funktsioon aitab värvipimedatel värve täpsemini näha"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Alistas <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäänud"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> täislaadimiseni"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tundmatu"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Laadimine"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"laadimine"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ei lae"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Vooluvõrgus, praegu ei saa laadida"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Täis"</string> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 4fd9add70470..d0c6c52ca7c1 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> bidez konektatuta"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s bidez erabilgarri"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Sakatu erregistratzeko"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ez dago Interneteko konexiorik"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Ezin da atzitu DNS zerbitzari pribatua"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Konexio mugatua"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ez dago Interneteko konexiorik"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopia (gorri-berdeak)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanopia (urdin-horia)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koloreen zuzenketa"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Kolore-zuzenketak kolore zehatzagoak ikusten laguntzen die koloreentzako itsutasuna duten pertsonei."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> guztiz kargatu arte"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ezezaguna"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Kargatzen"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"kargatzen"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ez da kargatzen ari"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Konektatuta dago. Ezin da kargatu une honetan."</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Beteta"</string> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index 32a98be394cb..f15174a88395 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"متصل شده ازطریق <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"در دسترس از طریق %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"برای ثبتنام ضربه بزنید"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"عدم اتصال به اینترنت"</string> <string name="private_dns_broken" msgid="1984159464346556931">"سرور DNS خصوصی قابل دسترسی نیست"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"اتصال محدود"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"عدم دسترسی به اینترنت"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"قرمزدشواربینی (قرمز-سبز)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"آبیدشواربینی (آبی-زرد)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"تصحیح رنگ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"تصحیح رنگ به افراد مبتلا به کوررنگی کمک میکند رنگها را دقیقتر ببینند"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"توسط <xliff:g id="TITLE">%1$s</xliff:g> لغو شد"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> شارژ باقی مانده است"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> تا شارژ کامل"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ناشناس"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"در حال شارژ شدن"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"درحال شارژ شدن"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"شارژ نمیشود"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"به برق وصل شده است، درحالحاضر شارژ نمیشود"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"پر"</string> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index 84c53723d6c6..1232db3ec9f7 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Yhdistetty (<xliff:g id="NAME">%1$s</xliff:g>)"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Käytettävissä seuraavan kautta: %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Rekisteröidy napauttamalla"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ei internetyhteyttä"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Ei pääsyä yksityiselle DNS-palvelimelle"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Rajallinen yhteys"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ei internetyhteyttä"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (puna-vihersokeus)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (sini-keltasokeus)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Värikorjaus"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Värinkorjaus auttaa värisokeita tulkitsemaan värejä"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tämän ohittaa <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noin <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäljellä"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> täyteen lataukseen"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tuntematon"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Ladataan"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ladataan"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ei laturissa"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Kytketty virtalähteeseen, lataaminen ei onnistu"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Täynnä"</string> diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index 995eab641719..6404df4edb00 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connecté sur le réseau <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Accessible par %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toucher pour vous connecter"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Aucune connexion Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Impossible d\'accéder au serveur DNS privé"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexion limitée"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Aucune connexion Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rouge/vert)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (bleu/jaune)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correction des couleurs"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correction des couleurs aide les personnes atteintes de daltonisme à mieux percevoir les couleurs"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> : <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Il reste environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> : <xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la charge complète"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charge en cours…"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"en cours de charge"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"N\'est pas en charge"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"L\'appareil est branché, mais il ne peut pas être chargé pour le moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Pleine"</string> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index 2afacab43f8d..e75063b84985 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connecté via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Appuyez ici pour vous connecter"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Aucun accès à Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Impossible d\'accéder au serveur DNS privé"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexion limitée"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Aucun accès à Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rouge/vert)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (bleu-jaune)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correction couleur"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correction des couleurs aide les personnes atteintes de daltonisme à mieux percevoir les couleurs"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la charge complète"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Batterie en charge"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"en charge…"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Pas en charge"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Appareil branché, mais impossible de le charger pour le moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Pleine"</string> diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index a106e60203e3..7fd32f9c4e84 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Wifi conectada a través de <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dispoñible a través de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca para rexistrarte"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Non hai conexión a Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Non se puido acceder ao servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Pouca conexión"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Non hai conexión a Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (vermello-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarelo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección da cor"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A corrección das cores axuda ás persoas con daltonismo a ver as cores con maior precisión"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> para completar a carga"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Descoñecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"cargando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Non se está cargando"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectouse, pero non se pode cargar neste momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Completa"</string> diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index 5f2d5cde3198..1513c57694d6 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> દ્વારા કનેક્ટ થયેલ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s દ્વારા ઉપલબ્ધ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"સાઇન અપ કરવા માટે ટૅપ કરો"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"કોઈ ઇન્ટરનેટ નથી"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ખાનગી DNS સર્વર ઍક્સેસ કરી શકાતા નથી"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"મર્યાદિત કનેક્શન"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ઇન્ટરનેટ ઍક્સેસ નથી"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"પ્રોટેનોમલી (લાલ-લીલો)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ટ્રાઇટેનોમલી(વાદળી-પીળો)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"રંગ સુધારણા"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"રંગ સુધારણા રંગ અંધત્વવાળા લોકોને વધુ સચોટ રંગો જોવામાં સહાય કરે છે"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> દ્વારા ઓવરરાઇડ થયું"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"લગભગ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> બાકી છે"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જ થવા માટે <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"અજાણ્યું"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ચાર્જ થઈ રહ્યું છે"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ચાર્જ થઈ રહ્યું છે"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ચાર્જ થઈ રહ્યું નથી"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"પ્લગ ઇન કરેલ, હમણાં ચાર્જ કરી શકતા નથી"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"પૂર્ણ"</string> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index d60eeadac7a6..c79c4a4090c6 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> के ज़रिए कनेक्ट किया गया"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s के द्वारा उपलब्ध"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप करने के लिए टैप करें"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"इंटरनेट कनेक्शन नहीं है"</string> <string name="private_dns_broken" msgid="1984159464346556931">"निजी डीएनएस सर्वर को ऐक्सेस नहीं किया जा सकता"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"सीमित कनेक्शन"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"इंटरनेट कनेक्शन नहीं है"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"लाल रंग पहचान न पाना (लाल-हरा)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"नीला रंग पहचान न पाना (नीला-पीला)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रंग सुधार"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"रंग में सुधार करने की सेटिंग, वर्णान्धता (कलर ब्लाइंडनेस) वाले लोगों को ज़्यादा सटीक रंग देखने में मदद करती है"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> के द्वारा ओवरराइड किया गया"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"बैटरी करीब <xliff:g id="TIME_REMAINING">%1$s</xliff:g> में खत्म हो जाएगी"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> में पूरा चार्ज हो जाएगा"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हो रही है"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"चार्ज हो रही है"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज नहीं हो रही है"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"प्लग इन है, अभी चार्ज नहीं हो सकती"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"पूरी"</string> diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index 28e34601444c..3ab567866c76 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezan putem mreže <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupno putem %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite da biste se registrirali"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nema interneta"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Nije moguće pristupiti privatnom DNS poslužitelju"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema interneta"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (crveno – zeleno)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (plavo – žuto)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekcija boje"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcija boje pomaže slijepima za boje da vide preciznije boje"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Premošćeno postavkom <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Još otprilike <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – napunit će se za <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"punjenje"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Uključen, trenutačno se ne može puniti"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Puna"</string> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index 22e03a779545..8d06f584631d 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Kapcsolódva a következőn keresztül: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Elérhető a következőn keresztül: %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Koppintson a regisztrációhoz"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nincs internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"A privát DNS-kiszolgálóhoz nem lehet hozzáférni"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Korlátozott kapcsolat"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nincs internetkapcsolat"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomália (piros– zöld)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomália (kék–sárga)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Színkorrekció"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A színkorrekció segít a színtévesztőknek abban, hogy pontosabban lássák a színeket"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Felülírva erre: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Körülbelül <xliff:g id="TIME_REMAINING">%1$s</xliff:g> maradt hátra"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> a feltöltésig"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ismeretlen"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Töltés"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"töltés"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nem tölt"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Csatlakoztatva, jelenleg nem tölt"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Feltöltve"</string> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index ecb615a40cc2..99cef2800ed6 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Միացված է <xliff:g id="NAME">%1$s</xliff:g>-ի միջոցով"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Հասանելի է %1$s-ի միջոցով"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Հպեք՝ գրանցվելու համար"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Կապ չկա"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Մասնավոր DNS սերվերն անհասանելի է"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Սահմանափակ կապ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ինտերնետ կապ չկա"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Պրոտանոմալիա (կարմիր-կանաչ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Տրիտանոմալիա (կապույտ-դեղին)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Գունաշտկում"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Գունաշտկումն օգնում է գունային դալտոնիզմ ունեցող մարդկանց ավելի ճշգրիտ տեսնել գույները"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Գերազանցված է <xliff:g id="TITLE">%1$s</xliff:g>-ից"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Լիցքը կբավարարի մոտ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> մինչև լիցքավորումը"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Անհայտ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Լիցքավորում"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"լիցքավորում"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Չի լիցքավորվում"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Միացված է հոսանքի աղբյուրին, սակայն այս պահին չի կարող լիցքավորվել"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Լիցքավորված է"</string> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index cfbda04f176d..25de382b755a 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Tersambung melalui <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tersedia melalui %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Ketuk untuk mendaftar"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Tidak ada internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Server DNS pribadi tidak dapat diakses"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Koneksi terbatas"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Tidak ada internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (merah-hijau)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (biru-kuning)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koreksi warna"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Koreksi warna membantu orang penderita buta warna untuk melihat warna yang lebih akurat"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Digantikan oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Sekitar <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi terisi penuh"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengisi daya"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"mengisi daya baterai"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Tidak mengisi daya"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Tercolok, tidak dapat mengisi baterai sekarang"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Penuh"</string> diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index ba4c00976ccb..8fe382a698d4 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Tenging í gegnum <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Í boði í gegnum %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Ýttu til að skrá þig"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Engin nettenging"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Ekki næst í DNS-einkaþjón"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Takmörkuð tenging"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Engin nettenging"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Litblinda (rauðgræn)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Litblinda (blágul)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Litaleiðrétting"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Litaleiðrétting hjálpar fólki með litblindu að sjá réttari liti"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Hnekkt af <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Um það bil <xliff:g id="TIME_REMAINING">%1$s</xliff:g> eftir"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> að fullri hleðslu"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Óþekkt"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Í hleðslu"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"í hleðslu"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ekki í hleðslu"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Í sambandi, ekki hægt að hlaða eins og er"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Fullhlaðin"</string> diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index 50e5777ed9d9..c9abc74fce07 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connesso tramite <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponibile tramite %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tocca per registrarti"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Internet assente"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Non è possibile accedere al server DNS privato"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connessione limitata"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nessuna connessione a Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalìa (rosso-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalìa (blu-giallo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correzione del colore"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correzione del colore consente alle persone daltoniche di vedere colori più accurati"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valore sostituito da <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo rimanente: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> circa"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> alla carica completa"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Sconosciuta"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"In carica"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"in carica"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Non in carica"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Collegato alla corrente. Impossibile caricare al momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Carica"</string> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index 9f1e457383e1..1921d032775f 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"מחוברת באמצעות <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"זמינה דרך %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"יש להקיש כדי להירשם"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"אין אינטרנט"</string> <string name="private_dns_broken" msgid="1984159464346556931">"לא ניתן לגשת לשרת DNS הפרטי"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"חיבור מוגבל"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"אין אינטרנט"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"פרוטנומליה (אדום-ירוק)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"טריטנומליה (כחול-צהוב)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"תיקון צבע"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"תיקון צבע עוזר למשתמשים עם עיוורון צבעים לראות צבעים מדויקים יותר"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"נעקף על ידי <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"הזמן הנותר: בערך <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> עד הטעינה"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"לא ידוע"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"בטעינה"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"בטעינה"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"לא בטעינה"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"המכשיר מחובר, אבל לא ניתן לטעון עכשיו"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"מלאה"</string> diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index 47020ed45794..e4e1ab91b770 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> で接続しました"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s経由で使用可能"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"タップして登録してください"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"インターネットに接続されていません"</string> <string name="private_dns_broken" msgid="1984159464346556931">"プライベート DNS サーバーにアクセスできません"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"接続が制限されています"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"インターネット未接続"</string> @@ -204,7 +203,7 @@ <string name="vpn_settings_not_available" msgid="2894137119965668920">"このユーザーはVPN設定を利用できません。"</string> <string name="tethering_settings_not_available" msgid="266821736434699780">"このユーザーはテザリング設定を利用できません"</string> <string name="apn_settings_not_available" msgid="1147111671403342300">"このユーザーはアクセスポイント名設定を利用できません"</string> - <string name="enable_adb" msgid="8072776357237289039">"USBデバッグ"</string> + <string name="enable_adb" msgid="8072776357237289039">"USB デバッグ"</string> <string name="enable_adb_summary" msgid="3711526030096574316">"USB接続時はデバッグモードにする"</string> <string name="clear_adb_keys" msgid="3010148733140369917">"USBデバッグの許可の取り消し"</string> <string name="bugreport_in_power" msgid="8664089072534638709">"バグレポートのショートカット"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"第一色弱(赤緑)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"第三色弱(青黄)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色補正"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"色補正機能を利用すると、色覚異常のユーザーがより正確に色を判別できるようになります"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g>によって上書き済み"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電完了まで <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"充電しています"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"充電していません"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"接続されていますが、現在、充電できません"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"フル"</string> diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml index 116488d1c151..b2cfb17cdd43 100644 --- a/packages/SettingsLib/res/values-ka/strings.xml +++ b/packages/SettingsLib/res/values-ka/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"დაკავშირებულია <xliff:g id="NAME">%1$s</xliff:g>-ით"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"ხელმისაწვდომია %1$s-ით"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"შეეხეთ რეგისტრაციისთვის"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ინტერნეტ-კავშირი არ არის"</string> <string name="private_dns_broken" msgid="1984159464346556931">"პირად DNS სერვერზე წვდომა შეუძლებელია"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"შეზღუდული კავშირი"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ინტერნეტ-კავშირი არ არის"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"პროტოანომალია (წითელი-მწვანე)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ტრიტანომალია (ლურჯი-ყვითელი)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ფერის კორექცია"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ფერის კორექცია ეხმარება ფერითი სიბრმავის მქონე ადამიანებს, უფრო ზუსტად გაარჩიონ ფერები"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"უკუგებულია <xliff:g id="TITLE">%1$s</xliff:g>-ის მიერ"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"დარჩა დაახლოებით <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> დატენვამდე"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"უცნობი"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"იტენება"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"იტენება"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"არ იტენება"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"მიერთებულია, დატენვა ამჟამად ვერ ხერხდება"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ბატარეა დატენილია"</string> diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index 3c4fdb739ef5..756bc06b630e 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> арқылы жалғанған"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s арқылы қолжетімді"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Тіркелу үшін түртіңіз."</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Интернетпен байланыс жоқ."</string> <string name="private_dns_broken" msgid="1984159464346556931">"Жеке DNS серверіне кіру мүмкін емес."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Шектеулі байланыс"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернетпен байланыс жоқ"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (қызыл-жасыл)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (көк-сары)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Түсті түзету"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Түсті түзету түсті ажырата алмайтын адамдарға оларды дәлірек көруге көмектеседі"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> үстінен басқан"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Шамамен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> қалды"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядталғанға дейін <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгісіз"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарядталуда"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"зарядталуда"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Зарядталу орындалып жатқан жоқ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Қосылған, зарядталмайды"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Толы"</string> diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index 87a4f19f3d67..cdf92b38a46c 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"ភ្ជាប់តាម <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"មានតាមរយៈ %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ចុចដើម្បីចុះឈ្មោះ"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"គ្មានអ៊ីនធឺណិតទេ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"មិនអាចចូលប្រើម៉ាស៊ីនមេ DNS ឯកជនបានទេ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ការតភ្ជាប់មានកម្រិត"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"គ្មានអ៊ីនធឺណិតទេ"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ក្រហមពណ៌បៃតង)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ពណ៌ខៀវ-លឿង)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ការកែពណ៌"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ការកែតម្រូវពណ៌ជួយដល់អ្នកដែលមិនអាចបែងចែកពណ៌ឱ្យមើលឃើញពណ៌ដែលត្រឹមត្រូវជាមុន"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"បដិសេធដោយ <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"នៅសល់ប្រហែល <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ទៀត"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ទៀតទើបត្រូវសាក"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"មិនស្គាល់"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"កំពុងបញ្ចូលថ្ម"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"កំពុងសាកថ្ម"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"មិនកំពុងបញ្ចូលថ្ម"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ដោតសាកថ្មរួចហើយ ប៉ុន្តែសាកថ្មមិនចូលទេឥឡូវនេះ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ពេញ"</string> diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index 144dddb1f7ff..4a24691f9606 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ಆ್ಯಪ್ ಮೂಲಕ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ಮೂಲಕ ಲಭ್ಯವಿದೆ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ಸೈನ್ ಅಪ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ಖಾಸಗಿ DNS ಸರ್ವರ್ ಅನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ಸೀಮಿತ ಸಂಪರ್ಕ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ಪ್ರೊಟನೋಮಲಿ (ಕೆಂಪು-ಹಸಿರು)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ಟ್ರಿಟನೋಮಲಿ (ನೀಲಿ-ಹಳದಿ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ವರ್ಣ ಅಂಧತ್ವ ಹೊಂದಿರುವ ಜನರಿಗೆ ಬಣ್ಣಗಳನ್ನು ಹೆಚ್ಚು ನಿಖರವಾಗಿ ವೀಕ್ಷಿಸಲು ಬಣ್ಣ ತಿದ್ದುಪಡಿ ಸಹಾಯ ಮಾಡುತ್ತದೆ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ಮೂಲಕ ಅತಿಕ್ರಮಿಸುತ್ತದೆ"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜ್ ಆಗಲು <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯ ಬೇಕು"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ಅಪರಿಚಿತ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ಚಾರ್ಜ್ ಆಗುತ್ತಿಲ್ಲ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ಪ್ಲಗ್ ಇನ್ ಮಾಡಲಾಗಿದೆ, ಇದೀಗ ಚಾರ್ಜ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ಭರ್ತಿ"</string> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index df9f21d800ef..51896e96f856 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>을(를) 통해 연결됨"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s을(를) 통해 사용 가능"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"탭하여 가입"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"인터넷 연결 없음"</string> <string name="private_dns_broken" msgid="1984159464346556931">"비공개 DNS 서버에 액세스할 수 없습니다."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"제한된 연결"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"인터넷 연결 없음"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"적색약(적녹)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"청색약(청황)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"색보정"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"색상 보정을 사용하면 색맹인 사용자가 색상을 정확하게 구분하는 데 도움이 됩니다."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> 우선 적용됨"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>, <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"남은 시간 약 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 완료까지 <xliff:g id="TIME">%2$s</xliff:g> 남음"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"알 수 없음"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"충전 중"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"충전 중"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"충전 안함"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"전원이 연결되었지만 현재 충전할 수 없음"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"충전 완료"</string> diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index 13c414404da2..061f6fd37c6b 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> аркылуу туташты"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s аркылуу жеткиликтүү"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Катталуу үчүн таптап коюңуз"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Интернет жок"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Жеке DNS сервери жеткиликсиз"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Байланыш чектелген"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернет жок"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (кызыл-жашыл)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (көк-сары)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Түсүн тууралоо"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Түстү тууралоо жөндөөсү түстөрдү айырмалап көрбөгөн адамдарга түстөрдү тагыраак билүүгө жардам берет"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> менен алмаштырылган"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Болжол менен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> калды"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> кийин кубатталат"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгисиз"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Кубатталууда"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"кубатталууда"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Кубат алган жок"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Сайылып турат, учурда кубаттоо мүмкүн эмес"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Толук"</string> diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml index 48224f4c2f81..bfbd8a9a673e 100644 --- a/packages/SettingsLib/res/values-lo/strings.xml +++ b/packages/SettingsLib/res/values-lo/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"ເຊື່ອມຕໍ່ຜ່ານ <xliff:g id="NAME">%1$s</xliff:g> ແລ້ວ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"ມີໃຫ້ຜ່ານ %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ແຕະເພື່ອສະໝັກ"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ບໍ່ມີອິນເຕີເນັດ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ບໍ່ສາມາດເຂົ້າເຖິງເຊີບເວີ DNS ສ່ວນຕົວໄດ້"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ການເຊື່ອມຕໍ່ຈຳກັດ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ບໍ່ມີອິນເຕີເນັດ"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ສີແດງ-ສີຂຽວ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ສີຟ້າ-ສີເຫຼືອງ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ການປັບແຕ່ງສີ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ການແກ້ໄຂສີຈະຊ່ວຍໃຫ້ຄົນທີ່ຕາບອດສີເຫັນສີຕ່າງໆໄດ້ຖືກຕ້ອງຍິ່ງຂຶ້ນ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"ຖືກແທນໂດຍ <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ຈົນກວ່າຈະສາກເຕັມ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ບໍ່ຮູ້ຈັກ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ກຳລັງສາກໄຟ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ກຳລັງສາກໄຟ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ບໍ່ໄດ້ສາກໄຟ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ສຽບສາຍແລ້ວ, ບໍ່ສາມາດສາກໄດ້ໃນຕອນນີ້"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ເຕັມ"</string> diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml index d080334062ad..76d06faec154 100644 --- a/packages/SettingsLib/res/values-lt/strings.xml +++ b/packages/SettingsLib/res/values-lt/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Prisijungta naudojant programą „<xliff:g id="NAME">%1$s</xliff:g>“"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Pasiekiama naudojant „%1$s“"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Palieskite, kad prisiregistruotumėte"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nėra interneto ryšio"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Privataus DNS serverio negalima pasiekti"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ribotas ryšys"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nėra interneto ryšio"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (raudona, žalia)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (mėlyna, geltona)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Spalvų taisymas"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Naudojant spalvų taisymo funkciją daltonikai gali geriau matyti spalvas"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nepaisyta naudojant nuostatą „<xliff:g id="TITLE">%1$s</xliff:g>“"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Liko maždaug <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – iki visiškos įkrovos liko <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nežinomas"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Kraunasi..."</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"įkraunama"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nekraunama"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Įjungta į maitinimo lizdą, bet šiuo metu įkrauti neįmanoma"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Visiškai įkrautas"</string> diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index e13a50d990a7..e4652d1a44eb 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Savienojums ar <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Pieejams, izmantojot %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Pieskarieties, lai reģistrētos"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nav interneta"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Nevar piekļūt privātam DNS serverim."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ierobežots savienojums"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nav piekļuves internetam"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomālija (sarkans/zaļš)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomālija (zils/dzeltens)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Krāsu korekcija"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Krāsu korekcija palīdz cilvēkiem ar krāsu aklumu redzēt precīzākas krāsas."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Jaunā preference: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> — <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Aptuvenais atlikušais laiks: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="TIME">%2$s</xliff:g> līdz pilnai uzlādei"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nezināms"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Uzlāde"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"notiek uzlāde"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenotiek uzlāde"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Pievienots, taču pašlaik nevar veikt uzlādi"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Pilns"</string> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index d027ffe4f44f..21b2f965d302 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Поврзано преку <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Достапно преку %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Допрете за да се регистрирате"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Нема интернет"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Не може да се пристапи до приватниот DNS-сервер"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена врска"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нема интернет"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалија (слепило за црвена и зелена)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалија (слепило за сина и жолта)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекција на бои"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Корекцијата на бои им помага на луѓето со далтонизам попрецизно да ги гледаат боите"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Прескокнато според <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Уште околу <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> до целосно полнење"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Се полни"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"се полни"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не се полни"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Приклучен е, но батеријата не може да се полни во моментов"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Полна"</string> diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml index e2a3d9fbd4ba..85aa000fc7f6 100644 --- a/packages/SettingsLib/res/values-ml/strings.xml +++ b/packages/SettingsLib/res/values-ml/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> മുഖേന കണക്റ്റ് ചെയ്തു"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s വഴി ലഭ്യം"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"സൈൻ അപ്പ് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ഇന്റർനെറ്റ് ഇല്ല"</string> <string name="private_dns_broken" msgid="1984159464346556931">"സ്വകാര്യ DNS സെർവർ ആക്സസ് ചെയ്യാനാവില്ല"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"പരിമിത കണക്ഷൻ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ഇന്റർനെറ്റ് ഇല്ല"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"പ്രോട്ടാനോമലി (ചുവപ്പ്-പച്ച)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ട്രിട്ടാനോമലി (നീല-മഞ്ഞ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"വർണ്ണം ക്രമീകരിക്കൽ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"വർണ്ണം ശരിയാക്കൽ, വർണ്ണാന്ധത ബാധിച്ച ആളുകൾക്ക് നിറങ്ങൾ കൂടുതൽ കൃത്യമായി കാണാൻ സഹായിക്കുന്നു"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ഉപയോഗിച്ച് അസാധുവാക്കി"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ഏതാണ്ട് <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ശേഷിക്കുന്നു"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - പൂർണ്ണമായി ചാർജാവാൻ <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"അജ്ഞാതം"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ചാർജ് ചെയ്യുന്നു"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ചാർജ് ചെയ്യുന്നു"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ചാർജ്ജുചെയ്യുന്നില്ല"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"പ്ലഗ് ഇൻ ചെയ്തു, ഇപ്പോൾ ചാർജ് ചെയ്യാനാവില്ല"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"നിറഞ്ഞു"</string> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index dd08c9e9fdfc..232148a490ba 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>-р холбогдсон"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s-р боломжтой"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Бүртгүүлэхийн тулд товшино уу"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Интернэт алга"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Хувийн DNS серверт хандах боломжгүй байна"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Хязгаарлагдмал холболт"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернэт алга"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномаль (улаан-ногоон)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомаль (цэнхэр-шар)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Өнгө тохируулах"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Өнгөний залруулга нь өнгөний сохортой хүмүүст илүү оновчтой өнгө харахад тусалдаг"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Давхарласан <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - цэнэглэх хүртэл <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Тодорхойгүй"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Цэнэглэж байна"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"цэнэглэж байна"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Цэнэглэхгүй байна"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Залгаастай тул одоо цэнэглэх боломжгүй"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Дүүрэн"</string> diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index fb7cd1f3cf31..8767b4f145b4 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> द्वारे कनेक्ट केले"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s द्वारे उपलब्ध"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप करण्यासाठी टॅप करा"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"इंटरनेट नाही"</string> <string name="private_dns_broken" msgid="1984159464346556931">"खाजगी DNS सर्व्हर ॲक्सेस करू शकत नाही"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"मर्यादित कनेक्शन"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"इंटरनेट नाही"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"क्षीण रक्तवर्णांधता (लाल-हिरवा)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"रंग दृष्टी कमतरता (निळा-पिवळा)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रंग सुधारणा"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"रंग सुधारणा ही वर्णांधता असलेल्या लोकांना रंग अधिक अचूक दिसण्यात मदत करते"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारे अधिलिखित"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"अंदाजे <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाकी आहे"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> पर्यंत पूर्ण चार्ज होईल"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज होत आहे"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"चार्ज होत आहे"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज होत नाही"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"प्लग इन केलेले आहे, आता चार्ज करू शकत नाही"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"पूर्ण"</string> diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index 72b15ff2a172..b79b80130d90 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Disambungkan melalui <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tersedia melalui %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Ketik untuk daftar"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Tiada Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Pelayan DNS peribadi tidak boleh diakses"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Sambungan terhad"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Tiada Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (merah-hijau)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (biru-kuning)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Pembetulan warna"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Pembetulan warna membantu orang yang mengalami kebutaan warna melihat warna yang lebih tepat"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Diatasi oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Kira-kira <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> sehingga dicas"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengecas"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"mengecas"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Tidak mengecas"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Dipalamkan, tidak boleh mengecas sekarang"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Penuh"</string> diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index 24017ecd8196..b2ad66418d53 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> မှတစ်ဆင့် ချိတ်ဆက်ထားသည်"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s မှတစ်ဆင့်ရနိုင်သည်"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"အကောင့်ဖွင့်ရန် တို့ပါ"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"အင်တာနက် မရှိပါ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"သီးသန့် ဒီအန်အက်စ် (DNS) ဆာဗာကို သုံး၍မရပါ။"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ချိတ်ဆက်မှု ကန့်သတ်ထားသည်"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"အင်တာနက် မရှိပါ"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (အနီ-အစိမ်း)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (အပြာ-အဝါ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"အရောင်ပြင်ဆင်မှု"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"အရောင်ပြင်ဆင်ခြင်းက အရောင်ကန်းသူများအတွက် ပိုမိုမှန်ကန်သော အရောင်များဖြင့် ကြည့်နိုင်ရန် ကူညီမည်"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> မှ ကျော်၍ လုပ်ထားသည်။"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ခန့် ကျန်သည်"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားပြည့်ရန် <xliff:g id="TIME">%2$s</xliff:g> ကျန်သည်"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"မသိ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"အားသွင်းနေပါသည်"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"အားသွင်းနေပါသည်"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"အားသွင်းမနေပါ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ပလပ်ထိုးထားသောကြောင့် ယခုအားသွင်း၍ မရသေးပါ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"အပြည့်"</string> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index aca13a78c453..6a1f158b50c7 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Tilkoblet via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tilgjengelig via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Trykk for å registrere deg"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ingen internettilkobling"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Den private DNS-tjeneren kan ikke nås"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Begrenset tilkobling"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ingen internettilkobling"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (rød-grønn)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (blå-gul)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Fargekorrigering"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Med fargekorrigering kan personer med fargeblindhet se mer nøyaktige farger"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overstyres av <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> gjenstår"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> til batteriet er fulladet"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukjent"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Lader"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"lader"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Lader ikke"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Laderen er koblet til – kan ikke lade akkurat nå"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Fullt"</string> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index b2c7518eb8d6..9cd5e2079e9e 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> मार्फत जडान गरिएको"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s मार्फत उपलब्ध"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप गर्न ट्याप गर्नुहोस्"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"इन्टरनेट छैन"</string> <string name="private_dns_broken" msgid="1984159464346556931">"निजी DNS सर्भरमाथि पहुँच प्राप्त गर्न सकिँदैन"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"सीमित जडान"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"इन्टरनेटमाथिको पहुँच छैन"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"प्रोटानेमली (रातो, हरियो)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ट्रिटानोमेली (निलो-पंहेलो)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रङ्ग सुधार"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"रङ सुधार गर्नुले रङ छुट्याउन नसक्ने मान्छेलाई थप सही रङ देख्न मद्दत गर्दछ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारा अधिरोहित"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"पूर्ण चार्ज हुन <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> लाग्छ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हुँदै"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"चार्ज हुँदै"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज भइरहेको छैन"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"प्लगइन गरिएको छ, अहिले नै चार्ज गर्न सकिँदैन"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"पूर्ण चार्ज भएको स्थिति"</string> diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index c74683bdf238..8d5dea4e37d4 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Verbonden via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Beschikbaar via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tik om aan te melden"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Geen internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Geen toegang tot privé-DNS-server"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Beperkte verbinding"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Geen internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rood-groen)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (blauw-geel)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Kleurcorrectie"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Met behulp van kleurcorrectie kunnen mensen die kleurenblind zijn, nauwkeurigere kleuren te zien krijgen"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overschreven door <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Nog ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> tot opgeladen"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Opladen"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"opladen"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Wordt niet opgeladen"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Aangesloten, kan nu niet opladen"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Volledig"</string> diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index 704fc42e7ddf..c0489c1dbc8f 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ଦ୍ବାରା ସଂଯୋଗ କରାଯାଇଛି"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ମାଧ୍ୟମରେ ଉପଲବ୍ଧ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ସାଇନ୍ ଅପ୍ ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ଇଣ୍ଟର୍ନେଟ୍ ସଂଯୋଗ ନାହିଁ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ବ୍ୟକ୍ତିଗତ DNS ସର୍ଭର୍ ଆକ୍ସେସ୍ କରିହେବ ନାହିଁ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ସୀମିତ ସଂଯୋଗ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"କୌଣସି ଇଣ୍ଟରନେଟ୍ ନାହିଁ"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ପ୍ରୋଟାନୋମାଲି (ଲାଲ୍-ସବୁଜ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ନୀଳ-ହଳଦିଆ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ରଙ୍ଗ ସଠିକତା"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"କଲର୍ କରେକ୍ସନ୍ ରଙ୍ଗ ଚିହ୍ନିବାରେ ସମସ୍ୟା ଥିବା ଲୋକମାନଙ୍କୁ ଅଧିକ ସଠିକ୍ ରଙ୍ଗ ଦେଖିବାରେ ସାହାଯ୍ୟ କରିଥାଏ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ଦ୍ୱାରା ଓଭର୍ରାଇଡ୍ କରାଯାଇଛି"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ପାଖାପାଖି <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ବଳକା ଅଛି"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ଚାର୍ଜ ହେବା ପର୍ଯ୍ୟନ୍ତ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ଅଜ୍ଞାତ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ଚାର୍ଜ ହେଉଛି"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ଚାର୍ଜ ହେଉଛି"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ଚାର୍ଜ ହେଉନାହିଁ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ପ୍ଲଗ୍ରେ ଲାଗିଛି, ହେଲେ ଏବେ ଚାର୍ଜ କରିପାରିବ ନାହିଁ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ଚାର୍ଜ ସମ୍ପୂର୍ଣ୍ଣ"</string> diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index bf5a32cd33b5..7355418c4d13 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ਰਾਹੀਂ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ਰਾਹੀਂ ਉਪਲਬਧ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ਸਾਈਨ-ਅੱਪ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ਨਿੱਜੀ ਡੋਮੇਨ ਨਾਮ ਪ੍ਰਣਾਲੀ (DNS) ਸਰਵਰ \'ਤੇ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ਸੀਮਤ ਕਨੈਕਸ਼ਨ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ਲਾਲ-ਹਰਾ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ਨੀਲਾ-ਪੀਲਾ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ਰੰਗ ਸੁਧਾਈ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ਰੰਗ ਸੁਧਾਈ ਨਾਲ ਰੰਗਾਂ ਦੇ ਅੰਨ੍ਹਾਪਣ ਦੇ ਸ਼ਿਕਾਰ ਲੋਕਾਂ ਦੀ ਵਧੇਰੇ ਸਟੀਕ ਰੰਗਾਂ ਨੂੰ ਦੇਖਣ ਵਿੱਚ ਮਦਦ ਕੀਤੀ ਜਾਂਦੀ ਹੈ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ਦੁਆਰਾ ਓਵਰਰਾਈਡ ਕੀਤਾ"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ਤੱਕ ਚਾਰਜ ਹੋ ਜਾਵੇਗੀ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ਅਗਿਆਤ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ਚਾਰਜ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ਚਾਰਜ ਨਹੀਂ ਹੋ ਰਿਹਾ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ਪਲੱਗ ਲੱਗਾ ਹੋਇਆ ਹੈ, ਇਸ ਸਮੇਂ ਚਾਰਜ ਨਹੀਂ ਹੋ ਸਕਦੀ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ਪੂਰੀ"</string> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index d907760c5567..a3ccb07a74a1 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Połączenie przez: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostępne przez %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Kliknij, by się zarejestrować"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Brak internetu"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Brak dostępu do prywatnego serwera DNS"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograniczone połączenie"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Brak internetu"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (czerwony-zielony)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (niebieski-żółty)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekcja kolorów"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcja kolorów pomaga osobom z zaburzeniami rozpoznawania barw lepiej je widzieć."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nadpisana przez <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Jeszcze około <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – do naładowania <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nieznane"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Ładowanie"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ładowanie"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nie podłączony"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Podłączony. Nie można teraz ładować"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Naładowana"</string> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index e61c84e01179..f4182bfd7896 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectado via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sem Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível acessar o servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexão limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção de cor"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A correção de cores ajuda pessoas com daltonismo a ver cores de forma mais precisa"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a carga completa"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"carregando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está carregando"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectado. Não é possível carregar no momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Carregada"</string> diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index 8ceeb6333210..2f206d472659 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Ligado via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível através de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sem Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível aceder ao servidor DNS."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ligação limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção da cor"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A correção de cor ajuda as pessoas com daltonismo a ver cores mais precisas."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Resta(m) cerca de <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> até ficar carregada"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"A carregar"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"a carregar…"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está a carregar"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Ligada à corrente, não é possível carregar neste momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Completo"</string> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index e61c84e01179..f4182bfd7896 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectado via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sem Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível acessar o servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexão limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção de cor"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A correção de cores ajuda pessoas com daltonismo a ver cores de forma mais precisa"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a carga completa"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"carregando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está carregando"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectado. Não é possível carregar no momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Carregada"</string> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index 5ad8dfd136de..6e8bbd14e649 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectat prin <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponibilă prin %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Atingeți pentru a vă înscrie"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Fără conexiune la internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Serverul DNS privat nu poate fi accesat"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexiune limitată"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Fără conexiune la internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (roșu-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (albastru-galben)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corecția culorii"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Corecția culorii ajută persoanele cu daltonism să vadă culori mai exacte"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valoare înlocuită de <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Timp aproximativ rămas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> până la încărcare"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Necunoscut"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Se încarcă"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"se încarcă"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nu se încarcă"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectat, nu se poate încărca chiar acum"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Complet"</string> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 104efdc57b45..87831c76dc85 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Подключено через приложение \"<xliff:g id="NAME">%1$s</xliff:g>\"."</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Доступно через %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Нажмите, чтобы зарегистрироваться"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Нет подключения к Интернету"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Доступа к частному DNS-серверу нет."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Подключение к сети ограничено."</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нет подключения к Интернету"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (красный/зеленый)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (синий/желтый)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Коррекция цвета"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Коррекция цвета помогает пользователям с нарушениями цветового зрения лучше различать изображение на экране."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Новая настройка: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"Уровень заряда – <xliff:g id="PERCENTAGE">%1$s</xliff:g>. <xliff:g id="TIME_STRING">%2$s</xliff:g>."</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Заряда хватит примерно на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до полной зарядки"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Идет зарядка"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"заряжается"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не заряжается"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Подключено, не заряжается"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Батарея заряжена"</string> diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml index f5dad8748b24..f0a823a01ead 100644 --- a/packages/SettingsLib/res/values-si/strings.xml +++ b/packages/SettingsLib/res/values-si/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> හරහා සම්බන්ධයි"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s හරහා ලබා ගැනීමට හැකිය"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ලියාපදිංචි වීමට තට්ටු කරන්න"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"අන්තර්ජාලය නැත"</string> <string name="private_dns_broken" msgid="1984159464346556931">"පුද්ගලික DNS සේවාදායකයට ප්රවේශ වීමට නොහැකිය"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"සීමිත සම්බන්ධතාව"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"අන්තර්ජාලය නැත"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"වර්ණ දුර්වලතාවය (රතු-කොළ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"වර්ණ අන්ධතාවය (නිල්-කහ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"වර්ණ නිවැරදි කිරීම"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"වර්ණ අන්ධතාවෙන් පෙළෙන පුද්ගලයන්ට වඩාත් නිරවද්ය වර්ණ බැලීමට වර්ණ නිවැරදි කිරීම සහාය වේ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> මගින් ඉක්මවන ලදී"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ක් පමණ ඉතිරියි"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය වන තෙක් <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"නොදනී"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ආරෝපණය වෙමින්"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ආරෝපණය වේ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ආරෝපණය නොවේ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"පේනුගත කර ඇත, මේ අවස්ථාවේදී ආරෝපණය කළ නොහැකිය"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"පූර්ණ"</string> diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 527dafb95a2e..fc2ba1dee469 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Pripojené prostredníctvom siete <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"K dispozícii prostredníctvom %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Prihláste sa klepnutím"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Bez internetu"</string> <string name="private_dns_broken" msgid="1984159464346556931">"K súkromnému serveru DNS sa nepodarilo získať prístup"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Obmedzené pripojenie"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Žiadny internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomália (červená a zelená)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomália (modrá a žltá)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Úprava farieb"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcia farieb pomáha farboslepým ľuďom vidieť presnejšie farby"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Prekonané predvoľbou <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zostáva približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznáme"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíja sa"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"nabíja sa"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenabíja sa"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Pripojené, ale nie je možné nabíjať"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Nabitá"</string> diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index 481695046625..cda2e067fdc2 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezava vzpostavljena prek omrežja <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Na voljo prek: %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dotaknite se, če se želite registrirati"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ni internetne povezave"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Do zasebnega strežnika DNS ni mogoče dostopati"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Omejena povezava"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Brez internetne povezave"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (rdeča – zelena)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (modra – rumena)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Popravljanje barv"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Popravljanje barv osebam z barvno slepoto pomaga, da vidijo bolj prave barve"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Preglasila nastavitev: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Še približno <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do polne napolnjenosti"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznano"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Polnjenje"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"polnjenje"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Se ne polni"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Priključeno, trenutno ni mogoče polniti"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Poln"</string> diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index 12511dd3582c..9854017a6a25 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Lidhur përmes <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"E mundshme përmes %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Trokit për t\'u regjistruar"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nuk ka internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Serveri privat DNS nuk mund të qaset"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Lidhje e kufizuar"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nuk ka internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (e kuqe - e gjelbër)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (e kaltër - e verdhë)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korrigjimi i ngjyrës"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korrigjimi i ngjyrës i ndihmon njerëzit me daltonizëm të shohin ngjyra më të sakta"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Mbivendosur nga <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> deri sa të karikohen"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"I panjohur"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Po karikohet"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"po karikohet"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nuk po karikohet"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Në prizë, por nuk mund të ngarkohet për momentin"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"E mbushur"</string> diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index 4724be52f87c..b59a568d1685 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Повезано преко: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Доступна је преко приступне тачке %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Додирните да бисте се регистровали"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Нема интернета"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Приступ приватном DNS серверу није успео"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена веза"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нема интернета"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалија (црвено-зелено)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалија (плаво-жуто)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекција боја"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Корекција боја помаже људима који су далтонисти да прецизније виде боје"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Замењује га <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>–<xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Преостало је око <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – напуниће се за <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Пуни се"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"пуни се"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не пуни се"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Прикључено је, али пуњење тренутно није могуће"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Пуна"</string> diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index 9d1bc42b613a..f9ed6d43a310 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Anslutet via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tillgängligt via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tryck för att logga in"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Inget internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Det går inte att komma åt den privata DNS-servern."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Begränsad anslutning"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Inget internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (rött-grönt)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (blått-gult)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Färgkorrigering"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Med färgkorrigering kan färgblinda personer se mer korrekta färger"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Har åsidosatts av <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Cirka <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kvar"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> till full laddning"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Okänd"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Laddar"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"laddas"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Laddar inte"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Inkopplad, kan inte laddas just nu"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Fullt"</string> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index 1e2489f89ff6..40e025a9b61f 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Imeunganishwa kupitia <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Inapatikana kupitia %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Gusa ili ujisajili"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Hakuna intaneti"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Seva ya faragha ya DNS haiwezi kufikiwa"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Muunganisho hafifu"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Hakuna intaneti"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (nyekundu-kijani)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (samawati-manjano)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Usahihishaji wa rangi"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Urekebishaji wa rangi huwasaidia watu wenye matatizo ya kutofautisha rangi ili waone rangi nyingi sahihi"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Imetanguliwa na <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zimesalia takribani <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> hadi ijae chaji"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Haijulikani"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Inachaji"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"inachaji"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Haichaji"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Haiwezi kuchaji kwa sasa"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Imejaa"</string> diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index fcb801b112a0..36a92ed1a706 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> மூலம் இணைக்கப்பட்டது"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s வழியாகக் கிடைக்கிறது"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"பதிவு செய்யத் தட்டவும்"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"இணைய இணைப்பு இல்லை"</string> <string name="private_dns_broken" msgid="1984159464346556931">"தனிப்பட்ட DNS சேவையகத்தை அணுக இயலாது"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"வரம்பிற்கு உட்பட்ட இணைப்பு"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"இணைய இணைப்பு இல்லை"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"நிறம் அடையாளங்காண முடியாமை (சிவப்பு-பச்சை)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"நிறம் அடையாளங்காண முடியாமை (நீலம்-மஞ்சள்)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"வண்ணத்திருத்தம்"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"வண்ணத் திருத்தத்தால் நிறக்குருடு உள்ளவர்களால் வண்ணங்களை இன்னும் துல்லியமாகப் பார்க்க முடியும்"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> மூலம் மேலெழுதப்பட்டது"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"கிட்டத்தட்ட <xliff:g id="TIME_REMAINING">%1$s</xliff:g> மீதமுள்ளது"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - முழு சார்ஜாக <xliff:g id="TIME">%2$s</xliff:g> ஆகும்"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"அறியப்படாத"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"சார்ஜ் ஆகிறது"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"சார்ஜ் ஆகிறது"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"சார்ஜ் செய்யப்படவில்லை"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"செருகப்பட்டது, ஆனால் இப்போது சார்ஜ் செய்ய முடியவில்லை"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"முழுவதும் சார்ஜ் ஆனது"</string> diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index 7a8045111baa..5ea380ccdf58 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ద్వారా కనెక్ట్ చేయబడింది"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ద్వారా అందుబాటులో ఉంది"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"సైన్ అప్ చేయడానికి నొక్కండి"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ఇంటర్నెట్ లేదు"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ప్రైవేట్ DNS సర్వర్ను యాక్సెస్ చేయడం సాధ్యపడదు"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"పరిమిత కనెక్షన్"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ఇంటర్నెట్ లేదు"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ప్రొటానోమలీ (ఎరుపు-ఆకుపచ్చ రంగు)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ట్రైటనోమలీ (నీలం-పసుపు రంగు)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"రంగు సవరణ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"రంగు సవరణ అనేది వర్ణాంధత్వం ఉన్న వ్యక్తులకు మరింత ఖచ్చితమైన రంగులను చూడడానికి సహాయపడుతుంది"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ద్వారా భర్తీ చేయబడింది"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జ్ అవ్వడానికి <xliff:g id="TIME">%2$s</xliff:g> పడుతుంది"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"తెలియదు"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ఛార్జ్ అవుతోంది"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ఛార్జ్ అవుతోంది"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ఛార్జ్ కావడం లేదు"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ప్లగ్ ఇన్ చేయబడింది, ప్రస్తుతం ఛార్జ్ చేయడం సాధ్యం కాదు"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"నిండింది"</string> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index 130e1c4318a1..15e74ae1acf4 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"เชื่อมต่อแล้วผ่าน <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"พร้อมใช้งานผ่านทาง %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"แตะเพื่อลงชื่อสมัครใช้"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ไม่มีอินเทอร์เน็ต"</string> <string name="private_dns_broken" msgid="1984159464346556931">"เข้าถึงเซิร์ฟเวอร์ DNS ไม่ได้"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"การเชื่อมต่อที่จำกัด"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ไม่มีอินเทอร์เน็ต"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ตาบอดจางสีแดง (สีแดง/เขียว)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ตาบอดจางสีน้ำเงิน (สีน้ำเงิน/เหลือง)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"การแก้สี"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"การแก้ไขสีช่วยให้ผู้ที่มีอาการตาบอดสีเห็นสีต่างๆ ได้ตรงตามจริงยิ่งขึ้น"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"แทนที่โดย <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"เหลืออีกประมาณ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> จนกว่าจะชาร์จ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ไม่ทราบ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"กำลังชาร์จ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"กำลังชาร์จ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ไม่ได้ชาร์จ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"เสียบอยู่ ไม่สามารถชาร์จได้ในขณะนี้"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"เต็ม"</string> diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index bc1a40560df0..801224492c1e 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Nakakonekta sa pamamagitan ng <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available sa pamamagitan ng %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"I-tap para mag-sign up"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Walang internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Hindi ma-access ang pribadong DNS server"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limitadong koneksyon"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Walang internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (pula-berde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (asul-dilaw)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Pagtatama ng kulay"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ang pagwawasto ng kulay ay nakakatulong sa mga taong may color blindness na makita ang mga mas tamang kulay"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Na-override ng <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Humigit-kumulang <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ang natitira"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> hanggang matapos mag-charge"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Hindi Kilala"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Nagcha-charge"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"nagcha-charge"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Hindi nagcha-charge"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Nakasaksak, hindi makapag-charge sa ngayon"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Puno"</string> diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index 4e263ef86cd5..b83ffcb16565 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ile bağlandı"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s üzerinden kullanılabilir"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Kaydolmak için dokunun"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"İnternet yok"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Gizli DNS sunucusuna erişilemiyor"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Sınırlı bağlantı"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"İnternet yok"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Kırmızı renk körlüğü (kırmızı-yeşil)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Mavi renk körlüğü (mavi-sarı)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Renk düzeltme"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Renk düzeltme, renk körlüğü olan kişilerin daha doğru renkler görmelerine yardımcı olur"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tarafından geçersiz kılındı"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Yaklaşık <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kaldı"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - şarj olmaya <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Bilinmiyor"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Şarj oluyor"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"şarj oluyor"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Şarj olmuyor"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Prize takıldı, şu anda şarj olamıyor"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Dolu"</string> diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index ad9f600ae74d..5fb46f8d8640 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Підключено через додаток <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Доступ через %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Торкніться, щоб увійти"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Немає Інтернету"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Немає доступу до приватного DNS-сервера"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Обмежене з’єднання"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Немає Інтернету"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалія (червоний – зелений)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалія (синій – жовтий)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекція кольору"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Корекція кольору допомагає людям із дальтонізмом бачити точніші кольори"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Замінено на <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Залишилося приблизно <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до повного заряду"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Невідомо"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Заряджається"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"заряджається"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не заряджається"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Підключено. Не вдається зарядити"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Акумулятор заряджено"</string> diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index b995da1ad37b..c6c10f0fb8c9 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> کے ذریعے منسلک"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"دستیاب بذریعہ %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"سائن اپ کے لیے تھپتھپائیں"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"انٹرنیٹ نہیں ہے"</string> <string name="private_dns_broken" msgid="1984159464346556931">"نجی DNS سرور تک رسائی حاصل نہیں کی جا سکی"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"محدود کنکشن"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"انٹرنیٹ نہیں ہے"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (سرخ سبز)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (نیلا پیلا)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"رنگ کی اصلاح"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"رنگ کی درستگی رنگ نہ دکھائی دینے والے لوگوں کی رنگوں کو مزید درست طریقے سے دیکھنے میں مدد کرتی ہے"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> کے ذریعہ منسوخ کردیا گیا"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> چارج ہونے تک"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"نامعلوم"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"چارج ہو رہا ہے"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"چارج ہو رہا ہے"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"چارج نہیں ہو رہا ہے"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"پلگ ان ہے، ابھی چارج نہیں کر سکتے"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"مکمل"</string> diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index f02b63940115..07a6249b7e73 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> orqali ulandi"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s orqali ishlaydi"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Yozilish uchun bosing"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Internetga ulanmagansiz"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Xususiy DNS server ishlamayapti"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Cheklangan aloqa"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Internet yo‘q"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaliya (qizil/yashil)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaliya (ko‘k/sariq)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Rangni tuzatish"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ranglarni sozlash ranglarni farqlashda muammosi bor insonlarga (masalan, daltoniklarga) aniq koʻrishda yordam beradi"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> bilan almashtirildi"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Taxminan <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qoldi"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> ichida toʻliq quvvat oladi"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Noma’lum"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Quvvat olmoqda"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"quvvat olmoqda"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Quvvat olmayapti"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Ulangan, lekin quvvat olmayapti"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"To‘la"</string> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index 8fcf1f2ef21c..7e3944cb3349 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Đã kết nối qua <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Có sẵn qua %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Nhấn để đăng ký"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Không có Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Không thể truy cập máy chủ DNS riêng tư"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Kết nối giới hạn"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Không có Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Mù màu đỏ không hoàn toàn (đỏ-xanh lục)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Mù màu (xanh lam-vàng)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Sửa màu"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Tùy chọn sửa màu giúp những người bị mù màu thấy màu sắc chính xác hơn"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Bị ghi đè bởi <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Còn khoảng <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> nữa là sạc xong"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Không xác định"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Đang sạc"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"đang sạc"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Hiện không sạc"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Đã cắm nhưng không thể sạc ngay"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Đầy"</string> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 755cbeee3499..3edbb4d58b59 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"已通过<xliff:g id="NAME">%1$s</xliff:g>连接到网络"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"可通过%1$s连接"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"点按即可注册"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"无法访问互联网"</string> <string name="private_dns_broken" msgid="1984159464346556931">"无法访问私人 DNS 服务器"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"网络连接受限"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"无法访问互联网"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"红色弱视(红绿不分)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"蓝色弱视(蓝黄不分)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色彩校正"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"颜色校正功能有助于色盲用户看到更准确的颜色"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已被“<xliff:g id="TITLE">%1$s</xliff:g>”覆盖"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"大约还可使用 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>后充满电"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"正在充电"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"正在充电"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"未在充电"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"已插入电源,但是现在无法充电"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"电量充足"</string> diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index e50acf5004fa..6174fe91a4a3 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"已透過「<xliff:g id="NAME">%1$s</xliff:g>」連線"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"可透過 %1$s 連線"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"輕按即可登入"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"沒有互聯網連線"</string> <string name="private_dns_broken" msgid="1984159464346556931">"無法存取私人 DNS 伺服器"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"連線受限"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"沒有互聯網連線"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"紅色弱視 (紅綠)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"藍色弱視 (藍黃)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色彩校正"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"色彩校正有助色盲人士看到更準確的顏色"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已由「<xliff:g id="TITLE">%1$s</xliff:g>」覆寫"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"還有大約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - 還需 <xliff:g id="TIME">%2$s</xliff:g>才能充滿電"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"正在充電"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"非充電中"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"已插入電源插座,但目前無法充電"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"電量已滿"</string> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index 7df6fed4d8fd..a150d16f1b27 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"透過「<xliff:g id="NAME">%1$s</xliff:g>」連線"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"可透過 %1$s 使用"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"輕觸即可註冊"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"沒有網際網路連線"</string> <string name="private_dns_broken" msgid="1984159464346556931">"無法存取私人 DNS 伺服器"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"連線能力受限"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"沒有網際網路連線"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"紅色弱視 (紅-綠)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"藍色弱視 (藍-黃)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色彩校正"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"色彩校正可協助色盲使用者看見較準確的色彩"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已改為<xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"還能使用約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充飽電"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"充電中"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"非充電中"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"已接上電源,但現在無法充電"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"電力充足"</string> diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index 46dffbe3aa33..003dda399d18 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Ixhumeke nge-<xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Iyatholakala nge-%1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Thepha ukuze ubhalisele"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ayikho i-inthanethi"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Iseva eyimfihlo ye-DNS ayikwazi ukufinyelelwa"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Iqoqo elikhawulelwe"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ayikho i-inthanethi"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"I-Protanomaly (bomvu-luhlaza)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"I-Tritanomaly (luhlaza okwesibhakabhaka-phuzi)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Ukulungiswa kombala"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ukulungisa umbala kusiza abantu abangaboni imibala ukubona ngokuqondile"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Igitshezwe ngaphezulu yi-<xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Cishe u-<xliff:g id="TIME_REMAINING">%1$s</xliff:g> osele"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ize igcwale"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Akwaziwa"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Iyashaja"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"iyashaja"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ayishaji"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Kuxhunyiwe, ayikwazi ukushaja khona manje"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Kugcwele"</string> 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 de523d9f9bc8..f4857932064f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -3,6 +3,7 @@ package com.android.settingslib; import android.annotation.ColorInt; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -13,6 +14,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.location.LocationManager; import android.media.AudioManager; @@ -27,9 +29,13 @@ import android.telephony.AccessNetworkConstants; import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; +import androidx.annotation.NonNull; + 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; @@ -117,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; } @@ -159,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; @@ -206,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. */ @@ -424,6 +453,19 @@ public class Utils { return state; } + /** + * Get the {@link Drawable} that represents the app icon + */ + public static @NonNull Drawable getBadgedIcon( + @NonNull Context context, @NonNull ApplicationInfo appInfo) { + final UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid); + try (IconFactory iconFactory = IconFactory.obtain(context)) { + final Bitmap iconBmp = iconFactory.createBadgedIconBitmap( + appInfo.loadUnbadgedIcon(context.getPackageManager()), user, false).icon; + return new BitmapDrawable(context.getResources(), iconBmp); + } + } + private static boolean isNotInIwlan(ServiceState serviceState) { final NetworkRegistrationInfo networkRegWlan = serviceState.getNetworkRegistrationInfo( NetworkRegistrationInfo.DOMAIN_PS, diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 19c666459723..af728887c917 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -59,6 +59,7 @@ import androidx.lifecycle.OnLifecycleEvent; import com.android.internal.R; import com.android.internal.util.ArrayUtils; +import com.android.settingslib.Utils; import java.io.File; import java.io.IOException; @@ -495,7 +496,7 @@ public class ApplicationsState { return; } synchronized (entry) { - entry.ensureIconLocked(mContext, mDrawableFactory); + entry.ensureIconLocked(mContext); } } @@ -1216,7 +1217,7 @@ public class ApplicationsState { AppEntry entry = mAppEntries.get(i); if (entry.icon == null || !entry.mounted) { synchronized (entry) { - if (entry.ensureIconLocked(mContext, mDrawableFactory)) { + if (entry.ensureIconLocked(mContext)) { if (!mRunning) { mRunning = true; Message m = mMainHandler.obtainMessage( @@ -1587,10 +1588,10 @@ public class ApplicationsState { } } - boolean ensureIconLocked(Context context, IconDrawableFactory drawableFactory) { + boolean ensureIconLocked(Context context) { if (this.icon == null) { if (this.apkFile.exists()) { - this.icon = drawableFactory.getBadgedIcon(info); + this.icon = Utils.getBadgedIcon(context, info); return true; } else { this.mounted = false; @@ -1601,7 +1602,7 @@ public class ApplicationsState { // its icon. if (this.apkFile.exists()) { this.mounted = true; - this.icon = drawableFactory.getBadgedIcon(info); + this.icon = Utils.getBadgedIcon(context, info); return true; } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index ddb7341b7366..1ebe91736ba1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -153,21 +153,6 @@ public class A2dpProfile implements LocalBluetoothProfile { return mService.getDevicesMatchingConnectionStates(states); } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -187,31 +172,37 @@ public class A2dpProfile implements LocalBluetoothProfile { return mService.getActiveDevice(); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } boolean isA2dpPlaying() { if (mService == null) return false; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java index 8ca5a74652dc..c7a5bd86c1cd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java @@ -115,21 +115,6 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { BluetoothProfile.STATE_DISCONNECTING}); } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -137,31 +122,37 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } boolean isAudioPlaying() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 50d3a5daeb84..3aa35cb48c38 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -195,7 +195,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> if (newProfileState == BluetoothProfile.STATE_CONNECTED) { if (profile instanceof MapProfile) { - profile.setPreferred(mDevice, true); + profile.setEnabled(mDevice, true); } if (!mProfiles.contains(profile)) { mRemovedProfiles.remove(profile); @@ -208,7 +208,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } } else if (profile instanceof MapProfile && newProfileState == BluetoothProfile.STATE_DISCONNECTED) { - profile.setPreferred(mDevice, false); + profile.setEnabled(mDevice, false); } else if (mLocalNapRoleConnected && profile instanceof PanProfile && ((PanProfile) profile).isLocalRoleNap(mDevice) && newProfileState == BluetoothProfile.STATE_DISCONNECTED) { @@ -250,12 +250,12 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> PbapServerProfile PbapProfile = mProfileManager.getPbapProfile(); if (PbapProfile != null && isConnectedProfile(PbapProfile)) { - PbapProfile.disconnect(mDevice); + PbapProfile.setEnabled(mDevice, false); } } public void disconnect(LocalBluetoothProfile profile) { - if (profile.disconnect(mDevice)) { + if (profile.setEnabled(mDevice, false)) { if (BluetoothUtils.D) { Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile)); } @@ -342,7 +342,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> if (!ensurePaired()) { return; } - if (profile.connect(mDevice)) { + if (profile.setEnabled(mDevice, true)) { if (BluetoothUtils.D) { Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile)); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index 218d0b2dc2c0..9dfc4d986745 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -114,21 +114,6 @@ public class HeadsetProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -164,31 +149,37 @@ public class HeadsetProfile implements LocalBluetoothProfile { return mService.getAudioState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index b82fb37a770f..a3b68b4b90b3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -151,21 +151,6 @@ public class HearingAidProfile implements LocalBluetoothProfile { return mService.getDevicesMatchingConnectionStates(states); } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -185,31 +170,37 @@ public class HearingAidProfile implements LocalBluetoothProfile { return mService.getActiveDevices(); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public void setVolume(int volume) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java index 678f2e37c6bf..66225a2bffca 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java @@ -125,23 +125,6 @@ final class HfpClientProfile implements LocalBluetoothProfile { } @Override - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - @Override - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - - @Override public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -150,7 +133,7 @@ final class HfpClientProfile implements LocalBluetoothProfile { } @Override - public boolean isPreferred(BluetoothDevice device) { + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } @@ -158,7 +141,7 @@ final class HfpClientProfile implements LocalBluetoothProfile { } @Override - public int getPreferred(BluetoothDevice device) { + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } @@ -166,17 +149,20 @@ final class HfpClientProfile implements LocalBluetoothProfile { } @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java index 35600b538d4d..8a2c4f8a1230 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java @@ -16,6 +16,8 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -102,20 +104,6 @@ public class HidDeviceProfile implements LocalBluetoothProfile { } @Override - public boolean connect(BluetoothDevice device) { - // Don't invoke method in service because settings is not allowed to connect this profile. - return false; - } - - @Override - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.disconnect(device); - } - - @Override public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -124,21 +112,24 @@ public class HidDeviceProfile implements LocalBluetoothProfile { } @Override - public boolean isPreferred(BluetoothDevice device) { + public boolean isEnabled(BluetoothDevice device) { return getConnectionStatus(device) != BluetoothProfile.STATE_DISCONNECTED; } @Override - public int getPreferred(BluetoothDevice device) { + public int getConnectionPolicy(BluetoothDevice device) { return PREFERRED_VALUE; } @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; // if set preferred to false, then disconnect to the current device - if (!preferred) { - mService.disconnect(device); + if (!enabled) { + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java index 588083e73481..3c24b4a095b9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java @@ -101,20 +101,6 @@ public class HidProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -122,29 +108,37 @@ public class HidProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) != CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { - if (mService == null) return; - if (preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; + if (mService == null) { + return false; + } + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java index 4b0ca7434f9a..f609e4311082 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java @@ -35,17 +35,26 @@ public interface LocalBluetoothProfile { */ boolean isAutoConnectable(); - boolean connect(BluetoothDevice device); - - boolean disconnect(BluetoothDevice device); - int getConnectionStatus(BluetoothDevice device); - boolean isPreferred(BluetoothDevice device); + /** + * Return {@code true} if the profile is enabled, otherwise return {@code false}. + * @param device the device to query for enable status + */ + boolean isEnabled(BluetoothDevice device); - int getPreferred(BluetoothDevice device); + /** + * Get the connection policy of the profile. + * @param device the device to query for enable status + */ + int getConnectionPolicy(BluetoothDevice device); - void setPreferred(BluetoothDevice device, boolean preferred); + /** + * Enable the profile if {@code enabled} is {@code true}, otherwise disable profile. + * @param device the device to set profile status + * @param enabled {@code true} for enable profile, otherwise disable profile. + */ + boolean setEnabled(BluetoothDevice device, boolean enabled); boolean isProfileReady(); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java index ae2acbea8e4d..c72efb7eec83 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -528,14 +528,14 @@ public class LocalBluetoothProfileManager { (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { profiles.add(mMapProfile); removedProfiles.remove(mMapProfile); - mMapProfile.setPreferred(device, true); + mMapProfile.setEnabled(device, true); } if ((mPbapProfile != null) && (mPbapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { profiles.add(mPbapProfile); removedProfiles.remove(mPbapProfile); - mPbapProfile.setPreferred(device, true); + mPbapProfile.setEnabled(device, true); } if (mMapClientProfile != null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java index 7d121aaa1ad1..19cb2f59f321 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java @@ -114,21 +114,6 @@ public final class MapClientProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -136,31 +121,37 @@ public final class MapClientProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java index a96a4e73feea..75c1926683ef 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java @@ -16,6 +16,7 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; import android.bluetooth.BluetoothAdapter; @@ -112,19 +113,6 @@ public class MapProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - Log.d(TAG, "connect() - should not get called"); - return false; // MAP never connects out - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -132,31 +120,37 @@ public class MapProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (enabled) { + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java index 8e3f3edcef10..5a6e6e8b830d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java @@ -40,27 +40,23 @@ final class OppProfile implements LocalBluetoothProfile { return false; } - public boolean connect(BluetoothDevice device) { - return false; - } - - public boolean disconnect(BluetoothDevice device) { - return false; - } - public int getConnectionStatus(BluetoothDevice device) { return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle OPP } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { return false; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; // Settings app doesn't handle OPP } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + return false; } public boolean isProfileReady() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java index 6638592e8be5..767df352b70f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -83,22 +86,6 @@ public class PanProfile implements LocalBluetoothProfile { return false; } - public boolean connect(BluetoothDevice device) { - if (mService == null) return false; - List<BluetoothDevice> sinks = mService.getConnectedDevices(); - if (sinks != null) { - for (BluetoothDevice sink : sinks) { - mService.disconnect(sink); - } - } - return mService.connect(device); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - return mService.disconnect(device); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -106,16 +93,36 @@ public class PanProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { return true; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { return -1; } - public void setPreferred(BluetoothDevice device, boolean preferred) { - // ignore: isPreferred is always true for PAN + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; + if (mService == null) { + return false; + } + + if (enabled) { + final List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.setConnectionPolicy(sink, CONNECTION_POLICY_FORBIDDEN); + } + } + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + } else { + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + } + + return isEnabled; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java index 56267fc596cf..0d11293a01b7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java @@ -126,23 +126,6 @@ public final class PbapClientProfile implements LocalBluetoothProfile { BluetoothProfile.STATE_DISCONNECTING}); } - public boolean connect(BluetoothDevice device) { - Log.d(TAG,"PBAPClientProfile got connect request"); - if (mService == null) { - return false; - } - Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress()); - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - Log.d(TAG,"PBAPClientProfile got disconnect request"); - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -150,31 +133,37 @@ public final class PbapClientProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java index f7c0bf5c8c9d..9e2e4a14124a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java @@ -91,34 +91,33 @@ public class PbapServerProfile implements LocalBluetoothProfile { return false; } - public boolean connect(BluetoothDevice device) { - /*Can't connect from server */ - return false; - - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) return BluetoothProfile.STATE_DISCONNECTED; return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { return false; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { return -1; } - public void setPreferred(BluetoothDevice device, boolean preferred) { - // ignore: isPreferred is always true for PBAP + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; + if (mService == null) { + return false; + } + + if (!enabled) { + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + } + + return isEnabled; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java index 3022c5b566eb..104f1d738000 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java @@ -111,21 +111,6 @@ final class SapProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -133,31 +118,37 @@ final class SapProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public List<BluetoothDevice> getConnectedDevices() { 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 12d054e307ba..d84788b2739f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java @@ -108,9 +108,9 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall Log.d(TAG, "addConnectableA2dpDevices() device : " + cachedDevice.getName() + ", is connected : " + cachedDevice.isConnected() - + ", is preferred : " + a2dpProfile.isPreferred(device)); + + ", is enabled : " + a2dpProfile.isEnabled(device)); - if (a2dpProfile.isPreferred(device) + if (a2dpProfile.isEnabled(device) && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) { addMediaDevice(cachedDevice); } @@ -143,9 +143,9 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall Log.d(TAG, "addConnectableHearingAidDevices() device : " + cachedDevice.getName() + ", is connected : " + cachedDevice.isConnected() - + ", is preferred : " + hapProfile.isPreferred(device)); + + ", is enabled : " + hapProfile.isEnabled(device)); - if (hapProfile.isPreferred(device) + if (hapProfile.isEnabled(device) && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) { addMediaDevice(cachedDevice); } @@ -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/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 44e70f436963..954eb9bf3cc4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -16,6 +16,9 @@ package com.android.settingslib.wifi; +import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; +import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED; + import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.Nullable; @@ -178,6 +181,7 @@ public class AccessPoint implements Comparable<AccessPoint> { static final String KEY_SCANRESULTS = "key_scanresults"; static final String KEY_SCOREDNETWORKCACHE = "key_scorednetworkcache"; static final String KEY_CONFIG = "key_config"; + static final String KEY_PASSPOINT_UNIQUE_ID = "key_passpoint_unique_id"; static final String KEY_FQDN = "key_fqdn"; static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name"; static final String KEY_EAPTYPE = "eap_psktype"; @@ -214,7 +218,7 @@ public class AccessPoint implements Comparable<AccessPoint> { public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE; public static final String KEY_PREFIX_AP = "AP:"; - public static final String KEY_PREFIX_FQDN = "FQDN:"; + public static final String KEY_PREFIX_PASSPOINT_UNIQUE_ID = "PASSPOINT:"; public static final String KEY_PREFIX_OSU = "OSU:"; private final Context mContext; @@ -247,6 +251,7 @@ public class AccessPoint implements Comparable<AccessPoint> { * Information associated with the {@link PasspointConfiguration}. Only maintaining * the relevant info to preserve spaces. */ + private String mPasspointUniqueId; private String mFqdn; private String mProviderFriendlyName; private boolean mIsRoaming = false; @@ -305,6 +310,9 @@ public class AccessPoint implements Comparable<AccessPoint> { mScoredNetworkCache.put(timedScore.getScore().networkKey.wifiKey.bssid, timedScore); } } + if (savedState.containsKey(KEY_PASSPOINT_UNIQUE_ID)) { + mPasspointUniqueId = savedState.getString(KEY_PASSPOINT_UNIQUE_ID); + } if (savedState.containsKey(KEY_FQDN)) { mFqdn = savedState.getString(KEY_FQDN); } @@ -348,6 +356,7 @@ public class AccessPoint implements Comparable<AccessPoint> { */ public AccessPoint(Context context, PasspointConfiguration config) { mContext = context; + mPasspointUniqueId = config.getUniqueId(); mFqdn = config.getHomeSp().getFqdn(); mProviderFriendlyName = config.getHomeSp().getFriendlyName(); mSubscriptionExpirationTimeInMillis = config.getSubscriptionExpirationTimeInMillis(); @@ -368,6 +377,7 @@ public class AccessPoint implements Comparable<AccessPoint> { mContext = context; networkId = config.networkId; mConfig = config; + mPasspointUniqueId = config.getKey(); mFqdn = config.FQDN; setScanResultsPasspoint(homeScans, roamingScans); updateKey(); @@ -404,7 +414,7 @@ public class AccessPoint implements Comparable<AccessPoint> { if (isPasspoint()) { mKey = getKey(mConfig); } else if (isPasspointConfig()) { - mKey = getKey(mFqdn); + mKey = getKey(mPasspointUniqueId); } else if (isOsuProvider()) { mKey = getKey(mOsuProvider); } else { // Non-Passpoint AP @@ -674,19 +684,19 @@ public class AccessPoint implements Comparable<AccessPoint> { */ public static String getKey(WifiConfiguration config) { if (config.isPasspoint()) { - return getKey(config.FQDN); + return getKey(config.getKey()); } else { return getKey(removeDoubleQuotes(config.SSID), config.BSSID, getSecurity(config)); } } /** - * Returns the AccessPoint key corresponding to a Passpoint network by its FQDN. + * Returns the AccessPoint key corresponding to a Passpoint network by its unique identifier. */ - public static String getKey(String fqdn) { + public static String getKey(String passpointUniqueId) { return new StringBuilder() - .append(KEY_PREFIX_FQDN) - .append(fqdn).toString(); + .append(KEY_PREFIX_PASSPOINT_UNIQUE_ID) + .append(passpointUniqueId).toString(); } /** @@ -763,7 +773,7 @@ public class AccessPoint implements Comparable<AccessPoint> { public boolean matches(WifiConfiguration config) { if (config.isPasspoint()) { - return (isPasspoint() && config.FQDN.equals(mConfig.FQDN)); + return (isPasspoint() && config.getKey().equals(mConfig.getKey())); } if (!ssid.equals(removeDoubleQuotes(config.SSID)) @@ -1049,7 +1059,7 @@ public class AccessPoint implements Comparable<AccessPoint> { public String getConfigName() { if (mConfig != null && mConfig.isPasspoint()) { return mConfig.providerFriendlyName; - } else if (mFqdn != null) { + } else if (mPasspointUniqueId != null) { return mProviderFriendlyName; } else { return ssid; @@ -1144,11 +1154,15 @@ public class AccessPoint implements Comparable<AccessPoint> { mInfo != null ? mInfo.getRequestingPackageName() : null)); } else { // not active if (mConfig != null && mConfig.hasNoInternetAccess()) { - int messageID = mConfig.getNetworkSelectionStatus().isNetworkPermanentlyDisabled() + int messageID = + mConfig.getNetworkSelectionStatus().getNetworkSelectionStatus() + == NETWORK_SELECTION_PERMANENTLY_DISABLED ? R.string.wifi_no_internet_no_reconnect : R.string.wifi_no_internet; summary.append(mContext.getString(messageID)); - } else if (mConfig != null && !mConfig.getNetworkSelectionStatus().isNetworkEnabled()) { + } else if (mConfig != null + && (mConfig.getNetworkSelectionStatus().getNetworkSelectionStatus() + != NETWORK_SELECTION_ENABLED)) { WifiConfiguration.NetworkSelectionStatus networkStatus = mConfig.getNetworkSelectionStatus(); switch (networkStatus.getNetworkSelectionDisableReason()) { @@ -1247,7 +1261,7 @@ public class AccessPoint implements Comparable<AccessPoint> { * Return true if this AccessPoint represents a Passpoint provider configuration. */ public boolean isPasspointConfig() { - return mFqdn != null && mConfig == null; + return mPasspointUniqueId != null && mConfig == null; } /** @@ -1303,8 +1317,12 @@ public class AccessPoint implements Comparable<AccessPoint> { if (info.isOsuAp() || mOsuStatus != null) { return (info.isOsuAp() && mOsuStatus != null); } else if (info.isPasspointAp() || isPasspoint()) { + // TODO: Use TextUtils.equals(info.getPasspointUniqueId(), mConfig.getKey()) when API + // is available return (info.isPasspointAp() && isPasspoint() - && TextUtils.equals(info.getPasspointFqdn(), mConfig.FQDN)); + && TextUtils.equals(info.getPasspointFqdn(), mConfig.FQDN) + && TextUtils.equals(info.getPasspointProviderFriendlyName(), + mConfig.providerFriendlyName)); } if (networkId != WifiConfiguration.INVALID_NETWORK_ID) { @@ -1370,6 +1388,9 @@ public class AccessPoint implements Comparable<AccessPoint> { if (mNetworkInfo != null) { savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo); } + if (mPasspointUniqueId != null) { + savedState.putString(KEY_PASSPOINT_UNIQUE_ID, mPasspointUniqueId); + } if (mFqdn != null) { savedState.putString(KEY_FQDN, mFqdn); } @@ -1942,11 +1963,11 @@ public class AccessPoint implements Comparable<AccessPoint> { return; } - String fqdn = passpointConfig.getHomeSp().getFqdn(); + String uniqueId = passpointConfig.getUniqueId(); for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pairing : wifiManager.getAllMatchingWifiConfigs(wifiManager.getScanResults())) { WifiConfiguration config = pairing.first; - if (TextUtils.equals(config.FQDN, fqdn)) { + if (TextUtils.equals(config.getKey(), uniqueId)) { List<ScanResult> homeScans = pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK); List<ScanResult> roamingScans = diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java index f21e466dd8ab..2fb2481ac117 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java @@ -84,7 +84,7 @@ public class TestAccessPointBuilder { bundle.putParcelable(AccessPoint.KEY_NETWORKINFO, mNetworkInfo); bundle.putParcelable(AccessPoint.KEY_WIFIINFO, mWifiInfo); if (mFqdn != null) { - bundle.putString(AccessPoint.KEY_FQDN, mFqdn); + bundle.putString(AccessPoint.KEY_PASSPOINT_UNIQUE_ID, mFqdn); } if (mProviderFriendlyName != null) { bundle.putString(AccessPoint.KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index 26abf715369c..586c154179dc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -606,7 +606,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro List<ScanResult> cachedScanResults = new ArrayList<>(mScanResultCache.values()); - // Add a unique Passpoint AccessPoint for each Passpoint profile's FQDN. + // Add a unique Passpoint AccessPoint for each Passpoint profile's unique identifier. accessPoints.addAll(updatePasspointAccessPoints( mWifiManager.getAllMatchingWifiConfigs(cachedScanResults), cachedAccessPoints)); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java index d4e0510e15a7..d23364952357 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java @@ -16,9 +16,13 @@ package com.android.settingslib.wifi; +import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; +import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNetworkSelectionDisableReason; + import android.content.Context; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; import android.net.wifi.WifiInfo; import android.os.SystemClock; @@ -41,7 +45,9 @@ public class WifiUtils { summary.append(" f=" + Integer.toString(info.getFrequency())); } summary.append(" " + getVisibilityStatus(accessPoint)); - if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) { + if (config != null + && (config.getNetworkSelectionStatus().getNetworkSelectionStatus() + != NETWORK_SELECTION_ENABLED)) { summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString()); if (config.getNetworkSelectionStatus().getDisableTime() > 0) { long now = System.currentTimeMillis(); @@ -58,15 +64,13 @@ public class WifiUtils { } if (config != null) { - WifiConfiguration.NetworkSelectionStatus networkStatus = - config.getNetworkSelectionStatus(); - for (int index = WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE; - index < WifiConfiguration.NetworkSelectionStatus - .NETWORK_SELECTION_DISABLED_MAX; index++) { - if (networkStatus.getDisableReasonCounter(index) != 0) { - summary.append(" " + WifiConfiguration.NetworkSelectionStatus - .getNetworkDisableReasonString(index) + "=" - + networkStatus.getDisableReasonCounter(index)); + NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus(); + for (int reason = 0; reason <= getMaxNetworkSelectionDisableReason(); reason++) { + if (networkStatus.getDisableReasonCounter(reason) != 0) { + summary.append(" ") + .append(NetworkSelectionStatus.getNetworkDisableReasonString(reason)) + .append("=") + .append(networkStatus.getDisableReasonCounter(reason)); } } } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java index 42f3cbb04cf8..bcabec858487 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java @@ -465,6 +465,8 @@ public class AccessPointTest { WifiConfiguration.NetworkSelectionStatus status = mock(WifiConfiguration.NetworkSelectionStatus.class); when(configuration.getNetworkSelectionStatus()).thenReturn(status); + when(status.getNetworkSelectionStatus()).thenReturn( + WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED); when(status.getNetworkSelectionDisableReason()).thenReturn( WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD); AccessPoint ap = new AccessPoint(mContext, configuration); @@ -1370,13 +1372,13 @@ public class AccessPointTest { public void testOsuAccessPointSummary_showsProvisioningUpdates() { OsuProvider provider = createOsuProvider(); Context spyContext = spy(new ContextWrapper(mContext)); - AccessPoint osuAccessPoint = new AccessPoint(spyContext, provider, - mScanResults); + when(spyContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager); Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>(); osuProviderConfigMap.put(provider, null); - when(spyContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager); when(mMockWifiManager.getMatchingPasspointConfigsForOsuProviders( Collections.singleton(provider))).thenReturn(osuProviderConfigMap); + AccessPoint osuAccessPoint = new AccessPoint(spyContext, provider, + mScanResults); osuAccessPoint.setListener(mMockAccessPointListener); 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/bluetooth/A2dpSinkProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java index ccb6646cf683..9bb2f22ddbcf 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothA2dpSink; @@ -68,18 +64,6 @@ public class A2dpSinkProfileTest { } @Test - public void connect_shouldConnectBluetoothA2dpSink() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothA2dpSink() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java index 91807609df1a..d121e0b2d2fb 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -68,18 +64,6 @@ public class HfpClientProfileTest { } @Test - public void connect_shouldConnectBluetoothHeadsetClient() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothHeadsetClient() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java index f38af70c7498..3665d9c10165 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java @@ -18,7 +18,6 @@ package com.android.settingslib.bluetooth; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -65,17 +64,6 @@ public class HidDeviceProfileTest { } @Test - public void connect_shouldReturnFalse() { - assertThat(mProfile.connect(mBluetoothDevice)).isFalse(); - } - - @Test - public void disconnect_shouldDisconnectBluetoothHidDevice() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java index 1425c381256b..25031a62294c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -68,18 +64,6 @@ public class MapClientProfileTest { } @Test - public void connect_shouldConnectBluetoothMapClient() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothMapClient() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java index 15f560bef73e..4305a3bc25a3 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -68,18 +64,6 @@ public class PbapClientProfileTest { } @Test - public void connect_shouldConnectBluetoothPbapClient() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothPbapClient() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java index 4f978a822890..e460eaf16bbf 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -67,18 +63,6 @@ public class SapProfileTest { } @Test - public void connect_shouldConnectBluetoothSap() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothSap() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); 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/BluetoothMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java index f27cef9620c5..0ee5ea8a2eed 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java @@ -96,7 +96,7 @@ public class BluetoothMediaManagerTest { when(mA2dpProfile.getConnectableDevices()).thenReturn(devices); when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice); when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - when(mA2dpProfile.isPreferred(bluetoothDevice)).thenReturn(true); + when(mA2dpProfile.isEnabled(bluetoothDevice)).thenReturn(true); assertThat(mMediaManager.mMediaDevices).isEmpty(); mMediaManager.startScan(); @@ -113,7 +113,7 @@ public class BluetoothMediaManagerTest { when(mA2dpProfile.getConnectableDevices()).thenReturn(devices); when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice); when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE); - when(mA2dpProfile.isPreferred(bluetoothDevice)).thenReturn(true); + when(mA2dpProfile.isEnabled(bluetoothDevice)).thenReturn(true); assertThat(mMediaManager.mMediaDevices).isEmpty(); mMediaManager.startScan(); @@ -141,7 +141,7 @@ public class BluetoothMediaManagerTest { when(mHapProfile.getConnectableDevices()).thenReturn(devices); when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice); when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - when(mHapProfile.isPreferred(bluetoothDevice)).thenReturn(true); + when(mHapProfile.isEnabled(bluetoothDevice)).thenReturn(true); assertThat(mMediaManager.mMediaDevices).isEmpty(); mMediaManager.startScan(); 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/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 9934e59d8d56..cd62420f39ac 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -425,7 +425,8 @@ final class SettingsState { } newState = oldState; } else { - newState = new Setting(name, value, makeDefault, packageName, tag); + newState = new Setting(name, value, makeDefault, packageName, tag, + forceNonSystemPackage); mSettings.put(name, newState); } @@ -1173,11 +1174,15 @@ final class SettingsState { public Setting(String name, String value, boolean makeDefault, String packageName, String tag) { + this(name, value, makeDefault, packageName, tag, false); + } + + Setting(String name, String value, boolean makeDefault, String packageName, + String tag, boolean forceNonSystemPackage) { this.name = name; // overrideableByRestore = true as the first initialization isn't considered a // modification. - update(value, makeDefault, packageName, tag, false, - /* overrideableByRestore */ true); + update(value, makeDefault, packageName, tag, forceNonSystemPackage, true); } public Setting(String name, String value, String defaultValue, diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java index d67a9bc97cb8..8ff595b3bc53 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java @@ -33,7 +33,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Log; -import org.junit.Ignore; import org.junit.Test; import java.util.concurrent.atomic.AtomicBoolean; @@ -692,125 +691,4 @@ public class SettingsProviderTest extends BaseSettingsProviderTest { cursor.close(); } } - - @Test - @Ignore("b/140250974") - public void testLocationModeChanges_viaFrontEndApi() throws Exception { - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_OFF), - UserHandle.USER_SYSTEM); - assertEquals( - "Wrong location providers", - "", - getStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - UserHandle.USER_SYSTEM)); - - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_BATTERY_SAVING), - UserHandle.USER_SYSTEM); - assertEquals( - "Wrong location providers", - "network", - getStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - UserHandle.USER_SYSTEM)); - - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY), - UserHandle.USER_SYSTEM); - assertEquals( - "Wrong location providers", - "gps,network", - getStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - UserHandle.USER_SYSTEM)); - } - - @Test - @Ignore("b/140250974") - public void testLocationProvidersAllowed_disableProviders() throws Exception { - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY), - UserHandle.USER_SYSTEM); - - // Disable providers that were enabled - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "-gps,-network"); - assertEquals( - "Wrong location providers", - "", - queryStringViaProviderApi( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED)); - - // Disable a provider that was not enabled - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "-test"); - assertEquals( - "Wrong location providers", - "", - queryStringViaProviderApi( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED)); - } - - @Test - @Ignore("b/140250974") - public void testLocationProvidersAllowed_enableAndDisable() throws Exception { - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_OFF), - UserHandle.USER_SYSTEM); - - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "+gps,+network,+test"); - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test"); - - assertEquals( - "Wrong location providers", - "gps,network", - queryStringViaProviderApi( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED)); - } - - @Test - @Ignore("b/140250974") - public void testLocationProvidersAllowedLocked_invalidInput() throws Exception { - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_OFF), - UserHandle.USER_SYSTEM); - - // update providers with a invalid string - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "+gps, invalid-string"); - - // Verifies providers list does not change - assertEquals( - "Wrong location providers", - "", - queryStringViaProviderApi( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED)); - } } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 6a89b71be897..5946f2158ac8 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -223,6 +223,11 @@ <!-- permissions required for CTS test - PhoneStateListenerTest --> <uses-permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH" /> + <!-- Permissions required for ganting and logging --> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/> + <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/> + <!-- Permission required for CTS test - UiModeManagerTest --> <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/> @@ -303,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/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 139a8c3f7411..1fe967b4750d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -645,7 +645,6 @@ <activity android:name=".controls.management.ControlsProviderSelectorActivity" android:label="Controls Providers" android:theme="@style/Theme.ControlsManagement" - android:exported="true" android:showForAllUsers="true" android:excludeFromRecents="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" diff --git a/packages/SystemUI/docs/QS-QQS.png b/packages/SystemUI/docs/QS-QQS.png Binary files differnew file mode 100644 index 000000000000..02de479cb8c0 --- /dev/null +++ b/packages/SystemUI/docs/QS-QQS.png diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md new file mode 100644 index 000000000000..b48ba6708313 --- /dev/null +++ b/packages/SystemUI/docs/qs-tiles.md @@ -0,0 +1,377 @@ +# Quick Settings Tiles (almost all there is to know about them) + +[TOC] + +## About this document + +This document is a more or less comprehensive summary of the state and infrastructure used by Quick Settings tiles. It provides descriptions about the lifecycle of a tile, how to create new tiles and how SystemUI manages and displays tiles, among other topics. + +## What are Quick Settings Tiles? + +Quick Settings (from now on, QS) is the expanded panel that contains shortcuts for the user to toggle many settings. This is opened by expanding the notification drawer twice (or once when phone is locked). Quick Quick Settings (QQS) is the smaller panel that appears on top of the notifications before expanding twice and contains some of the toggles with no text. + +Each of these toggles that appear either in QS or QQS are called Quick Settings Tiles (or tiles for short). They allow the user to enable or disable settings quickly and sometimes provides access to more comprehensive settings pages. + +The following image shows QQS on the left and QS on the right, with the tiles highlighted. + + + +QS Tiles usually depend on one or more Controllers that bind the tile with the necessary service. Controllers are obtained by the backend and used for communication between the user and the device. + +### A note on multi-user support + +All the classes described in this document that live inside SystemUI are only instantiated in the process of user 0. The different controllers that back the QS Tiles (also instantiated just in user 0) are user aware and provide an illusion of different instances for different users. + +For an example on this, see [`RotationLockController`](/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java). This controller for the `RotationLockTile` listens to changes in all users. + +## What are tiles made of? + +### Tile backend + +QS Tiles are composed of the following backend classes. + +* [`QSTile`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java): Interface providing common behavior for all Tiles. This class also contains some useful utility classes needed for the tiles. + * `Icon`: Defines the basic interface for an icon as used by the tiles. + * `State`: Encapsulates the state of the Tile in order to communicate between the backend and the UI. +* [`QSTileImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java): Abstract implementation of `QSTile`, providing basic common behavior for all tiles. Also implements extensions for different types of `Icon`. All tiles currently defined in SystemUI subclass from this implementation. +* [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles): Each tile from SystemUI is defined here by a class that extends `QSTileImpl`. These implementations connect to corresponding controllers. The controllers serve two purposes: + * track the state of the device and notify the tile when a change has occurred (for example, bluetooth connected to a device) + * accept actions from the tiles to modify the state of the phone (for example, enablind and disabling wifi). +* [`CustomTile`](/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java): Equivalent to the tiles in the previous item, but used for 3rd party tiles. In depth information to be found in [`CustomTile`](#customtile) + +All the elements in SystemUI that work with tiles operate on `QSTile` or the interfaces defined in it. However, all the current implementations of tiles in SystemUI subclass from `QSTileImpl`, as it takes care of many common situations. Throughout this document, we will focus on `QSTileImpl` as examples of tiles. + +The interfaces in `QSTile` as well as other interfaces described in this document can be used to implement plugins to add additional tiles or different behavior. For more information, see [plugins.md](plugins.md) + +#### Tile State + +Each tile has an associated `State` object that is used to communicate information to the corresponding view. The base class `State` has (among others) the following fields: + +* **`state`**: one of `Tile#STATE_UNAVAILABLE`, `Tile#STATE_ACTIVE`, `Tile#STATE_INACTIVE`. +* **`icon`**; icon to display. It may depend on the current state. +* **`label`**: usually the name of the tile. +* **`secondaryLabel`**: text to display in a second line. Usually extra state information. +* **`contentDescription`** +* **`expandedAccessibilityClassName`**: usually `Switch.class.getName()` for boolean Tiles. This will make screen readers read the current state of the tile as well as the new state when it's toggled. For this, the Tile has to use `BooleanState`. +* **`handlesLongClick`**: whether the Tile will handle long click. If it won't, it should be set to `false` so it will not be announced for accessibility. + +Setting any of these fields during `QSTileImpl#handleUpdateState` will update the UI after it. + +Additionally. `BooleanState` has a `value` boolean field that usually would be set to `state == Tile#STATE_ACTIVE`. This is used by accessibility services along with `expandedAccessibilityClassName`. + +#### SystemUI tiles + +Each tile defined in SystemUI extends `QSTileImpl`. This abstract class implements some common functions and leaves others to be implemented by each tile, in particular those that determine how to handle different events (refresh, click, etc.). + +For more information on how to implement a tile in SystemUI, see [Implementing a SystemUI tile](#implementing-a-systemui-tile). + +### Tile views + +Each Tile has a couple of associated views for displaying it in QS and QQS. These views are updated after the backend updates the `State` using `QSTileImpl#handleUpdateState`. + +* **[`com.android.systemui.plugins.qs.QSTileView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java)**: Abstract class that provides basic Tile functionality. These allows external [Factories](#qsfactory) to create Tiles. +* **[`QSTileBaseView`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java)**: Implementation of `QSTileView` used in QQS that takes care of most of the features of the view: + * Holding the icon + * Background color and shape + * Ripple + * Click listening +* **[`QSTileView`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java)**: Extends `QSTileBaseView`to add label support. Used in QS. +* **[`QSIconView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java)** +* **[`QSIconViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java)** + +#### QSIconView and QSIconViewImpl + +`QSIconView` is an interface that define the basic actions that icons have to respond to. Its base implementation in SystemUI is `QSIconViewImpl` and it and its subclasses are used by all QS tiles. + +This `ViewGroup` is a container for the icon used in each tile. It has methods to apply the current `State` of the tile, modifying the icon (color and animations). Classes that inherit from this can add other details that are modified when the `State` changes. + +Each `QSTileImpl` can specify that they use a particular implementation of this class when creating an icon. + +### How are the backend and the views related? + +The backend of the tiles (all the implementations of `QSTileImpl`) communicate with the views by using a `State`. The backend populates the state, and then the view maps the state to a visual representation. + +It's important to notice that the state of the tile (internal or visual) is not directly modified by a user action like clicking on the tile. Instead, acting on a tile produces internal state changes on the device, and those trigger the changes on the tile state and UI. + +When a container for tiles (`QuickQSPanel` or `QSPanel`) has to display tiles, they create a [`TileRecord`](/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java). This associates the corresponding `QSTile` with its `QSTileView`, doing the following: + +* Create the corresponding `QSTileView` to display in that container. +* Create a callback for `QSTile` to call when its state changes. Note that a single tile will normally have up to two callbacks: one for QS and one for QQS. + +#### Life of a tile click + +This is a brief run-down of what happens when a user clicks on a tile. Internal changes on the device (for example, changes from Settings) will trigger this process starting in step 3. Throughout this section, we assume that we are dealing with a `QSTileImpl`. + +1. User clicks on tile. The following calls happen in sequence: + 1. `QSTileBaseView#onClickListener`. + 2. `QSTile#click`. + 3. `QSTileImpl#handleClick`. This last call sets the new state for the device by using the associated controller. +2. State in the device changes. This is normally outside of SystemUI's control. +3. Controller receives a callback (or `Intent`) indicating the change in the device. The following calls happen: + 1. `QSTileImpl#refreshState`, maybe passing an object with necessary information regarding the new state. + 2. `QSTileImpl#handleRefreshState` +4. `QSTileImpl#handleUpdateState` is called to update the state with the new information. This information can be obtained both from the `Object` passed to `refreshState` as well as from the controller. +5. If the state has changed (in at least one element), `QSTileImpl#handleStateChanged` is called. This will trigger a call to all the associated `QSTile.Callback#onStateChanged`, passing the new `State`. +6. `QSTileView#onStateChanged` is called and this calls `QSTileView#handleStateChanged`. This method maps the state into the view: + * The tile is rippled and the color changes to match the new state. + * `QSIconView.setIcon` is called to apply the correct state to the icon and the correct icon to the view. + * If the tile is a `QSTileView` (in expanded QS), the labels are changed. + +## Third party tiles (TileService) + +A third party tile is any Quick Settings tile that is provided by an app (that's not SystemUI). This is implemented by developers subclassing [`TileService`](/core/java/android/service/quicksettings/TileService.java) and interacting with its API. + +### API classes + +The classes that define the public API are in [core/java/android/service/quicksettings](core/java/android/service/quicksettings). + +#### Tile + +Parcelable class used to communicate information about the state between the external app and SystemUI. The class supports the following fields: + +* Label +* Subtitle +* Icon +* State (`Tile#STATE_ACTIVE`, `Tile#STATE_INACTIVE`, `Tile#STATE_UNAVAILABLE`) +* Content description + +Additionally, it provides a method to notify SystemUI that the information may have changed and the tile should be refreshed. + +#### TileService + +This is an abstract Service that needs to be implemented by the developer. The Service manifest must have the permission `android.permission.BIND_QUICK_SETTINGS_TILE` and must respond to the action `android.service.quicksettings.action.QS_TILE`. This will allow SystemUI to find the available tiles and display them to the user. + +The implementer is responsible for creating the methods that will respond to the following calls from SystemUI: + +* **`onTileAdded`**: called when the tile is added to QS. +* **`onTileRemoved`**: called when the tile is removed from QS. +* **`onStartListening`**: called when QS is opened and the tile is showing. This marks the start of the window when calling `getQSTile` is safe and will provide the correct object. +* **`onStopListening`**: called when QS is closed or the tile is no longer visible by the user. This marks the end of the window described in `onStartListening`. +* **`onClick`**: called when the user clicks on the tile. + +Additionally, the following final methods are provided: + +* ```java + public final Tile getQsTile() + ``` + + Provides the tile object that can be modified. This should only be called in the window between `onStartListening` and `onStopListening`. + +* ```java + public final boolean isLocked() + + public final boolean isSecure() + ``` + + Provide information about the secure state of the device. This can be used by the tile to accept or reject actions on the tile. + +* ```java + public final void unlockAndRun(Runnable) + ``` + + May prompt the user to unlock the device if locked. Once the device is unlocked, it runs the given `Runnable`. + +* ```java + public final void showDialog(Dialog) + ``` + + Shows the provided dialog. + +##### Binding + +When the Service is bound, a callback Binder is provided by SystemUI for all the callbacks, as well as an identifier token (`Binder`). This token is used in the callbacks to identify this `TileService` and match it to the corresponding tile. + +The tiles are bound once immediately on creation. After that, the tile is bound whenever it should start listening. When the panels are closed, and the tile is set to stop listening, it will be unbound after a delay of `TileServiceManager#UNBIND_DELAY` (30s), if it's not set to listening again. + +##### Active tile + +A `TileService` can be declared as an active tile by adding specific meta-data to its manifest (see [TileService#META_DATA_ACTIVE_TILE](https://developer.android.com/reference/android/service/quicksettings/TileService#META_DATA_ACTIVE_TILE)). In this case, it won't receive a call of `onStartListening` when QS is opened. Instead, the tile must request listening status by making a call to `TileService#requestListeningState` with its component name. This will initiate a window that will last until the tile is updated. + +The tile will also be granted listening status if it's clicked by the user. + +### SystemUI classes + +The following sections describe the classes that live in SystemUI to support third party tiles. These classes live in [SystemUI/src/com/android/systemui/qs/external](/packages/SystemUI/src/com/android/systemui/qs/external/) + +#### CustomTile + +This class is an subclass of `QSTileImpl` to be used with third party tiles. It provides similar behavior to SystemUI tiles as well as handling exclusive behavior like lifting default icons and labels from the application manifest. + +#### TileServices + +This class is the central controller for all tile services that are currently in Quick Settings as well as provides the support for starting new ones. It is also an implementation of the `Binder` that receives all calls from current `TileService` components and dispatches them to SystemUI or the corresponding `CustomTile`. + +Whenever a binder call is made to this class, it matches the corresponding token assigned to the `TileService` with the `ComponentName` and verifies that the call comes from the right UID to prevent spoofing. + +As this class is the only one that's aware of every `TileService` that's currently bound, it is also in charge of requesting some to be unbound whenever there is a low memory situation. + +#### TileLifecycleManager + +This class is in charge of binding and unbinding to a particular `TileService` when necessary, as well as sending the corresponding binder calls. It does not decide whether the tile should be bound or unbound, unless it's requested to process a message. It additionally handles errors in the `Binder` as well as changes in the corresponding component (like updates and enable/disable). + +The class has a queue that stores requests while the service is not bound, to be processed as soon as the service is bound. + +Each `TileService` gets assigned an exclusive `TileLifecycleManager` when its corresponding tile is added to the set of current ones and kept as long as the tile is available to the user. + +#### TileServiceManager + +Each instance of this class is an intermediary between the `TileServices` controller and a `TileLifecycleManager` corresponding to a particular `TileService`. + +This class handles management of the service, including: + +* Deciding when to bind and unbind, requesting it to the `TileLifecycleManager`. +* Relaying messages to the `TileService` through the `TileLifecycleManager`. +* Determining the service's bind priority (to deal with OOM situations). +* Detecting when the package/component has been removed in order to remove the tile and references to it. + +## How are tiles created/instantiated? + +This section describes the classes that aid in the creation of each tile as well as the complete lifecycle of a tile. First we describe two important interfaces/classes. + +### QSTileHost + +This class keeps track of the tiles selected by the current user (backed in the Secure Setting `sysui_qs_tiles`) to be displayed in Quick Settings. Whenever the value of this setting changes (or on device start), the whole list of tiles is read. This is compared with the current tiles, destroying unnecessary ones and creating needed ones. + +It additionally provides a point of communication between the tiles and the StatusBar, for example to open it and collapse it. And a way for the StatusBar service to add tiles (only works for `CustomTile`). + +#### Tile specs + +Each single tile is identified by a spec, which is a unique String for that type of tile. The current tiles are stored as a Setting string of comma separated values of these specs. Additionally, the default tiles (that appear on a fresh system) configuration value is stored likewise. + +SystemUI tile specs are usually a single simple word identifying the tile (like `wifi` or `battery`). Custom tile specs are always a string of the form `custom(...)` where the ellipsis is a flattened String representing the `ComponentName` for the corresponding `TileService`. + +### QSFactory + +This interface provides a way of creating tiles and views from a spec. It can be used in plugins to provide different definitions for tiles. + +In SystemUI there is only one implementation of this factory and that is the default factory (`QSFactoryImpl`) in `QSTileHost`. + +#### QSFactoryImpl + +This class implements two methods as specified in the `QSFactory` interface: + +* ```java + public QSTile createTile(String) + ``` + + Creates a tile (backend) from a given spec. The factory has providers for all of the SystemUI tiles, returning one when the correct spec is used. + + If the spec is not recognized but it has the `custom(` prefix, the factory tries to create a `CustomTile` for the component in the spec. This could fail (the component is not a valid `TileService` or is not enabled) and will be detected later when the tile is polled to determine if it's available. + +* ```java + public QSTileView createTileView(QSTile, boolean) + ``` + + Creates a view for the corresponding `QSTile`. The second parameter determines if the view that is created should be a collapsed one (for using in QQS) or not (for using in QS). + +### Lifecycle of a Tile + +We describe first the parts of the lifecycle that are common to SystemUI tiles and third party tiles. Following that, there will be a section with the steps that are exclusive to third party tiles. + +1. The tile is added through the QS customizer by the user. This will immediately save the new list of tile specs to the Secure Setting `sysui_qs_tiles`. This step could also happend if `StatusBar` adds tiles (either through adb, or through its service interface as with the `DevelopmentTiles`). +2. This triggers a "setting changed" that is caught by `QSTileHost`. This class processes the new value of the setting and finds out that there is a new spec in the list. Alternatively, when the device is booted, all tiles in the setting are considered as "new". +3. `QSTileHost` calls all the available `QSFactory` classes that it has registered in order to find the first one that will be able to create a tile with that spec. Assume that `QSFactoryImpl` managed to create the tile, which is some implementation of `QSTile` (either a SystemUI subclass of `QSTileImpl` or a `CustomTile`). If the tile is available, it's stored in a map and things proceed forward. +4. `QSTileHost` calls its callbacks indicating that the tiles have changed. In particular, `QSPanel` and `QuickQSPanel` receive this call with the full list of tiles. We will focus on these two classes. +5. For each tile in this list, a `QSTileView` is created (collapsed or expanded) and attached to a `TileRecord` containing the tile backend and the view. Additionally: + * a callback is attached to the tile to communicate between the backend and the view or the panel. + * the click listeners in the tile are attached to those of the view. +6. The tile view is added to the corresponding layout. + +When the tile is removed from the list of current tiles, all these classes are properly disposed including removing the callbacks and making sure that the backends remove themselves from the controllers they were listening to. + +#### Lifecycle of a CustomTile + +In step 3 of the previous process, when a `CustomTile` is created, additional steps are taken to ensure the proper binding to the service as described in [Third party tiles (TileService)](#third-party-tiles-tileservice). + +1. The `CustomTile` obtains the `TileServices` class from the `QSTileHost` and request the creation of a `TileServiceManager` with its token. As the spec for the `CustomTile` contains the `ComponentName` of the associated service, this can be used to bind to it. +2. The `TileServiceManager` creates its own `TileLifecycleManager` to take care of binding to the service. +3. `TileServices` creates maps between the token, the `CustomTile`, the `TileServiceManager`, the token and the `ComponentName`. + +## Implementing a tile + +This section describes necessary and recommended steps when implementing a Quick Settings tile. Some of them are optional and depend on the requirements of the tile. + +### Implementing a SystemUI tile + +1. Create a class (preferably in [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles)) implementing `QSTileImpl` with a particular type of `State` as a parameter. +2. Create an injectable constructor taking a `QSHost` and whichever classes are needed for the tile's operation. Normally this would be other SystemUI controllers. +3. Implement the methods described in [Abstract methods in QSTileImpl](#abstract-methods-in-qstileimpl). Look at other tiles for help. Some considerations to have in mind: + * If the tile will not support long click (like the `FlashlightTile`), set `state.handlesLongClick` to `false` (maybe in `newTileState`). + * Changes to the tile state (either from controllers or from clicks) should call `refreshState`. + * Use only `handleUpdateState` to modify the values of the state to the new ones. This can be done by polling controllers or through the `arg` parameter. + * If the controller is not a `CallbackController`, respond to `handleSetListening` by attaching/dettaching from controllers. + * Implement `isAvailable` so the tile will not be created when it's not necessary. +4. In `QSFactoryImpl`: + * Inject a `Provider` for the tile created before. + * Add a case to the `switch` with a unique String spec for the chosen tile. +5. In [SystemUI/res/values/config.xml](/packages/SystemUI/res/values/config.xml), modify `quick_settings_tiles_stock` and add the spec defined in the previous step. If necessary, add it also to `quick_settings_tiles_default`. The first one contains a list of all the tiles that SystemUI knows how to create (to show to the user in the customization screen). The second one contains only the default tiles that the user will experience on a fresh boot or after they reset their tiles. + +#### Abstract methods in QSTileImpl + +Following are methods that need to be implemented when creating a new SystemUI tile. `TState` is a type variable of type `State`. + +* ```java + public TState newTileState() + ``` + + Creates a new `State` for this tile to use. Each time the state changes, it is copied into a new one and the corresponding fields are modified. The framework provides `State`, `BooleanState` (has an on and off state and provides this as a content description), `SignalState` (`BooleanState` with `activityIn` and `activityOut`), and `SlashState` (can be rotated or slashed through). + + If a tile has special behavior (no long click, no ripple), it can be set in its state here. + +* ```java + public void handleSetListening(boolean) + ``` + + Initiates or terminates listening behavior, like listening to Callbacks from controllers. This gets triggered when QS is expanded or collapsed (i.e., when the tile is visible and actionable). Most tiles (like `WifiTile`) do not implement this. Instead, Tiles are LifecycleOwner and are marked as `RESUMED` or `DESTROYED` in `QSTileImpl#handleListening` and handled as part of the lifecycle of [CallbackController](/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java) + +* ```java + public QSIconView createTileView(Context) + ``` + + Allows a Tile to use a `QSIconView` different from `QSIconViewImpl` (see [Tile views](#tile-views)), which is the default defined in `QSTileImpl` + +* ```java + public Intent getLongClickIntent() + ``` + + Determines the `Intent` launched when the Tile is long pressed. + +* ```java + protected void handleClick() + + protected void handleSecondaryClick() + + protected void handleLongClick() + ``` + + Handles what to do when the Tile is clicked. In general, a Tile will make calls to its controller here and maybe update its state immediately (by calling `QSTileImpl#refreshState`). A Tile can also decide to ignore the click here, if it's `Tile#STATE_UNAVAILABLE`. + + By default long click redirects to click and long click launches the intent defined in `getLongClickIntent`. + +* ```java + protected void handleUpdateState(TState, Object) + ``` + + Updates the `State` of the Tile based on the state of the device as provided by the respective controller. It will be called every time the Tile becomes visible, is interacted with or `QSTileImpl#refreshState` is called. After this is done, the updated state will be reflected in the UI. + +* ```java + public int getMetricsCategory() + ``` + + Identifier for this Tile, as defined in [proto/src/metrics_constants/metrics_constants.proto](/proto/src/metrics_constants/metrics_constants.proto). This is used to log events related to this Tile. + +* ```java + public boolean isAvailable() + ``` + + Determines if a Tile is available to be used (for example, disable `WifiTile` in devices with no Wifi support). If this is false, the Tile will be destroyed upon creation. + +* ```java + public CharSequence getTileLabel() + ``` + + Provides a default label for this Tile. Used by the QS Panel customizer to show a name next to each available tile. + +### Implementing a third party tile + +For information about this, use the Android Developer documentation for [TileService](https://developer.android.com/reference/android/service/quicksettings/TileService).
\ No newline at end of file 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/drawable-nodpi/android_11_dial.xml b/packages/SystemUI/res/drawable-nodpi/android_11_dial.xml new file mode 100644 index 000000000000..73fd37f1bdd6 --- /dev/null +++ b/packages/SystemUI/res/drawable-nodpi/android_11_dial.xml @@ -0,0 +1,63 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path + android:pathData="M77.773,51.064h-1.583c-0.217,0 -0.393,-0.176 -0.393,-0.393v-1.46c0,-0.217 0.176,-0.393 0.393,-0.393h3.466c0.217,0 0.393,0.176 0.393,0.393v9.921c0,0.217 -0.176,0.393 -0.393,0.393h-1.49c-0.217,0 -0.393,-0.176 -0.393,-0.393V51.064z" + android:fillColor="#F86734"/> + <path + android:pathData="M83.598,51.064h-1.583c-0.217,0 -0.393,-0.176 -0.393,-0.393v-1.46c0,-0.217 0.176,-0.393 0.393,-0.393h3.466c0.217,0 0.393,0.176 0.393,0.393v9.921c0,0.217 -0.176,0.393 -0.393,0.393h-1.49c-0.217,0 -0.393,-0.176 -0.393,-0.393V51.064z" + android:fillColor="#F86734"/> + <path + android:pathData="M70.044,75.974m-0.644,0a0.644,0.644 0,1 1,1.288 0a0.644,0.644 0,1 1,-1.288 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M56.896,80.985m-0.718,0a0.718,0.718 0,1 1,1.436 0a0.718,0.718 0,1 1,-1.436 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M43.408,78.881m-0.795,0a0.795,0.795 0,1 1,1.59 0a0.795,0.795 0,1 1,-1.59 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M32.419,70.115m-0.874,0a0.874,0.874 0,1 1,1.748 0a0.874,0.874 0,1 1,-1.748 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M27.306,56.992m-0.954,0a0.954,0.954 0,1 1,1.908 0a0.954,0.954 0,1 1,-1.908 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M29.313,43.489m-1.036,0a1.036,1.036 0,1 1,2.072 0a1.036,1.036 0,1 1,-2.072 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M37.988,32.445m-1.118,0a1.118,1.118 0,1 1,2.236 0a1.118,1.118 0,1 1,-2.236 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M51.137,27.064m-1.201,0a1.201,1.201 0,1 1,2.402 0a1.201,1.201 0,1 1,-2.402 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M64.553,28.868m-1.284,0a1.284,1.284 0,1 1,2.568 0a1.284,1.284 0,1 1,-2.568 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M75.522,37.652m-1.368,0a1.368,1.368 0,1 1,2.736 0a1.368,1.368 0,1 1,-2.736 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M87.942,115.052l-47.557,-47.557l26.869,-26.87l47.557,47.558z"> + <aapt:attr name="android:fillColor"> + <gradient + android:startY="56.087" + android:startX="55.8464" + android:endY="100.0297" + android:endX="99.7891" + android:type="linear"> + <item android:offset="0" android:color="#3F000000"/> + <item android:offset="1" android:color="#00000000"/> + </gradient> + </aapt:attr> + </path> + <path + android:pathData="M53.928,54.17m-18.999,0a18.999,18.999 0,1 1,37.998 0a18.999,18.999 0,1 1,-37.998 0" + android:fillColor="#3ddc84"/> + <path + android:pathData="M66.353,54.17m-3.185,0a3.185,3.185 0,1 1,6.37 0a3.185,3.185 0,1 1,-6.37 0" + android:fillColor="#FFFFFF"/> +</vector> diff --git a/packages/SystemUI/res/drawable-nodpi/icon.xml b/packages/SystemUI/res/drawable-nodpi/icon.xml index 7a68c032d8be..7f8d4fa8833f 100644 --- a/packages/SystemUI/res/drawable-nodpi/icon.xml +++ b/packages/SystemUI/res/drawable-nodpi/icon.xml @@ -15,5 +15,5 @@ Copyright (C) 2018 The Android Open Source Project --> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@drawable/icon_bg"/> - <foreground android:drawable="@drawable/q"/> + <foreground android:drawable="@drawable/android_11_dial"/> </adaptive-icon> diff --git a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml index 2a54dfad8191..31b2a7f9a333 100644 --- a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml +++ b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml @@ -14,5 +14,5 @@ Copyright (C) 2018 The Android Open Source Project limitations under the License. --> <color xmlns:android="http://schemas.android.com/apk/res/android" - android:color="#77C360" /> + android:color="#073042" /> diff --git a/packages/SystemUI/res/drawable-nodpi/q.xml b/packages/SystemUI/res/drawable-nodpi/q.xml deleted file mode 100644 index 0f42d2e20040..000000000000 --- a/packages/SystemUI/res/drawable-nodpi/q.xml +++ /dev/null @@ -1,40 +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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="108dp" - android:height="108dp" - android:viewportWidth="108.0" - android:viewportHeight="108.0"> - <group - android:name="scale" - android:pivotX="54" android:pivotY="54" - android:scaleX="0.9" - android:scaleY="0.9"> - <group - android:name="nudge" - android:translateX="24" - android:translateY="23.5"> - <path - android:name="tail" - android:fillColor="#FFFFFF" - android:pathData="M21.749674,34.122784l-9.431964,9.529709l-6.31771,-6.2529106l15.736504,-15.899582l64.765724,65.16436l-6.3046494,6.266083z"/> - <path - android:name="counter" - android:fillColor="#FFFFFF" - android:pathData="M30,9.32352941 C41.6954418,9.32352941 51.1764706,18.8045582 51.1764706,30.5 C51.1764706,42.1954418 41.6954418,51.6764706 30,51.6764706 C18.3045582,51.6764706 8.82352941,42.1954418 8.82352941,30.5 C8.82352941,18.8045582 18.3045582,9.32352941 30,9.32352941 L30,9.32352941 Z M30,0.5 C13.4314575,0.5 -5.53805368e-15,13.9314575 -7.10542736e-15,30.5 C-1.02401747e-14,47.0685425 13.4314575,60.5 30,60.5 C46.5685425,60.5 60,47.0685425 60,30.5 C59.9805514,13.9395201 46.5604799,0.519448617 30,0.5 Z"/> - </group> - </group> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml new file mode 100644 index 000000000000..24e063506250 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#FF000000" + android:pathData="M12,2l-5.5,9h11L12,2zM12,5.84L13.93,9h-3.87L12,5.84zM17.5,13c-2.49,0 -4.5,2.01 -4.5,4.5s2.01,4.5 4.5,4.5 4.5,-2.01 4.5,-4.5 -2.01,-4.5 -4.5,-4.5zM17.5,20c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5zM3,21.5h8v-8L3,13.5v8zM5,15.5h4v4L5,19.5v-4z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_touch.xml b/packages/SystemUI/res/drawable/ic_touch.xml new file mode 100644 index 000000000000..4f6698de5251 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_touch.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. +--> +<!-- maybe need android:fillType="evenOdd" --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M9,7.5V11.24C7.79,10.43 7,9.06 7,7.5C7,5.01 9.01,3 11.5,3C13.99,3 16,5.01 16,7.5C16,9.06 15.21,10.43 14,11.24V7.5C14,6.12 12.88,5 11.5,5C10.12,5 9,6.12 9,7.5ZM14.3,13.61L18.84,15.87C19.37,16.09 19.75,16.63 19.75,17.25C19.75,17.31 19.74,17.38 19.73,17.45L18.98,22.72C18.87,23.45 18.29,24 17.54,24H10.75C10.34,24 9.96,23.83 9.69,23.56L4.75,18.62L5.54,17.82C5.74,17.62 6.02,17.49 6.33,17.49C6.39,17.49 6.4411,17.4989 6.4922,17.5078C6.5178,17.5122 6.5433,17.5167 6.57,17.52L10,18.24V7.5C10,6.67 10.67,6 11.5,6C12.33,6 13,6.67 13,7.5V13.5H13.76C13.95,13.5 14.13,13.54 14.3,13.61Z" + /> +</vector> diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml index a1c593fa455c..b14bc7de06c4 100644 --- a/packages/SystemUI/res/layout/auth_credential_password_view.xml +++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml @@ -73,6 +73,7 @@ android:layout_width="208dp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" + android:minHeight="48dp" android:gravity="center" android:inputType="textPassword" android:maxLength="500" 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/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml new file mode 100644 index 000000000000..df576d83323f --- /dev/null +++ b/packages/SystemUI/res/layout/screen_record_dialog.xml @@ -0,0 +1,142 @@ +<?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_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:background="@drawable/rounded_bg_full"> + + <!-- Header --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center" + android:padding="@dimen/screenrecord_dialog_padding"> + <ImageView + android:layout_width="@dimen/screenrecord_logo_size" + android:layout_height="@dimen/screenrecord_logo_size" + android:src="@drawable/ic_screenrecord" + android:tint="@color/GM2_red_500" + android:layout_marginBottom="@dimen/screenrecord_dialog_padding"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:text="@string/screenrecord_start_label"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/screenrecord_description" + android:textAppearance="?android:attr/textAppearanceSmall" + android:paddingTop="@dimen/screenrecord_dialog_padding" + android:paddingBottom="@dimen/screenrecord_dialog_padding"/> + + <!-- Options --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <ImageView + android:layout_width="@dimen/screenrecord_logo_size" + android:layout_height="@dimen/screenrecord_logo_size" + android:src="@drawable/ic_mic_26dp" + android:tint="@color/GM2_grey_700" + android:layout_gravity="center" + android:layout_weight="0" + android:layout_marginRight="@dimen/screenrecord_dialog_padding"/> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_weight="1"> + <TextView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center_vertical" + android:text="@string/screenrecord_audio_label" + android:textColor="?android:attr/textColorPrimary" + android:textAppearance="?android:attr/textAppearanceMedium"/> + <TextView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/audio_type" + android:text="@string/screenrecord_mic_label" + android:textAppearance="?android:attr/textAppearanceSmall"/> + </LinearLayout> + <Switch + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_weight="0" + android:id="@+id/screenrecord_audio_switch"/> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <ImageView + android:layout_width="@dimen/screenrecord_logo_size" + android:layout_height="@dimen/screenrecord_logo_size" + android:src="@drawable/ic_touch" + android:tint="@color/GM2_grey_700" + android:layout_gravity="center" + android:layout_marginRight="@dimen/screenrecord_dialog_padding"/> + <Switch + android:layout_width="match_parent" + android:layout_height="48dp" + android:id="@+id/screenrecord_taps_switch" + android:text="@string/screenrecord_taps_label" + android:textColor="?android:attr/textColorPrimary" + android:textAppearance="?android:attr/textAppearanceMedium"/> + </LinearLayout> + </LinearLayout> + + <!-- hr --> + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/GM2_grey_300"/> + + <!-- Buttons --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="@dimen/screenrecord_dialog_padding"> + <Button + android:id="@+id/button_cancel" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="0" + android:layout_gravity="start" + android:text="@string/cancel" + style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"/> + <Space + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1"/> + <Button + android:id="@+id/button_start" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="0" + android:layout_gravity="end" + android:text="@string/screenrecord_start" + style="@android:style/Widget.DeviceDefault.Button.Colored"/> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index d72ce5eea78d..8de2df592eda 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -487,9 +487,6 @@ <!-- Whether or not to add a "people" notifications section --> <bool name="config_usePeopleFiltering">false</bool> - <!-- Package name for controls plugin --> - <string name="config_controlsPluginPackageName" translatable="false">com.android.systemui.controls.panel</string> - <!-- Defines the blacklist for system icons. That is to say, the icons in the status bar that are part of the blacklist are never displayed. Each item in the blacklist must be a string defined in core/res/res/config.xml to properly blacklist the icon. diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 9c997e88c0c2..7a3395c229a0 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1090,7 +1090,7 @@ <!-- Blur radius on status bar window and power menu --> <dimen name="min_window_blur_radius">1px</dimen> - <dimen name="max_window_blur_radius">100px</dimen> + <dimen name="max_window_blur_radius">150px</dimen> <!-- How much into a DisplayCutout's bounds we can go, on each side --> <dimen name="display_cutout_margin_consumption">0px</dimen> @@ -1209,5 +1209,7 @@ <dimen name="controls_card_margin">2dp</dimen> - + <!-- Screen Record --> + <dimen name="screenrecord_dialog_padding">18dp</dimen> + <dimen name="screenrecord_logo_size">24dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 07a926fa75d3..b85b51e4f2cc 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -217,15 +217,31 @@ your organization</string> <!-- Notification title displayed for screen recording [CHAR LIMIT=50]--> - <string name="screenrecord_name">Screen Recording</string> + <string name="screenrecord_name">Screen Recorder</string> <!-- Description of the screen recording notification channel [CHAR LIMIT=NONE]--> <string name="screenrecord_channel_description">Ongoing notification for a screen record session</string> - <!-- Label for the button to begin screen recording [CHAR LIMIT=NONE]--> - <string name="screenrecord_start_label">Start Recording</string> - <!-- Label for the checkbox to enable microphone input during screen recording [CHAR LIMIT=NONE]--> - <string name="screenrecord_mic_label">Record voiceover</string> + <!-- Title for the screen prompting the user to begin recording their screen [CHAR LIMIT=NONE]--> + <string name="screenrecord_start_label">Start Recording?</string> + <!-- Message reminding the user that sensitive information may be captured during a screen recording [CHAR_LIMIT=NONE]--> + <string name="screenrecord_description">While recording, Android System can capture any sensitive information that\u2019s visible on your screen or played on your device. This includes passwords, payment info, photos, messages, and audio.</string> + <!-- Label for a switch to enable recording audio [CHAR LIMIT=NONE]--> + <string name="screenrecord_audio_label">Record audio</string> + <!-- Label for the option to record audio from the device [CHAR LIMIT=NONE]--> + <string name="screenrecord_device_audio_label">Device audio</string> + <!-- Description of what audio may be captured from the device [CHAR LIMIT=NONE]--> + <string name="screenrecord_device_audio_description">Sound from your device, like music, calls, and ringtones</string> + <!-- Label for the option to enable microphone input during screen recording [CHAR LIMIT=NONE]--> + <string name="screenrecord_mic_label">Microphone</string> + <!-- Label for an option to record audio from both device and microphone [CHAR LIMIT=NONE]--> + <string name="screenrecord_device_audio_and_mic_label">Device audio and microphone</string> + <!-- Button to start a screen recording [CHAR LIMIT=50]--> + <string name="screenrecord_start">Start</string> + <!-- Notification text displayed when we are recording the screen [CHAR LIMIT=100]--> + <string name="screenrecord_ongoing_screen_only">Recording screen</string> + <!-- Notification text displayed when we are recording both the screen and audio [CHAR LIMIT=100]--> + <string name="screenrecord_ongoing_screen_and_audio">Recording screen and audio</string> <!-- Label for the checkbox to enable showing location of touches during screen recording [CHAR LIMIT=NONE]--> - <string name="screenrecord_taps_label">Show taps</string> + <string name="screenrecord_taps_label">Show touches on screen</string> <!-- Label for notification that the user can tap to stop and save the screen recording [CHAR LIMIT=NONE] --> <string name="screenrecord_stop_text">Tap to stop</string> <!-- Label for notification action to stop and save the screen recording [CHAR LIMIT=35] --> @@ -250,6 +266,8 @@ <string name="screenrecord_delete_error">Error deleting screen recording</string> <!-- A toast message shown when the screen recording cannot be started due to insufficient permissions [CHAR LIMIT=NONE] --> <string name="screenrecord_permission_error">Failed to get permissions</string> + <!-- A toast message shown when the screen recording cannot be started due to a generic error [CHAR LIMIT=NONE] --> + <string name="screenrecord_start_error">Error starting screen recording</string> <!-- Title for the USB function chooser in UsbPreferenceActivity. [CHAR LIMIT=30] --> <string name="usb_preference_title">USB file transfer options</string> 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/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index f61f585cfe7c..f48210cf90c4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -42,7 +42,6 @@ import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.animation.Animation; -import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; @@ -251,9 +250,9 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe SliceItem item = rc.getSliceItem(); final Uri itemTag = item.getSlice().getUri(); // Try to reuse the view if already exists in the layout - KeyguardSliceButton button = mRow.findViewWithTag(itemTag); + KeyguardSliceTextView button = mRow.findViewWithTag(itemTag); if (button == null) { - button = new KeyguardSliceButton(mContext); + button = new KeyguardSliceTextView(mContext); button.setTextColor(blendedColor); button.setTag(itemTag); final int viewIndex = i - (mHasHeader ? 1 : 0); @@ -316,8 +315,8 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe int childCount = mRow.getChildCount(); for (int i = 0; i < childCount; i++) { View v = mRow.getChildAt(i); - if (v instanceof Button) { - ((Button) v).setTextColor(blendedColor); + if (v instanceof TextView) { + ((TextView) v).setTextColor(blendedColor); } } } @@ -500,8 +499,8 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); - if (child instanceof KeyguardSliceButton) { - ((KeyguardSliceButton) child).setMaxWidth(width / childCount); + if (child instanceof KeyguardSliceTextView) { + ((KeyguardSliceTextView) child).setMaxWidth(width / childCount); } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -527,13 +526,13 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe * Representation of an item that appears under the clock on main keyguard message. */ @VisibleForTesting - static class KeyguardSliceButton extends Button implements + static class KeyguardSliceTextView extends TextView implements ConfigurationController.ConfigurationListener { @StyleRes private static int sStyleId = R.style.TextAppearance_Keyguard_Secondary; - public KeyguardSliceButton(Context context) { + KeyguardSliceTextView(Context context) { super(context, null /* attrs */, 0 /* styleAttr */, sStyleId); onDensityOrFontScaleChanged(); setEllipsize(TruncateAt.END); 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 b8d32aec30e3..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); @@ -583,11 +589,13 @@ public class AuthContainerView extends LinearLayout * @return */ public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) { + final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + | WindowManager.LayoutParams.FLAG_SECURE; final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, - WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + windowFlags, PixelFormat.TRANSLUCENT); lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; lp.setTitle("BiometricPrompt"); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 7c07c9d81000..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}. + * 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}. * - * The cancellation of summaries with children associated with bubbles are also handled in this - * method. User-cancelled summaries are tracked by {@link BubbleData#addSummaryToSuppress}. - * - * @return true if we want to intercept the dismissal of the entry, else false + * @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; - 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 - && !isUserCreatedBubble(bubble.getKey())) { - // 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 6062a3d45be0..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() { @@ -533,10 +528,6 @@ public class BubbleStackView extends FrameLayout { mBubbleContainer.addView(mOverflowBtn, 0, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - mOverflowBtn.setOnClickListener(v -> { - setSelectedBubble(null); - }); - TypedArray ta = mContext.obtainStyledAttributes( new int[]{android.R.attr.colorBackgroundFloating}); int bgColor = ta.getColor(0, Color.WHITE /* default */); @@ -742,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() { @@ -856,6 +840,10 @@ public class BubbleStackView extends FrameLayout { updateBubbleZOrdersAndDotPosition(false /* animate */); } + void showOverflow() { + setSelectedBubble(null); + } + /** * Changes the currently selected bubble. If the stack is already expanded, the newly selected * bubble will be shown immediately. This does not change the expanded state or change the @@ -942,14 +930,12 @@ 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) + && isIntersecting(mOverflowBtn, x, y)) { + return mOverflowBtn; + } // Could be tapping or dragging a bubble while expanded for (int i = 0; i < getBubbleCount(); i++) { BadgedImageView view = (BadgedImageView) mBubbleContainer.getChildAt(i); @@ -1164,7 +1150,6 @@ public class BubbleStackView extends FrameLayout { } return; } - hideBubbleMenu(); mStackAnimationController.cancelStackPositionAnimations(); mBubbleContainer.setActiveController(mStackAnimationController); hideFlyoutImmediate(); @@ -1566,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); @@ -1804,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 fdeaf1f016c3..645696d0bcac 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -24,6 +24,7 @@ import android.view.View; import android.view.ViewConfiguration; import com.android.systemui.Dependency; +import com.android.systemui.R; /** * Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing, @@ -57,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; @@ -77,7 +76,6 @@ class BubbleTouchHandler implements View.OnTouchListener { mTouchSlopSquared = touchSlop * touchSlop; mBubbleData = bubbleData; mStack = stackView; - mContext = context; } @Override @@ -94,24 +92,19 @@ 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)) { + + if (mTouchedView.getId() == R.id.bubble_overflow_button) { + mStack.showOverflow(); + } // Not touching anything touchable, but we shouldn't collapse (e.g. touching edge // of expanded view). - mStack.hideBubbleMenu(); resetForNextGesture(); return false; } @@ -134,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 { @@ -150,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; @@ -163,7 +146,6 @@ class BubbleTouchHandler implements View.OnTouchListener { } if (mMovedEnough) { - mStack.removeCallbacks(mShowBubbleMenuRunnable); if (isStack) { mStack.onDragged(viewX, viewY); } else if (isFlyout) { @@ -194,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(); @@ -222,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/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index a6f1d84877c5..7de1557ebc65 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -19,9 +19,12 @@ package com.android.systemui.controls.controller import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.ComponentName +import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.database.ContentObserver +import android.net.Uri import android.os.Environment import android.os.UserHandle import android.provider.Settings @@ -30,6 +33,7 @@ import android.service.controls.actions.ControlAction import android.util.ArrayMap 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 com.android.systemui.broadcast.BroadcastDispatcher @@ -53,15 +57,16 @@ class ControlsControllerImpl @Inject constructor ( private val uiController: ControlsUiController, private val bindingController: ControlsBindingController, private val listingController: ControlsListingController, - broadcastDispatcher: BroadcastDispatcher, + private val broadcastDispatcher: BroadcastDispatcher, optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, dumpController: DumpController ) : Dumpable, ControlsController { companion object { private const val TAG = "ControlsControllerImpl" - const val CONTROLS_AVAILABLE = "systemui.controls_available" - const val USER_CHANGE_RETRY_DELAY = 500L // ms + internal const val CONTROLS_AVAILABLE = "systemui.controls_available" + internal val URI = Settings.Secure.getUriFor(CONTROLS_AVAILABLE) + private const val USER_CHANGE_RETRY_DELAY = 500L // ms } // Map of map: ComponentName -> (String -> ControlInfo). @@ -69,9 +74,11 @@ class ControlsControllerImpl @Inject constructor ( @GuardedBy("currentFavorites") private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>() - private var userChanging = true - override var available = Settings.Secure.getInt( - context.contentResolver, CONTROLS_AVAILABLE, 0) != 0 + private var userChanging: Boolean = true + + private val contentResolver: ContentResolver + get() = context.contentResolver + override var available = Settings.Secure.getInt(contentResolver, CONTROLS_AVAILABLE, 0) != 0 private set private var currentUser = context.user @@ -95,8 +102,8 @@ class ControlsControllerImpl @Inject constructor ( val fileName = Environment.buildPath( userContext.filesDir, ControlsFavoritePersistenceWrapper.FILE_NAME) persistenceWrapper.changeFile(fileName) - available = Settings.Secure.getIntForUser( - context.contentResolver, CONTROLS_AVAILABLE, 0) != 0 + available = Settings.Secure.getIntForUser(contentResolver, CONTROLS_AVAILABLE, + /* default */ 0, newUser.identifier) != 0 synchronized(currentFavorites) { currentFavorites.clear() } @@ -123,6 +130,25 @@ class ControlsControllerImpl @Inject constructor ( } } + @VisibleForTesting + internal val settingObserver = object : ContentObserver(null) { + override fun onChange(selfChange: Boolean, uri: Uri, userId: Int) { + // Do not listen to changes in the middle of user change, those will be read by the + // user-switch receiver. + if (userChanging || userId != currentUserId) { + return + } + available = Settings.Secure.getIntForUser(contentResolver, CONTROLS_AVAILABLE, + /* default */ 0, currentUserId) != 0 + synchronized(currentFavorites) { + currentFavorites.clear() + } + if (available) { + loadFavorites() + } + } + } + init { dumpController.registerDumpable(this) if (available) { @@ -135,6 +161,7 @@ class ControlsControllerImpl @Inject constructor ( executor, UserHandle.ALL ) + contentResolver.registerContentObserver(URI, false, settingObserver, UserHandle.USER_ALL) } private fun confirmAvailability(): Boolean { 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 fad2d94d6cf3..78e0e8b81b44 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -34,14 +34,17 @@ import android.widget.ImageView import android.widget.TextView import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.R const val MIN_LEVEL = 0 const val MAX_LEVEL = 10000 +private const val UPDATE_DELAY_IN_MILLIS = 3000L class ControlViewHolder( val layout: ViewGroup, - val controlsController: ControlsController + val controlsController: ControlsController, + val uiExecutor: DelayableExecutor ) { val icon: ImageView = layout.requireViewById(R.id.icon) val status: TextView = layout.requireViewById(R.id.status) @@ -52,6 +55,7 @@ class ControlViewHolder( val clipLayer: ClipDrawable val gd: GradientDrawable lateinit var cws: ControlWithState + var cancelUpdate: Runnable? = null init { val ld = layout.getBackground() as LayerDrawable @@ -63,6 +67,8 @@ class ControlViewHolder( fun bindData(cws: ControlWithState) { this.cws = cws + cancelUpdate?.run() + val (status, template) = cws.control?.let { title.setText(it.getTitle()) subtitle.setText(it.getSubtitle()) @@ -86,6 +92,27 @@ class ControlViewHolder( findBehavior(status, template).apply(this, cws) } + fun actionResponse(@ControlAction.ResponseResult response: Int) { + val text = when (response) { + ControlAction.RESPONSE_OK -> "Success" + ControlAction.RESPONSE_FAIL -> "Error" + else -> "" + } + + if (!text.isEmpty()) { + val previousText = status.getText() + val previousTextExtra = statusExtra.getText() + + cancelUpdate = uiExecutor.executeDelayed({ + status.setText(previousText) + statusExtra.setText(previousTextExtra) + }, UPDATE_DELAY_IN_MILLIS) + + status.setText(text) + statusExtra.setText("") + } + } + fun action(action: ControlAction) { controlsController.action(cws.ci, action) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt index b07a75d5e757..d70c86fc3266 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -22,6 +22,8 @@ import android.service.controls.actions.ControlAction import android.view.ViewGroup interface ControlsUiController { + val available: Boolean + fun show(parent: ViewGroup) fun hide() fun onRefreshState(componentName: ComponentName, controls: List<Control>) 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 a777faf57fce..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.util.concurrent.Executor +import java.text.Collator + import javax.inject.Inject import javax.inject.Singleton @@ -104,18 +110,41 @@ class TokenProviderConnection(val cc: ControlsController, val context: Context) } } +private data class ControlKey(val componentName: ComponentName, val controlId: String) + @Singleton class ControlsUiControllerImpl @Inject constructor ( val controlsController: Lazy<ControlsController>, val context: Context, - @Main val uiExecutor: Executor + @Main val uiExecutor: DelayableExecutor, + @Background val bgExecutor: DelayableExecutor, + val controlsListingController: Lazy<ControlsListingController> ) : ControlsUiController { private lateinit var controlInfos: List<ControlInfo> - private val controlsById = mutableMapOf<Pair<ComponentName, String>, ControlWithState>() - private val controlViewsById = mutableMapOf<String, ControlViewHolder>() + private val controlsById = mutableMapOf<ControlKey, ControlWithState>() + private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>() private lateinit var parent: ViewGroup + 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()") @@ -125,7 +154,7 @@ class ControlsUiControllerImpl @Inject constructor ( controlInfos.map { ControlWithState(it, null) - }.associateByTo(controlsById) { Pair(it.ci.component, it.ci.controlId) } + }.associateByTo(controlsById) { ControlKey(it.ci.component, it.ci.controlId) } if (controlInfos.isEmpty()) { showInitialSetupView() @@ -148,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 { @@ -178,9 +225,10 @@ class ControlsUiControllerImpl @Inject constructor ( val item = inflater.inflate( R.layout.controls_base_item, lastRow, false) as ViewGroup lastRow.addView(item) - val cvh = ControlViewHolder(item, controlsController.get()) - cvh.bindData(controlsById.get(Pair(it.component, it.controlId))!!) - controlViewsById.put(it.controlId, cvh) + val cvh = ControlViewHolder(item, controlsController.get(), uiExecutor) + val key = ControlKey(it.component, it.controlId) + cvh.bindData(controlsById.getValue(key)) + controlViewsById.put(key, cvh) } if ((controlInfos.size % 2) == 1) { @@ -200,31 +248,35 @@ class ControlsUiControllerImpl @Inject constructor ( parent.removeAllViews() controlsById.clear() controlViewsById.clear() + controlsListingController.get().removeCallback(listingCallback) } override fun onRefreshState(componentName: ComponentName, controls: List<Control>) { Log.d(TAG, "onRefreshState()") controls.forEach { c -> - controlsById.get(Pair(componentName, c.getControlId()))?.let { + controlsById.get(ControlKey(componentName, c.getControlId()))?.let { Log.d(TAG, "onRefreshState() for id: " + c.getControlId()) val cws = ControlWithState(it.ci, c) - controlsById.put(Pair(componentName, c.getControlId()), cws) + val key = ControlKey(componentName, c.getControlId()) + controlsById.put(key, cws) uiExecutor.execute { - controlViewsById.get(c.getControlId())?.bindData(cws) + controlViewsById.get(key)?.bindData(cws) } } } } override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) { - Log.d(TAG, "onActionResponse()") - TODO("not implemented") + val key = ControlKey(componentName, controlId) + uiExecutor.execute { + controlViewsById.get(key)?.actionResponse(response) + } } - 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/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt index 093c99f57a9a..da52c6f8ee21 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt @@ -108,10 +108,18 @@ private val deviceIconMap = mapOf<Int, IconState>( DeviceTypes.TYPE_OUTLET to IconState( R.drawable.ic_power_off_gm2_24px, R.drawable.ic_power_gm2_24px + ), + DeviceTypes.TYPE_VACUUM to IconState( + R.drawable.ic_vacuum_gm2_24px, + R.drawable.ic_vacuum_gm2_24px + ), + DeviceTypes.TYPE_MOP to IconState( + R.drawable.ic_vacuum_gm2_24px, + R.drawable.ic_vacuum_gm2_24px ) ).withDefault { IconState( - R.drawable.ic_light_off_gm2_24px, - R.drawable.ic_lightbulb_outline_gm2_24px + R.drawable.ic_device_unknown_gm2_24px, + R.drawable.ic_device_unknown_gm2_24px ) } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index 53a23b89f943..0ec739fecaa7 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -30,6 +30,7 @@ import android.view.IWindowManager; import android.view.LayoutInflater; import com.android.internal.logging.MetricsLogger; +import com.android.internal.util.NotificationMessagingUtil; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.dagger.qualifiers.Background; @@ -191,6 +192,12 @@ public class DependencyProvider { return new AlwaysOnDisplayPolicy(context); } + /***/ + @Provides + public NotificationMessagingUtil provideNotificationMessagingUtil(Context context) { + return new NotificationMessagingUtil(context); + } + /** */ @Provides public ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 7b541991088c..f068d9c10e86 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -40,6 +40,7 @@ import com.android.systemui.statusbar.notification.collection.inflation.Notifica import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.people.PeopleHubModule; +import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import com.android.systemui.statusbar.phone.KeyguardLiftController; import com.android.systemui.statusbar.phone.StatusBar; @@ -68,7 +69,9 @@ import dagger.Provides; NotificationsModule.class, PeopleHubModule.class, }, - subcomponents = {StatusBarComponent.class, NotificationRowComponent.class}) + subcomponents = {StatusBarComponent.class, + NotificationRowComponent.class, + ExpandableNotificationRowComponent.class}) public abstract class SystemUIModule { @Binds diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 45c07a3e4693..b3fc027d1ac7 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1725,7 +1725,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (!(mBackgroundDrawable instanceof ScrimDrawable)) { return; } - ((ScrimDrawable) mBackgroundDrawable).setColor(Color.BLACK, animate); + ((ScrimDrawable) mBackgroundDrawable).setColor(colors.supportsDarkText() ? Color.WHITE + : Color.BLACK, animate); View decorView = getWindow().getDecorView(); if (colors.supportsDarkText()) { decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | @@ -1899,9 +1900,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } private boolean shouldShowControls() { - return isCurrentUserOwner() - && !mKeyguardManager.isDeviceLocked() - && Settings.Secure.getInt(mContext.getContentResolver(), - "systemui.controls_available", 0) == 1; + return !mKeyguardManager.isDeviceLocked() + && mControlsUiController.getAvailable(); } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index c9c38d31e865..146f36bcd04f 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -36,8 +36,6 @@ import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.plugins.GlobalActions; import com.android.systemui.plugins.GlobalActionsPanelPlugin; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.ScrimController; @@ -49,8 +47,7 @@ import javax.inject.Inject; import dagger.Lazy; -public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, - PluginListener<GlobalActionsPanelPlugin> { +public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks { private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f; @@ -60,12 +57,9 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, private final DeviceProvisionedController mDeviceProvisionedController; private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension; private final BlurUtils mBlurUtils; - private GlobalActionsPanelPlugin mPlugin; private final CommandQueue mCommandQueue; private GlobalActionsDialog mGlobalActionsDialog; private boolean mDisabled; - private final PluginManager mPluginManager; - private final String mPluginPackageName; @Inject public GlobalActionsImpl(Context context, CommandQueue commandQueue, @@ -74,7 +68,6 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, mGlobalActionsDialogLazy = globalActionsDialogLazy; mKeyguardStateController = Dependency.get(KeyguardStateController.class); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); - mPluginManager = Dependency.get(PluginManager.class); mCommandQueue = commandQueue; mBlurUtils = blurUtils; mCommandQueue.addCallback(this); @@ -82,17 +75,11 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, .newExtension(GlobalActionsPanelPlugin.class) .withPlugin(GlobalActionsPanelPlugin.class) .build(); - mPluginPackageName = mContext.getString( - com.android.systemui.R.string.config_controlsPluginPackageName); - mPluginManager.addPluginListener( - GlobalActionsPanelPlugin.ACTION, this, GlobalActionsPanelPlugin.class, true); } @Override public void destroy() { mCommandQueue.removeCallback(this); - mPluginManager.removePluginListener(this); - if (mPlugin != null) mPlugin.onDestroy(); if (mGlobalActionsDialog != null) { mGlobalActionsDialog.destroy(); mGlobalActionsDialog = null; @@ -105,7 +92,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, mGlobalActionsDialog = mGlobalActionsDialogLazy.get(); mGlobalActionsDialog.showDialog(mKeyguardStateController.isShowing(), mDeviceProvisionedController.isDeviceProvisioned(), - mPlugin != null ? mPlugin : mPanelExtension.get()); + mPanelExtension.get()); Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth(); } @@ -205,16 +192,4 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, mGlobalActionsDialog.dismissDialog(); } } - - @Override - public void onPluginConnected(GlobalActionsPanelPlugin plugin, Context pluginContext) { - if (pluginContext.getPackageName().equals(mPluginPackageName)) { - mPlugin = plugin; - } - } - - @Override - public void onPluginDisconnected(GlobalActionsPanelPlugin plugin) { - mPlugin = null; - } } diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java index 657a308224ae..11e215d0d358 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java @@ -153,11 +153,11 @@ public class EglHelper { return true; } - private boolean checkExtensionCapability(String extName) { + boolean checkExtensionCapability(String extName) { return mExts.contains(extName); } - private int getWcgCapability() { + int getWcgCapability() { if (checkExtensionCapability(EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH)) { return EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT; } @@ -212,7 +212,7 @@ public class EglHelper { if (wcg && checkExtensionCapability(KHR_GL_COLOR_SPACE) && wcgCapability > 0) { attrs = new int[] {EGL_GL_COLORSPACE_KHR, wcgCapability, EGL_NONE}; } - mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, attrs, 0); + mEglSurface = askCreatingEglWindowSurface(surfaceHolder, attrs, 0 /* offset */); } else { Log.w(TAG, "Create EglSurface failed: hasEglDisplay=" + hasEglDisplay() + ", has valid surface=" + surfaceHolder.getSurface().isValid()); @@ -235,6 +235,10 @@ public class EglHelper { return true; } + EGLSurface askCreatingEglWindowSurface(SurfaceHolder holder, int[] attrs, int offset) { + return eglCreateWindowSurface(mEglDisplay, mEglConfig, holder, attrs, offset); + } + /** * Destroy EglSurface. */ @@ -242,7 +246,7 @@ public class EglHelper { if (hasEglSurface()) { eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(mEglDisplay, mEglSurface); - mEglSurface = null; + mEglSurface = EGL_NO_SURFACE; } } @@ -296,7 +300,7 @@ public class EglHelper { public void destroyEglContext() { if (hasEglContext()) { eglDestroyContext(mEglDisplay, mEglContext); - mEglContext = null; + mEglContext = EGL_NO_CONTEXT; } } @@ -340,11 +344,16 @@ public class EglHelper { destroyEglContext(); } if (hasEglDisplay()) { - eglTerminate(mEglDisplay); + terminateEglDisplay(); } mEglReady = false; } + void terminateEglDisplay() { + eglTerminate(mEglDisplay); + mEglDisplay = EGL_NO_DISPLAY; + } + /** * Called to dump current state. * @param prefix prefix. 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/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index eba2e7874574..3ae627d27def 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -19,10 +19,6 @@ package com.android.systemui.pip.phone; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import android.animation.AnimationHandler; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.TimeAnimator; import android.annotation.Nullable; import android.app.ActivityManager.StackInfo; import android.app.IActivityManager; @@ -36,6 +32,7 @@ import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.util.Log; +import android.view.Choreographer; import androidx.dynamicanimation.animation.SpringForce; @@ -88,9 +85,11 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call /** PIP's current bounds on the screen. */ private final Rect mBounds = new Rect(); + private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider = + new SfVsyncFrameCallbackProvider(); + /** - * Bounds that are animated using the physics animator. PIP is moved to these bounds whenever - * the {@link #mVsyncTimeAnimator} ticks. + * Bounds that are animated using the physics animator. */ private final Rect mAnimatedBounds = new Rect(); @@ -100,12 +99,16 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call private PhysicsAnimator<Rect> mAnimatedBoundsPhysicsAnimator = PhysicsAnimator.getInstance( mAnimatedBounds); + /** Callback that re-sizes PIP to the animated bounds. */ + private final Choreographer.FrameCallback mResizePipVsyncCallback = + l -> resizePipUnchecked(mAnimatedBounds); + /** - * Time animator whose frame timing comes from the SurfaceFlinger vsync frame provider. At each - * frame, PIP is moved to {@link #mAnimatedBounds}, which are animated asynchronously using - * physics animations. + * Update listener that posts a vsync frame callback to resize PIP to {@link #mAnimatedBounds}. */ - private TimeAnimator mVsyncTimeAnimator; + private final PhysicsAnimator.UpdateListener<Rect> mResizePipVsyncUpdateListener = + (target, values) -> + mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback); /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */ private PhysicsAnimator.FlingConfig mFlingConfigX; @@ -126,39 +129,7 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call mMenuController = menuController; mSnapAlgorithm = snapAlgorithm; mFlingAnimationUtils = flingAnimationUtils; - final AnimationHandler vsyncFrameCallbackProvider = new AnimationHandler(); - vsyncFrameCallbackProvider.setProvider(new SfVsyncFrameCallbackProvider()); - onConfigurationChanged(); - - // Construct a time animator that uses the vsync frame provider. Physics animations can't - // use custom frame providers, since they rely on constant time between frames to run the - // physics simulations. To work around this, we physically-animate a second set of bounds, - // and apply those animating bounds to the PIP in-sync via this TimeAnimator. - mVsyncTimeAnimator = new TimeAnimator() { - @Override - public AnimationHandler getAnimationHandler() { - return vsyncFrameCallbackProvider; - } - }; - - // When the time animator ticks, move PIP to the animated bounds. - mVsyncTimeAnimator.setTimeListener( - (animation, totalTime, deltaTime) -> - resizePipUnchecked(mAnimatedBounds)); - - // Add a listener for cancel/end events that moves PIP to the final animated bounds. - mVsyncTimeAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationCancel(Animator animation) { - resizePipUnchecked(mAnimatedBounds); - } - - @Override - public void onAnimationEnd(Animator animation) { - resizePipUnchecked(mAnimatedBounds); - } - }); } /** @@ -429,7 +400,6 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call */ private void cancelAnimations() { mAnimatedBoundsPhysicsAnimator.cancel(); - mVsyncTimeAnimator.cancel(); } /** @@ -457,10 +427,8 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call cancelAnimations(); mAnimatedBoundsPhysicsAnimator - .withEndActions( - mVsyncTimeAnimator::cancel) + .addUpdateListener(mResizePipVsyncUpdateListener) .start(); - mVsyncTimeAnimator.start(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index 0134aa3a15df..5de6d1c42b4f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -169,7 +169,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) { v.setText(mContext.getString( com.android.internal.R.string.bugreport_status, - Build.VERSION.RELEASE, + Build.VERSION.RELEASE_OR_CODENAME, Build.ID)); v.setVisibility(View.VISIBLE); } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java index 9e3e94ce4186..6c697184f082 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java @@ -36,6 +36,7 @@ import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; +import android.os.Handler; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -370,6 +371,13 @@ public class QSMediaPlayer { if (mSeamless == null) { return; } + Handler handler = mSeamless.getHandler(); + handler.post(() -> { + updateChipInternal(device); + }); + } + + private void updateChipInternal(MediaDevice device) { ColorStateList fgTintList = ColorStateList.valueOf(mForegroundColor); // Update the outline color 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/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index e1b61c670a12..e559694df375 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -109,10 +109,31 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); + /** + * Provides a new {@link TState} of the appropriate type to use between this tile and the + * corresponding view. + * + * @return new state to use by the tile. + */ public abstract TState newTileState(); + /** + * Handles clicks by the user. + * + * Calls to the controller should be made here to set the new state of the device. + */ abstract protected void handleClick(); + /** + * Update state of the tile based on device state + * + * Called whenever the state of the tile needs to be updated, either after user + * interaction or from callbacks from the controller. It populates {@code state} with the + * information to display to the user. + * + * @param state {@link TState} to populate with information to display + * @param arg additional arguments needed to populate {@code state} + */ abstract protected void handleUpdateState(TState state, Object arg); /** @@ -177,6 +198,12 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy return mHost; } + /** + * Return the {@link QSIconView} to be used by this tile's view. + * + * @param context view context for the view + * @return icon view for this tile + */ public QSIconView createTileView(Context context) { return new QSIconViewImpl(context); } @@ -298,11 +325,20 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mCallbacks.clear(); } + /** + * Handles secondary click on the tile. + * + * Defaults to {@link QSTileImpl#handleClick} + */ protected void handleSecondaryClick() { // Default to normal click. handleClick(); } + /** + * Handles long click on the tile by launching the {@link Intent} defined in + * {@link QSTileImpl#getLongClickIntent} + */ protected void handleLongClick() { if (mQSSettingsPanelOption == QSSettingsPanel.USE_DETAIL) { showDetail(true); @@ -312,6 +348,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy getLongClickIntent(), 0); } + /** + * Returns an intent to be launched when the tile is long pressed. + * + * @return the intent to launch + */ public abstract Intent getLongClickIntent(); protected void handleRefreshState(Object arg) { @@ -427,6 +468,10 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } } + /** + * Provides a default label for the tile. + * @return default label for the tile. + */ public abstract CharSequence getTileLabel(); public static int getColorForState(Context context, int state) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index 001e09406e3a..42f80109e045 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -21,6 +21,7 @@ import android.content.ComponentName; import android.content.Intent; import android.os.UserManager; import android.service.quicksettings.Tile; +import android.util.Log; import android.widget.Switch; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -200,6 +201,14 @@ public class HotspotTile extends QSTileImpl<BooleanState> { mCallbackInfo.numConnectedDevices = numDevices; refreshState(mCallbackInfo); } + + @Override + public void onHotspotAvailabilityChanged(boolean available) { + if (!available) { + Log.d(TAG, "Tile removed. Hotspot no longer available"); + mHost.removeTile(getTileSpec()); + } + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index b091ad8c0db8..626f298055a0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -16,7 +16,6 @@ package com.android.systemui.screenrecord; -import android.app.Activity; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -26,16 +25,21 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.Icon; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.media.MediaRecorder; +import android.media.projection.IMediaProjection; +import android.media.projection.IMediaProjectionManager; import android.media.projection.MediaProjection; import android.media.projection.MediaProjectionManager; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; import android.provider.MediaStore; import android.provider.Settings; import android.util.DisplayMetrics; @@ -83,7 +87,6 @@ public class RecordingService extends Service { private static final int AUDIO_SAMPLE_RATE = 44100; private final RecordingController mController; - private MediaProjectionManager mMediaProjectionManager; private MediaProjection mMediaProjection; private Surface mInputSurface; private VirtualDisplay mVirtualDisplay; @@ -134,13 +137,30 @@ public class RecordingService extends Service { switch (action) { case ACTION_START: - int resultCode = intent.getIntExtra(EXTRA_RESULT_CODE, Activity.RESULT_CANCELED); mUseAudio = intent.getBooleanExtra(EXTRA_USE_AUDIO, false); mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false); - Intent data = intent.getParcelableExtra(EXTRA_DATA); - if (data != null) { - mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data); + try { + IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE); + IMediaProjectionManager mediaService = + IMediaProjectionManager.Stub.asInterface(b); + IMediaProjection proj = mediaService.createProjection(getUserId(), + getPackageName(), + MediaProjectionManager.TYPE_SCREEN_CAPTURE, false); + IBinder projection = proj.asBinder(); + if (projection == null) { + Log.e(TAG, "Projection was null"); + Toast.makeText(this, R.string.screenrecord_start_error, Toast.LENGTH_LONG) + .show(); + return Service.START_NOT_STICKY; + } + mMediaProjection = new MediaProjection(getApplicationContext(), + IMediaProjection.Stub.asInterface(projection)); startRecording(); + } catch (RemoteException e) { + e.printStackTrace(); + Toast.makeText(this, R.string.screenrecord_start_error, Toast.LENGTH_LONG) + .show(); + return Service.START_NOT_STICKY; } break; @@ -195,9 +215,6 @@ public class RecordingService extends Service { @Override public void onCreate() { super.onCreate(); - - mMediaProjectionManager = - (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); } /** @@ -269,6 +286,7 @@ public class RecordingService extends Service { } private void createRecordingNotification() { + Resources res = getResources(); NotificationChannel channel = new NotificationChannel( CHANNEL_ID, getString(R.string.screenrecord_name), @@ -281,11 +299,15 @@ public class RecordingService extends Service { Bundle extras = new Bundle(); extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, - getResources().getString(R.string.screenrecord_name)); + res.getString(R.string.screenrecord_name)); + + String notificationTitle = mUseAudio + ? res.getString(R.string.screenrecord_ongoing_screen_and_audio) + : res.getString(R.string.screenrecord_ongoing_screen_only); mRecordingNotificationBuilder = new Notification.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_screenrecord) - .setContentTitle(getResources().getString(R.string.screenrecord_name)) + .setContentTitle(notificationTitle) .setContentText(getResources().getString(R.string.screenrecord_stop_text)) .setUsesChronometer(true) .setColorized(true) @@ -332,8 +354,7 @@ public class RecordingService extends Service { Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_screenrecord) - .setContentTitle(getResources().getString(R.string.screenrecord_name)) - .setContentText(getResources().getString(R.string.screenrecord_save_message)) + .setContentTitle(getResources().getString(R.string.screenrecord_save_message)) .setContentIntent(PendingIntent.getActivity( this, REQUEST_CODE, diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java index 8324986123bd..566f12b42795 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java @@ -16,15 +16,14 @@ package com.android.systemui.screenrecord; -import android.Manifest; import android.app.Activity; import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.media.projection.MediaProjectionManager; import android.os.Bundle; -import android.widget.Toast; +import android.view.Gravity; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.Button; +import android.widget.Switch; import com.android.systemui.R; @@ -34,15 +33,11 @@ import javax.inject.Inject; * Activity to select screen recording options */ public class ScreenRecordDialog extends Activity { - private static final int REQUEST_CODE_VIDEO_ONLY = 200; - private static final int REQUEST_CODE_VIDEO_TAPS = 201; - private static final int REQUEST_CODE_PERMISSIONS = 299; - private static final int REQUEST_CODE_VIDEO_AUDIO = 300; - private static final int REQUEST_CODE_VIDEO_AUDIO_TAPS = 301; - private static final int REQUEST_CODE_PERMISSIONS_AUDIO = 399; private static final long DELAY_MS = 3000; private final RecordingController mController; + private Switch mAudioSwitch; + private Switch mTapsSwitch; @Inject public ScreenRecordDialog(RecordingController controller) { @@ -52,81 +47,42 @@ public class ScreenRecordDialog extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - requestScreenCapture(); - } - private void requestScreenCapture() { - MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService( - Context.MEDIA_PROJECTION_SERVICE); - Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent(); + Window window = getWindow(); + // Inflate the decor view, so the attributes below are not overwritten by the theme. + window.getDecorView(); + window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + window.setGravity(Gravity.TOP); + + setContentView(R.layout.screen_record_dialog); + + Button cancelBtn = findViewById(R.id.button_cancel); + cancelBtn.setOnClickListener(v -> { + finish(); + }); - // TODO get saved settings - boolean useAudio = false; - boolean showTaps = false; - if (useAudio) { - startActivityForResult(permissionIntent, - showTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO); - } else { - startActivityForResult(permissionIntent, - showTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY); - } + Button startBtn = findViewById(R.id.button_start); + startBtn.setOnClickListener(v -> { + requestScreenCapture(); + finish(); + }); + + mAudioSwitch = findViewById(R.id.screenrecord_audio_switch); + mTapsSwitch = findViewById(R.id.screenrecord_taps_switch); } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - boolean showTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS - || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS); - boolean useAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO - || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS); - switch (requestCode) { - case REQUEST_CODE_VIDEO_TAPS: - case REQUEST_CODE_VIDEO_AUDIO_TAPS: - case REQUEST_CODE_VIDEO_ONLY: - case REQUEST_CODE_VIDEO_AUDIO: - if (resultCode == RESULT_OK) { - PendingIntent startIntent = PendingIntent.getForegroundService( - this, RecordingService.REQUEST_CODE, RecordingService.getStartIntent( - ScreenRecordDialog.this, resultCode, data, useAudio, - showTaps), - PendingIntent.FLAG_UPDATE_CURRENT - ); - PendingIntent stopIntent = PendingIntent.getService( - this, RecordingService.REQUEST_CODE, - RecordingService.getStopIntent(this), - PendingIntent.FLAG_UPDATE_CURRENT); - mController.startCountdown(DELAY_MS, startIntent, stopIntent); - } else { - Toast.makeText(this, - getResources().getString(R.string.screenrecord_permission_error), - Toast.LENGTH_SHORT).show(); - } - finish(); - break; - case REQUEST_CODE_PERMISSIONS: - int permission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE); - if (permission != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, - getResources().getString(R.string.screenrecord_permission_error), - Toast.LENGTH_SHORT).show(); - finish(); - } else { - requestScreenCapture(); - } - break; - case REQUEST_CODE_PERMISSIONS_AUDIO: - int videoPermission = checkSelfPermission( - Manifest.permission.WRITE_EXTERNAL_STORAGE); - int audioPermission = checkSelfPermission(Manifest.permission.RECORD_AUDIO); - if (videoPermission != PackageManager.PERMISSION_GRANTED - || audioPermission != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, - getResources().getString(R.string.screenrecord_permission_error), - Toast.LENGTH_SHORT).show(); - finish(); - } else { - requestScreenCapture(); - } - break; - } + private void requestScreenCapture() { + boolean useAudio = mAudioSwitch.isChecked(); + boolean showTaps = mTapsSwitch.isChecked(); + PendingIntent startIntent = PendingIntent.getForegroundService(this, + RecordingService.REQUEST_CODE, + RecordingService.getStartIntent( + ScreenRecordDialog.this, RESULT_OK, null, useAudio, showTaps), + PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent stopIntent = PendingIntent.getService(this, + RecordingService.REQUEST_CODE, + RecordingService.getStopIntent(this), + PendingIntent.FLAG_UPDATE_CURRENT); + mController.startCountdown(DELAY_MS, startIntent, stopIntent); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java index 22fd37ceaebd..eb580c450730 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java @@ -22,11 +22,4 @@ package com.android.systemui.statusbar; */ public interface InflationTask { void abort(); - - /** - * Supersedes an existing task. i.e another task was superceeded by this. - * - * @param task the task that was previously running - */ - default void supersedeTask(InflationTask task) {} } 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/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java index 81833a4022a9..d0e238a66330 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java @@ -28,7 +28,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.policy.HeadsUpManager; import javax.inject.Inject; @@ -64,8 +63,8 @@ public class NotificationAlertingManager { notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override - public void onEntryInflated(NotificationEntry entry, int inflatedFlags) { - showAlertingView(entry, inflatedFlags); + public void onEntryInflated(NotificationEntry entry) { + showAlertingView(entry); } @Override @@ -90,12 +89,11 @@ public class NotificationAlertingManager { /** * Adds the entry to the respective alerting manager if the content view was inflated and * the entry should still alert. - * - * @param entry entry to add - * @param inflatedFlags flags representing content views that were inflated */ - private void showAlertingView(NotificationEntry entry, @InflationFlag int inflatedFlags) { - if ((inflatedFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { + private void showAlertingView(NotificationEntry entry) { + // TODO: Instead of this back and forth, we should listen to changes in heads up and + // cancel on-going heads up view inflation using the bind pipeline. + if (entry.getRow().getPrivateLayout().getHeadsUpChild() != null) { // Possible for shouldHeadsUp to change between the inflation starting and ending. // If it does and we no longer need to heads up, we should free the view. if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java index f6b55838989c..25253a15b125 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java @@ -24,7 +24,6 @@ import androidx.annotation.NonNull; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; /** * Listener interface for changes sent by NotificationEntryManager. @@ -62,7 +61,7 @@ public interface NotificationEntryListener { /** * Called when a notification's views are inflated for the first time. */ - default void onEntryInflated(NotificationEntry entry, @InflationFlag int inflatedFlags) { + default void onEntryInflated(NotificationEntry entry) { } /** 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 6bb377e89278..916da6eca0c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_ERROR; +import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; import android.annotation.NonNull; @@ -44,10 +45,9 @@ import com.android.systemui.statusbar.NotificationUiAdjustment; 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.NotifEvent; -import com.android.systemui.statusbar.notification.logging.NotifLog; +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.NotificationLogger; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -96,6 +96,7 @@ import dagger.Lazy; */ @Singleton public class NotificationEntryManager implements + CommonNotifCollection, Dumpable, InflationCallback, VisualStabilityManager.Callback { @@ -126,10 +127,13 @@ 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; private final LeakDetector mLeakDetector; + private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>(); private final KeyguardEnvironment mKeyguardEnvironment; private final NotificationGroupManager mGroupManager; @@ -139,7 +143,6 @@ public class NotificationEntryManager implements private NotificationPresenter mPresenter; private RankingMap mLatestRankingMap; - private NotifLog mNotifLog; @VisibleForTesting final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders @@ -180,7 +183,7 @@ public class NotificationEntryManager implements @Inject public NotificationEntryManager( - NotifLog notifLog, + NotificationEntryManagerLogger logger, NotificationGroupManager groupManager, NotificationRankingManager rankingManager, KeyguardEnvironment keyguardEnvironment, @@ -189,7 +192,7 @@ public class NotificationEntryManager implements Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy, LeakDetector leakDetector, ForegroundServiceDismissalFeatureController fgsFeatureController) { - mNotifLog = notifLog; + mLogger = logger; mGroupManager = groupManager; mRankingManager = rankingManager; mKeyguardEnvironment = keyguardEnvironment; @@ -287,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); } } @@ -318,17 +320,16 @@ public class NotificationEntryManager implements } @Override - public void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { mPendingNotifications.remove(entry.getKey()); // If there was an async task started after the removal, we don't want to add it back to // 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, inflatedFlags); + listener.onEntryInflated(entry); } addActiveNotification(entry); updateNotifications("onAsyncInflationFinished"); @@ -337,7 +338,6 @@ public class NotificationEntryManager implements } } else { for (NotificationEntryListener listener : mNotificationEntryListeners) { - mNotifLog.log(NotifEvent.INFLATED, entry); listener.onEntryReinflated(entry); } } @@ -419,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; } } @@ -434,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"); } } } @@ -457,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; } } @@ -483,11 +477,17 @@ 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); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + // NEM doesn't have a good knowledge of reasons so defaulting to unknown. + listener.onEntryRemoved(entry, REASON_UNKNOWN); + } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryCleanUp(entry); + } } } } @@ -553,6 +553,10 @@ public class NotificationEntryManager implements mLeakDetector.trackInstance(entry); + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryInit(entry); + } + // Construct the expanded view. if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { mNotificationRowBinderLazy.get() @@ -562,10 +566,13 @@ 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); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryAdded(entry); + } } public void addNotification(StatusBarNotification notification, RankingMap ranking) { @@ -596,10 +603,13 @@ 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); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryUpdated(entry); + } if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { mNotificationRowBinderLazy.get() @@ -674,6 +684,9 @@ public class NotificationEntryManager implements for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onNotificationRankingUpdated(rankingMap); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onRankingUpdate(rankingMap); + } } private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) { @@ -785,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); @@ -862,6 +875,11 @@ public class NotificationEntryManager implements return mReadOnlyNotifications.size() != 0; } + @Override + public void addCollectionListener(NotifCollectionListener listener) { + mNotifCollectionListeners.add(listener); + } + /* * End annexation * ----- 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/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java index 7a6d4f1c7d7b..9272e51b6aa0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java @@ -149,9 +149,7 @@ public class NotifInflaterImpl implements NotifInflater { } @Override - public void onAsyncInflationFinished( - NotificationEntry entry, - int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { if (mExternalInflationCallback != null) { mExternalInflationCallback.onInflationFinished(entry); } 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 9142388374e3..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 @@ -23,7 +23,9 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga 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.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; @@ -66,7 +68,7 @@ import javax.inject.Singleton; * 9. The list is handed off to the view layer to be rendered */ @Singleton -public class NotifPipeline { +public class NotifPipeline implements CommonNotifCollection { private final NotifCollection mNotifCollection; private final ShadeListBuilder mShadeListBuilder; @@ -89,23 +91,28 @@ public class NotifPipeline { return mNotifCollection.getActiveNotifs(); } - /** - * Registers a listener to be informed when there is a notification entry event such as an add, - * update, or remove. - */ + @Override public void addCollectionListener(NotifCollectionListener listener) { mNotifCollection.addCollectionListener(listener); } /** * 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 df65dacf12dd..006d40ddbac5 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,8 +66,10 @@ 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.ExpandableNotificationRowController; import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; @@ -104,6 +106,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; @@ -149,6 +154,7 @@ public final class NotificationEntry extends ListEntry { private NotificationEntry parent; // our parent (if we're in a group) private ExpandableNotificationRow row; // the outer expanded view + private ExpandableNotificationRowController mRowController; private int mCachedContrastColor = COLOR_INVALID; private int mCachedContrastColorIsFor = COLOR_INVALID; @@ -275,7 +281,12 @@ public final class NotificationEntry extends ListEntry { return mHasInflationError; } - void setHasInflationError(boolean hasError) { + /** + * Set whether the notification has an error while inflating. + * + * TODO: Move this into an inflation error manager class. + */ + public void setHasInflationError(boolean hasError) { mHasInflationError = hasError; } @@ -415,6 +426,14 @@ public final class NotificationEntry extends ListEntry { this.row = row; } + public ExpandableNotificationRowController getRowController() { + return mRowController; + } + + public void setRowController(ExpandableNotificationRowController controller) { + mRowController = controller; + } + @Nullable public List<NotificationEntry> getChildren() { if (row == null) { @@ -595,12 +614,8 @@ public final class NotificationEntry extends ListEntry { public void setInflationTask(InflationTask abortableTask) { // abort any existing inflation - InflationTask existing = mRunningTask; abortTask(); mRunningTask = abortableTask; - if (existing != null && mRunningTask != null) { - mRunningTask.supersedeTask(existing); - } } public void onInflationTaskFinished() { 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 2a7683a8c7c2..e8a62e48e75e 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 @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.collection.inflation; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; -import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import android.annotation.Nullable; @@ -40,16 +39,19 @@ import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationClicker; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; +import com.android.systemui.statusbar.notification.row.RowContentBindParams; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.notification.row.RowInflaterTask; +import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.Objects; @@ -64,36 +66,32 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private static final String TAG = "NotificationViewManager"; - private final NotificationGroupManager mGroupManager; - private final NotificationGutsManager mGutsManager; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; + private final Context mContext; - private final NotificationRowContentBinder mRowContentBinder; + private final NotifBindPipeline mNotifBindPipeline; + private final RowContentBindStage mRowContentBindStage; private final NotificationMessagingUtil mMessagingUtil; - private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = - this::logNotificationExpansion; private final NotificationRemoteInputManager mNotificationRemoteInputManager; private final NotificationLockscreenUserManager mNotificationLockscreenUserManager; - private final boolean mAllowLongPress; - private final KeyguardBypassController mKeyguardBypassController; - private final StatusBarStateController mStatusBarStateController; private NotificationPresenter mPresenter; private NotificationListContainer mListContainer; - private HeadsUpManager mHeadsUpManager; private NotificationRowContentBinder.InflationCallback mInflationCallback; - private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private BindRowCallback mBindRowCallback; private NotificationClicker mNotificationClicker; private final Provider<RowInflaterTask> mRowInflaterTaskProvider; - private final NotificationLogger mNotificationLogger; + private final ExpandableNotificationRowComponent.Builder + mExpandableNotificationRowComponentBuilder; @Inject public NotificationRowBinderImpl( Context context, + NotificationMessagingUtil notificationMessagingUtil, NotificationRemoteInputManager notificationRemoteInputManager, NotificationLockscreenUserManager notificationLockscreenUserManager, - NotificationRowContentBinder rowContentBinder, + NotifBindPipeline notifBindPipeline, + RowContentBindStage rowContentBindStage, @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, KeyguardBypassController keyguardBypassController, StatusBarStateController statusBarStateController, @@ -101,20 +99,16 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { NotificationGutsManager notificationGutsManager, NotificationInterruptionStateProvider notificationInterruptionStateProvider, Provider<RowInflaterTask> rowInflaterTaskProvider, - NotificationLogger logger) { + ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder) { mContext = context; - mRowContentBinder = rowContentBinder; - mMessagingUtil = new NotificationMessagingUtil(context); + mNotifBindPipeline = notifBindPipeline; + mRowContentBindStage = rowContentBindStage; + mMessagingUtil = notificationMessagingUtil; mNotificationRemoteInputManager = notificationRemoteInputManager; mNotificationLockscreenUserManager = notificationLockscreenUserManager; - mAllowLongPress = allowLongPress; - mKeyguardBypassController = keyguardBypassController; - mStatusBarStateController = statusBarStateController; - mGroupManager = notificationGroupManager; - mGutsManager = notificationGutsManager; mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mRowInflaterTaskProvider = rowInflaterTaskProvider; - mNotificationLogger = logger; + mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder; } /** @@ -122,13 +116,10 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { */ public void setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer, - HeadsUpManager headsUpManager, BindRowCallback bindRowCallback) { mPresenter = presenter; mListContainer = listContainer; - mHeadsUpManager = headsUpManager; mBindRowCallback = bindRowCallback; - mOnAppOpsClickListener = mGutsManager::openGuts; } public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) { @@ -143,9 +134,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { * Inflates the views for the given entry (possibly asynchronously). */ @Override - public void inflateViews( - NotificationEntry entry, - Runnable onDismissRunnable) + public void inflateViews(NotificationEntry entry, Runnable onDismissRunnable) throws InflationException { ViewGroup parent = mListContainer.getViewParentForNotification(entry); PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, @@ -156,64 +145,39 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { entry.updateIcons(mContext, sbn); entry.reset(); updateNotification(entry, pmUser, sbn, entry.getRow()); - entry.getRow().setOnDismissRunnable(onDismissRunnable); + entry.getRowController().setOnDismissRunnable(onDismissRunnable); } else { entry.createIcons(mContext, sbn); mRowInflaterTaskProvider.get().inflate(mContext, parent, entry, row -> { - bindRow(entry, pmUser, sbn, row, onDismissRunnable); + // Setup the controller for the view. + ExpandableNotificationRowComponent component = + mExpandableNotificationRowComponentBuilder + .expandableNotificationRow(row) + .notificationEntry(entry) + .onDismissRunnable(onDismissRunnable) + .inflationCallback(mInflationCallback) + .rowContentBindStage(mRowContentBindStage) + .onExpandClickListener(mPresenter) + .build(); + ExpandableNotificationRowController rowController = + component.getExpandableNotificationRowController(); + rowController.init(); + entry.setRowController(rowController); + bindRow(entry, pmUser, sbn, row); updateNotification(entry, pmUser, sbn, row); }); } } + //TODO: This method associates a row with an entry, but eventually needs to not do that private void bindRow(NotificationEntry entry, PackageManager pmUser, - StatusBarNotification sbn, ExpandableNotificationRow row, - Runnable onDismissRunnable) { - // Get the app name. - // Note that Notification.Builder#bindHeaderAppName has similar logic - // but since this field is used in the guts, it must be accurate. - // Therefore we will only show the application label, or, failing that, the - // package name. No substitutions. - final String pkg = sbn.getPackageName(); - String appname = pkg; - try { - final ApplicationInfo info = pmUser.getApplicationInfo(pkg, - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS); - if (info != null) { - appname = String.valueOf(pmUser.getApplicationLabel(info)); - } - } catch (PackageManager.NameNotFoundException e) { - // Do nothing - } - - row.initialize( - appname, - sbn.getKey(), - mExpansionLogger, - mKeyguardBypassController, - mGroupManager, - mHeadsUpManager, - mRowContentBinder, - mPresenter); - - // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely - row.setStatusBarStateController(mStatusBarStateController); - row.setInflationCallback(mInflationCallback); - row.setAppOpsOnClickListener(mOnAppOpsClickListener); - if (mAllowLongPress) { - row.setLongPressListener(mGutsManager::openGuts); - } + StatusBarNotification sbn, ExpandableNotificationRow row) { mListContainer.bindRow(row); mNotificationRemoteInputManager.bindRow(row); - - row.setOnDismissRunnable(onDismissRunnable); - row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - if (ENABLE_REMOTE_INPUT) { - row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); - } - + entry.setRow(row); + row.setEntry(entry); + mNotifBindPipeline.manageRow(entry, row); mBindRowCallback.onBindRow(entry, pmUser, sbn, row); } @@ -247,13 +211,11 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { } } - //TODO: This method associates a row with an entry, but eventually needs to not do that private void updateNotification( NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row) { - row.setIsLowPriority(entry.isAmbient()); // Extract target SDK version. try { @@ -268,31 +230,36 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { // TODO: should updates to the entry be happening somewhere else? entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); - entry.setRow(row); row.setOnActivatedListener(mPresenter); - boolean useIncreasedCollapsedHeight = + final boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn, entry.getImportance()); - boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight + final boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && !mPresenter.isPresenterFullyCollapsed(); - row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); - row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); - row.setEntry(entry); + final boolean isLowPriority = entry.isAmbient(); + + RowContentBindParams params = mRowContentBindStage.getStageParams(entry); + params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); + params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); + params.setUseLowPriority(entry.isAmbient()); if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) { - row.setInflationFlags(FLAG_CONTENT_VIEW_HEADS_UP); + params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); } + //TODO: Replace this API with RowContentBindParams directly row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry)); - row.inflateViews(); + params.rebindAllContentViews(); + mRowContentBindStage.requestRebind(entry, en -> { + row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight); + row.setUsesIncreasedHeadsUpHeight(useIncreasedHeadsUp); + row.setIsLowPriority(isLowPriority); + mInflationCallback.onAsyncInflationFinished(en); + }); // bind the click event to the content area Objects.requireNonNull(mNotificationClicker).register(row, sbn); } - private void logNotificationExpansion(String key, boolean userAction, boolean expanded) { - mNotificationLogger.onExpansionChanged(key, userAction, expanded); - } - /** Callback for when a row is bound to an entry. */ public interface BindRowCallback { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java new file mode 100644 index 000000000000..171816fd28da --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java @@ -0,0 +1,37 @@ +/* + * 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.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +/** + * A notification collection that manages the list of {@link NotificationEntry}s that will be + * rendered. + * + * TODO: (b/145659174) Once we fully switch off {@link NotificationEntryManager} to + * {@link NotifPipeline}, we probably won't need this, but having it for now makes it easy to + * switch between the two. + */ +public interface CommonNotifCollection { + /** + * Registers a listener to be informed when notifications are created, added, updated, removed, + * or deleted. + */ + void addCollectionListener(NotifCollectionListener listener); +} 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/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index c7666e47d4b4..39f4dfacc2c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -19,6 +19,10 @@ package com.android.systemui.statusbar.notification.dagger; import android.content.Context; import com.android.systemui.R; +import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl; import com.android.systemui.statusbar.notification.init.NotificationsControllerStub; @@ -45,4 +49,16 @@ public class NotificationsModule { return stubController.get(); } } + + /** + * Provide the active notification collection managing the notifications to render. + */ + @Provides + @Singleton + public CommonNotifCollection provideCommonNotifCollection( + FeatureFlags featureFlags, + Lazy<NotifPipeline> pipeline, + NotificationEntryManager entryManager) { + return featureFlags.isNewNotifPipelineRenderingEnabled() ? pipeline.get() : entryManager; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 61e3192eba3c..3e0bcbb796bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager import com.android.systemui.statusbar.notification.NotificationListController import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer +import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper import com.android.systemui.statusbar.phone.NotificationGroupManager @@ -55,6 +56,7 @@ class NotificationsControllerImpl @Inject constructor( private val notificationListener: NotificationListener, private val entryManager: NotificationEntryManager, private val newNotifPipeline: Lazy<NotifPipelineInitializer>, + private val notifBindPipelineInitializer: NotifBindPipelineInitializer, private val deviceProvisionedController: DeviceProvisionedController, private val notificationRowBinder: NotificationRowBinderImpl, private val remoteInputUriController: RemoteInputUriController, @@ -88,7 +90,6 @@ class NotificationsControllerImpl @Inject constructor( notificationRowBinder.setUpWithPresenter( presenter, listContainer, - headsUpManager, bindRowCallback) if (featureFlags.isNewNotifPipelineEnabled) { @@ -98,6 +99,7 @@ class NotificationsControllerImpl @Inject constructor( if (featureFlags.isNewNotifPipelineRenderingEnabled) { // TODO } else { + notifBindPipelineInitializer.initialize() notificationRowBinder.setInflationCallback(entryManager) remoteInputUriController.attach(entryManager) 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/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt index 88b41471a063..fc221d43b3dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt @@ -93,8 +93,7 @@ class PeopleHubDataSourceImpl @Inject constructor( private val peopleHubManagerForUser = SparseArray<PeopleHubManager>() private val notificationEntryListener = object : NotificationEntryListener { - override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) = - addVisibleEntry(entry) + override fun onEntryInflated(entry: NotificationEntry) = addVisibleEntry(entry) override fun onEntryReinflated(entry: NotificationEntry) = addVisibleEntry(entry) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 50a20374fee5..3eac229af3f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -33,16 +33,14 @@ import android.view.accessibility.AccessibilityManager; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; -import com.android.systemui.Dependency; +import com.android.systemui.Gefingerpoken; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; -import com.android.systemui.statusbar.phone.DoubleTapHelper; /** * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf} @@ -94,14 +92,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR = new PathInterpolator(0, 0, 0.5f, 1); private int mTintedRippleColor; - protected int mNormalRippleColor; - private final AccessibilityManager mAccessibilityManager; - private final DoubleTapHelper mDoubleTapHelper; + private int mNormalRippleColor; + private Gefingerpoken mTouchHandler; private boolean mDimmed; - protected int mBgTint = NO_COLOR; - private float mBgAlpha = 1f; + int mBgTint = NO_COLOR; /** * Flag to indicate that the notification has been touched once and the second touch will @@ -116,7 +112,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private Interpolator mCurrentAppearInterpolator; private Interpolator mCurrentAlphaInterpolator; - protected NotificationBackgroundView mBackgroundNormal; + NotificationBackgroundView mBackgroundNormal; private NotificationBackgroundView mBackgroundDimmed; private ObjectAnimator mBackgroundAnimator; private RectF mAppearAnimationRect = new RectF(); @@ -130,7 +126,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private boolean mLastInSection; private boolean mFirstInSection; private boolean mIsBelowSpeedBump; - private final FalsingManager mFalsingManager; private float mNormalBackgroundVisibilityAmount; private float mDimmedBackgroundFadeInAmount = -1; @@ -154,38 +149,25 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView */ private boolean mNeedsDimming; private int mDimmedAlpha; - private boolean mBlockNextTouch; private boolean mIsHeadsUpAnimation; private int mHeadsUpAddStartLocation; private float mHeadsUpLocation; private boolean mIsAppearing; private boolean mDismissed; private boolean mRefocusOnDismiss; + private OnDimmedListener mOnDimmedListener; + private AccessibilityManager mAccessibilityManager; public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f); mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f); - mFalsingManager = Dependency.get(FalsingManager.class); // TODO: inject into a controller. setClipChildren(false); setClipToPadding(false); updateColors(); - mAccessibilityManager = AccessibilityManager.getInstance(mContext); - - mDoubleTapHelper = new DoubleTapHelper(this, (active) -> { - if (active) { - makeActive(); - } else { - makeInactive(true /* animate */); - } - }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap); initDimens(); } - public FalsingManager getFalsingManager() { - return mFalsingManager; - } - private void updateColors() { mNormalColor = mContext.getColor(R.color.notification_material_background_color); mTintedRippleColor = mContext.getColor( @@ -236,32 +218,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim); } - private final Runnable mTapTimeoutRunnable = new Runnable() { - @Override - public void run() { - makeInactive(true /* animate */); - } - }; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN - && disallowSingleClick(ev) && !isTouchExplorationEnabled()) { - if (!mActivated) { - return true; - } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) { - mBlockNextTouch = true; - makeInactive(true /* animate */); - return true; - } + if (mTouchHandler != null && mTouchHandler.onInterceptTouchEvent(ev)) { + return true; } return super.onInterceptTouchEvent(ev); } - private boolean isTouchExplorationEnabled() { - return mAccessibilityManager.isTouchExplorationEnabled(); - } - protected boolean disallowSingleClick(MotionEvent ev) { return false; } @@ -270,25 +235,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView return false; } - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean result; - if (mBlockNextTouch) { - mBlockNextTouch = false; - return false; - } - if (mNeedsDimming && !isTouchExplorationEnabled() && isInteractive()) { - boolean wasActivated = mActivated; - result = handleTouchEventDimmed(event); - if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) { - removeCallbacks(mTapTimeoutRunnable); - } - } else { - result = super.onTouchEvent(event); - } - return result; - } - /** * @return whether this view is interactive and can be double tapped */ @@ -313,28 +259,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } - public void setRippleAllowed(boolean allowed) { + void setRippleAllowed(boolean allowed) { mBackgroundNormal.setPressedAllowed(allowed); } - private boolean handleTouchEventDimmed(MotionEvent event) { - if (mNeedsDimming && !mDimmed) { - // We're actually dimmed, but our content isn't dimmable, let's ensure we have a ripple - super.onTouchEvent(event); - } - return mDoubleTapHelper.onTouchEvent(event, getActualHeight()); - } - - @Override - public boolean performClick() { - if (!mNeedsDimming || isTouchExplorationEnabled()) { - return super.performClick(); - } - return false; - } - - private void makeActive() { - mFalsingManager.onNotificationActive(); + void makeActive() { startActivateAnimation(false /* reverse */); mActivated = true; if (mOnActivatedListener != null) { @@ -388,19 +317,25 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundNormal.animate() .alpha(reverse ? 0f : 1f) .setInterpolator(alphaInterpolator) - .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float animatedFraction = animation.getAnimatedFraction(); - if (reverse) { - animatedFraction = 1.0f - animatedFraction; - } - setNormalBackgroundVisibilityAmount(animatedFraction); + .setUpdateListener(animation -> { + float animatedFraction = animation.getAnimatedFraction(); + if (reverse) { + animatedFraction = 1.0f - animatedFraction; } + setNormalBackgroundVisibilityAmount(animatedFraction); }) .setDuration(ACTIVATE_ANIMATION_LENGTH); } + @Override + public boolean performClick() { + if (!mNeedsDimming || (mAccessibilityManager != null + && mAccessibilityManager.isTouchExplorationEnabled())) { + return super.performClick(); + } + return false; + } + /** * Cancels the hotspot and makes the notification inactive. */ @@ -418,11 +353,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView if (mOnActivatedListener != null) { mOnActivatedListener.onActivationReset(this); } - removeCallbacks(mTapTimeoutRunnable); } public void setDimmed(boolean dimmed, boolean fade) { mNeedsDimming = dimmed; + if (mOnDimmedListener != null) { + mOnDimmedListener.onSetDimmed(dimmed); + } dimmed &= isDimmable(); if (mDimmed != dimmed) { mDimmed = dimmed; @@ -439,13 +376,17 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView return true; } + public boolean isDimmed() { + return mDimmed; + } + private void updateOutlineAlpha() { float alpha = NotificationStackScrollLayout.BACKGROUND_ALPHA_DIMMED; alpha = (alpha + (1.0f - alpha) * mNormalBackgroundVisibilityAmount); setOutlineAlpha(alpha); } - public void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) { + private void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) { mNormalBackgroundVisibilityAmount = normalBackgroundVisibilityAmount; updateOutlineAlpha(); } @@ -473,14 +414,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView /** * Sets the tint color of the background */ - public void setTintColor(int color) { + protected void setTintColor(int color) { setTintColor(color, false); } /** * Sets the tint color of the background */ - public void setTintColor(int color, boolean animated) { + void setTintColor(int color, boolean animated) { if (color != mBgTint) { mBgTint = color; updateBackgroundTint(animated); @@ -562,13 +503,10 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mStartTint = mCurrentBackgroundTint; mTargetTint = color; mBackgroundColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); - mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint, - animation.getAnimatedFraction()); - setBackgroundTintColor(newColor); - } + mBackgroundColorAnimator.addUpdateListener(animation -> { + int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint, + animation.getAnimatedFraction()); + setBackgroundTintColor(newColor); }); mBackgroundColorAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); mBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR); @@ -643,11 +581,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } protected void updateBackgroundAlpha(float transformationAmount) { - mBgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f; + float bgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f; if (mDimmedBackgroundFadeInAmount != -1) { - mBgAlpha *= mDimmedBackgroundFadeInAmount; + bgAlpha *= mDimmedBackgroundFadeInAmount; } - mBackgroundDimmed.setAlpha(mBgAlpha); + mBackgroundDimmed.setAlpha(bgAlpha); } protected void resetBackgroundAlpha() { @@ -671,7 +609,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundDimmed.setVisibility(View.INVISIBLE); mBackgroundNormal.setVisibility(View.VISIBLE); mBackgroundNormal.setAlpha(1f); - removeCallbacks(mTapTimeoutRunnable); // make in inactive to avoid it sticking around active makeInactive(false /* animate */); } @@ -783,14 +720,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAppearAnimator.setInterpolator(Interpolators.LINEAR); mAppearAnimator.setDuration( (long) (duration * Math.abs(mAppearAnimationFraction - targetValue))); - mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - mAppearAnimationFraction = (float) animation.getAnimatedValue(); - updateAppearAnimationAlpha(); - updateAppearRect(); - invalidate(); - } + mAppearAnimator.addUpdateListener(animation -> { + mAppearAnimationFraction = (float) animation.getAnimatedValue(); + updateAppearAnimationAlpha(); + updateAppearRect(); + invalidate(); }); if (animationListener != null) { mAppearAnimator.addListener(animationListener); @@ -921,7 +855,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView getCurrentBackgroundRadiusBottom()); } - protected void applyBackgroundRoundness(float topRadius, float bottomRadius) { + private void applyBackgroundRoundness(float topRadius, float bottomRadius) { mBackgroundDimmed.setRoundness(topRadius, bottomRadius); mBackgroundNormal.setRoundness(topRadius, bottomRadius); } @@ -963,7 +897,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } - protected int getRippleColor() { + private int getRippleColor() { if (mBgTint != 0) { return mTintedRippleColor; } else { @@ -1010,10 +944,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mOnActivatedListener = onActivatedListener; } - public boolean hasSameBgColor(ActivatableNotificationView otherView) { - return calculateBgColor() == otherView.calculateBgColor(); - } - @Override public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd, int outlineTranslation) { @@ -1071,8 +1001,24 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView return mRefocusOnDismiss || isAccessibilityFocused(); } + void setTouchHandler(Gefingerpoken touchHandler) { + mTouchHandler = touchHandler; + } + + void setOnDimmedListener(OnDimmedListener onDimmedListener) { + mOnDimmedListener = onDimmedListener; + } + + public void setAccessibilityManager(AccessibilityManager accessibilityManager) { + mAccessibilityManager = accessibilityManager; + } + public interface OnActivatedListener { void onActivated(ActivatableNotificationView view); void onActivationReset(ActivatableNotificationView view); } + + interface OnDimmedListener { + void onSetDimmed(boolean dimmed); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java index 18993ffec357..8465658079f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java @@ -16,9 +16,13 @@ package com.android.systemui.statusbar.notification.row; +import android.view.MotionEvent; +import android.view.View; import android.view.accessibility.AccessibilityManager; +import com.android.systemui.Gefingerpoken; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.statusbar.phone.DoubleTapHelper; import javax.inject.Inject; @@ -27,21 +31,102 @@ import javax.inject.Inject; */ public class ActivatableNotificationViewController { private final ActivatableNotificationView mView; + private final ExpandableOutlineViewController mExpandableOutlineViewController; private final AccessibilityManager mAccessibilityManager; private final FalsingManager mFalsingManager; + private DoubleTapHelper mDoubleTapHelper; + private boolean mNeedsDimming; + + private TouchHandler mTouchHandler = new TouchHandler(); @Inject public ActivatableNotificationViewController(ActivatableNotificationView view, + ExpandableOutlineViewController expandableOutlineViewController, AccessibilityManager accessibilityManager, FalsingManager falsingManager) { mView = view; + mExpandableOutlineViewController = expandableOutlineViewController; mAccessibilityManager = accessibilityManager; mFalsingManager = falsingManager; + + mView.setOnActivatedListener(new ActivatableNotificationView.OnActivatedListener() { + @Override + public void onActivated(ActivatableNotificationView view) { + mFalsingManager.onNotificationActive(); + } + + @Override + public void onActivationReset(ActivatableNotificationView view) { + } + }); } /** * Initialize the controller, setting up handlers and other behavior. */ public void init() { + mExpandableOutlineViewController.init(); + mDoubleTapHelper = new DoubleTapHelper(mView, (active) -> { + if (active) { + mView.makeActive(); + mFalsingManager.onNotificationActive(); + } else { + mView.makeInactive(true /* animate */); + } + }, mView::performClick, mView::handleSlideBack, mFalsingManager::onNotificationDoubleTap); + mView.setOnTouchListener(mTouchHandler); + mView.setTouchHandler(mTouchHandler); + mView.setOnDimmedListener(dimmed -> { + mNeedsDimming = dimmed; + }); + mView.setAccessibilityManager(mAccessibilityManager); + } + + class TouchHandler implements Gefingerpoken, View.OnTouchListener { + private boolean mBlockNextTouch; + + @Override + public boolean onTouch(View v, MotionEvent ev) { + boolean result; + if (mBlockNextTouch) { + mBlockNextTouch = false; + return true; + } + if (mNeedsDimming && !mAccessibilityManager.isTouchExplorationEnabled() + && mView.isInteractive()) { + if (mNeedsDimming && !mView.isDimmed()) { + // We're actually dimmed, but our content isn't dimmable, + // let's ensure we have a ripple + return false; + } + result = mDoubleTapHelper.onTouchEvent(ev, mView.getActualHeight()); + } else { + return false; + } + return result; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN + && mView.disallowSingleClick(ev) + && !mAccessibilityManager.isTouchExplorationEnabled()) { + if (!mView.isActivated()) { + return true; + } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) { + mBlockNextTouch = true; + mView.makeInactive(true /* animate */); + return true; + } + } + return false; + } + /** + * Use {@link #onTouch(View, MotionEvent) instead}. + */ + @Override + public boolean onTouchEvent(MotionEvent ev) { + return false; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java new file mode 100644 index 000000000000..1cf6b4f2321c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java @@ -0,0 +1,76 @@ +/* + * 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.row; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.os.CancellationSignal; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; + +/** + * A {@link BindRequester} is a general superclass for something that notifies + * {@link NotifBindPipeline} when it needs it to kick off a bind run. + */ +public abstract class BindRequester { + private @Nullable BindRequestListener mBindRequestListener; + + /** + * Notifies the listener that some parameters/state has changed for some notification and that + * content needs to be bound again. + * + * The caller can also specify a callback for when the entire bind pipeline completes, i.e. + * when the change is fully propagated to the final view. The caller can cancel this + * callback with the returned cancellation signal. + * + * @param callback callback after bind completely finishes + * @return cancellation signal to cancel callback + */ + public final CancellationSignal requestRebind( + @NonNull NotificationEntry entry, + @Nullable BindCallback callback) { + CancellationSignal signal = new CancellationSignal(); + if (mBindRequestListener != null) { + mBindRequestListener.onBindRequest(entry, signal, callback); + } + return signal; + } + + final void setBindRequestListener(BindRequestListener listener) { + mBindRequestListener = listener; + } + + /** + * Listener interface for when content needs to be bound again. + */ + public interface BindRequestListener { + + /** + * Called when {@link #requestRebind} is called. + * + * @param entry notification that has outdated content + * @param signal cancellation signal to cancel callback + * @param callback callback after content is fully updated + */ + void onBindRequest( + @NonNull NotificationEntry entry, + @NonNull CancellationSignal signal, + @Nullable BindCallback callback); + + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java new file mode 100644 index 000000000000..29447caa1240 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java @@ -0,0 +1,104 @@ +/* + * 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.row; + +import android.annotation.MainThread; +import android.util.ArrayMap; + +import androidx.annotation.NonNull; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +import java.util.Map; + +/** + * A {@link BindStage} is an abstraction for a unit of work in inflating/binding/unbinding + * views to a notification. Used by {@link NotifBindPipeline}. + * + * Clients may also use {@link #getStageParams} to provide parameters for this stage for a given + * notification and request a rebind. + * + * @param <Params> params to do this stage + */ +@MainThread +public abstract class BindStage<Params> extends BindRequester { + + private Map<NotificationEntry, Params> mContentParams = new ArrayMap<>(); + + /** + * Execute the stage asynchronously. + * + * @param row notification top-level view to bind views to + * @param callback callback after stage finishes + */ + protected abstract void executeStage( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row, + @NonNull StageCallback callback); + + /** + * Abort the stage if in progress. + * + * @param row notification top-level view to bind views to + */ + protected abstract void abortStage( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row); + + /** + * Get the stage parameters for the entry. Clients should use this to modify how the stage + * handles the notification content. + */ + public final Params getStageParams(@NonNull NotificationEntry entry) { + Params params = mContentParams.get(entry); + if (params == null) { + throw new IllegalStateException( + String.format("Entry does not have any stage parameters. key: %s", + entry.getKey())); + } + return params; + } + + /** + * Create a params entry for the notification for this stage. + */ + final void createStageParams(@NonNull NotificationEntry entry) { + mContentParams.put(entry, newStageParams()); + } + + /** + * Delete params entry for notification. + */ + final void deleteStageParams(@NonNull NotificationEntry entry) { + mContentParams.remove(entry); + } + + /** + * Create a new, empty stage params object. + */ + protected abstract Params newStageParams(); + + /** + * Interface for callback. + */ + interface StageCallback { + /** + * Callback for when the stage is complete. + */ + void onStageFinished(NotificationEntry entry); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 253be2fcb5ca..551731824570 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -19,8 +19,6 @@ package com.android.systemui.statusbar.notification.row; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; @@ -44,7 +42,6 @@ import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.os.SystemClock; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.AttributeSet; @@ -74,11 +71,11 @@ import com.android.internal.widget.CachingIconView; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarIconView; @@ -88,8 +85,6 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.NotificationCounters; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.notification.stack.AmbientState; @@ -127,14 +122,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f; private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); - /** - * Content views that must be inflated at all times. - */ - @InflationFlag - static final int REQUIRED_INFLATION_FLAGS = - FLAG_CONTENT_VIEW_CONTRACTED - | FLAG_CONTENT_VIEW_EXPANDED; - private boolean mUpdateBackgroundOnUpdate; private boolean mNotificationTranslationFinished = false; @@ -149,7 +136,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private StatusBarStateController mStatusbarStateController; private KeyguardBypassController mBypassController; private LayoutListener mLayoutListener; - private NotificationRowContentBinder mNotificationContentBinder; + private RowContentBindStage mRowContentBindStage; private int mIconTransformContentShift; private int mIconTransformContentShiftNoIcon; private int mMaxHeadsUpHeightBeforeN; @@ -211,6 +198,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private NotificationGuts mGuts; private NotificationEntry mEntry; private String mAppName; + private FalsingManager mFalsingManager; /** * Whether or not the notification is using the heads up view and should peek from the top. @@ -244,10 +232,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private ExpandableNotificationRow mNotificationParent; private OnExpandClickListener mOnExpandClickListener; private View.OnClickListener mOnAppOpsClickListener; - private InflationCallback mInflationCallback; private boolean mIsChildInGroup; - private @InflationFlag int mInflationFlags = REQUIRED_INFLATION_FLAGS; - private final BindParams mBindParams = new BindParams(); // Listener will be called when receiving a long click event. // Use #setLongPressPosition to optionally assign positional data with the long press. @@ -460,24 +445,25 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * Inflate views based off the inflation flags set. Inflation happens asynchronously. - */ - public void inflateViews() { - mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams, - false /* forceInflate */, mInflationCallback); - } - - /** * Marks a content view as freeable, setting it so that future inflations do not reinflate * and ensuring that the view is freed when it is safe to remove. * + * TODO: This should be moved to the respective coordinator and call + * {@link RowContentBindParams#freeContentViews} directly after disappear animation + * finishes instead of depending on binding API to know when it's "safe". + * * @param inflationFlag flag corresponding to the content view to be freed */ public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) { // View should not be reinflated in the future - clearInflationFlags(inflationFlag); - Runnable freeViewRunnable = - () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag); + Runnable freeViewRunnable = () -> { + // Possible for notification to be removed after free request. + if (!isRemoved()) { + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.freeContentViews(inflationFlag); + mRowContentBindStage.requestRebind(mEntry, null /* callback */); + } + }; switch (inflationFlag) { case FLAG_CONTENT_VIEW_HEADS_UP: getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, @@ -492,35 +478,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * Set flags for content views that should be inflated - * - * @param flags flags to inflate - */ - public void setInflationFlags(@InflationFlag int flags) { - mInflationFlags |= flags; - } - - /** - * Clear flags for content views that should not be inflated - * - * @param flags flags that should not be inflated - */ - public void clearInflationFlags(@InflationFlag int flags) { - mInflationFlags &= ~flags; - mInflationFlags |= REQUIRED_INFLATION_FLAGS; - } - - /** - * Whether or not a content view should be inflated. - * - * @param flag the flag corresponding to the content view - * @return true if the flag is set, false otherwise - */ - public boolean isInflationFlagSet(@InflationFlag int flag) { - return ((mInflationFlags & flag) != 0); - } - - /** * Caches whether or not this row contains a system notification. Note, this is only cached * once per notification as the packageInfo can't technically change for a notification row. */ @@ -838,13 +795,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } mNotificationParent = isChildInGroup ? parent : null; mPrivateLayout.setIsChildInGroup(isChildInGroup); - mBindParams.isChildInGroup = isChildInGroup; + // TODO: Move inflation logic out of this call if (mIsChildInGroup != isChildInGroup) { mIsChildInGroup = isChildInGroup; if (mIsLowPriority) { - int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; - mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams, - false /* forceInflate */, mInflationCallback); + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.setUseLowPriority(mIsLowPriority); + mRowContentBindStage.requestRebind(mEntry, null /* callback */); } } resetBackgroundAlpha(); @@ -1130,20 +1087,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mEntry.setInitializationTime(SystemClock.elapsedRealtime()); - Dependency.get(PluginManager.class).addPluginListener(this, - NotificationMenuRowPlugin.class, false /* Allow multiple */); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - Dependency.get(PluginManager.class).removePluginListener(this); - } - - @Override public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) { boolean existed = mMenuRow != null && mMenuRow.getMenuView() != null; if (existed) { @@ -1243,8 +1186,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView l.reInflateViews(); } mEntry.getSbn().clearPackageContext(); - mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams, - true /* forceInflate */, mInflationCallback); + // TODO: Move content inflation logic out of this call + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.setNeedsReinflation(true); + mRowContentBindStage.requestRebind(mEntry, null /* callback */); } @Override @@ -1480,7 +1425,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mIsBlockingHelperShowing && mNotificationTranslationFinished; } - public void setOnDismissRunnable(Runnable onDismissRunnable) { + void setOnDismissRunnable(Runnable onDismissRunnable) { mOnDismissRunnable = onDismissRunnable; } @@ -1598,7 +1543,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void setIsLowPriority(boolean isLowPriority) { mIsLowPriority = isLowPriority; mPrivateLayout.setIsLowPriority(isLowPriority); - mBindParams.isLowPriority = mIsLowPriority; if (mChildrenContainer != null) { mChildrenContainer.setIsLowPriority(isLowPriority); } @@ -1608,36 +1552,25 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mIsLowPriority; } - public void setUseIncreasedCollapsedHeight(boolean use) { + public void setUsesIncreasedCollapsedHeight(boolean use) { mUseIncreasedCollapsedHeight = use; - mBindParams.usesIncreasedHeight = use; } - public void setUseIncreasedHeadsUpHeight(boolean use) { + public void setUsesIncreasedHeadsUpHeight(boolean use) { mUseIncreasedHeadsUpHeight = use; - mBindParams.usesIncreasedHeadsUpHeight = use; - } - - /** - * Set callback for notification content inflation - * - * @param callback inflation callback - */ - public void setInflationCallback(InflationCallback callback) { - mInflationCallback = callback; } public void setNeedsRedaction(boolean needsRedaction) { + // TODO: Move inflation logic out of this call and remove this method if (mNeedsRedaction != needsRedaction) { mNeedsRedaction = needsRedaction; + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); if (needsRedaction) { - setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC); - mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC, - mBindParams, false /* forceInflate */, mInflationCallback); + params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC); } else { - clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC); - freeContentViewWhenSafe(FLAG_CONTENT_VIEW_PUBLIC); + params.freeContentViews(FLAG_CONTENT_VIEW_PUBLIC); } + mRowContentBindStage.requestRebind(mEntry, null /* callback */); } } @@ -1650,7 +1583,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mMenuRow = new NotificationMenuRow(mContext); mImageResolver = new NotificationInlineImageResolver(context, new NotificationInlineImageCache()); - mMediaManager = Dependency.get(NotificationMediaManager.class); initDimens(); } @@ -1664,8 +1596,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView KeyguardBypassController bypassController, NotificationGroupManager groupManager, HeadsUpManager headsUpManager, - NotificationRowContentBinder rowContentBinder, - OnExpandClickListener onExpandClickListener) { + RowContentBindStage rowContentBindStage, + OnExpandClickListener onExpandClickListener, + NotificationMediaManager notificationMediaManager, + OnAppOpsClickListener onAppOpsClickListener, + FalsingManager falsingManager, + StatusBarStateController statusBarStateController) { mAppName = appName; if (mMenuRow != null && mMenuRow.getMenuView() != null) { mMenuRow.setAppName(mAppName); @@ -1676,11 +1612,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mGroupManager = groupManager; mPrivateLayout.setGroupManager(groupManager); mHeadsUpManager = headsUpManager; - mNotificationContentBinder = rowContentBinder; + mRowContentBindStage = rowContentBindStage; mOnExpandClickListener = onExpandClickListener; - } - - public void setStatusBarStateController(StatusBarStateController statusBarStateController) { + mMediaManager = notificationMediaManager; + setAppOpsOnClickListener(onAppOpsClickListener); + mFalsingManager = falsingManager; mStatusbarStateController = statusBarStateController; } @@ -1772,7 +1708,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mOnAppOpsClickListener; } - public void setAppOpsOnClickListener(ExpandableNotificationRow.OnAppOpsClickListener l) { + void setAppOpsOnClickListener(ExpandableNotificationRow.OnAppOpsClickListener l) { mOnAppOpsClickListener = v -> { createMenu(); NotificationMenuRowPlugin provider = getProvider(); @@ -2241,7 +2177,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * @param allowChildExpansion whether a call to this method allows expanding children */ public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { - getFalsingManager().setNotificationExpanded(); + mFalsingManager.setNotificationExpanded(); if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion && !mChildrenContainer.showingAsLowPriority()) { final boolean wasExpanded = mGroupManager.isGroupExpanded(mEntry.getSbn()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java new file mode 100644 index 000000000000..39fab439ad07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -0,0 +1,161 @@ +/* + * 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.row; + +import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; +import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; + +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; +import com.android.systemui.statusbar.notification.row.dagger.AppName; +import com.android.systemui.statusbar.notification.row.dagger.DismissRunnable; +import com.android.systemui.statusbar.notification.row.dagger.NotificationKey; +import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope; +import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.util.time.SystemClock; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * Controller for {@link ExpandableNotificationRow}. + */ +@NotificationRowScope +public class ExpandableNotificationRowController { + private final ExpandableNotificationRow mView; + private final ActivatableNotificationViewController mActivatableNotificationViewController; + private final NotificationMediaManager mMediaManager; + private final PluginManager mPluginManager; + private final SystemClock mClock; + private final String mAppName; + private final String mNotificationKey; + private final KeyguardBypassController mKeyguardBypassController; + private final NotificationGroupManager mNotificationGroupManager; + private final RowContentBindStage mRowContentBindStage; + private final NotificationLogger mNotificationLogger; + private final HeadsUpManager mHeadsUpManager; + private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener; + private final StatusBarStateController mStatusBarStateController; + private final NotificationRowContentBinder.InflationCallback mInflationCallback; + + private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = + this::logNotificationExpansion; + private final ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; + private final NotificationGutsManager mNotificationGutsManager; + private Runnable mOnDismissRunnable; + private final FalsingManager mFalsingManager; + private final boolean mAllowLongPress; + + @Inject + public ExpandableNotificationRowController(ExpandableNotificationRow view, + ActivatableNotificationViewController activatableNotificationViewController, + NotificationMediaManager mediaManager, PluginManager pluginManager, + SystemClock clock, @AppName String appName, @NotificationKey String notificationKey, + KeyguardBypassController keyguardBypassController, + NotificationGroupManager notificationGroupManager, + RowContentBindStage rowContentBindStage, + NotificationLogger notificationLogger, HeadsUpManager headsUpManager, + ExpandableNotificationRow.OnExpandClickListener onExpandClickListener, + StatusBarStateController statusBarStateController, + NotificationRowContentBinder.InflationCallback inflationCallback, + NotificationGutsManager notificationGutsManager, + @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, + @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager) { + mView = view; + mActivatableNotificationViewController = activatableNotificationViewController; + mMediaManager = mediaManager; + mPluginManager = pluginManager; + mClock = clock; + mAppName = appName; + mNotificationKey = notificationKey; + mKeyguardBypassController = keyguardBypassController; + mNotificationGroupManager = notificationGroupManager; + mRowContentBindStage = rowContentBindStage; + mNotificationLogger = notificationLogger; + mHeadsUpManager = headsUpManager; + mOnExpandClickListener = onExpandClickListener; + mStatusBarStateController = statusBarStateController; + mInflationCallback = inflationCallback; + mNotificationGutsManager = notificationGutsManager; + mOnDismissRunnable = onDismissRunnable; + mOnAppOpsClickListener = mNotificationGutsManager::openGuts; + mAllowLongPress = allowLongPress; + mFalsingManager = falsingManager; + } + + /** + * Initialize the controller. + */ + public void init() { + mActivatableNotificationViewController.init(); + mView.initialize( + mAppName, + mNotificationKey, + mExpansionLogger, + mKeyguardBypassController, + mNotificationGroupManager, + mHeadsUpManager, + mRowContentBindStage, + mOnExpandClickListener, + mMediaManager, + mOnAppOpsClickListener, + mFalsingManager, + mStatusBarStateController + ); + mView.setOnDismissRunnable(mOnDismissRunnable); + mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + if (mAllowLongPress) { + mView.setLongPressListener(mNotificationGutsManager::openGuts); + } + if (ENABLE_REMOTE_INPUT) { + mView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); + } + + mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + mView.getEntry().setInitializationTime(mClock.elapsedRealtime()); + mPluginManager.addPluginListener(mView, + NotificationMenuRowPlugin.class, false /* Allow multiple */); + } + + @Override + public void onViewDetachedFromWindow(View v) { + mPluginManager.removePluginListener(mView); + } + }); + } + + private void logNotificationExpansion(String key, boolean userAction, boolean expanded) { + mNotificationLogger.onExpansionChanged(key, userAction, expanded); + } + + /** */ + public void setOnDismissRunnable(Runnable onDismissRunnable) { + mOnDismissRunnable = onDismissRunnable; + mView.setOnDismissRunnable(onDismissRunnable); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java new file mode 100644 index 000000000000..75c9d1e6f2fc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java @@ -0,0 +1,41 @@ +/* + * 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.row; + +import javax.inject.Inject; + +/** + * Controller for {@link ExpandableOutlineView}. + */ +public class ExpandableOutlineViewController { + private final ExpandableOutlineView mView; + private final ExpandableViewController mExpandableViewController; + + @Inject + public ExpandableOutlineViewController(ExpandableOutlineView view, + ExpandableViewController expandableViewController) { + mView = view; + mExpandableViewController = expandableViewController; + } + + /** + * Initialize the controller. + */ + public void init() { + mExpandableViewController.init(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java new file mode 100644 index 000000000000..e14ca8c4e590 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java @@ -0,0 +1,37 @@ +/* + * 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.row; + +import javax.inject.Inject; + +/** + * Controller for {@link ExpandableView}. + */ +public class ExpandableViewController { + private final ExpandableView mView; + + @Inject + public ExpandableViewController(ExpandableView view) { + mView = view; + } + + /** + * Initialize the controller. + */ + public void init() { + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java new file mode 100644 index 000000000000..af2d0844412a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java @@ -0,0 +1,207 @@ +/* + * 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.row; + +import android.util.ArrayMap; +import android.util.ArraySet; +import android.widget.FrameLayout; + +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.os.CancellationSignal; + +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; + +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * {@link NotifBindPipeline} is responsible for converting notifications from their data form to + * their actual inflated views. It is essentially a control class that composes notification view + * binding logic (i.e. {@link BindStage}) in response to explicit bind requests. At the end of the + * pipeline, the notification's bound views are guaranteed to be correct and up-to-date, and any + * registered callbacks will be called. + * + * The pipeline ensures that a notification's top-level view and its content views are bound. + * Currently, a notification's top-level view, the {@link ExpandableNotificationRow} is essentially + * just a {@link FrameLayout} for various different content views that are switched in and out as + * appropriate. These include a contracted view, expanded view, heads up view, and sensitive view on + * keyguard. See {@link InflationFlag}. These content views themselves can have child views added + * on depending on different factors. For example, notification actions and smart replies are views + * that are dynamically added to these content views after they're inflated. Finally, aside from + * the app provided content views, System UI itself also provides some content views that are shown + * occasionally (e.g. {@link NotificationGuts}). Many of these are business logic specific views + * and the requirements surrounding them may change over time, so the pipeline must handle + * composing the logic as necessary. + * + * Note that bind requests do not only occur from add/updates from updates from the app. For + * example, the user may make changes to device settings (e.g. sensitive notifications on lock + * screen) or we may want to make certain optimizations for the sake of memory or performance (e.g + * freeing views when not visible). Oftentimes, we also need to wait for these changes to complete + * before doing something else (e.g. moving a notification to the top of the screen to heads up). + * The pipeline thus handles bind requests from across the system and provides a way for + * requesters to know when the change is propagated to the view. + * + * Right now, we only support one attached {@link BindStage} which just does all the binding but we + * should eventually support multiple stages once content inflation is made more modular. + * In particular, row inflation/binding, which is handled by {@link NotificationRowBinder} should + * probably be moved here in the future as a stage. Right now, the pipeline just manages content + * views and assumes that a row is given to it when it's inflated. + */ +@MainThread +@Singleton +public final class NotifBindPipeline { + private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>(); + private BindStage mStage; + + @Inject + NotifBindPipeline(NotificationEntryManager entryManager) { + entryManager.addNotificationEntryListener(mEntryListener); + } + + /** + * Set the bind stage for binding notification row content. + */ + public void setStage( + BindStage stage) { + mStage = stage; + mStage.setBindRequestListener(this::onBindRequested); + } + + /** + * Start managing the row's content for a given notification. + */ + public void manageRow( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row) { + final BindEntry bindEntry = getBindEntry(entry); + bindEntry.row = row; + if (bindEntry.invalidated) { + startPipeline(entry); + } + } + + private void onBindRequested( + @NonNull NotificationEntry entry, + @NonNull CancellationSignal signal, + @Nullable BindCallback callback) { + final BindEntry bindEntry = getBindEntry(entry); + if (bindEntry == null) { + // Invalidating views for a notification that is not active. + return; + } + + bindEntry.invalidated = true; + + // Put in new callback. + if (callback != null) { + final Set<BindCallback> callbacks = bindEntry.callbacks; + callbacks.add(callback); + signal.setOnCancelListener(() -> callbacks.remove(callback)); + } + + startPipeline(entry); + } + + /** + * Run the pipeline for the notification, ensuring all views are bound when finished. Call all + * callbacks when the run finishes. If a run is already in progress, it is restarted. + */ + private void startPipeline(NotificationEntry entry) { + if (mStage == null) { + throw new IllegalStateException("No stage was ever set on the pipeline"); + } + + final BindEntry bindEntry = mBindEntries.get(entry); + final ExpandableNotificationRow row = bindEntry.row; + if (row == null) { + // Row is not managed yet but may be soon. Stop for now. + return; + } + + mStage.abortStage(entry, row); + mStage.executeStage(entry, row, (en) -> onPipelineComplete(en)); + } + + private void onPipelineComplete(NotificationEntry entry) { + final BindEntry bindEntry = getBindEntry(entry); + + bindEntry.invalidated = false; + + final Set<BindCallback> callbacks = bindEntry.callbacks; + for (BindCallback cb : callbacks) { + cb.onBindFinished(entry); + } + callbacks.clear(); + } + + //TODO: Move this to onManageEntry hook when we split that from add/remove + private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { + @Override + public void onPendingEntryAdded(NotificationEntry entry) { + mBindEntries.put(entry, new BindEntry()); + mStage.createStageParams(entry); + } + + @Override + public void onEntryRemoved(NotificationEntry entry, + @Nullable NotificationVisibility visibility, + boolean removedByUser) { + BindEntry bindEntry = mBindEntries.remove(entry); + ExpandableNotificationRow row = bindEntry.row; + if (row != null) { + mStage.abortStage(entry, row); + } + mStage.deleteStageParams(entry); + } + }; + + private @NonNull BindEntry getBindEntry(NotificationEntry entry) { + final BindEntry bindEntry = mBindEntries.get(entry); + if (bindEntry == null) { + throw new IllegalStateException( + String.format("Attempting bind on an inactive notification. key: %s", + entry.getKey())); + } + return bindEntry; + } + + /** + * Interface for bind callback. + */ + public interface BindCallback { + /** + * Called when all views are fully bound on the notification. + */ + void onBindFinished(NotificationEntry entry); + } + + private class BindEntry { + public ExpandableNotificationRow row; + public final Set<BindCallback> callbacks = new ArraySet<>(); + public boolean invalidated; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java new file mode 100644 index 000000000000..7754991557ea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java @@ -0,0 +1,47 @@ +/* + * 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.row; + +import javax.inject.Inject; + +/** + * Initialize {@link NotifBindPipeline} with all its mandatory stages and dynamically added stages. + * + * In the future, coordinators should be able to register their own {@link BindStage} to the + * {@link NotifBindPipeline}. + */ +public class NotifBindPipelineInitializer { + NotifBindPipeline mNotifBindPipeline; + RowContentBindStage mRowContentBindStage; + + @Inject + NotifBindPipelineInitializer( + NotifBindPipeline pipeline, + RowContentBindStage stage) { + mNotifBindPipeline = pipeline; + mRowContentBindStage = stage; + // TODO: Inject coordinators and allow them to add BindStages in initialize + } + + /** + * Hooks up stages to the pipeline. + */ + public void initialize() { + // Mandatory bind stages + mNotifBindPipeline.setStage(mRowContentBindStage); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java index a6e5c2b79968..b62dfa8a20f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java @@ -22,10 +22,9 @@ import android.widget.RemoteViews; import androidx.annotation.Nullable; -import com.android.internal.statusbar.NotificationVisibility; -import com.android.systemui.statusbar.notification.NotificationEntryListener; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import java.util.Map; @@ -40,8 +39,8 @@ public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache { new ArrayMap<>(); @Inject - NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) { - entryManager.addNotificationEntryListener(mEntryListener); + NotifRemoteViewCacheImpl(CommonNotifCollection collection) { + collection.addCollectionListener(mCollectionListener); } @Override @@ -93,17 +92,14 @@ public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache { contentViews.clear(); } - private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { + private final NotifCollectionListener mCollectionListener = new NotifCollectionListener() { @Override - public void onPendingEntryAdded(NotificationEntry entry) { + public void onEntryInit(NotificationEntry entry) { mNotifCachedContentViews.put(entry, new SparseArray<>()); } @Override - public void onEntryRemoved( - NotificationEntry entry, - @Nullable NotificationVisibility visibility, - boolean removedByUser) { + public void onEntryCleanUp(NotificationEntry entry) { mNotifCachedContentViews.remove(entry); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index e1a6747b5398..566da65e37f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -68,7 +68,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final NotifRemoteViewCache mRemoteViewCache; @Inject - public NotificationContentInflater( + NotificationContentInflater( NotifRemoteViewCache remoteViewCache, NotificationRemoteInputManager remoteInputManager) { mRemoteViewCache = remoteViewCache; @@ -575,7 +575,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder entry.headsUpStatusBarText = result.headsUpStatusBarText; entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic; if (endListener != null) { - endListener.onAsyncInflationFinished(entry, reInflateFlags); + endListener.onAsyncInflationFinished(entry); } return true; } @@ -641,7 +641,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final boolean mUsesIncreasedHeight; private final InflationCallback mCallback; private final boolean mUsesIncreasedHeadsUpHeight; - private @InflationFlag int mReInflateFlags; + private final @InflationFlag int mReInflateFlags; private final NotifRemoteViewCache mRemoteViewCache; private ExpandableNotificationRow mRow; private Exception mError; @@ -739,25 +739,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder } @Override - public void supersedeTask(InflationTask task) { - if (task instanceof AsyncInflationTask) { - // We want to inflate all flags of the previous task as well - mReInflateFlags |= ((AsyncInflationTask) task).mReInflateFlags; - } - } - - @Override public void handleInflationException(NotificationEntry entry, Exception e) { handleError(e); } @Override - public void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { mEntry.onInflationTaskFinished(); mRow.onNotificationUpdated(); if (mCallback != null) { - mCallback.onAsyncInflationFinished(mEntry, inflatedFlags); + mCallback.onAsyncInflationFinished(mEntry); } // Notify the resolver that the inflation task has finished, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java index 9b95bff4921c..9bd8d4782672 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java @@ -101,7 +101,7 @@ public interface NotificationRowContentBinder { */ int FLAG_CONTENT_VIEW_PUBLIC = 1 << 3; - int FLAG_CONTENT_VIEW_ALL = ~0; + int FLAG_CONTENT_VIEW_ALL = (1 << 4) - 1; /** * Parameters for content view binding @@ -146,9 +146,7 @@ public interface NotificationRowContentBinder { * Callback for after the content views finish inflating. * * @param entry the entry with the content views set - * @param inflatedFlags the flags associated with the content views that were inflated */ - void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags); + void onAsyncInflationFinished(NotificationEntry entry); } } 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 new file mode 100644 index 000000000000..5170d0b85b17 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java @@ -0,0 +1,165 @@ +/* + * 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.row; + +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; + +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; + +/** + * Parameters for {@link RowContentBindStage}. + */ +public final class RowContentBindParams { + private boolean mUseLowPriority; + private boolean mUseChildInGroup; + private boolean mUseIncreasedHeight; + private boolean mUseIncreasedHeadsUpHeight; + private boolean mViewsNeedReinflation; + private @InflationFlag int mContentViews = DEFAULT_INFLATION_FLAGS; + + /** + * Content views that are out of date and need to be rebound. + * + * TODO: This should go away once {@link NotificationContentInflater} is broken down into + * smaller stages as then the stage itself would be invalidated. + */ + private @InflationFlag int mDirtyContentViews = mContentViews; + + /** + * Set whether content should use a low priority version of its content views. + */ + public void setUseLowPriority(boolean useLowPriority) { + if (mUseLowPriority != useLowPriority) { + mDirtyContentViews |= (FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED); + } + mUseLowPriority = useLowPriority; + } + + public boolean useLowPriority() { + return mUseLowPriority; + } + + /** + * Set whether content should use group child version of its content views. + */ + public void setUseChildInGroup(boolean useChildInGroup) { + if (mUseChildInGroup != useChildInGroup) { + mDirtyContentViews |= (FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED); + } + mUseChildInGroup = useChildInGroup; + } + + public boolean useChildInGroup() { + return mUseChildInGroup; + } + + /** + * Set whether content should use an increased height version of its contracted view. + */ + public void setUseIncreasedCollapsedHeight(boolean useIncreasedHeight) { + if (mUseIncreasedHeight != useIncreasedHeight) { + mDirtyContentViews |= FLAG_CONTENT_VIEW_CONTRACTED; + } + mUseIncreasedHeight = useIncreasedHeight; + } + + public boolean useIncreasedHeight() { + return mUseIncreasedHeight; + } + + /** + * Set whether content should use an increased height version of its heads up view. + */ + public void setUseIncreasedHeadsUpHeight(boolean useIncreasedHeadsUpHeight) { + if (mUseIncreasedHeadsUpHeight != useIncreasedHeadsUpHeight) { + mDirtyContentViews |= FLAG_CONTENT_VIEW_HEADS_UP; + } + mUseIncreasedHeadsUpHeight = useIncreasedHeadsUpHeight; + } + + public boolean useIncreasedHeadsUpHeight() { + return mUseIncreasedHeadsUpHeight; + } + + /** + * Require the specified content views to be bound after the rebind request. + * + * @see InflationFlag + */ + public void requireContentViews(@InflationFlag int contentViews) { + @InflationFlag int newContentViews = contentViews &= ~mContentViews; + mContentViews |= contentViews; + mDirtyContentViews |= newContentViews; + } + + /** + * Free the content view so that it will no longer be bound after the rebind request. + * + * @see InflationFlag + */ + public void freeContentViews(@InflationFlag int contentViews) { + mContentViews &= ~contentViews; + mDirtyContentViews &= ~contentViews; + } + + public @InflationFlag int getContentViews() { + return mContentViews; + } + + /** + * 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() { + mDirtyContentViews = 0; + } + + public @InflationFlag int getDirtyContentViews() { + return mDirtyContentViews; + } + + /** + * Set whether all content views need to be reinflated even if cached. + * + * TODO: This should probably be a more global config on {@link NotifBindPipeline} since this + * generally corresponds to a Context/Configuration change that all stages should know about. + */ + public void setNeedsReinflation(boolean needsReinflation) { + mViewsNeedReinflation = needsReinflation; + @InflationFlag int currentContentViews = mContentViews; + mDirtyContentViews |= currentContentViews; + } + + public boolean needsReinflation() { + return mViewsNeedReinflation; + } + + /** + * Content views that should be inflated by default for all notifications. + */ + @InflationFlag private static final int DEFAULT_INFLATION_FLAGS = + FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java new file mode 100644 index 000000000000..f1241799d3d1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java @@ -0,0 +1,118 @@ +/* + * 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.row; + +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; + +import android.os.RemoteException; +import android.service.notification.StatusBarNotification; + +import androidx.annotation.NonNull; + +import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * A stage that binds all content views for an already inflated {@link ExpandableNotificationRow}. + * + * In the farther future, the binder logic and consequently this stage should be broken into + * smaller stages. + */ +@Singleton +public class RowContentBindStage extends BindStage<RowContentBindParams> { + private final NotificationRowContentBinder mBinder; + private final IStatusBarService mStatusBarService; + + @Inject + RowContentBindStage( + NotificationRowContentBinder binder, + IStatusBarService statusBarService) { + mBinder = binder; + mStatusBarService = statusBarService; + } + + @Override + protected void executeStage( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row, + @NonNull StageCallback callback) { + RowContentBindParams params = getStageParams(entry); + + // Resolve content to bind/unbind. + @InflationFlag int inflationFlags = params.getContentViews(); + @InflationFlag int invalidatedFlags = params.getDirtyContentViews(); + + @InflationFlag int contentToBind = invalidatedFlags & inflationFlags; + @InflationFlag int contentToUnbind = inflationFlags ^ FLAG_CONTENT_VIEW_ALL; + + // Bind/unbind with parameters + mBinder.unbindContent(entry, row, contentToUnbind); + + BindParams bindParams = new BindParams(); + bindParams.isLowPriority = params.useLowPriority(); + bindParams.isChildInGroup = params.useChildInGroup(); + bindParams.usesIncreasedHeight = params.useIncreasedHeight(); + bindParams.usesIncreasedHeadsUpHeight = params.useIncreasedHeadsUpHeight(); + boolean forceInflate = params.needsReinflation(); + + InflationCallback inflationCallback = new InflationCallback() { + @Override + public void handleInflationException(NotificationEntry entry, Exception e) { + entry.setHasInflationError(true); + try { + final StatusBarNotification sbn = entry.getSbn(); + mStatusBarService.onNotificationError( + sbn.getPackageName(), + sbn.getTag(), + sbn.getId(), + sbn.getUid(), + sbn.getInitialPid(), + e.getMessage(), + sbn.getUserId()); + } catch (RemoteException ex) { + } + } + + @Override + public void onAsyncInflationFinished(NotificationEntry entry) { + entry.setHasInflationError(false); + getStageParams(entry).clearDirtyContentViews(); + callback.onStageFinished(entry); + } + }; + mBinder.cancelBind(entry, row); + mBinder.bindContent(entry, row, contentToBind, bindParams, forceInflate, inflationCallback); + } + + @Override + protected void abortStage( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row) { + mBinder.cancelBind(entry, row); + } + + @Override + protected RowContentBindParams newStageParams() { + return new RowContentBindParams(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java index c173b4dbaebe..6feffe654630 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java @@ -26,7 +26,6 @@ import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import com.android.systemui.R; import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import javax.inject.Inject; @@ -37,7 +36,6 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf private static final String TAG = "RowInflaterTask"; private static final boolean TRACE_ORIGIN = true; - private final NotificationRowComponent.Builder mNotificationRowComponentBuilder; private RowInflationFinishedListener mListener; private NotificationEntry mEntry; @@ -45,10 +43,7 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf private Throwable mInflateOrigin; @Inject - public RowInflaterTask( - NotificationRowComponent.Builder notificationRowComponentBuilder) { - super(); - mNotificationRowComponentBuilder = notificationRowComponentBuilder; + public RowInflaterTask() { } /** @@ -75,12 +70,6 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf public void onInflateFinished(View view, int resid, ViewGroup parent) { if (!mCancelled) { try { - // Setup the controller for the view. - NotificationRowComponent component = mNotificationRowComponentBuilder - .activatableNotificationView((ActivatableNotificationView) view) - .build(); - component.getActivatableNotificationViewController().init(); - mEntry.onInflationTaskFinished(); mListener.onInflationFinished((ExpandableNotificationRow) view); } catch (Throwable t) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java new file mode 100644 index 000000000000..a3dfa608c709 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java @@ -0,0 +1,37 @@ +/* + * 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.row.dagger; + +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; +import com.android.systemui.statusbar.notification.row.ExpandableOutlineView; +import com.android.systemui.statusbar.notification.row.ExpandableView; + +import dagger.Binds; +import dagger.Module; + +/** + * Module for NotificationRowComponent. + */ +@Module +public interface ActivatableNotificationViewModule { + /** ExpandableView is provided as an instance of ActivatableNotificationView. */ + @Binds + ExpandableView bindExpandableView(ActivatableNotificationView view); + /** ExpandableOutlineView is provided as an instance of ActivatableNotificationView. */ + @Binds + ExpandableOutlineView bindExpandableOutlineView(ActivatableNotificationView view); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java new file mode 100644 index 000000000000..1dbca0cd5527 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java @@ -0,0 +1,30 @@ +/* + * 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.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface AppName { +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java new file mode 100644 index 000000000000..433114224289 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java @@ -0,0 +1,30 @@ +/* + * 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.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface DismissRunnable { +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java new file mode 100644 index 000000000000..6d6d3e446f53 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java @@ -0,0 +1,122 @@ +/* + * 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.row.dagger; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.service.notification.StatusBarNotification; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; +import com.android.systemui.statusbar.phone.StatusBar; + +import dagger.Binds; +import dagger.BindsInstance; +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +/** + * Dagger Component for a {@link ExpandableNotificationRow}. + */ +@Subcomponent(modules = {ExpandableNotificationRowComponent.ExpandableNotificationRowModule.class, + ActivatableNotificationViewModule.class}) +@NotificationRowScope +public interface ExpandableNotificationRowComponent { + + /** + * Builder for {@link NotificationRowComponent}. + */ + @Subcomponent.Builder + interface Builder { + // TODO: NotificationEntry contains a reference to ExpandableNotificationRow, so it + // should be possible to pull one from the other, but they aren't connected at the time + // this component is constructed. + @BindsInstance + Builder expandableNotificationRow(ExpandableNotificationRow view); + @BindsInstance + Builder notificationEntry(NotificationEntry entry); + @BindsInstance + Builder onDismissRunnable(@DismissRunnable Runnable runnable); + @BindsInstance + Builder rowContentBindStage(RowContentBindStage rowContentBindStage); + @BindsInstance + Builder inflationCallback(NotificationRowContentBinder.InflationCallback inflationCallback); + @BindsInstance + Builder onExpandClickListener(ExpandableNotificationRow.OnExpandClickListener presenter); + ExpandableNotificationRowComponent build(); + } + + /** + * Creates a ExpandableNotificationRowController. + */ + @NotificationRowScope + ExpandableNotificationRowController getExpandableNotificationRowController(); + + /** + * Dagger Module that extracts interesting properties from an ExpandableNotificationRow. + */ + @Module + abstract class ExpandableNotificationRowModule { + + /** ExpandableNotificationRow is provided as an instance of ActivatableNotificationView. */ + @Binds + abstract ActivatableNotificationView bindExpandableView(ExpandableNotificationRow view); + + @Provides + static StatusBarNotification provideStatusBarNotification( + NotificationEntry notificationEntry) { + return notificationEntry.getSbn(); + } + + @Provides + @NotificationKey + static String provideNotificationKey(StatusBarNotification statusBarNotification) { + return statusBarNotification.getKey(); + } + + @Provides + @AppName + static String provideAppName(Context context, StatusBarNotification statusBarNotification) { + // Get the app name. + // Note that Notification.Builder#bindHeaderAppName has similar logic + // but since this field is used in the guts, it must be accurate. + // Therefore we will only show the application label, or, failing that, the + // package name. No substitutions. + PackageManager pmUser = StatusBar.getPackageManagerForUser( + context, statusBarNotification.getUser().getIdentifier()); + final String pkg = statusBarNotification.getPackageName(); + try { + final ApplicationInfo info = pmUser.getApplicationInfo(pkg, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS); + if (info != null) { + return String.valueOf(pmUser.getApplicationLabel(info)); + } + } catch (PackageManager.NameNotFoundException e) { + // Do nothing + } + + return pkg; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java new file mode 100644 index 000000000000..b1fff383cd5d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java @@ -0,0 +1,30 @@ +/* + * 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.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface NotificationKey { +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java index f16ea7ae23e9..1f535c5e3f56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java @@ -16,24 +16,17 @@ package com.android.systemui.statusbar.notification.row.dagger; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; - -import javax.inject.Scope; - import dagger.BindsInstance; import dagger.Subcomponent; /** * Dagger subcomponent for Notification related views. */ -@Subcomponent(modules = {}) -@NotificationRowComponent.NotificationRowScope +@Subcomponent(modules = {ActivatableNotificationViewModule.class}) +@NotificationRowScope public interface NotificationRowComponent { /** * Builder for {@link NotificationRowComponent}. @@ -46,14 +39,6 @@ public interface NotificationRowComponent { } /** - * Scope annotation for singleton items within the StatusBarComponent. - */ - @Documented - @Retention(RUNTIME) - @Scope - @interface NotificationRowScope {} - - /** * Creates a ActivatableNotificationViewController. */ @NotificationRowScope diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java new file mode 100644 index 000000000000..4555b839a3f2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java @@ -0,0 +1,32 @@ +/* + * 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.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Scope; + +/** + * Scope annotation for singleton items within the StatusBarComponent. + */ +@Documented +@Retention(RUNTIME) +@Scope +public @interface NotificationRowScope {} 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/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index 896b6e570da2..bdca9a452484 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -28,11 +28,12 @@ import com.android.systemui.Dependency; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.AlertingNotificationManager; -import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; +import com.android.systemui.statusbar.notification.row.RowContentBindParams; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup; import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -67,6 +68,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private final ArrayMap<String, PendingAlertInfo> mPendingAlerts = new ArrayMap<>(); private HeadsUpManager mHeadsUpManager; + private final RowContentBindStage mRowContentBindStage; private final NotificationGroupManager mGroupManager = Dependency.get(NotificationGroupManager.class); @@ -75,8 +77,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private boolean mIsDozing; @Inject - public NotificationGroupAlertTransferHelper() { + public NotificationGroupAlertTransferHelper(RowContentBindStage bindStage) { Dependency.get(StatusBarStateController.class).addCallback(this); + mRowContentBindStage = bindStage; } /** Causes the TransferHelper to register itself as a listener to the appropriate classes. */ @@ -190,21 +193,6 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis } } - // Called when the entry's reinflation has finished. If there is an alert pending, we - // then show the alert. - @Override - public void onEntryReinflated(NotificationEntry entry) { - PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey()); - if (alertInfo != null) { - if (alertInfo.isStillValid()) { - alertNotificationWhenPossible(entry, mHeadsUpManager); - } else { - // The transfer is no longer valid. Free the content. - entry.getRow().freeContentViewWhenSafe(mHeadsUpManager.getContentFlag()); - } - } - } - @Override public void onEntryRemoved( @Nullable NotificationEntry entry, @@ -392,10 +380,21 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private void alertNotificationWhenPossible(@NonNull NotificationEntry entry, @NonNull AlertingNotificationManager alertManager) { @InflationFlag int contentFlag = alertManager.getContentFlag(); - if (!entry.getRow().isInflationFlagSet(contentFlag)) { + final RowContentBindParams params = mRowContentBindStage.getStageParams(entry); + if ((params.getContentViews() & contentFlag) == 0) { mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry)); - entry.getRow().setInflationFlags(contentFlag); - entry.getRow().inflateViews(); + params.requireContentViews(contentFlag); + mRowContentBindStage.requestRebind(entry, en -> { + PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey()); + if (alertInfo != null) { + if (alertInfo.isStillValid()) { + alertNotificationWhenPossible(entry, mHeadsUpManager); + } else { + // The transfer is no longer valid. Free the content. + entry.getRow().freeContentViewWhenSafe(mHeadsUpManager.getContentFlag()); + } + } + }); return; } if (alertManager.isAlerting(entry.getKey())) { @@ -426,9 +425,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis /** * The notification is still pending inflation but we've decided that we no longer need * the content view (e.g. suppression might have changed and we decided we need to transfer - * back). However, there is no way to abort just this inflation if other inflation requests - * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead - * we just flag it as aborted and free when it's inflated. + * back). + * + * TODO: Replace this entire structure with {@link RowContentBindStage#requestRebind)}. */ boolean mAbortOnInflation; 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/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java index 830b50e35490..8231f8b3a09b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java @@ -30,5 +30,6 @@ public interface HotspotController extends CallbackController<Callback>, Dumpabl interface Callback { void onHotspotChanged(boolean enabled, int numDevices); + default void onHotspotAvailabilityChanged(boolean available) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java index df9c3f4d6e26..d0904049d85a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.policy; import android.app.ActivityManager; import android.content.Context; import android.net.ConnectivityManager; +import android.net.TetheringManager; import android.net.wifi.WifiClient; import android.net.wifi.WifiManager; import android.os.Handler; @@ -26,6 +27,8 @@ import android.os.HandlerExecutor; import android.os.UserManager; import android.util.Log; +import com.android.internal.util.ConcurrentUtils; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import java.io.FileDescriptor; @@ -46,36 +49,63 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final ArrayList<Callback> mCallbacks = new ArrayList<>(); - private final ConnectivityManager mConnectivityManager; + private final TetheringManager mTetheringManager; private final WifiManager mWifiManager; private final Handler mMainHandler; private final Context mContext; private int mHotspotState; private volatile int mNumConnectedDevices; + private volatile boolean mIsTetheringSupported; + private volatile boolean mHasTetherableWifiRegexs; private boolean mWaitingForTerminalState; + private TetheringManager.TetheringEventCallback mTetheringCallback = + new TetheringManager.TetheringEventCallback() { + @Override + public void onTetheringSupported(boolean supported) { + super.onTetheringSupported(supported); + if (mIsTetheringSupported != supported) { + mIsTetheringSupported = supported; + fireHotspotAvailabilityChanged(); + } + } + + @Override + public void onTetherableInterfaceRegexpsChanged( + TetheringManager.TetheringInterfaceRegexps reg) { + super.onTetherableInterfaceRegexpsChanged(reg); + final boolean newValue = reg.getTetherableWifiRegexs().size() != 0; + if (mHasTetherableWifiRegexs != newValue) { + mHasTetherableWifiRegexs = newValue; + fireHotspotAvailabilityChanged(); + } + } + }; + /** * Controller used to retrieve information related to a hotspot. */ @Inject - public HotspotControllerImpl(Context context, @Main Handler mainHandler) { + public HotspotControllerImpl(Context context, @Main Handler mainHandler, + @Background Handler backgroundHandler) { mContext = context; - mConnectivityManager = - (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + mTetheringManager = context.getSystemService(TetheringManager.class); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mMainHandler = mainHandler; + mTetheringManager.registerTetheringEventCallback( + new HandlerExecutor(backgroundHandler), mTetheringCallback); } @Override public boolean isHotspotSupported() { - return mConnectivityManager.isTetheringSupported() - && mConnectivityManager.getTetherableWifiRegexs().length != 0 + return mIsTetheringSupported && mHasTetherableWifiRegexs && UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser()); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("HotspotController state:"); + pw.print(" available="); pw.println(isHotspotSupported()); pw.print(" mHotspotState="); pw.println(stateToString(mHotspotState)); pw.print(" mNumConnectedDevices="); pw.println(mNumConnectedDevices); pw.print(" mWaitingForTerminalState="); pw.println(mWaitingForTerminalState); @@ -152,17 +182,18 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof if (enabled) { mWaitingForTerminalState = true; if (DEBUG) Log.d(TAG, "Starting tethering"); - mConnectivityManager.startTethering(ConnectivityManager.TETHERING_WIFI, false, - new ConnectivityManager.OnStartTetheringCallback() { + mTetheringManager.startTethering(ConnectivityManager.TETHERING_WIFI, + ConcurrentUtils.DIRECT_EXECUTOR, + new TetheringManager.StartTetheringCallback() { @Override - public void onTetheringFailed() { + public void onTetheringFailed(final int result) { if (DEBUG) Log.d(TAG, "onTetheringFailed"); maybeResetSoftApState(); fireHotspotChangedCallback(); } }); } else { - mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI); + mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI); } } @@ -177,10 +208,25 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof * (as it can be blocked). */ private void fireHotspotChangedCallback() { + List<Callback> list; synchronized (mCallbacks) { - for (Callback callback : mCallbacks) { - callback.onHotspotChanged(isHotspotEnabled(), mNumConnectedDevices); - } + list = new ArrayList<>(mCallbacks); + } + for (Callback callback : list) { + callback.onHotspotChanged(isHotspotEnabled(), mNumConnectedDevices); + } + } + + /** + * Sends a hotspot available changed callback. + */ + private void fireHotspotAvailabilityChanged() { + List<Callback> list; + synchronized (mCallbacks) { + list = new ArrayList<>(mCallbacks); + } + for (Callback callback : list) { + callback.onHotspotAvailabilityChanged(isHotspotSupported()); } } @@ -206,7 +252,7 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof switch (mHotspotState) { case WifiManager.WIFI_AP_STATE_FAILED: // TODO(b/110697252): must be called to reset soft ap state after failure - mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI); + mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI); // Fall through case WifiManager.WIFI_AP_STATE_ENABLED: case WifiManager.WIFI_AP_STATE_DISABLED: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 2907cd41a0db..8dfcb0ac215d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -134,6 +134,8 @@ public class UserSwitcherController implements Dumpable { mBroadcastDispatcher.registerReceiver( mReceiver, filter, null /* handler */, UserHandle.SYSTEM); + mSimpleUserSwitcher = shouldUseSimpleUserSwitcher(); + mSecondaryUserServiceIntent = new Intent(context, SystemUISecondaryUserService.class); filter = new IntentFilter(); @@ -258,22 +260,20 @@ public class UserSwitcherController implements Dumpable { && mUserManager.canAddMoreUsers(); boolean createIsRestricted = !addUsersWhenLocked; - if (!mSimpleUserSwitcher) { - if (guestRecord == null) { - if (canCreateGuest) { - guestRecord = new UserRecord(null /* info */, null /* picture */, - true /* isGuest */, false /* isCurrent */, - false /* isAddUser */, createIsRestricted, canSwitchUsers); - checkIfAddUserDisallowedByAdminOnly(guestRecord); - records.add(guestRecord); - } - } else { - int index = guestRecord.isCurrent ? 0 : records.size(); - records.add(index, guestRecord); + if (guestRecord == null) { + if (canCreateGuest) { + guestRecord = new UserRecord(null /* info */, null /* picture */, + true /* isGuest */, false /* isCurrent */, + false /* isAddUser */, createIsRestricted, canSwitchUsers); + checkIfAddUserDisallowedByAdminOnly(guestRecord); + records.add(guestRecord); } + } else { + int index = guestRecord.isCurrent ? 0 : records.size(); + records.add(index, guestRecord); } - if (!mSimpleUserSwitcher && canCreateUser) { + if (canCreateUser) { UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */, false /* isGuest */, false /* isCurrent */, true /* isAddUser */, createIsRestricted, canSwitchUsers); @@ -562,8 +562,7 @@ public class UserSwitcherController implements Dumpable { private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) { public void onChange(boolean selfChange) { - mSimpleUserSwitcher = Settings.Global.getInt(mContext.getContentResolver(), - SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0; + mSimpleUserSwitcher = shouldUseSimpleUserSwitcher(); mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0; refreshUsers(UserHandle.USER_NULL); @@ -579,6 +578,7 @@ public class UserSwitcherController implements Dumpable { final UserRecord u = mUsers.get(i); pw.print(" "); pw.println(u.toString()); } + pw.println("mSimpleUserSwitcher=" + mSimpleUserSwitcher); } public String getCurrentUserName(Context context) { @@ -717,6 +717,13 @@ public class UserSwitcherController implements Dumpable { } } + private boolean shouldUseSimpleUserSwitcher() { + int defaultSimpleUserSwitcher = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_expandLockScreenUserSwitcher) ? 1 : 0; + return Settings.Global.getInt(mContext.getContentResolver(), + SIMPLE_USER_SWITCHER_GLOBAL_SETTING, defaultSimpleUserSwitcher) != 0; + } + public void startActivity(Intent intent) { mActivityStarter.startActivity(intent, true); } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java index a60ca6201419..49ada1a5e41e 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java @@ -158,7 +158,7 @@ public class DemoModeFragment extends PreferenceFragment implements OnPreference String demoTime = "1010"; // 10:10, a classic choice of horologists try { - String[] versionParts = android.os.Build.VERSION.RELEASE.split("\\."); + String[] versionParts = android.os.Build.VERSION.RELEASE_OR_CODENAME.split("\\."); int majorVersion = Integer.valueOf(versionParts[0]); demoTime = String.format("%02d00", majorVersion % 24); } catch (IllegalArgumentException ex) { diff --git a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt new file mode 100644 index 000000000000..70bcc21426b1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt @@ -0,0 +1,320 @@ +package com.android.systemui.util + +import android.graphics.Rect +import android.util.Log +import com.android.systemui.util.FloatingContentCoordinator.FloatingContent +import java.util.HashMap +import javax.inject.Inject +import javax.inject.Singleton + +/** Tag for debug logging. */ +private const val TAG = "FloatingCoordinator" + +/** + * Coordinates the positions and movement of floating content, such as PIP and Bubbles, to ensure + * that they don't overlap. If content does overlap due to content appearing or moving, the + * coordinator will ask content to move to resolve the conflict. + * + * After implementing [FloatingContent], content should call [onContentAdded] to begin coordination. + * Subsequently, call [onContentMoved] whenever the content moves, and the coordinator will move + * other content out of the way. [onContentRemoved] should be called when the content is removed or + * no longer visible. + */ +@Singleton +class FloatingContentCoordinator @Inject constructor() { + + /** + * Represents a piece of floating content, such as PIP or the Bubbles stack. Provides methods + * that allow the [FloatingContentCoordinator] to determine the current location of the content, + * as well as the ability to ask it to move out of the way of other content. + * + * The default implementation of [calculateNewBoundsOnOverlap] moves the content up or down, + * depending on the position of the conflicting content. You can override this method if you + * want your own custom conflict resolution logic. + */ + interface FloatingContent { + + /** + * Return the bounds claimed by this content. This should include the bounds occupied by the + * content itself, as well as any padding, if desired. The coordinator will ensure that no + * other content is located within these bounds. + * + * If the content is animating, this method should return the bounds to which the content is + * animating. If that animation is cancelled, or updated, be sure that your implementation + * of this method returns the appropriate bounds, and call [onContentMoved] so that the + * coordinator moves other content out of the way. + */ + fun getFloatingBoundsOnScreen(): Rect + + /** + * Return the area within which this floating content is allowed to move. When resolving + * conflicts, the coordinator will never ask your content to move to a position where any + * part of the content would be out of these bounds. + */ + fun getAllowedFloatingBoundsRegion(): Rect + + /** + * Called when the coordinator needs this content to move to the given bounds. It's up to + * you how to do that. + * + * Note that if you start an animation to these bounds, [getFloatingBoundsOnScreen] should + * return the destination bounds, not the in-progress animated bounds. This is so the + * coordinator knows where floating content is going to be and can resolve conflicts + * accordingly. + */ + fun moveToBounds(bounds: Rect) + + /** + * Called by the coordinator when it needs to find a new home for this floating content, + * because a new or moving piece of content is now overlapping with it. + * + * [findAreaForContentVertically] and [findAreaForContentAboveOrBelow] are helpful utility + * functions that will find new bounds for your content automatically. Unless you require + * specific conflict resolution logic, these should be sufficient. By default, this method + * delegates to [findAreaForContentVertically]. + * + * @param overlappingContentBounds The bounds of the other piece of content, which + * necessitated this content's relocation. Your new position must not overlap with these + * bounds. + * @param otherContentBounds The bounds of any other pieces of floating content. Your new + * position must not overlap with any of these either. These bounds are guaranteed to be + * non-overlapping. + * @return The new bounds for this content. + */ + @JvmDefault + fun calculateNewBoundsOnOverlap( + overlappingContentBounds: Rect, + otherContentBounds: List<Rect> + ): Rect { + return findAreaForContentVertically( + getFloatingBoundsOnScreen(), + overlappingContentBounds, + otherContentBounds, + getAllowedFloatingBoundsRegion()) + } + } + + /** The bounds of all pieces of floating content added to the coordinator. */ + private val allContentBounds: MutableMap<FloatingContent, Rect> = HashMap() + + /** + * Makes the coordinator aware of a new piece of floating content, and moves any existing + * content out of the way, if necessary. + * + * If you don't want your new content to move existing content, use [getOccupiedBounds] to find + * an unoccupied area, and move the content there before calling this method. + */ + fun onContentAdded(newContent: FloatingContent) { + updateContentBounds() + allContentBounds[newContent] = newContent.getFloatingBoundsOnScreen() + maybeMoveConflictingContent(newContent) + } + + /** + * Called to notify the coordinator that a piece of floating content has moved (or is animating) + * to a new position, and that any conflicting floating content should be moved out of the way. + * + * The coordinator will call [FloatingContent.getFloatingBoundsOnScreen] to find the new bounds + * for the moving content. If you're animating the content, be sure that your implementation of + * getFloatingBoundsOnScreen returns the bounds to which it's animating, not the content's + * current bounds. + * + * If the animation moving this content is cancelled or updated, you'll need to call this method + * again, to ensure that content is moved out of the way of the latest bounds. + * + * @param content The content that has moved. + */ + @JvmOverloads + fun onContentMoved(content: FloatingContent) { + if (!allContentBounds.containsKey(content)) { + Log.wtf(TAG, "Received onContentMoved call before onContentAdded! " + + "This should never happen.") + return + } + + updateContentBounds() + maybeMoveConflictingContent(content) + } + + /** + * Called to notify the coordinator that a piece of floating content has been removed or is no + * longer visible. + */ + fun onContentRemoved(removedContent: FloatingContent) { + allContentBounds.remove(removedContent) + } + + /** + * Returns a set of Rects that represent the bounds of all of the floating content on the + * screen. + * + * [onContentAdded] will move existing content out of the way if the added content intersects + * existing content. That's fine - but if your specific starting position is not important, you + * can use this function to find unoccupied space for your content before calling + * [onContentAdded], so that moving existing content isn't necessary. + */ + fun getOccupiedBounds(): Collection<Rect> { + return allContentBounds.values + } + + /** + * Identifies any pieces of content that are now overlapping with the given content, and asks + * them to move out of the way. + */ + private fun maybeMoveConflictingContent(fromContent: FloatingContent) { + val conflictingNewBounds = allContentBounds[fromContent]!! + allContentBounds + // Filter to content that intersects with the new bounds. That's content that needs + // to move. + .filter { (content, bounds) -> + content != fromContent && Rect.intersects(conflictingNewBounds, bounds) } + // Tell that content to get out of the way, and save the bounds it says it's moving + // (or animating) to. + .forEach { (content, bounds) -> + content.moveToBounds( + content.calculateNewBoundsOnOverlap( + conflictingNewBounds, + // Pass all of the content bounds except the bounds of the + // content we're asking to move, and the conflicting new bounds + // (since those are passed separately). + otherContentBounds = allContentBounds.values + .minus(bounds) + .minus(conflictingNewBounds))) + allContentBounds[content] = content.getFloatingBoundsOnScreen() + } + } + + /** + * Update [allContentBounds] by calling [FloatingContent.getFloatingBoundsOnScreen] for all + * content and saving the result. + */ + private fun updateContentBounds() { + allContentBounds.keys.forEach { allContentBounds[it] = it.getFloatingBoundsOnScreen() } + } + + companion object { + /** + * Finds new bounds for the given content, either above or below its current position. The + * new bounds won't intersect with the newly overlapping rect or the exclusion rects, and + * will be within the allowed bounds unless no possible position exists. + * + * You can use this method to help find a new position for your content when the coordinator + * calls [FloatingContent.moveToAreaExcluding]. + * + * @param contentRect The bounds of the content for which we're finding a new home. + * @param newlyOverlappingRect The bounds of the content that forced this relocation by + * intersecting with the content we now need to move. If the overlapping content is + * overlapping the top half of this content, we'll try to move this content downward if + * possible (since the other content is 'pushing' it down), and vice versa. + * @param exclusionRects Any other areas that we need to avoid when finding a new home for + * the content. These areas must be non-overlapping with each other. + * @param allowedBounds The area within which we're allowed to find new bounds for the + * content. + * @return New bounds for the content that don't intersect the exclusion rects or the + * newly overlapping rect, and that is within bounds unless no possible in-bounds position + * exists. + */ + @JvmStatic + fun findAreaForContentVertically( + contentRect: Rect, + newlyOverlappingRect: Rect, + exclusionRects: Collection<Rect>, + allowedBounds: Rect + ): Rect { + // If the newly overlapping Rect's center is above the content's center, we'll prefer to + // find a space for this content that is below the overlapping content, since it's + // 'pushing' it down. This may not be possible due to to screen bounds, in which case + // we'll find space in the other direction. + val overlappingContentPushingDown = + newlyOverlappingRect.centerY() < contentRect.centerY() + + // Filter to exclusion rects that are above or below the content that we're finding a + // place for. Then, split into two lists - rects above the content, and rects below it. + var (rectsToAvoidAbove, rectsToAvoidBelow) = exclusionRects + .filter { rectToAvoid -> rectsIntersectVertically(rectToAvoid, contentRect) } + .partition { rectToAvoid -> rectToAvoid.top < contentRect.top } + + // Lazily calculate the closest possible new tops for the content, above and below its + // current location. + val newContentBoundsAbove by lazy { findAreaForContentAboveOrBelow( + contentRect, + exclusionRects = rectsToAvoidAbove.plus(newlyOverlappingRect), + findAbove = true) } + val newContentBoundsBelow by lazy { findAreaForContentAboveOrBelow( + contentRect, + exclusionRects = rectsToAvoidBelow.plus(newlyOverlappingRect), + findAbove = false) } + + val positionAboveInBounds by lazy { allowedBounds.contains(newContentBoundsAbove) } + val positionBelowInBounds by lazy { allowedBounds.contains(newContentBoundsBelow) } + + // Use the 'below' position if the content is being overlapped from the top, unless it's + // out of bounds. Also use it if the content is being overlapped from the bottom, but + // the 'above' position is out of bounds. Otherwise, use the 'above' position. + val usePositionBelow = + overlappingContentPushingDown && positionBelowInBounds || + !overlappingContentPushingDown && !positionAboveInBounds + + // Return the content rect, but offset to reflect the new position. + return if (usePositionBelow) newContentBoundsBelow else newContentBoundsAbove + } + + /** + * Finds a new position for the given content, either above or below its current position + * depending on whether [findAbove] is true or false, respectively. This new position will + * not intersect with any of the [exclusionRects]. + * + * This method is useful as a helper method for implementing your own conflict resolution + * logic. Otherwise, you'd want to use [findAreaForContentVertically], which takes screen + * bounds and conflicting bounds' location into account when deciding whether to move to new + * bounds above or below the current bounds. + * + * @param contentRect The content we're finding an area for. + * @param exclusionRects The areas we need to avoid when finding a new area for the content. + * These areas must be non-overlapping with each other. + * @param findAbove Whether we are finding an area above the content's current position, + * rather than an area below it. + */ + fun findAreaForContentAboveOrBelow( + contentRect: Rect, + exclusionRects: Collection<Rect>, + findAbove: Boolean + ): Rect { + // Sort the rects, since we want to move the content as little as possible. We'll + // start with the rects closest to the content and move outward. If we're finding an + // area above the content, that means we sort in reverse order to search the rects + // from highest to lowest y-value. + val sortedExclusionRects = + exclusionRects.sortedBy { if (findAbove) -it.top else it.top } + + val proposedNewBounds = Rect(contentRect) + for (exclusionRect in sortedExclusionRects) { + // If the proposed new bounds don't intersect with this exclusion rect, that + // means there's room for the content here. We know this because the rects are + // sorted and non-overlapping, so any subsequent exclusion rects would be higher + // (or lower) than this one and can't possibly intersect if this one doesn't. + if (!Rect.intersects(proposedNewBounds, exclusionRect)) { + break + } else { + // Otherwise, we need to keep searching for new bounds. If we're finding an + // area above, propose new bounds that place the content just above the + // exclusion rect. If we're finding an area below, propose new bounds that + // place the content just below the exclusion rect. + val verticalOffset = + if (findAbove) -contentRect.height() else exclusionRect.height() + proposedNewBounds.offsetTo( + proposedNewBounds.left, + exclusionRect.top + verticalOffset) + } + } + + return proposedNewBounds + } + + /** Returns whether or not the two Rects share any of the same space on the X axis. */ + private fun rectsIntersectVertically(r1: Rect, r2: Rect): Boolean { + return (r1.left >= r2.left && r1.left <= r2.right) || + (r1.right <= r2.right && r1.right >= r2.left) + } + } +}
\ No newline at end of file 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/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java index 364ee666e17d..ffe8c285b4f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java @@ -31,8 +31,8 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.util.Assert; import org.junit.Before; 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 f2642594802d..c6c7b87da544 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java @@ -36,6 +36,7 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricPrompt; import android.os.Bundle; +import android.os.IBinder; import android.os.UserManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -43,6 +44,7 @@ import android.testing.TestableLooper.RunWithLooper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ScrollView; @@ -175,6 +177,28 @@ public class AuthContainerViewTest extends SysuiTestCase { assertEquals(Utils.CREDENTIAL_PATTERN, mAuthContainer.mCredentialView.mCredentialType); } + @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 = + AuthContainerView.getLayoutParams(windowToken); + assertTrue((layoutParams.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0); + } + private void initializeContainer(int authenticators) { AuthContainerView.Config config = new AuthContainerView.Config(); config.mContext = mContext; 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 dcaf4ec6f8ae..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,23 +55,26 @@ 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; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; 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; +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; @@ -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/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index c9f5b40e8f9f..f40fc94763ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -39,10 +39,10 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.bubbles.BubbleData.TimeSource; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.google.common.collect.ImmutableList; 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/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index 897091f69f36..e3bcdc8b0b60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -32,12 +32,13 @@ import androidx.test.filters.SmallTest import com.android.systemui.DumpController import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ControlStatus +import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -82,7 +83,7 @@ class ControlsControllerImplTest : SysuiTestCase() { private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver> private lateinit var delayableExecutor: FakeExecutor - private lateinit var controller: ControlsController + private lateinit var controller: ControlsControllerImpl companion object { fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() @@ -416,5 +417,70 @@ class ControlsControllerImplTest : SysuiTestCase() { verify(listingController).changeUser(UserHandle.of(otherUser)) assertTrue(controller.getFavoriteControls().isEmpty()) assertEquals(otherUser, controller.currentUserId) + assertTrue(controller.available) + } + + @Test + fun testDisableFeature_notAvailable() { + Settings.Secure.putIntForUser(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 0, user) + controller.settingObserver.onChange(false, ControlsControllerImpl.URI, 0) + assertFalse(controller.available) + } + + @Test + fun testDisableFeature_clearFavorites() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + assertFalse(controller.getFavoriteControls().isEmpty()) + + Settings.Secure.putIntForUser(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 0, user) + controller.settingObserver.onChange(false, ControlsControllerImpl.URI, user) + assertTrue(controller.getFavoriteControls().isEmpty()) + } + + @Test + fun testDisableFeature_noChangeForNotCurrentUser() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + Settings.Secure.putIntForUser(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 0, otherUser) + controller.settingObserver.onChange(false, ControlsControllerImpl.URI, otherUser) + + assertTrue(controller.available) + assertFalse(controller.getFavoriteControls().isEmpty()) + } + + @Test + fun testCorrectUserSettingOnUserChange() { + Settings.Secure.putIntForUser(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 0, otherUser) + + val intent = Intent(Intent.ACTION_USER_SWITCHED).apply { + putExtra(Intent.EXTRA_USER_HANDLE, otherUser) + } + val pendingResult = mock(BroadcastReceiver.PendingResult::class.java) + `when`(pendingResult.sendingUserId).thenReturn(otherUser) + broadcastReceiverCaptor.value.pendingResult = pendingResult + + broadcastReceiverCaptor.value.onReceive(mContext, intent) + + assertFalse(controller.available) + } + + @Test + fun testCountFavoritesForComponent_singleComponent() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(0, controller.countFavoritesForComponent(TEST_COMPONENT_2)) + } + + @Test + fun testCountFavoritesForComponent_multipleComponents() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT_2)) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java index a5722e211f98..4b47093bb951 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java @@ -16,52 +16,116 @@ package com.android.systemui.glwallpaper; -import static org.junit.Assert.*; -import static org.mockito.Mockito.RETURNS_DEFAULTS; -import static org.mockito.Mockito.mock; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atMost; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.graphics.PixelFormat; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; +import android.view.Surface; +import android.view.SurfaceControl; import android.view.SurfaceHolder; +import android.view.SurfaceSession; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; - +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper public class EglHelperTest extends SysuiTestCase { - @Mock + @Spy private EglHelper mEglHelper; + @Mock private SurfaceHolder mSurfaceHolder; @Before public void setUp() throws Exception { - mEglHelper = mock(EglHelper.class, RETURNS_DEFAULTS); - mSurfaceHolder = mock(SurfaceHolder.class, RETURNS_DEFAULTS); + MockitoAnnotations.initMocks(this); + prepareSurface(); + } + + @After + public void tearDown() { + mSurfaceHolder.getSurface().destroy(); + mSurfaceHolder = null; + } + + private void prepareSurface() { + final SurfaceSession session = new SurfaceSession(); + final SurfaceControl control = new SurfaceControl.Builder(session) + .setName("Test") + .setBufferSize(100, 100) + .setFormat(PixelFormat.RGB_888) + .build(); + final Surface surface = new Surface(); + surface.copyFrom(control); + when(mSurfaceHolder.getSurface()).thenReturn(surface); + assertThat(mSurfaceHolder.getSurface()).isNotNull(); + assertThat(mSurfaceHolder.getSurface().isValid()).isTrue(); } @Test public void testInit_finish() { mEglHelper.init(mSurfaceHolder, false /* wideColorGamut */); + assertThat(mEglHelper.hasEglDisplay()).isTrue(); + assertThat(mEglHelper.hasEglContext()).isTrue(); + assertThat(mEglHelper.hasEglSurface()).isTrue(); + verify(mEglHelper).askCreatingEglWindowSurface( + any(SurfaceHolder.class), eq(null), anyInt()); + + mEglHelper.finish(); + assertThat(mEglHelper.hasEglSurface()).isFalse(); + assertThat(mEglHelper.hasEglContext()).isFalse(); + assertThat(mEglHelper.hasEglDisplay()).isFalse(); + } + + @Test + public void testInit_finish_wide_gamut() { + // In EglHelper, EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490; + doReturn(0x3490).when(mEglHelper).getWcgCapability(); + // In EglHelper, KHR_GL_COLOR_SPACE = "EGL_KHR_gl_colorspace"; + doReturn(true).when(mEglHelper).checkExtensionCapability("EGL_KHR_gl_colorspace"); + ArgumentCaptor<int[]> ac = ArgumentCaptor.forClass(int[].class); + // {EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT, EGL_NONE} + final int[] expectedArgument = new int[] {0x309D, 0x3490, 0x3038}; + + mEglHelper.init(mSurfaceHolder, true /* wideColorGamut */); + verify(mEglHelper) + .askCreatingEglWindowSurface(any(SurfaceHolder.class), ac.capture(), anyInt()); + assertThat(ac.getValue()).isNotNull(); + assertThat(ac.getValue()).isEqualTo(expectedArgument); mEglHelper.finish(); } @Test public void testFinish_shouldNotCrash() { - assertFalse(mEglHelper.hasEglDisplay()); - assertFalse(mEglHelper.hasEglSurface()); - assertFalse(mEglHelper.hasEglContext()); + mEglHelper.terminateEglDisplay(); + assertThat(mEglHelper.hasEglDisplay()).isFalse(); + assertThat(mEglHelper.hasEglSurface()).isFalse(); + assertThat(mEglHelper.hasEglContext()).isFalse(); mEglHelper.finish(); + verify(mEglHelper, never()).destroyEglContext(); + verify(mEglHelper, never()).destroyEglSurface(); + verify(mEglHelper, atMost(1)).terminateEglDisplay(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java new file mode 100644 index 000000000000..d881fd51712d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java @@ -0,0 +1,106 @@ +/* + * 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.glwallpaper; + +import static com.android.systemui.glwallpaper.GLWallpaperRenderer.SurfaceProxy; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.app.WallpaperManager; +import android.app.WallpaperManager.ColorManagementProxy; +import android.graphics.Bitmap; +import android.graphics.ColorSpace; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class ImageWallpaperRendererTest extends SysuiTestCase { + + private WallpaperManager mWpmSpy; + private SurfaceProxy mSurfaceProxy; + + @Before + public void setUp() throws Exception { + final WallpaperManager wpm = mContext.getSystemService(WallpaperManager.class); + mWpmSpy = spy(wpm); + mContext.addMockSystemService(WallpaperManager.class, mWpmSpy); + + mSurfaceProxy = new SurfaceProxy() { + @Override + public void requestRender() { + // NO-op + } + + @Override + public void preRender() { + // No-op + } + + @Override + public void postRender() { + // No-op + } + }; + } + + @Test + public void testWcgContent() throws IOException { + final Bitmap srgbBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); + final Bitmap p3Bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888, + false /* hasAlpha */, ColorSpace.get(ColorSpace.Named.DISPLAY_P3)); + + final ColorManagementProxy proxy = new ColorManagementProxy(mContext); + final ColorManagementProxy cmProxySpy = spy(proxy); + final Set<ColorSpace> supportedWideGamuts = new HashSet<>(); + supportedWideGamuts.add(ColorSpace.get(ColorSpace.Named.DISPLAY_P3)); + + try { + doReturn(true).when(mWpmSpy).shouldEnableWideColorGamut(); + doReturn(cmProxySpy).when(mWpmSpy).getColorManagementProxy(); + doReturn(supportedWideGamuts).when(cmProxySpy).getSupportedColorSpaces(); + + mWpmSpy.setBitmap(p3Bitmap); + ImageWallpaperRenderer rendererP3 = new ImageWallpaperRenderer(mContext, mSurfaceProxy); + assertThat(rendererP3.isWcgContent()).isTrue(); + + mWpmSpy.setBitmap(srgbBitmap); + ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mContext, mSurfaceProxy); + assertThat(renderer.isWcgContent()).isFalse(); + } finally { + srgbBitmap.recycle(); + p3Bitmap.recycle(); + } + } + +} 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/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index 63c911b53db9..60163f26bb2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -49,6 +49,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java index 4103edee6255..9d667a9a91c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java @@ -26,8 +26,8 @@ import android.testing.TestableLooper.RunWithLooper; import android.widget.FrameLayout; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import org.junit.Assert; import org.junit.Before; 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 20c67fae6c9d..5a0a495e1f85 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 @@ -58,10 +58,13 @@ import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.NotificationVisibility; +import com.android.internal.util.NotificationMessagingUtil; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -79,16 +82,17 @@ 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; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.row.RowContentBindParams; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.notification.row.RowInflaterTask; -import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; +import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -96,15 +100,18 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.Assert; import com.android.systemui.util.leak.LeakDetector; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Arrays; @@ -137,11 +144,16 @@ 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; - @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder; + @Mock private NotificationMediaManager mNotificationMediaManager; + @Mock private ExpandableNotificationRowComponent.Builder + mExpandableNotificationRowComponentBuilder; + @Mock private ExpandableNotificationRowComponent mExpandableNotificationRowComponent; + @Mock private FalsingManager mFalsingManager; + @Mock private KeyguardBypassController mKeyguardBypassController; + @Mock private StatusBarStateController mStatusBarStateController; private int mId; private NotificationEntry mEntry; @@ -190,7 +202,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); mDependency.injectMockDependency(SmartReplyController.class); - mDependency.injectMockDependency(NotificationMediaManager.class); mCountDownLatch = new CountDownLatch(1); @@ -206,40 +217,35 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.expandedIcon = mock(StatusBarIconView.class); - NotificationContentInflater contentBinder = new NotificationContentInflater( - mock(NotifRemoteViewCache.class), - mRemoteInputManager); - contentBinder.setInflateSynchronously(true); - - when(mNotificationRowComponentBuilder.activatableNotificationView(any())) - .thenReturn(mNotificationRowComponentBuilder); - when(mNotificationRowComponentBuilder.build()).thenReturn( - () -> mActivatableNotificationViewController); + RowContentBindStage bindStage = mock(RowContentBindStage.class); + when(bindStage.getStageParams(any())).thenReturn(new RowContentBindParams()); NotificationRowBinderImpl notificationRowBinder = new NotificationRowBinderImpl(mContext, + new NotificationMessagingUtil(mContext), mRemoteInputManager, mLockscreenUserManager, - contentBinder, + mock(NotifBindPipeline.class), + bindStage, true, /* allowLongPress */ - mock(KeyguardBypassController.class), - mock(StatusBarStateController.class), + mKeyguardBypassController, + mStatusBarStateController, mGroupManager, mGutsManager, mNotificationInterruptionStateProvider, - () -> new RowInflaterTask(mNotificationRowComponentBuilder), - mock(NotificationLogger.class)); + RowInflaterTask::new, + mExpandableNotificationRowComponentBuilder); when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false); when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false); mEntryManager = new TestableNotificationEntryManager( - mNotifLog, + mLogger, mGroupManager, new NotificationRankingManager( - () -> mock(NotificationMediaManager.class), + () -> mNotificationMediaManager, mGroupManager, mHeadsUpManager, mock(NotificationFilter.class), - mNotifLog, + mLogger, mock(NotificationSectionsFeatureManager.class), mock(PeopleNotificationIdentifier.class), mock(HighPriorityProvider.class)), @@ -254,13 +260,55 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntryManager.addNotificationEntryListener(mEntryListener); mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor); - notificationRowBinder.setUpWithPresenter( - mPresenter, mListContainer, mHeadsUpManager, mBindCallback); + notificationRowBinder.setUpWithPresenter(mPresenter, mListContainer, mBindCallback); notificationRowBinder.setInflationCallback(mEntryManager); notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class)); setUserSentiment( mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL); + + ArgumentCaptor<ExpandableNotificationRow> viewCaptor = + ArgumentCaptor.forClass(ExpandableNotificationRow.class); + when(mExpandableNotificationRowComponentBuilder + .expandableNotificationRow(viewCaptor.capture())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .notificationEntry(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .onDismissRunnable(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .inflationCallback(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .onExpandClickListener(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + + when(mExpandableNotificationRowComponentBuilder.build()) + .thenReturn(mExpandableNotificationRowComponent); + when(mExpandableNotificationRowComponent.getExpandableNotificationRowController()) + .thenAnswer((Answer<ExpandableNotificationRowController>) invocation -> + new ExpandableNotificationRowController( + viewCaptor.getValue(), + mock(ActivatableNotificationViewController.class), + mNotificationMediaManager, + mock(PluginManager.class), + new FakeSystemClock(), + "FOOBAR", "FOOBAR", + mKeyguardBypassController, + mGroupManager, + bindStage, + mock(NotificationLogger.class), + mHeadsUpManager, + mPresenter, + mStatusBarStateController, + mEntryManager, + mGutsManager, + true, + null, + mFalsingManager + )); } @After @@ -269,7 +317,10 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.abortTask(); } + // TODO: These tests are closer to functional tests and we should move them to their own file. + // and also strip some of the verifies that make the test too complex @Test + @Ignore public void testAddNotification() throws Exception { TestableLooper.get(this).processAllMessages(); @@ -306,6 +357,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test + @Ignore public void testUpdateNotification() throws Exception { TestableLooper.get(this).processAllMessages(); @@ -331,6 +383,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test + @Ignore public void testUpdateNotification_prePostEntryOrder() throws Exception { TestableLooper.get(this).processAllMessages(); @@ -399,7 +452,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction()))); mEntryManager.updateNotificationRanking(mRankingMap); - verify(mRow).setEntry(eq(mEntry)); assertEquals(1, mEntry.getSmartActions().size()); assertEquals("action", mEntry.getSmartActions().get(0).title); verify(mEntryListener).onNotificationRankingUpdated(mRankingMap); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java index 5aed61b98ad9..1116a333125e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java @@ -42,11 +42,11 @@ import com.android.systemui.ForegroundServiceController; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; 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 7431459aac8f..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,13 +42,13 @@ 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) - override fun onAsyncInflationFinished(entry: NotificationEntry?, inflatedFlags: Int) { - super.onAsyncInflationFinished(entry, inflatedFlags) + override fun onAsyncInflationFinished(entry: NotificationEntry) { + super.onAsyncInflationFinished(entry) countDownLatch.countDown() } 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/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 3d79ce15bfb6..a8918103c4a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -21,7 +21,6 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -51,7 +50,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; @@ -147,15 +145,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - public void setNeedsRedactionSetsInflationFlag() throws Exception { - ExpandableNotificationRow row = mNotificationTestHelper.createRow(); - - row.setNeedsRedaction(true); - - assertTrue(row.isInflationFlagSet(FLAG_CONTENT_VIEW_PUBLIC)); - } - - @Test public void setNeedsRedactionFreesViewWhenFalse() throws Exception { ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL); row.setNeedsRedaction(true); @@ -205,9 +194,8 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testClickSound() throws Exception { assertTrue("Should play sounds by default.", mGroupRow.isSoundEffectsEnabled()); - StatusBarStateController mock = mock(StatusBarStateController.class); + StatusBarStateController mock = mNotificationTestHelper.getStatusBarStateController(); when(mock.isDozing()).thenReturn(true); - mGroupRow.setStatusBarStateController(mock); mGroupRow.setSecureStateProvider(()-> false); assertFalse("Shouldn't play sounds when dark and trusted.", mGroupRow.isSoundEffectsEnabled()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java new file mode 100644 index 000000000000..8f9f65d12762 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java @@ -0,0 +1,158 @@ +/* + * 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.row; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.annotation.NonNull; +import androidx.core.os.CancellationSignal; +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NotifBindPipelineTest extends SysuiTestCase { + + private NotifBindPipeline mBindPipeline; + private TestBindStage mStage = new TestBindStage(); + + @Mock private NotificationEntry mEntry; + @Mock private ExpandableNotificationRow mRow; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + NotificationEntryManager entryManager = mock(NotificationEntryManager.class); + + mBindPipeline = new NotifBindPipeline(entryManager); + mBindPipeline.setStage(mStage); + + ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = + ArgumentCaptor.forClass(NotificationEntryListener.class); + verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture()); + NotificationEntryListener entryListener = entryListenerCaptor.getValue(); + + entryListener.onPendingEntryAdded(mEntry); + } + + @Test + public void testCallbackCalled() { + // GIVEN a bound row + mBindPipeline.manageRow(mEntry, mRow); + + // WHEN content is invalidated + BindCallback callback = mock(BindCallback.class); + mStage.requestRebind(mEntry, callback); + + // WHEN stage finishes its work + mStage.doWorkSynchronously(); + + // THEN the callback is called when bind finishes + verify(callback).onBindFinished(mEntry); + } + + @Test + public void testCallbackCancelled() { + // GIVEN a bound row + mBindPipeline.manageRow(mEntry, mRow); + + // GIVEN an in-progress pipeline run + BindCallback callback = mock(BindCallback.class); + CancellationSignal signal = mStage.requestRebind(mEntry, callback); + + // WHEN the callback is cancelled. + signal.cancel(); + + // WHEN the stage finishes all its work + mStage.doWorkSynchronously(); + + // THEN the callback is not called when bind finishes + verify(callback, never()).onBindFinished(mEntry); + } + + @Test + public void testMultipleCallbacks() { + // GIVEN a bound row + mBindPipeline.manageRow(mEntry, mRow); + + // WHEN the pipeline is invalidated. + BindCallback callback = mock(BindCallback.class); + mStage.requestRebind(mEntry, callback); + + // WHEN the pipeline is invalidated again before the work completes. + BindCallback callback2 = mock(BindCallback.class); + mStage.requestRebind(mEntry, callback2); + + // WHEN the stage finishes all work. + mStage.doWorkSynchronously(); + + // THEN both callbacks are called when the bind finishes + verify(callback).onBindFinished(mEntry); + verify(callback2).onBindFinished(mEntry); + } + + /** + * Bind stage for testing where asynchronous work can be synchronously controlled. + */ + private static class TestBindStage extends BindStage { + private List<Runnable> mExecutionRequests = new ArrayList<>(); + + @Override + protected void executeStage(@NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row, @NonNull StageCallback callback) { + mExecutionRequests.add(() -> callback.onStageFinished(entry)); + } + + @Override + protected void abortStage(@NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row) { + + } + + @Override + protected Object newStageParams() { + return null; + } + + public void doWorkSynchronously() { + for (Runnable work: mExecutionRequests) { + work.run(); + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java index d7214f3b9228..20cc01accbc3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java @@ -32,10 +32,10 @@ import android.widget.RemoteViews; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.notification.NotificationEntryListener; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import org.junit.Before; import org.junit.Test; @@ -50,7 +50,7 @@ public class NotifRemoteViewCacheImplTest extends SysuiTestCase { private NotifRemoteViewCacheImpl mNotifRemoteViewCache; private NotificationEntry mEntry; - private NotificationEntryListener mEntryListener; + private NotifCollectionListener mEntryListener; @Mock private RemoteViews mRemoteViews; @Before @@ -58,19 +58,17 @@ public class NotifRemoteViewCacheImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mEntry = new NotificationEntryBuilder().build(); - NotificationEntryManager entryManager = mock(NotificationEntryManager.class); - mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager); - ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = - ArgumentCaptor.forClass(NotificationEntryListener.class); - verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture()); + CommonNotifCollection collection = mock(CommonNotifCollection.class); + mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(collection); + ArgumentCaptor<NotifCollectionListener> entryListenerCaptor = + ArgumentCaptor.forClass(NotifCollectionListener.class); + verify(collection).addCollectionListener(entryListenerCaptor.capture()); mEntryListener = entryListenerCaptor.getValue(); + mEntryListener.onEntryInit(mEntry); } @Test public void testPutCachedView() { - // GIVEN an initialized cache for an entry. - mEntryListener.onPendingEntryAdded(mEntry); - // WHEN a notification's cached remote views is put in. mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); @@ -85,7 +83,6 @@ public class NotifRemoteViewCacheImplTest extends SysuiTestCase { @Test public void testRemoveCachedView() { // GIVEN a cache with a cached view. - mEntryListener.onPendingEntryAdded(mEntry); mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); // WHEN we remove the cached view. @@ -98,7 +95,6 @@ public class NotifRemoteViewCacheImplTest extends SysuiTestCase { @Test public void testClearCache() { // GIVEN a non-empty cache. - mEntryListener.onPendingEntryAdded(mEntry); mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java index 444a6e5b4b13..1dfe7bc33373 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java @@ -46,7 +46,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.util.Assert; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index cb9da6a40cb9..8a42e5fb4ea0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -49,9 +49,7 @@ import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; @@ -200,8 +198,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { } @Override - public void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { countDownLatch.countDown(); } }, mRow.getPrivateLayout(), null, null, new HashMap<>(), @@ -219,34 +216,6 @@ public class NotificationContentInflaterTest extends SysuiTestCase { assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)); } - /* Cancelling requires us to be on the UI thread otherwise we might have a race */ - @Test - public void testSupersedesExistingTask() { - mNotificationInflater.bindContent( - mRow.getEntry(), - mRow, - FLAG_CONTENT_VIEW_ALL, - new BindParams(), - false /* forceInflate */, - null /* callback */); - - // Trigger inflation of contracted only. - mNotificationInflater.bindContent( - mRow.getEntry(), - mRow, - FLAG_CONTENT_VIEW_CONTRACTED, - new BindParams(), - false /* forceInflate */, - null /* callback */); - - InflationTask runningTask = mRow.getEntry().getRunningTask(); - NotificationContentInflater.AsyncInflationTask asyncInflationTask = - (NotificationContentInflater.AsyncInflationTask) runningTask; - assertEquals("Successive inflations don't inherit the previous flags!", - FLAG_CONTENT_VIEW_ALL, asyncInflationTask.getReInflateFlags()); - runningTask.abort(); - } - @Test public void doesntReapplyDisallowedRemoteView() throws Exception { mBuilder.setStyle(new Notification.MediaStyle()); @@ -349,8 +318,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { } @Override - public void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { if (expectingException) { exceptionHolder.setException(new RuntimeException( "Inflation finished even though there should be an error")); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 4e27770982e5..bbb6723135a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -66,7 +66,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 457bbe23334b..35b55087873b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -11,10 +11,10 @@ * 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 + * limitations under the License. */ -package com.android.systemui.statusbar; +package com.android.systemui.statusbar.notification.row; import static android.app.Notification.FLAG_BUBBLE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; @@ -24,6 +24,7 @@ import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanki import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import android.annotation.Nullable; import android.app.ActivityManager; @@ -40,17 +41,21 @@ import android.text.TextUtils; import android.view.LayoutInflater; import android.widget.RemoteViews; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.TestableDependency; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.bubbles.BubblesTestActivity; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.SmartReplyController; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener; -import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -58,6 +63,8 @@ import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.tests.R; +import org.mockito.ArgumentCaptor; + import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -82,6 +89,10 @@ public class NotificationTestHelper { private final NotificationGroupManager mGroupManager; private ExpandableNotificationRow mRow; private HeadsUpManagerPhone mHeadsUpManager; + private final NotifBindPipeline mBindPipeline; + private final NotificationEntryListener mBindPipelineEntryListener; + private final RowContentBindStage mBindStage; + private StatusBarStateController mStatusBarStateController; public NotificationTestHelper(Context context, TestableDependency dependency) { mContext = context; @@ -89,12 +100,29 @@ public class NotificationTestHelper { dependency.injectMockDependency(BubbleController.class); dependency.injectMockDependency(NotificationShadeWindowController.class); dependency.injectMockDependency(SmartReplyController.class); - StatusBarStateController stateController = mock(StatusBarStateController.class); - mGroupManager = new NotificationGroupManager(stateController); - mHeadsUpManager = new HeadsUpManagerPhone(mContext, stateController, + mStatusBarStateController = mock(StatusBarStateController.class); + mGroupManager = new NotificationGroupManager(mStatusBarStateController); + mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController, mock(KeyguardBypassController.class)); mHeadsUpManager.setUp(null, mGroupManager, null, null); mGroupManager.setHeadsUpManager(mHeadsUpManager); + + + NotificationContentInflater contentBinder = new NotificationContentInflater( + mock(NotifRemoteViewCache.class), + mock(NotificationRemoteInputManager.class)); + contentBinder.setInflateSynchronously(true); + mBindStage = new RowContentBindStage(contentBinder, mock(IStatusBarService.class)); + + NotificationEntryManager entryManager = mock(NotificationEntryManager.class); + + mBindPipeline = new NotifBindPipeline(entryManager); + mBindPipeline.setStage(mBindStage); + + ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = + ArgumentCaptor.forClass(NotificationEntryListener.class); + verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture()); + mBindPipelineEntryListener = entryListenerCaptor.getValue(); } /** @@ -173,9 +201,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); } /** @@ -185,7 +221,7 @@ public class NotificationTestHelper { */ public ExpandableNotificationRow createBubble(@Nullable PendingIntent deleteIntent) throws Exception { - return createBubble(makeBubbleMetadata(deleteIntent), PKG); + return createBubble(makeBubbleMetadata(deleteIntent), PKG, false); } /** @@ -195,8 +231,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); @@ -281,6 +323,10 @@ public class NotificationTestHelper { return notificationBuilder.build(); } + public StatusBarStateController getStatusBarStateController() { + return mStatusBarStateController; + } + private ExpandableNotificationRow generateRow( Notification notification, String pkg, @@ -331,10 +377,8 @@ public class NotificationTestHelper { entry.createIcons(mContext, entry.getSbn()); row.setEntry(entry); - NotificationContentInflater contentBinder = new NotificationContentInflater( - mock(NotifRemoteViewCache.class), - mock(NotificationRemoteInputManager.class)); - contentBinder.setInflateSynchronously(true); + mBindPipelineEntryListener.onPendingEntryAdded(entry); + mBindPipeline.manageRow(entry, row); row.initialize( APP_NAME, @@ -343,12 +387,15 @@ public class NotificationTestHelper { mock(KeyguardBypassController.class), mGroupManager, mHeadsUpManager, - contentBinder, - mock(OnExpandClickListener.class)); + mBindStage, + mock(OnExpandClickListener.class), + mock(NotificationMediaManager.class), + mock(ExpandableNotificationRow.OnAppOpsClickListener.class), + mock(FalsingManager.class), + mStatusBarStateController); row.setAboveShelfChangedListener(aboveShelf -> { }); - - row.setInflationFlags(extraInflationFlags); - inflateAndWait(row); + mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags); + inflateAndWait(entry, mBindStage); // This would be done as part of onAsyncInflationFinished, but we skip large amounts of // the callback chain, so we need to make up for not adding it to the group manager @@ -357,24 +404,10 @@ public class NotificationTestHelper { return row; } - private static void inflateAndWait(ExpandableNotificationRow row) throws Exception { + private static void inflateAndWait(NotificationEntry entry, RowContentBindStage stage) + throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); - NotificationContentInflater.InflationCallback callback = - new NotificationContentInflater.InflationCallback() { - @Override - public void handleInflationException(NotificationEntry entry, - Exception e) { - countDownLatch.countDown(); - } - - @Override - public void onAsyncInflationFinished(NotificationEntry entry, - int inflatedFlags) { - countDownLatch.countDown(); - } - }; - row.setInflationCallback(callback); - row.inflateViews(); + stage.requestRebind(entry, en -> countDownLatch.countDown()); assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)); } 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 new file mode 100644 index 000000000000..775f722b13f9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -0,0 +1,269 @@ +/* + * 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.row; + +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; + +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class RowContentBindStageTest extends SysuiTestCase { + + private RowContentBindStage mRowContentBindStage; + + @Mock private NotificationRowContentBinder mBinder; + @Mock private NotificationEntry mEntry; + @Mock private ExpandableNotificationRow mRow; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mRowContentBindStage = new RowContentBindStage(mBinder, + mock(IStatusBarService.class)); + mRowContentBindStage.createStageParams(mEntry); + } + + @Test + 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); + params.requireContentViews(flags); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder binds inflation flags. + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(flags), + any(), + anyBoolean(), + any()); + } + + @Test + public void testFreeContentViews() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + + // WHEN inflation flags are cleared and stage executed. + final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; + params.freeContentViews(flags); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder unbinds flags. + verify(mBinder).unbindContent(eq(mEntry), any(), eq(flags)); + } + + @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); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN low priority is set and stage executed. + params.setUseLowPriority(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with use low priority and contracted/expanded are called to bind. + ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED), + bindParamsCaptor.capture(), + anyBoolean(), + any()); + BindParams usedParams = bindParamsCaptor.getValue(); + assertTrue(usedParams.isLowPriority); + } + + @Test + public void testSetUseGroupInChild() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN use group is set and stage executed. + params.setUseChildInGroup(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with use group view and contracted/expanded are called to bind. + ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED), + bindParamsCaptor.capture(), + anyBoolean(), + any()); + BindParams usedParams = bindParamsCaptor.getValue(); + assertTrue(usedParams.isChildInGroup); + } + + @Test + public void testSetUseIncreasedHeight() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN use increased height is set and stage executed. + params.setUseIncreasedCollapsedHeight(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with group view and contracted is bound. + ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_CONTRACTED), + bindParamsCaptor.capture(), + anyBoolean(), + any()); + BindParams usedParams = bindParamsCaptor.getValue(); + assertTrue(usedParams.usesIncreasedHeight); + } + + @Test + public void testSetUseIncreasedHeadsUpHeight() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN use increased heads up height is set and stage executed. + params.setUseIncreasedHeadsUpHeight(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with use group view and heads up is bound. + ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_HEADS_UP), + bindParamsCaptor.capture(), + anyBoolean(), + any()); + BindParams usedParams = bindParamsCaptor.getValue(); + assertTrue(usedParams.usesIncreasedHeadsUpHeight); + } + + @Test + public void testSetNeedsReinflation() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN needs reinflation is set. + params.setNeedsReinflation(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with forceInflate and all views are requested to bind. + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_ALL), + any(), + eq(true), + any()); + } + + @Test + public void testSupersedesPreviousContentViews() { + // GIVEN a view with content view bind already in progress. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + int defaultFlags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; + params.requireContentViews(defaultFlags); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // WHEN we bind with another content view before the first finishes. + params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with BOTH content views. + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(defaultFlags), + any(), + anyBoolean(), + any()); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(defaultFlags | FLAG_CONTENT_VIEW_HEADS_UP), + any(), + anyBoolean(), + any()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java index d280f185edd3..0790cb7ca6c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java @@ -25,8 +25,8 @@ import android.widget.RemoteViews; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.tests.R; import org.junit.Assert; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java index 4f45f680f475..038eff7fa5dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java @@ -38,8 +38,8 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import org.junit.Before; import org.junit.Test; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java index 14e2fded6cdc..9567f3386dda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java @@ -29,8 +29,8 @@ import android.widget.TextView; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.util.Assert; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java index ddd2884ec311..1773175450ff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java @@ -25,8 +25,8 @@ import android.view.View; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import org.junit.Assert; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java index 34a309f1d80c..2d1bc7890aed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java @@ -30,12 +30,11 @@ import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.util.DeviceConfigProxy; @@ -59,8 +58,6 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { private ExpandableNotificationRow mFirst; private ExpandableNotificationRow mSecond; @Mock - private StatusBarStateController mStatusBarStateController; - @Mock private KeyguardBypassController mBypassController; @Before @@ -150,13 +147,12 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mFirst, mSecond), createSection(null, null) }); - ExpandableNotificationRow row = new NotificationTestHelper(getContext(), mDependency) - .createRow(); + NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency); + ExpandableNotificationRow row = testHelper.createRow(); NotificationEntry entry = mock(NotificationEntry.class); when(entry.getRow()).thenReturn(row); - when(mStatusBarStateController.isDozing()).thenReturn(true); - row.setStatusBarStateController(mStatusBarStateController); + when(testHelper.getStatusBarStateController().isDozing()).thenReturn(true); row.setHeadsUp(true); mRoundnessManager.onHeadsUpStateChanged(entry, true); Assert.assertEquals(1f, row.getCurrentBottomRoundness(), 0.0f); 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/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index 7448dbd0d116..f71d0fc4b43e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -35,9 +35,9 @@ import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.HeadsUpStatusBarView; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.policy.KeyguardStateController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java index 5b54fba5b3b5..e171a2894d2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java @@ -16,8 +16,12 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -38,6 +42,9 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; +import com.android.systemui.statusbar.notification.row.RowContentBindParams; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.policy.HeadsUpManager; import org.junit.Before; @@ -47,6 +54,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -62,8 +70,8 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { private NotificationGroupManager mGroupManager; private HeadsUpManager mHeadsUpManager; @Mock private NotificationEntryManager mNotificationEntryManager; - @Captor - private ArgumentCaptor<NotificationEntryListener> mListenerCaptor; + @Mock private RowContentBindStage mBindStage; + @Captor private ArgumentCaptor<NotificationEntryListener> mListenerCaptor; private NotificationEntryListener mNotificationEntryListener; private final HashMap<String, NotificationEntry> mPendingEntries = new HashMap<>(); private final NotificationGroupTestHelper mGroupTestHelper = @@ -72,6 +80,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { @Before public void setup() { + MockitoAnnotations.initMocks(this); mDependency.injectMockDependency(BubbleController.class); mHeadsUpManager = new HeadsUpManager(mContext) {}; @@ -82,7 +91,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager); mGroupManager.setHeadsUpManager(mHeadsUpManager); - mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(); + when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams()); + + mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(mBindStage); mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager); mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager); @@ -97,6 +108,10 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mHeadsUpManager.showNotification(summaryEntry); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(); + RowContentBindParams params = new RowContentBindParams(); + params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + // Summary will be suppressed because there is only one child. mGroupManager.onEntryAdded(summaryEntry); mGroupManager.onEntryAdded(childEntry); @@ -160,8 +175,8 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(); mHeadsUpManager.showNotification(summaryEntry); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); mGroupManager.onEntryAdded(summaryEntry); mGroupManager.onEntryAdded(childEntry); @@ -178,15 +193,16 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(); mHeadsUpManager.showNotification(summaryEntry); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); mGroupManager.onEntryAdded(summaryEntry); mGroupManager.onEntryAdded(childEntry); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(true); - mNotificationEntryListener.onEntryReinflated(childEntry); + // Child entry finishes its inflation. + ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class); + verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture()); + callbackCaptor.getValue().onBindFinished(childEntry); // Alert is immediately removed from summary, and we show child as its content is inflated. assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey())); @@ -199,8 +215,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + NotificationEntry childEntry2 = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY); mHeadsUpManager.showNotification(summaryEntry); @@ -214,9 +231,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupManager.onEntryAdded(childEntry2); // Child entry finishes its inflation. - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(true); - mNotificationEntryListener.onEntryReinflated(childEntry); + ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class); + verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture()); + callbackCaptor.getValue().onBindFinished(childEntry); verify(childEntry.getRow(), times(1)).freeContentViewWhenSafe(mHeadsUpManager .getContentFlag()); @@ -229,8 +246,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + mHeadsUpManager.showNotification(summaryEntry); // Trigger a transfer of alert state from summary to child. mGroupManager.onEntryAdded(summaryEntry); @@ -247,8 +265,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + mHeadsUpManager.showNotification(summaryEntry); // Trigger a transfer of alert state from summary to child. mGroupManager.onEntryAdded(summaryEntry); @@ -270,8 +289,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY, 47); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + mHeadsUpManager.showNotification(summaryEntry); // Trigger a transfer of alert state from summary to child. mGroupManager.onEntryAdded(summaryEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java index 54dc728e0c8b..d405fea78170 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -87,7 +86,6 @@ public final class NotificationGroupTestHelper { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); entry.setRow(row); when(row.getEntry()).thenReturn(entry); - when(row.isInflationFlagSet(anyInt())).thenReturn(true); return entry; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index fea4b8bbbb04..50276106f8d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -61,7 +61,6 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; @@ -70,6 +69,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java index 631c580a490d..cd91f22bb753 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.policy; +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.eq; @@ -24,10 +26,12 @@ import static org.mockito.Mockito.doAnswer; 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.net.ConnectivityManager; +import android.net.TetheringManager; import android.net.wifi.WifiManager; import android.os.Handler; +import android.os.UserManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -38,11 +42,14 @@ import com.android.systemui.SysuiTestCase; 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 org.mockito.invocation.InvocationOnMock; import java.util.ArrayList; +import java.util.Collections; import java.util.concurrent.Executor; @SmallTest @@ -51,13 +58,19 @@ import java.util.concurrent.Executor; public class HotspotControllerImplTest extends SysuiTestCase { @Mock - private ConnectivityManager mConnectivityManager; + private TetheringManager mTetheringManager; @Mock private WifiManager mWifiManager; @Mock + private UserManager mUserManager; + @Mock private HotspotController.Callback mCallback1; @Mock private HotspotController.Callback mCallback2; + @Mock + private TetheringManager.TetheringInterfaceRegexps mTetheringInterfaceRegexps; + @Captor + private ArgumentCaptor<TetheringManager.TetheringEventCallback> mTetheringCallbackCaptor; private HotspotControllerImpl mController; private TestableLooper mLooper; @@ -66,8 +79,13 @@ public class HotspotControllerImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mLooper = TestableLooper.get(this); - mContext.addMockSystemService(ConnectivityManager.class, mConnectivityManager); mContext.addMockSystemService(WifiManager.class, mWifiManager); + mContext.addMockSystemService(TetheringManager.class, mTetheringManager); + mContext.addMockSystemService(UserManager.class, mUserManager); + + when(mUserManager.isUserAdmin(anyInt())).thenReturn(true); + when(mTetheringInterfaceRegexps.getTetherableWifiRegexs()).thenReturn( + Collections.singletonList("test")); doAnswer((InvocationOnMock invocation) -> { ((WifiManager.SoftApCallback) invocation.getArgument(1)) @@ -76,7 +94,11 @@ public class HotspotControllerImplTest extends SysuiTestCase { }).when(mWifiManager).registerSoftApCallback(any(Executor.class), any(WifiManager.SoftApCallback.class)); - mController = new HotspotControllerImpl(mContext, new Handler(mLooper.getLooper())); + Handler handler = new Handler(mLooper.getLooper()); + + mController = new HotspotControllerImpl(mContext, handler, handler); + verify(mTetheringManager) + .registerTetheringEventCallback(any(), mTetheringCallbackCaptor.capture()); } @Test @@ -117,4 +139,28 @@ public class HotspotControllerImplTest extends SysuiTestCase { verify(mWifiManager, never()).unregisterSoftApCallback(any()); } + @Test + public void testDefault_hotspotNotSupported() { + assertFalse(mController.isHotspotSupported()); + } + + @Test + public void testHotspotSupported_rightConditions() { + mTetheringCallbackCaptor.getValue().onTetheringSupported(true); + mTetheringCallbackCaptor.getValue() + .onTetherableInterfaceRegexpsChanged(mTetheringInterfaceRegexps); + + assertTrue(mController.isHotspotSupported()); + } + + @Test + public void testHotspotSupported_callbackCalledOnChange() { + mController.addCallback(mCallback1); + mTetheringCallbackCaptor.getValue().onTetheringSupported(true); + mTetheringCallbackCaptor.getValue() + .onTetherableInterfaceRegexpsChanged(mTetheringInterfaceRegexps); + + verify(mCallback1).onHotspotAvailabilityChanged(true); + } + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 390e812b3613..df622542e22c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -39,9 +39,9 @@ import androidx.test.filters.SmallTest; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.util.Assert; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt new file mode 100644 index 000000000000..8eecde1f4f7c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt @@ -0,0 +1,218 @@ +package com.android.systemui.util + +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) +@SmallTest +class FloatingContentCoordinatorTest : SysuiTestCase() { + + private val screenBounds = Rect(0, 0, 1000, 1000) + + private val rect100px = Rect() + private val rect100pxFloating = FloatingRect(rect100px) + + private val rect200px = Rect() + private val rect200pxFloating = FloatingRect(rect200px) + + private val rect300px = Rect() + private val rect300pxFloating = FloatingRect(rect300px) + + private val floatingCoordinator = FloatingContentCoordinator() + + @Before + fun setup() { + rect100px.set(0, 0, 100, 100) + rect200px.set(0, 0, 200, 200) + rect300px.set(0, 0, 300, 300) + } + + @After + fun tearDown() { + // We need to remove this stuff since it's a singleton object and it'll be there for the + // next test. + floatingCoordinator.onContentRemoved(rect100pxFloating) + floatingCoordinator.onContentRemoved(rect200pxFloating) + floatingCoordinator.onContentRemoved(rect300pxFloating) + } + + @Test + fun testOnContentAdded() { + // Add rect1, and verify that the coordinator didn't move it. + floatingCoordinator.onContentAdded(rect100pxFloating) + assertEquals(rect100px.top, 0) + + // Add rect2, which intersects rect1. Verify that rect2 was not moved, since newly added + // content is allowed to remain where it is. rect1 should have been moved below rect2 + // since it was in the way. + floatingCoordinator.onContentAdded(rect200pxFloating) + assertEquals(rect200px.top, 0) + assertEquals(rect100px.top, 200) + + verifyRectSizes() + } + + @Test + fun testOnContentRemoved() { + // Add rect1, and remove it. Then add rect2. Since rect1 was removed before that, it should + // no longer be considered in the way, so it shouldn't move when rect2 is added. + floatingCoordinator.onContentAdded(rect100pxFloating) + floatingCoordinator.onContentRemoved(rect100pxFloating) + floatingCoordinator.onContentAdded(rect200pxFloating) + + assertEquals(rect100px.top, 0) + assertEquals(rect200px.top, 0) + + verifyRectSizes() + } + + @Test + fun testOnContentMoved_twoRects() { + // Add rect1, which is at y = 0. + floatingCoordinator.onContentAdded(rect100pxFloating) + + // Move rect2 down to 500px, where it won't conflict with rect1. + rect200px.offsetTo(0, 500) + floatingCoordinator.onContentAdded(rect200pxFloating) + + // Then, move it to 0px where it will absolutely conflict with rect1. + rect200px.offsetTo(0, 0) + floatingCoordinator.onContentMoved(rect200pxFloating) + + // The coordinator should have left rect2 alone, and moved rect1 below it. rect1 should now + // be at y = 200. + assertEquals(rect200px.top, 0) + assertEquals(rect100px.top, 200) + + verifyRectSizes() + + // Move rect2 to y = 275px. Since this puts it at the bottom half of rect1, it should push + // rect1 upward and leave rect2 alone. + rect200px.offsetTo(0, 275) + floatingCoordinator.onContentMoved(rect200pxFloating) + + assertEquals(rect200px.top, 275) + assertEquals(rect100px.top, 175) + + verifyRectSizes() + + // Move rect2 to y = 110px. This makes it intersect rect1 again, but above its center of + // mass. That means rect1 should be pushed downward. + rect200px.offsetTo(0, 110) + floatingCoordinator.onContentMoved(rect200pxFloating) + + assertEquals(rect200px.top, 110) + assertEquals(rect100px.top, 310) + + verifyRectSizes() + } + + @Test + fun testOnContentMoved_threeRects() { + floatingCoordinator.onContentAdded(rect100pxFloating) + + // Add rect2, which should displace rect1 to y = 200 + floatingCoordinator.onContentAdded(rect200pxFloating) + assertEquals(rect200px.top, 0) + assertEquals(rect100px.top, 200) + + // Add rect3, which should completely cover both rect1 and rect2. That should cause them to + // move away. The order in which they do so is non-deterministic, so just make sure none of + // the three Rects intersect. + floatingCoordinator.onContentAdded(rect300pxFloating) + + assertFalse(Rect.intersects(rect100px, rect200px)) + assertFalse(Rect.intersects(rect100px, rect300px)) + assertFalse(Rect.intersects(rect200px, rect300px)) + + // Move rect2 to intersect both rect1 and rect3. + rect200px.offsetTo(0, 150) + floatingCoordinator.onContentMoved(rect200pxFloating) + + assertFalse(Rect.intersects(rect100px, rect200px)) + assertFalse(Rect.intersects(rect100px, rect300px)) + assertFalse(Rect.intersects(rect200px, rect300px)) + } + + @Test + fun testOnContentMoved_respectsUpperBounds() { + // Add rect1, which is at y = 0. + floatingCoordinator.onContentAdded(rect100pxFloating) + + // Move rect2 down to 500px, where it won't conflict with rect1. + rect200px.offsetTo(0, 500) + floatingCoordinator.onContentAdded(rect200pxFloating) + + // Then, move it to 90px where it will conflict with rect1, but with a center of mass below + // that of rect1's. This would normally mean that rect1 moves upward. However, since it's at + // the top of the screen, it should go downward instead. + rect200px.offsetTo(0, 90) + floatingCoordinator.onContentMoved(rect200pxFloating) + + // rect2 should have been left alone, rect1 is now below rect2 at y = 290px even though it + // was intersected from below. + assertEquals(rect200px.top, 90) + assertEquals(rect100px.top, 290) + } + + @Test + fun testOnContentMoved_respectsLowerBounds() { + // Put rect1 at the bottom of the screen and add it. + rect100px.offsetTo(0, screenBounds.bottom - 100) + floatingCoordinator.onContentAdded(rect100pxFloating) + + // Put rect2 at the bottom as well. Since its center of mass is above rect1's, rect1 would + // normally move downward. Since it's at the bottom of the screen, it should go upward + // instead. + rect200px.offsetTo(0, 800) + floatingCoordinator.onContentAdded(rect200pxFloating) + + assertEquals(rect200px.top, 800) + assertEquals(rect100px.top, 700) + } + + /** + * Tests that the rect sizes didn't change when the coordinator manipulated them. This allows us + * to assert only the value of rect.top in tests, since if top, width, and height are correct, + * that means top/left/right/bottom are all correct. + */ + private fun verifyRectSizes() { + assertEquals(100, rect100px.width()) + assertEquals(200, rect200px.width()) + assertEquals(300, rect300px.width()) + + assertEquals(100, rect100px.height()) + assertEquals(200, rect200px.height()) + assertEquals(300, rect300px.height()) + } + + /** + * Helper class that uses [floatingCoordinator.findAreaForContentVertically] to move a + * Rect when needed. + */ + inner class FloatingRect( + private val underlyingRect: Rect + ) : FloatingContentCoordinator.FloatingContent { + override fun moveToBounds(bounds: Rect) { + underlyingRect.set(bounds) + } + + override fun getAllowedFloatingBoundsRegion(): Rect { + return screenBounds + } + + override fun getFloatingBoundsOnScreen(): Rect { + return underlyingRect + } + } +}
\ No newline at end of file diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index cf2b1f06d769..4efe93439b42 100644 --- a/packages/Tethering/Android.bp +++ b/packages/Tethering/Android.bp @@ -28,6 +28,7 @@ java_defaults { "netd_aidl_interface-unstable-java", "netlink-client", "networkstack-aidl-interfaces-unstable-java", + "android.hardware.tetheroffload.config-V1.0-java", "android.hardware.tetheroffload.control-V1.0-java", "net-utils-framework-common", ], @@ -48,26 +49,26 @@ android_library { // Due to b/143733063, APK can't access a jni lib that is in APEX (but not in the APK). cc_library { name: "libtetherutilsjni", + sdk_version: "current", srcs: [ "jni/android_net_util_TetheringUtils.cpp", ], shared_libs: [ - "libcgrouprc", - "libnativehelper_compat_libc++", - "libvndksupport", - ], - static_libs: [ - "android.hardware.tetheroffload.config@1.0", "liblog", - "libbase", - "libbinderthreadstate", - "libcutils", - "libhidlbase", - "libjsoncpp", - "libprocessgroup", - "libutils", + "libnativehelper_compat_libc++", ], + // We cannot use plain "libc++" here to link libc++ dynamically because it results in: + // java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++_shared.so" not found + // even if "libc++" is added into jni_libs below. Adding "libc++_shared" into jni_libs doesn't + // build because soong complains of: + // module Tethering missing dependencies: libc++_shared + // + // So, link libc++ statically. This means that we also need to ensure that all the C++ libraries + // we depend on do not dynamically link libc++. This is currently the case, because liblog is + // C-only and libnativehelper_compat_libc also uses stl: "c++_static". + stl: "c++_static", + cflags: [ "-Wall", "-Werror", @@ -86,9 +87,8 @@ java_defaults { // Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies // explicitly. jni_libs: [ - "libcgrouprc", + "liblog", "libnativehelper_compat_libc++", - "libvndksupport", "libtetherutilsjni", ], resource_dirs: [ 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/jni/android_net_util_TetheringUtils.cpp b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp index 1cf8f988432c..549344064405 100644 --- a/packages/Tethering/jni/android_net_util_TetheringUtils.cpp +++ b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp @@ -16,123 +16,18 @@ #include <errno.h> #include <error.h> -#include <hidl/HidlSupport.h> #include <jni.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> -#include <linux/netfilter/nfnetlink.h> -#include <linux/netlink.h> #include <net/if.h> #include <netinet/icmp6.h> #include <sys/socket.h> -#include <android-base/unique_fd.h> -#include <android/hardware/tetheroffload/config/1.0/IOffloadConfig.h> #define LOG_TAG "TetheringUtils" -#include <utils/Log.h> +#include <android/log.h> namespace android { -using hardware::hidl_handle; -using hardware::hidl_string; -using hardware::tetheroffload::config::V1_0::IOffloadConfig; - -namespace { - -inline const sockaddr * asSockaddr(const sockaddr_nl *nladdr) { - return reinterpret_cast<const sockaddr *>(nladdr); -} - -int conntrackSocket(unsigned groups) { - base::unique_fd s(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER)); - if (s.get() < 0) return -errno; - - const struct sockaddr_nl bind_addr = { - .nl_family = AF_NETLINK, - .nl_pad = 0, - .nl_pid = 0, - .nl_groups = groups, - }; - if (bind(s.get(), asSockaddr(&bind_addr), sizeof(bind_addr)) != 0) { - return -errno; - } - - const struct sockaddr_nl kernel_addr = { - .nl_family = AF_NETLINK, - .nl_pad = 0, - .nl_pid = 0, - .nl_groups = groups, - }; - if (connect(s.get(), asSockaddr(&kernel_addr), sizeof(kernel_addr)) != 0) { - return -errno; - } - - return s.release(); -} - -// Return a hidl_handle that owns the file descriptor owned by fd, and will -// auto-close it (otherwise there would be double-close problems). -// -// Rely upon the compiler to eliminate the constexprs used for clarity. -hidl_handle handleFromFileDescriptor(base::unique_fd fd) { - hidl_handle h; - - static constexpr int kNumFds = 1; - static constexpr int kNumInts = 0; - native_handle_t *nh = native_handle_create(kNumFds, kNumInts); - nh->data[0] = fd.release(); - - static constexpr bool kTakeOwnership = true; - h.setTo(nh, kTakeOwnership); - - return h; -} - -} // namespace - -static jboolean android_net_util_configOffload( - JNIEnv* /* env */) { - sp<IOffloadConfig> configInterface = IOffloadConfig::getService(); - if (configInterface.get() == nullptr) { - ALOGD("Could not find IOffloadConfig service."); - return false; - } - - // Per the IConfigOffload definition: - // - // fd1 A file descriptor bound to the following netlink groups - // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY). - // - // fd2 A file descriptor bound to the following netlink groups - // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY). - base::unique_fd - fd1(conntrackSocket(NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY)), - fd2(conntrackSocket(NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY)); - if (fd1.get() < 0 || fd2.get() < 0) { - ALOGE("Unable to create conntrack handles: %d/%s", errno, strerror(errno)); - return false; - } - - hidl_handle h1(handleFromFileDescriptor(std::move(fd1))), - h2(handleFromFileDescriptor(std::move(fd2))); - - bool rval(false); - hidl_string msg; - const auto status = configInterface->setHandles(h1, h2, - [&rval, &msg](bool success, const hidl_string& errMsg) { - rval = success; - msg = errMsg; - }); - if (!status.isOk() || !rval) { - ALOGE("IOffloadConfig::setHandles() error: '%s' / '%s'", - status.description().c_str(), msg.c_str()); - // If status is somehow not ok, make sure rval captures this too. - rval = false; - } - - return rval; -} - static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd, jint ifIndex) { @@ -229,7 +124,6 @@ static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject j */ static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ - { "configOffload", "()Z", (void*) android_net_util_configOffload }, { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_util_setupRaSocket }, }; @@ -242,7 +136,7 @@ int register_android_net_util_TetheringUtils(JNIEnv* env) { extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { JNIEnv *env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { - ALOGE("ERROR: GetEnv failed"); + __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed"); return JNI_ERR; } diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java index fa543bdce735..5a6d5c1cbfb0 100644 --- a/packages/Tethering/src/android/net/util/TetheringUtils.java +++ b/packages/Tethering/src/android/net/util/TetheringUtils.java @@ -24,14 +24,6 @@ import java.net.SocketException; * {@hide} */ public class TetheringUtils { - - /** - * Offload management process need to know conntrack rules to support NAT, but it may not have - * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and - * share them with offload management process. - */ - public static native boolean configOffload(); - /** * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements. * @param fd the socket's {@link FileDescriptor}. diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java index 90b9d3f148dc..b54571720857 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java @@ -18,19 +18,28 @@ package com.android.server.connectivity.tethering; import static android.net.util.TetheringUtils.uint16; +import android.hardware.tetheroffload.config.V1_0.IOffloadConfig; import android.hardware.tetheroffload.control.V1_0.IOffloadControl; import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback; import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate; import android.hardware.tetheroffload.control.V1_0.NetworkProtocol; import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent; +import android.net.netlink.NetlinkSocket; import android.net.util.SharedLog; -import android.net.util.TetheringUtils; +import android.net.util.SocketUtils; import android.os.Handler; +import android.os.NativeHandle; import android.os.RemoteException; +import android.system.ErrnoException; +import android.system.Os; import android.system.OsConstants; import com.android.internal.annotations.VisibleForTesting; +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.SocketAddress; +import java.net.SocketException; import java.util.ArrayList; @@ -49,6 +58,10 @@ public class OffloadHardwareInterface { private static final String NO_INTERFACE_NAME = ""; private static final String NO_IPV4_ADDRESS = ""; private static final String NO_IPV4_GATEWAY = ""; + // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h + private static final int NF_NETLINK_CONNTRACK_NEW = 1; + private static final int NF_NETLINK_CONNTRACK_UPDATE = 2; + private static final int NF_NETLINK_CONNTRACK_DESTROY = 4; private final Handler mHandler; private final SharedLog mLog; @@ -121,9 +134,103 @@ public class OffloadHardwareInterface { return DEFAULT_TETHER_OFFLOAD_DISABLED; } - /** Configure offload management process. */ + /** + * Offload management process need to know conntrack rules to support NAT, but it may not have + * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and + * share them with offload management process. + */ public boolean initOffloadConfig() { - return TetheringUtils.configOffload(); + IOffloadConfig offloadConfig; + try { + offloadConfig = IOffloadConfig.getService(); + } catch (RemoteException e) { + mLog.e("getIOffloadConfig error " + e); + return false; + } + if (offloadConfig == null) { + mLog.e("Could not find IOffloadConfig service"); + return false; + } + // Per the IConfigOffload definition: + // + // h1 provides a file descriptor bound to the following netlink groups + // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY). + // + // h2 provides a file descriptor bound to the following netlink groups + // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY). + final NativeHandle h1 = createConntrackSocket( + NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY); + if (h1 == null) return false; + + final NativeHandle h2 = createConntrackSocket( + NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY); + if (h2 == null) { + closeFdInNativeHandle(h1); + return false; + } + + final CbResults results = new CbResults(); + try { + offloadConfig.setHandles(h1, h2, + (boolean success, String errMsg) -> { + results.mSuccess = success; + results.mErrMsg = errMsg; + }); + } catch (RemoteException e) { + record("initOffloadConfig, setHandles fail", e); + return false; + } + // Explicitly close FDs. + closeFdInNativeHandle(h1); + closeFdInNativeHandle(h2); + + record("initOffloadConfig, setHandles results:", results); + return results.mSuccess; + } + + private void closeFdInNativeHandle(final NativeHandle h) { + try { + h.close(); + } catch (IOException | IllegalStateException e) { + // IllegalStateException means fd is already closed, do nothing here. + // Also nothing we can do if IOException. + } + } + + private NativeHandle createConntrackSocket(final int groups) { + FileDescriptor fd; + try { + fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER); + } catch (ErrnoException e) { + mLog.e("Unable to create conntrack socket " + e); + return null; + } + + final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups); + try { + Os.bind(fd, sockAddr); + } catch (ErrnoException | SocketException e) { + mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e); + try { + SocketUtils.closeSocket(fd); + } catch (IOException ie) { + // Nothing we can do here + } + return null; + } + try { + Os.connect(fd, sockAddr); + } catch (ErrnoException | SocketException e) { + mLog.e("connect to kernel fail for groups " + groups + " error: " + e); + try { + SocketUtils.closeSocket(fd); + } catch (IOException ie) { + // Nothing we can do here + } + return null; + } + + return new NativeHandle(fd, true); } /** Initialize the tethering offload HAL. */ 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 07abe1adeb52..64c16e41dbf5 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -272,13 +272,6 @@ public class Tethering { mStateReceiver = new StateReceiver(); - mNetdCallback = new NetdCallback(); - try { - mNetd.registerUnsolicitedEventListener(mNetdCallback); - } catch (RemoteException e) { - mLog.e("Unable to register netd UnsolicitedEventListener"); - } - final UserManager userManager = (UserManager) mContext.getSystemService( Context.USER_SERVICE); mTetheringRestriction = new UserRestrictionActionListener(userManager, this); @@ -287,6 +280,14 @@ public class Tethering { // Load tethering configuration. updateConfiguration(); + // NetdCallback should be registered after updateConfiguration() to ensure + // TetheringConfiguration is created. + mNetdCallback = new NetdCallback(); + try { + mNetd.registerUnsolicitedEventListener(mNetdCallback); + } catch (RemoteException e) { + mLog.e("Unable to register netd UnsolicitedEventListener"); + } startStateMachineUpdaters(mHandler); startTrackDefaultNetwork(); @@ -1943,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) { @@ -1952,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/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java index c67fe9ffa916..1e8109cb393c 100644 --- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java +++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java @@ -23,6 +23,8 @@ import android.util.Log; public class PacNative { private static final String TAG = "PacProxy"; + private static final PacNative sInstance = new PacNative(); + private String mCurrentPac; private boolean mIsActive; @@ -39,8 +41,12 @@ public class PacNative { System.loadLibrary("jni_pacprocessor"); } - PacNative() { + private PacNative() { + + } + public static PacNative getInstance() { + return sInstance; } public synchronized boolean startPacSupport() { diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java index 74391eb676d1..b006d6e1fa7b 100644 --- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java +++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java @@ -31,43 +31,27 @@ import java.net.URL; public class PacService extends Service { private static final String TAG = "PacService"; - private PacNative mPacNative; - private ProxyServiceStub mStub; + private PacNative mPacNative = PacNative.getInstance(); + private ProxyServiceStub mStub = new ProxyServiceStub(); @Override public void onCreate() { super.onCreate(); - if (mPacNative == null) { - mPacNative = new PacNative(); - mStub = new ProxyServiceStub(mPacNative); - } + mPacNative.startPacSupport(); } @Override public void onDestroy() { + mPacNative.stopPacSupport(); super.onDestroy(); - if (mPacNative != null) { - mPacNative.stopPacSupport(); - mPacNative = null; - mStub = null; - } } @Override public IBinder onBind(Intent intent) { - if (mPacNative == null) { - mPacNative = new PacNative(); - mStub = new ProxyServiceStub(mPacNative); - } return mStub; } - private static class ProxyServiceStub extends IProxyService.Stub { - private final PacNative mPacNative; - - public ProxyServiceStub(PacNative pacNative) { - mPacNative = pacNative; - } + private class ProxyServiceStub extends IProxyService.Stub { @Override public String resolvePacFile(String host, String url) throws RemoteException { @@ -102,20 +86,12 @@ public class PacService extends Service { @Override public void startPacSystem() throws RemoteException { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - Log.e(TAG, "Only system user is allowed to call startPacSystem"); - throw new SecurityException(); - } - mPacNative.startPacSupport(); + //TODO: remove } @Override public void stopPacSystem() throws RemoteException { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - Log.e(TAG, "Only system user is allowed to call stopPacSystem"); - throw new SecurityException(); - } - mPacNative.stopPacSupport(); + //TODO: remove } } } diff --git a/services/Android.bp b/services/Android.bp index 28c8aeea4e53..416f448a965f 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -30,6 +30,7 @@ filegroup { ":services.usb-sources", ":services.voiceinteraction-sources", ":service-permission-sources", + ":service-statsd-sources", ], visibility: ["//visibility:private"], } @@ -75,7 +76,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/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java index 5d170d34d77d..b74be7e3f345 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java @@ -30,6 +30,13 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_RIGHT; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_UP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN; @@ -133,6 +140,13 @@ class GestureManifold implements GestureMatcher.StateChangeListener { new MultiFingerMultiTap(mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP, this)); mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this)); + // Four-finger taps. + mMultiFingerGestures.add( + new MultiFingerMultiTap(mContext, 4, 1, GESTURE_4_FINGER_SINGLE_TAP, this)); + mMultiFingerGestures.add( + new MultiFingerMultiTap(mContext, 4, 2, GESTURE_4_FINGER_DOUBLE_TAP, this)); + mMultiFingerGestures.add( + new MultiFingerMultiTap(mContext, 4, 3, GESTURE_4_FINGER_TRIPLE_TAP, this)); // Two-finger swipes. mMultiFingerGestures.add( new MultiFingerSwipe(context, 2, DOWN, GESTURE_2_FINGER_SWIPE_DOWN, this)); @@ -151,6 +165,15 @@ class GestureManifold implements GestureMatcher.StateChangeListener { new MultiFingerSwipe(context, 3, RIGHT, GESTURE_3_FINGER_SWIPE_RIGHT, this)); mMultiFingerGestures.add( new MultiFingerSwipe(context, 3, UP, GESTURE_3_FINGER_SWIPE_UP, this)); + // Four-finger swipes. + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 4, DOWN, GESTURE_4_FINGER_SWIPE_DOWN, this)); + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 4, LEFT, GESTURE_4_FINGER_SWIPE_LEFT, this)); + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 4, RIGHT, GESTURE_4_FINGER_SWIPE_RIGHT, this)); + mMultiFingerGestures.add( + new MultiFingerSwipe(context, 4, UP, GESTURE_4_FINGER_SWIPE_UP, this)); } /** diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java index 8249239e3602..a14584a63bb2 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java @@ -139,7 +139,7 @@ class MultiFingerSwipe extends GestureMatcher { final int actionIndex = getActionIndex(rawEvent); final int pointerId = rawEvent.getPointerId(actionIndex); int pointerIndex = rawEvent.getPointerCount() - 1; - if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) { + if (pointerId < 0) { // Nonsensical pointer id. cancelGesture(event, rawEvent, policyFlags); return; @@ -185,7 +185,7 @@ class MultiFingerSwipe extends GestureMatcher { } final int actionIndex = getActionIndex(rawEvent); final int pointerId = rawEvent.getPointerId(actionIndex); - if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) { + if (pointerId < 0) { // Nonsensical pointer id. cancelGesture(event, rawEvent, policyFlags); return; @@ -224,7 +224,7 @@ class MultiFingerSwipe extends GestureMatcher { mCurrentFingerCount -= 1; final int actionIndex = getActionIndex(event); final int pointerId = event.getPointerId(actionIndex); - if (pointerId < 0 || pointerId > rawEvent.getPointerCount() - 1) { + if (pointerId < 0) { // Nonsensical pointer id. cancelGesture(event, rawEvent, policyFlags); return; @@ -250,11 +250,29 @@ class MultiFingerSwipe extends GestureMatcher { @Override protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - for (int pointerIndex = 0; pointerIndex < rawEvent.getPointerCount(); ++pointerIndex) { + for (int pointerIndex = 0; pointerIndex < mTargetFingerCount; ++pointerIndex) { + if (mPointerIds[pointerIndex] == INVALID_POINTER_ID) { + // Fingers have started to move before the required number of fingers are down. + // However, they can still move less than the touch slop and still be considered + // touching, not moving. + // So we just ignore fingers that haven't been assigned a pointer id and process + // those who have. + continue; + } if (DEBUG) { Slog.d(getGestureName(), "Processing move on finger " + pointerIndex); } int index = rawEvent.findPointerIndex(mPointerIds[pointerIndex]); + if (index < 0) { + // This finger is not present in this event. It could have gone up just before this + // movement. + if (DEBUG) { + Slog.d( + getGestureName(), + "Finger " + pointerIndex + " not found in this event. skipping."); + } + continue; + } final float x = rawEvent.getX(index); final float y = rawEvent.getY(index); if (x < 0f || y < 0f) { diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 1a4fc32a76cd..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 = @@ -818,27 +833,26 @@ final class AutofillManagerServiceImpl } } - void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId, - @Nullable Bundle clientState) { + void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId) { synchronized (mLock) { if (mAugmentedAutofillEventHistory == null || mAugmentedAutofillEventHistory.getSessionId() != sessionId) { return; } mAugmentedAutofillEventHistory.addEvent( - new Event(Event.TYPE_DATASET_SELECTED, suggestionId, clientState, null, null, + new Event(Event.TYPE_DATASET_SELECTED, suggestionId, null, null, null, null, null, null, null, null, null)); } } - void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState) { + void logAugmentedAutofillShown(int sessionId) { synchronized (mLock) { if (mAugmentedAutofillEventHistory == null || mAugmentedAutofillEventHistory.getSessionId() != sessionId) { return; } mAugmentedAutofillEventHistory.addEvent( - new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null, + new Event(Event.TYPE_DATASETS_SHOWN, null, null, null, null, null, null, null, null, null, null)); } @@ -1227,16 +1241,15 @@ final class AutofillManagerServiceImpl } @Override - public void logAugmentedAutofillShown(int sessionId, Bundle clientState) { - AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId, - clientState); + public void logAugmentedAutofillShown(int sessionId) { + AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId); } @Override - public void logAugmentedAutofillSelected(int sessionId, String suggestionId, - Bundle clientState) { + public void logAugmentedAutofillSelected(int sessionId, + String suggestionId) { AutofillManagerServiceImpl.this.logAugmentedAutofillSelected(sessionId, - suggestionId, clientState); + suggestionId); } @Override diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java new file mode 100644 index 000000000000..1fc48d235add --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java @@ -0,0 +1,168 @@ +/* + * 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.autofill; + +import static com.android.server.autofill.Helper.sDebug; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.RemoteException; +import android.util.Log; +import android.view.autofill.AutofillId; +import android.view.inputmethod.InlineSuggestionsRequest; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.view.IInlineSuggestionsRequestCallback; +import com.android.internal.view.IInlineSuggestionsResponseCallback; +import com.android.server.inputmethod.InputMethodManagerInternal; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Maintains an inline suggestion autofill session. + * + * <p> This class is thread safe. + */ +final class InlineSuggestionSession { + + private static final String TAG = "InlineSuggestionSession"; + private static final int INLINE_REQUEST_TIMEOUT_MS = 1000; + + @NonNull + private final InputMethodManagerInternal mInputMethodManagerInternal; + private final int mUserId; + @NonNull + private final ComponentName mComponentName; + @NonNull + private final Object mLock; + + @GuardedBy("mLock") + @Nullable + private CompletableFuture<ImeResponse> mPendingImeResponse; + + InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal, + int userId, ComponentName componentName) { + mInputMethodManagerInternal = inputMethodManagerInternal; + mUserId = userId; + mComponentName = componentName; + mLock = new Object(); + } + + public void createRequest(@NonNull AutofillId currentViewId) { + synchronized (mLock) { + cancelCurrentRequest(); + mPendingImeResponse = new CompletableFuture<>(); + mInputMethodManagerInternal.onCreateInlineSuggestionsRequest( + mUserId, mComponentName, currentViewId, + new InlineSuggestionsRequestCallbackImpl(mPendingImeResponse)); + } + } + + @Nullable + public ImeResponse waitAndGetImeResponse() { + CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse(); + if (pendingImeResponse == null || pendingImeResponse.isCancelled()) { + return null; + } + try { + return pendingImeResponse.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + Log.w(TAG, "Exception getting inline suggestions request in time: " + e); + } catch (CancellationException e) { + Log.w(TAG, "Inline suggestions request cancelled"); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + return null; + } + + private void cancelCurrentRequest() { + CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse(); + if (pendingImeResponse != null) { + pendingImeResponse.cancel(true); + } + } + + @Nullable + @GuardedBy("mLock") + private CompletableFuture<ImeResponse> getPendingImeResponse() { + synchronized (mLock) { + return mPendingImeResponse; + } + } + + private static final class InlineSuggestionsRequestCallbackImpl + extends IInlineSuggestionsRequestCallback.Stub { + + private final CompletableFuture<ImeResponse> mResponse; + + private InlineSuggestionsRequestCallbackImpl(CompletableFuture<ImeResponse> response) { + mResponse = response; + } + + @Override + public void onInlineSuggestionsUnsupported() throws RemoteException { + if (sDebug) { + Log.d(TAG, "onInlineSuggestionsUnsupported() called."); + } + mResponse.cancel(true); + } + + @Override + public void onInlineSuggestionsRequest(InlineSuggestionsRequest request, + IInlineSuggestionsResponseCallback callback) throws RemoteException { + if (sDebug) { + Log.d(TAG, "onInlineSuggestionsRequest() received: " + request); + } + if (request != null && callback != null) { + mResponse.complete(new ImeResponse(request, callback)); + } else { + mResponse.cancel(true); + } + } + } + + /** + * A data class wrapping IME responses for the inline suggestion request. + */ + public static class ImeResponse { + @NonNull + private final InlineSuggestionsRequest mRequest; + + @NonNull + private final IInlineSuggestionsResponseCallback mCallback; + + ImeResponse(@NonNull InlineSuggestionsRequest request, + @NonNull IInlineSuggestionsResponseCallback callback) { + mRequest = request; + mCallback = callback; + } + + public InlineSuggestionsRequest getRequest() { + return mRequest; + } + + public IInlineSuggestionsResponseCallback getCallback() { + return mCallback; + } + } +} diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index 5e6f6fea4dda..d93ac68bde1e 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -55,6 +55,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.IResultReceiver; import com.android.internal.util.ArrayUtils; import com.android.internal.view.IInlineSuggestionsResponseCallback; +import com.android.server.autofill.ui.InlineSuggestionFactory; import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; @@ -144,7 +145,8 @@ final class RemoteAugmentedAutofillService int taskId, @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId, @Nullable AutofillValue focusedValue, @Nullable InlineSuggestionsRequest inlineSuggestionsRequest, - @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback) { + @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback, + @NonNull Runnable onErrorCallback) { long requestTime = SystemClock.elapsedRealtime(); AtomicReference<ICancellationSignal> cancellationRef = new AtomicReference<>(); @@ -161,12 +163,12 @@ final class RemoteAugmentedAutofillService focusedId, focusedValue, requestTime, inlineSuggestionsRequest, new IFillCallback.Stub() { @Override - public void onSuccess(@Nullable Dataset[] inlineSuggestionsData, - @Nullable Bundle clientState) { + public void onSuccess(@Nullable Dataset[] inlineSuggestionsData) { mCallbacks.resetLastResponse(); maybeRequestShowInlineSuggestions(sessionId, - inlineSuggestionsData, focusedId, - inlineSuggestionsCallback, client, clientState); + inlineSuggestionsRequest, inlineSuggestionsData, + focusedId, inlineSuggestionsCallback, client, + onErrorCallback); requestAutofill.complete(null); } @@ -229,31 +231,35 @@ 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, @Nullable Bundle clientState) { - if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null) { + @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback) { + if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null + || request == null) { return; } mCallbacks.setLastResponse(sessionId); + try { inlineSuggestionsCallback.onInlineSuggestionsResponse( InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse( - inlineSuggestionsData, focusedId, mContext, + request, inlineSuggestionsData, focusedId, mContext, dataset -> { mCallbacks.logAugmentedAutofillSelected(sessionId, - dataset.getId(), clientState); + dataset.getId()); try { client.autofill(sessionId, dataset.getFieldIds(), dataset.getFieldValues()); } catch (RemoteException e) { Slog.w(TAG, "Encounter exception autofilling the values"); } - })); + }, onErrorCallback)); } catch (RemoteException e) { Slog.w(TAG, "Exception sending inline suggestions response back to IME."); } - mCallbacks.logAugmentedAutofillShown(sessionId, clientState); + + mCallbacks.logAugmentedAutofillShown(sessionId); } @Override @@ -275,9 +281,8 @@ final class RemoteAugmentedAutofillService void setLastResponse(int sessionId); - void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState); + void logAugmentedAutofillShown(int sessionId); - void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId, - @Nullable Bundle clientState); + void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId); } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 415ecd8cfd63..53f85ea7b119 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -100,9 +100,9 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.ArrayUtils; -import com.android.internal.view.IInlineSuggestionsRequestCallback; import com.android.internal.view.IInlineSuggestionsResponseCallback; import com.android.server.autofill.ui.AutoFillUI; +import com.android.server.autofill.ui.InlineSuggestionFactory; import com.android.server.autofill.ui.PendingUi; import com.android.server.inputmethod.InputMethodManagerInternal; @@ -113,11 +113,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; /** @@ -158,9 +153,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** uid the session is for */ public final int uid; - /** user id the session is for */ - public final int userId; - /** ID of the task associated with this session's activity */ public final int taskId; @@ -309,11 +301,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private boolean mForAugmentedAutofillOnly; - @NonNull - private final InputMethodManagerInternal mInputMethodManagerInternal; - @Nullable - private InlineSuggestionsRequestCallbackImpl mInlineSuggestionsRequestCallback; + private final InlineSuggestionSession mInlineSuggestionSession; /** * Receiver of assist data from the app's {@link Activity}. @@ -414,14 +403,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList<FillContext> contexts = mergePreviousSessionLocked(/* forSave= */ false); - final InlineSuggestionsRequest suggestionsRequest = - mInlineSuggestionsRequestCallback != null - ? mInlineSuggestionsRequestCallback.getRequest() : null; + final InlineSuggestionSession.ImeResponse imeResponse = + mInlineSuggestionSession.waitAndGetImeResponse(); request = new FillRequest(requestId, contexts, mClientState, flags, - suggestionsRequest); + imeResponse != null ? imeResponse.getRequest() : null); } + if (mActivityToken != null) { + mService.sendActivityAssistDataToContentCapture(mActivityToken, resultData); + } mRemoteFillService.onFillRequest(request); } @@ -614,75 +605,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState, int newState, int flags) { if (isInlineSuggestionsEnabled()) { - mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl(); - mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(userId, - mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback); + mInlineSuggestionSession.createRequest(mCurrentViewId); } requestNewFillResponseLocked(viewState, newState, flags); } - private static final class InlineSuggestionsRequestCallbackImpl - extends IInlineSuggestionsRequestCallback.Stub { - private static final int INLINE_REQUEST_TIMEOUT_MS = 1000; - - private final CompletableFuture<InlineSuggestionsRequest> mRequest; - private final CompletableFuture<IInlineSuggestionsResponseCallback> mResponseCallback; - - private InlineSuggestionsRequestCallbackImpl() { - mRequest = new CompletableFuture<>(); - mResponseCallback = new CompletableFuture<>(); - } - - @Override - public void onInlineSuggestionsUnsupported() throws RemoteException { - if (sDebug) { - Log.d(TAG, "inline suggestions request unsupported, " - + "falling back to regular autofill"); - } - mRequest.cancel(true); - mResponseCallback.cancel(true); - } - - @Override - public void onInlineSuggestionsRequest(InlineSuggestionsRequest request, - IInlineSuggestionsResponseCallback callback) throws RemoteException { - if (sDebug) { - Log.d(TAG, "onInlineSuggestionsRequest() received: " + request); - } - mRequest.complete(request); - mResponseCallback.complete(callback); - } - - @Nullable - private InlineSuggestionsRequest getRequest() { - try { - return mRequest.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions request in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions request cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - return null; - } - - @Nullable - private IInlineSuggestionsResponseCallback getResponseCallback() { - try { - return mResponseCallback.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions callback in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions callback cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - return null; - } - } - /** * Reads a new structure and then request a new fill response from the fill service. */ @@ -762,7 +690,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFlags = flags; this.taskId = taskId; this.uid = uid; - this.userId = userId; mStartTime = SystemClock.elapsedRealtime(); mService = service; mLock = lock; @@ -781,7 +708,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mForAugmentedAutofillOnly = forAugmentedAutofillOnly; setClientLocked(client); - mInputMethodManagerInternal = inputMethodManagerInternal; + mInlineSuggestionSession = new InlineSuggestionSession(inputMethodManagerInternal, userId, + componentName); mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags)); @@ -2673,15 +2601,16 @@ 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 (requestShowInlineSuggestionsLocked(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; + } } } - getUiForShowing().showFillUi(filledId, response, filterText, mService.getServicePackageName(), mComponentName, serviceLabel, serviceIcon, this, id, mCompatMode); @@ -2716,26 +2645,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 requestShowInlineSuggestionsLocked(@NonNull FillResponse response) { final List<Dataset> datasets = response.getDatasets(); if (datasets == null) { Log.w(TAG, "response returned null datasets"); return false; } + final InlineSuggestionSession.ImeResponse imeResponse = + mInlineSuggestionSession.waitAndGetImeResponse(); + if (imeResponse == null) { + Log.w(TAG, "Session input method callback is not set yet"); + return false; + } + + final InlineSuggestionsRequest request = imeResponse.getRequest(); InlineSuggestionsResponse inlineSuggestionsResponse = - InlineSuggestionFactory.createInlineSuggestionsResponse(response.getRequestId(), - datasets.toArray(new Dataset[]{}), mCurrentViewId, mContext, this); - try { - inlineContentCallback.onInlineSuggestionsResponse(inlineSuggestionsResponse); + InlineSuggestionFactory.createInlineSuggestionsResponse(request, + response.getRequestId(), + datasets.toArray(new Dataset[]{}), response.getInlineActions(), + mCurrentViewId, mContext, this, () -> { + synchronized (mLock) { + requestHideFillUi(mCurrentViewId); + } + }); + try { + imeResponse.getCallback().onInlineSuggestionsResponse(inlineSuggestionsResponse); } catch (RemoteException e) { Log.w(TAG, "onFillReady() remote error calling onInlineSuggestionsResponse()"); return false; @@ -3017,14 +2952,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId); + // There are 3 cases when augmented autofill should ask IME for a new request: + // 1. standard autofill provider is None + // 2. standard autofill provider doesn't support inline (and returns null response) + // 3. standard autofill provider supports inline, but isn't called because the field + // doesn't want autofill + if (mForAugmentedAutofillOnly || !isInlineSuggestionsEnabled()) { + if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); + mInlineSuggestionSession.createRequest(mCurrentViewId); + } + InlineSuggestionSession.ImeResponse imeResponse = + mInlineSuggestionSession.waitAndGetImeResponse(); final InlineSuggestionsRequest inlineSuggestionsRequest = - mInlineSuggestionsRequestCallback != null - ? mInlineSuggestionsRequestCallback.getRequest() : null; + imeResponse != null ? imeResponse.getRequest() : null; final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback = - mInlineSuggestionsRequestCallback != null - ? mInlineSuggestionsRequestCallback.getResponseCallback() : null; + imeResponse != null ? imeResponse.getCallback() : null; remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId, - currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback); + currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback, () -> { + synchronized (mLock) { + cancelAugmentedAutofillLocked(); + } + }); if (mAugmentedAutofillDestroyer == null) { mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest(); diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java index cb6c8f5d3ea5..95a4a191a52e 100644 --- a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.autofill; +package com.android.server.autofill.ui; import static com.android.server.autofill.Helper.sDebug; @@ -28,22 +28,21 @@ 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.view.inline.IInlineContentCallback; import com.android.internal.view.inline.IInlineContentProvider; import com.android.server.UiThread; -import com.android.server.autofill.ui.AutoFillUI; -import com.android.server.autofill.ui.InlineSuggestionUi; import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; - -/** - * @hide - */ public final class InlineSuggestionFactory { private static final String TAG = "InlineSuggestionFactory"; @@ -62,47 +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 InlineSuggestionUiCallback inlineSuggestionUiCallback, + @NonNull Runnable onErrorCallback) { if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called"); - - final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>(); - final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context); - for (Dataset dataset : datasets) { - final int fieldIndex = dataset.getFieldIds().indexOf(autofillId); - if (fieldIndex < 0) { - Slog.w(TAG, "AutofillId=" + autofillId + " not found in dataset"); - return null; - } - final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation( - fieldIndex); - if (inlinePresentation == null) { - Slog.w(TAG, "InlinePresentation not found in dataset"); - return null; - } - InlineSuggestion inlineSuggestion = createAugmentedInlineSuggestion(dataset, - inlinePresentation, inlineSuggestionUi, inlineSuggestionUiCallback); - inlineSuggestions.add(inlineSuggestion); - } - return new InlineSuggestionsResponse(inlineSuggestions); + 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 AutoFillUI.AutoFillUiCallback client, + @NonNull Runnable onErrorCallback) { 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( + boolean isAugmented, @NonNull InlineSuggestionsRequest request, + @NonNull Dataset[] datasets, @Nullable List<InlinePresentation> inlineActions, + @NonNull AutofillId autofillId, @NonNull Context context, + @NonNull Runnable onErrorCallback, + @NonNull BiFunction<Dataset, Integer, View.OnClickListener> onClickListenerFactory) { final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>(); - final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context); - for (Dataset dataset : datasets) { + final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context, + onErrorCallback); + 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"); @@ -114,52 +113,82 @@ public final class InlineSuggestionFactory { Slog.w(TAG, "InlinePresentation not found in dataset"); return null; } - InlineSuggestion inlineSuggestion = createInlineSuggestion(requestId, dataset, - fieldIndex, - inlinePresentation, inlineSuggestionUi, client); + 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); + isAugmented ? InlineSuggestionInfo.SOURCE_PLATFORM + : InlineSuggestionInfo.SOURCE_AUTOFILL, new String[]{""}, + InlineSuggestionInfo.TYPE_ACTION); final View.OnClickListener onClickListener = v -> { - inlineSuggestionUiCallback.autofill(dataset); + // 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(); }; - final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo, + 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/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java new file mode 100644 index 000000000000..e813dae76d92 --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java @@ -0,0 +1,83 @@ +/* + * 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.autofill.ui; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.Log; +import android.util.MathUtils; +import android.view.MotionEvent; +import android.view.ViewConfiguration; +import android.widget.FrameLayout; + +import com.android.server.LocalServices; +import com.android.server.wm.WindowManagerInternal; + +/** + * This class is the root view for an inline suggestion. It is responsible for + * detecting the click on the item and to also transfer input focus to the IME + * window if we detect the user is scrolling. + */ + // TODO(b/146453086) Move to ExtServices and add @SystemApi to transfer touch focus +@SuppressLint("ViewConstructor") +class InlineSuggestionRoot extends FrameLayout { + private static final String LOG_TAG = InlineSuggestionRoot.class.getSimpleName(); + + private final @NonNull Runnable mOnErrorCallback; + private final int mTouchSlop; + + private float mDownX; + private float mDownY; + + InlineSuggestionRoot(@NonNull Context context, @NonNull Runnable onErrorCallback) { + super(context); + mOnErrorCallback = onErrorCallback; + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + setFocusable(false); + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouchEvent(@NonNull MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + mDownX = event.getX(); + mDownY = event.getY(); + } break; + + case MotionEvent.ACTION_MOVE: { + final float distance = MathUtils.dist(mDownX, mDownY, + event.getX(), event.getY()); + if (distance > mTouchSlop) { + transferTouchFocusToImeWindow(); + } + } break; + } + return super.onTouchEvent(event); + } + + private void transferTouchFocusToImeWindow() { + final WindowManagerInternal windowManagerInternal = LocalServices.getService( + WindowManagerInternal.class); + if (!windowManagerInternal.transferTouchFocusToImeWindow(getViewRootImpl().getInputToken(), + getContext().getDisplayId())) { + Log.e(LOG_TAG, "Cannot transfer touch focus from suggestion to IME"); + mOnErrorCallback.run(); + } + } +} diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java index 2adefeabfcc8..bf148a642bbd 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java @@ -67,10 +67,12 @@ public class InlineSuggestionUi { // (int)}. This name is a single string of the form "package:type/entry". private static final Pattern RESOURCE_NAME_PATTERN = Pattern.compile("([^:]+):([^/]+)/(\\S+)"); - private final Context mContext; + private final @NonNull Context mContext; + private final @NonNull Runnable mOnErrorCallback; - public InlineSuggestionUi(Context context) { + InlineSuggestionUi(@NonNull Context context, @NonNull Runnable onErrorCallback) { this.mContext = context; + mOnErrorCallback = onErrorCallback; } /** @@ -94,15 +96,17 @@ public class InlineSuggestionUi { } final View suggestionView = renderSlice(inlinePresentation.getSlice(), contextThemeWrapper); - if (onClickListener != null) { - suggestionView.setOnClickListener(onClickListener); - } + + final InlineSuggestionRoot suggestionRoot = new InlineSuggestionRoot( + mContext, mOnErrorCallback); + suggestionRoot.addView(suggestionView); + suggestionRoot.setOnClickListener(onClickListener); WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height, WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT); - wvr.addView(suggestionView, lp); + wvr.addView(suggestionRoot, lp); return sc; } 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 a603fa975b74..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: [ @@ -115,8 +115,8 @@ java_library_static { "android.hardware.health-V2.0-java", "android.hardware.light-java", "android.hardware.weaver-V1.0-java", - "android.hardware.biometrics.face-V1.0-java", - "android.hardware.biometrics.fingerprint-V2.1-java", + "android.hardware.biometrics.face-V1.1-java", + "android.hardware.biometrics.fingerprint-V2.2-java", "android.hardware.oemlock-V1.0-java", "android.hardware.configstore-V1.0-java", "android.hardware.contexthub-V1.0-java", diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index d0cd7ffee1cb..687fb7d7200d 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -66,6 +66,7 @@ import android.database.ContentObserver; import android.net.CaptivePortal; import android.net.ConnectionInfo; import android.net.ConnectivityDiagnosticsManager.ConnectivityReport; +import android.net.ConnectivityDiagnosticsManager.DataStallReport; import android.net.ConnectivityManager; import android.net.ICaptivePortal; import android.net.IConnectivityDiagnosticsCallback; @@ -601,7 +602,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private Set<String> mWolSupportedInterfaces; - private TelephonyManager mTelephonyManager; + private final TelephonyManager mTelephonyManager; private final AppOpsManager mAppOpsManager; private final LocationPermissionChecker mLocationPermissionChecker; @@ -960,6 +961,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mDeps = Objects.requireNonNull(deps, "missing Dependencies"); mSystemProperties = mDeps.getSystemProperties(); mNetIdManager = mDeps.makeNetIdManager(); + mContext = Objects.requireNonNull(context, "missing Context"); mMetricsLog = logger; mDefaultRequest = createDefaultInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST); @@ -988,7 +990,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS); - mContext = Objects.requireNonNull(context, "missing Context"); mNMS = Objects.requireNonNull(netManager, "missing INetworkManagementService"); mStatsService = Objects.requireNonNull(statsService, "missing INetworkStatsService"); mPolicyManager = Objects.requireNonNull(policyManager, "missing INetworkPolicyManager"); @@ -1168,6 +1169,7 @@ public class ConnectivityService extends IConnectivityManager.Stub int transportType, NetworkRequest.Type type) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); + netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); if (transportType > -1) { netCap.addTransportType(transportType); } @@ -1698,10 +1700,12 @@ public class ConnectivityService extends IConnectivityManager.Stub return newLp; } - private void restrictRequestUidsForCaller(NetworkCapabilities nc) { + private void restrictRequestUidsForCallerAndSetRequestorInfo(NetworkCapabilities nc, + int callerUid, String callerPackageName) { if (!checkSettingsPermission()) { - nc.setSingleUid(Binder.getCallingUid()); + nc.setSingleUid(callerUid); } + nc.setRequestorUidAndPackageName(callerUid, callerPackageName); nc.setAdministratorUids(Collections.EMPTY_LIST); // Clear owner UID; this can never come from an app. @@ -3016,6 +3020,21 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override + public void notifyDataStallSuspected( + long timestampMillis, int detectionMethod, PersistableBundle extras) { + final Message msg = + mConnectivityDiagnosticsHandler.obtainMessage( + ConnectivityDiagnosticsHandler.EVENT_DATA_STALL_SUSPECTED, + detectionMethod, mNetId, timestampMillis); + msg.setData(new Bundle(extras)); + + // NetworkStateTrackerHandler currently doesn't take any actions based on data + // stalls so send the message directly to ConnectivityDiagnosticsHandler and avoid + // the cost of going through two handlers. + mConnectivityDiagnosticsHandler.sendMessage(msg); + } + + @Override public int getInterfaceVersion() { return this.VERSION; } @@ -3104,7 +3123,13 @@ public class ConnectivityService extends IConnectivityManager.Stub handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties)); } - private void updateLingerState(NetworkAgentInfo nai, long now) { + /** + * Updates the linger state from the network requests inside the NAI. + * @param nai the agent info to update + * @param now the timestamp of the event causing this update + * @return whether the network was lingered as a result of this update + */ + private boolean updateLingerState(@NonNull final NetworkAgentInfo nai, final long now) { // 1. Update the linger timer. If it's changed, reschedule or cancel the alarm. // 2. If the network was lingering and there are now requests, unlinger it. // 3. If this network is unneeded (which implies it is not lingering), and there is at least @@ -3115,12 +3140,15 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.unlinger(); logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER); } else if (unneeded(nai, UnneededFor.LINGER) && nai.getLingerExpiry() > 0) { - int lingerTime = (int) (nai.getLingerExpiry() - now); - if (DBG) log("Lingering " + nai.name() + " for " + lingerTime + "ms"); + if (DBG) { + final int lingerTime = (int) (nai.getLingerExpiry() - now); + log("Lingering " + nai.name() + " for " + lingerTime + "ms"); + } nai.linger(); logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER); - notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime); + return true; } + return false; } private void handleAsyncChannelHalfConnect(Message msg) { @@ -3464,7 +3492,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } // If there are still lingered requests on this network, don't tear it down, // but resume lingering instead. - updateLingerState(nai, SystemClock.elapsedRealtime()); + final long now = SystemClock.elapsedRealtime(); + if (updateLingerState(nai, now)) { + notifyNetworkLosing(nai, now); + } if (unneeded(nai, UnneededFor.TEARDOWN)) { if (DBG) log("no live requests for " + nai.name() + "; disconnecting"); teardownUnneededNetwork(nai); @@ -4189,6 +4220,19 @@ public class ConnectivityService extends IConnectivityManager.Stub final int connectivityInfo = encodeBool(hasConnectivity); mHandler.sendMessage( mHandler.obtainMessage(EVENT_REVALIDATE_NETWORK, uid, connectivityInfo, network)); + + final NetworkAgentInfo nai; + if (network == null) { + nai = getDefaultNetwork(); + } else { + nai = getNetworkAgentInfoForNetwork(network); + } + if (nai != null) { + mConnectivityDiagnosticsHandler.sendMessage( + mConnectivityDiagnosticsHandler.obtainMessage( + ConnectivityDiagnosticsHandler.EVENT_NETWORK_CONNECTIVITY_REPORTED, + connectivityInfo, 0, nai)); + } } private void handleReportNetworkConnectivity( @@ -5275,7 +5319,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // This checks that the passed capabilities either do not request a // specific SSID/SignalStrength, or the calling app has permission to do so. private void ensureSufficientPermissionsForRequest(NetworkCapabilities nc, - int callerPid, int callerUid) { + int callerPid, int callerUid, String callerPackageName) { if (null != nc.getSSID() && !checkSettingsPermission(callerPid, callerUid)) { throw new SecurityException("Insufficient permissions to request a specific SSID"); } @@ -5285,6 +5329,7 @@ public class ConnectivityService extends IConnectivityManager.Stub throw new SecurityException( "Insufficient permissions to request a specific signal strength"); } + mAppOpsManager.checkPackage(callerUid, callerPackageName); } private ArrayList<Integer> getSignalStrengthThresholds(NetworkAgentInfo nai) { @@ -5331,7 +5376,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(ns); - ns.assertValidFromUid(Binder.getCallingUid()); } private void ensureValid(NetworkCapabilities nc) { @@ -5343,7 +5387,9 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities, - Messenger messenger, int timeoutMs, IBinder binder, int legacyType) { + Messenger messenger, int timeoutMs, IBinder binder, int legacyType, + @NonNull String callingPackageName) { + final int callingUid = Binder.getCallingUid(); final NetworkRequest.Type type = (networkCapabilities == null) ? NetworkRequest.Type.TRACK_DEFAULT : NetworkRequest.Type.REQUEST; @@ -5351,7 +5397,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // the default network request. This allows callers to keep track of // the system default network. if (type == NetworkRequest.Type.TRACK_DEFAULT) { - networkCapabilities = createDefaultNetworkCapabilitiesForUid(Binder.getCallingUid()); + networkCapabilities = createDefaultNetworkCapabilitiesForUid(callingUid); enforceAccessPermission(); } else { networkCapabilities = new NetworkCapabilities(networkCapabilities); @@ -5363,13 +5409,14 @@ public class ConnectivityService extends IConnectivityManager.Stub } ensureRequestableCapabilities(networkCapabilities); ensureSufficientPermissionsForRequest(networkCapabilities, - Binder.getCallingPid(), Binder.getCallingUid()); + Binder.getCallingPid(), callingUid, callingPackageName); // Set the UID range for this request to the single UID of the requester, or to an empty // set of UIDs if the caller has the appropriate permission and UIDs have not been set. // This will overwrite any allowed UIDs in the requested capabilities. Though there // are no visible methods to set the UIDs, an app could use reflection to try and get // networks for other apps so it's essential that the UIDs are overwritten. - restrictRequestUidsForCaller(networkCapabilities); + restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities, + callingUid, callingPackageName); if (timeoutMs < 0) { throw new IllegalArgumentException("Bad timeout specified"); @@ -5444,16 +5491,18 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkRequest pendingRequestForNetwork(NetworkCapabilities networkCapabilities, - PendingIntent operation) { + PendingIntent operation, @NonNull String callingPackageName) { Objects.requireNonNull(operation, "PendingIntent cannot be null."); + final int callingUid = Binder.getCallingUid(); networkCapabilities = new NetworkCapabilities(networkCapabilities); enforceNetworkRequestPermissions(networkCapabilities); enforceMeteredApnPolicy(networkCapabilities); ensureRequestableCapabilities(networkCapabilities); ensureSufficientPermissionsForRequest(networkCapabilities, - Binder.getCallingPid(), Binder.getCallingUid()); + Binder.getCallingPid(), callingUid, callingPackageName); ensureValidNetworkSpecifier(networkCapabilities); - restrictRequestUidsForCaller(networkCapabilities); + restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities, + callingUid, callingPackageName); NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.REQUEST); @@ -5501,15 +5550,16 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities, - Messenger messenger, IBinder binder) { + Messenger messenger, IBinder binder, @NonNull String callingPackageName) { + final int callingUid = Binder.getCallingUid(); if (!hasWifiNetworkListenPermission(networkCapabilities)) { enforceAccessPermission(); } NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); ensureSufficientPermissionsForRequest(networkCapabilities, - Binder.getCallingPid(), Binder.getCallingUid()); - restrictRequestUidsForCaller(nc); + Binder.getCallingPid(), callingUid, callingPackageName); + restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName); // Apps without the CHANGE_NETWORK_STATE permission can't use background networks, so // make all their listens include NET_CAPABILITY_FOREGROUND. That way, they will get // onLost and onAvailable callbacks when networks move in and out of the background. @@ -5529,17 +5579,17 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public void pendingListenForNetwork(NetworkCapabilities networkCapabilities, - PendingIntent operation) { + PendingIntent operation, @NonNull String callingPackageName) { Objects.requireNonNull(operation, "PendingIntent cannot be null."); + final int callingUid = Binder.getCallingUid(); if (!hasWifiNetworkListenPermission(networkCapabilities)) { enforceAccessPermission(); } ensureValid(networkCapabilities); ensureSufficientPermissionsForRequest(networkCapabilities, - Binder.getCallingPid(), Binder.getCallingUid()); - + Binder.getCallingPid(), callingUid, callingPackageName); final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); - restrictRequestUidsForCaller(nc); + restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName); NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN); @@ -6552,6 +6602,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private ArrayMap<NetworkRequestInfo, NetworkAgentInfo> computeRequestReassignmentForNetwork( + @NonNull final NetworkReassignment changes, @NonNull final NetworkAgentInfo newNetwork) { final int score = newNetwork.getCurrentScore(); final ArrayMap<NetworkRequestInfo, NetworkAgentInfo> reassignedRequests = new ArrayMap<>(); @@ -6562,7 +6613,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // requests or not, and doesn't affect the network's score. if (nri.request.isListen()) continue; - final NetworkAgentInfo currentNetwork = nri.mSatisfier; + // The reassignment has been seeded with the initial assignment, therefore + // getReassignment can't be null and mNewNetwork is only null if there was no + // satisfier in the first place or there was an explicit reassignment to null. + final NetworkAgentInfo currentNetwork = changes.getReassignment(nri).mNewNetwork; final boolean satisfies = newNetwork.satisfies(nri.request); if (newNetwork == currentNetwork && satisfies) continue; @@ -6578,11 +6632,16 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (currentNetwork == null || currentNetwork.getCurrentScore() < score) { reassignedRequests.put(nri, newNetwork); + changes.addRequestReassignment(new NetworkReassignment.RequestReassignment( + nri, currentNetwork, newNetwork)); } } else if (newNetwork == currentNetwork) { reassignedRequests.put(nri, null); + changes.addRequestReassignment(new NetworkReassignment.RequestReassignment( + nri, currentNetwork, null)); } } + return reassignedRequests; } @@ -6612,7 +6671,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (VDBG || DDBG) log("rematching " + newNetwork.name()); final ArrayMap<NetworkRequestInfo, NetworkAgentInfo> reassignedRequests = - computeRequestReassignmentForNetwork(newNetwork); + computeRequestReassignmentForNetwork(changes, newNetwork); // Find and migrate to this Network any NetworkRequests for // which this network is now the best. @@ -6621,39 +6680,37 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkRequestInfo nri = entry.getKey(); final NetworkAgentInfo previousSatisfier = nri.mSatisfier; final NetworkAgentInfo newSatisfier = entry.getValue(); - changes.addRequestReassignment(new NetworkReassignment.RequestReassignment( - nri, previousSatisfier, newSatisfier)); - if (newSatisfier != null) { - if (VDBG) log("rematch for " + newSatisfier.name()); - if (previousSatisfier != null) { - if (VDBG || DDBG) { - log(" accepting network in place of " + previousSatisfier.name()); - } - previousSatisfier.removeRequest(nri.request.requestId); - previousSatisfier.lingerRequest(nri.request, now, mLingerDelayMs); - } else { - if (VDBG || DDBG) log(" accepting network in place of null"); - } - newSatisfier.unlingerRequest(nri.request); - if (!newSatisfier.addRequest(nri.request)) { - Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request); + updateSatisfiersForRematchRequest(nri, previousSatisfier, newSatisfier, now); + } + } + + private void updateSatisfiersForRematchRequest(@NonNull final NetworkRequestInfo nri, + @Nullable final NetworkAgentInfo previousSatisfier, + @Nullable final NetworkAgentInfo newSatisfier, + final long now) { + if (newSatisfier != null) { + if (VDBG) log("rematch for " + newSatisfier.name()); + if (previousSatisfier != null) { + if (VDBG || DDBG) { + log(" accepting network in place of " + previousSatisfier.name()); } + previousSatisfier.removeRequest(nri.request.requestId); + previousSatisfier.lingerRequest(nri.request, now, mLingerDelayMs); } else { - // If "newNetwork" is listed as satisfying "nri" but no longer satisfies "nri", - // mark it as no longer satisfying "nri". Because networks are processed by - // rematchAllNetworksAndRequests() in descending score order, "currentNetwork" will - // match "newNetwork" before this loop will encounter a "currentNetwork" with higher - // score than "newNetwork" and where "currentNetwork" no longer satisfies "nri". - // This means this code doesn't have to handle the case where "currentNetwork" no - // longer satisfies "nri" when "currentNetwork" does not equal "newNetwork". - if (DBG) { - log("Network " + newNetwork.name() + " stopped satisfying" + - " request " + nri.request.requestId); - } - newNetwork.removeRequest(nri.request.requestId); + if (VDBG || DDBG) log(" accepting network in place of null"); } - nri.mSatisfier = newSatisfier; + newSatisfier.unlingerRequest(nri.request); + if (!newSatisfier.addRequest(nri.request)) { + Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request); + } + } else { + if (DBG) { + log("Network " + previousSatisfier.name() + " stopped satisfying" + + " request " + nri.request.requestId); + } + previousSatisfier.removeRequest(nri.request.requestId); } + nri.mSatisfier = newSatisfier; } /** @@ -6731,13 +6788,20 @@ public class ConnectivityService extends IConnectivityManager.Stub processNewlySatisfiedListenRequests(event.mNetwork); } + final ArrayList<NetworkAgentInfo> lingeredNetworks = new ArrayList<>(); for (final NetworkAgentInfo nai : nais) { // Rematching may have altered the linger state of some networks, so update all linger // timers. updateLingerState reads the state from the network agent and does nothing // if the state has not changed : the source of truth is controlled with // NetworkAgentInfo#lingerRequest and NetworkAgentInfo#unlingerRequest, which have been // called while rematching the individual networks above. - updateLingerState(nai, now); + if (updateLingerState(nai, now)) { + lingeredNetworks.add(nai); + } + } + + for (final NetworkAgentInfo nai : lingeredNetworks) { + notifyNetworkLosing(nai, now); } updateLegacyTypeTrackerAndVpnLockdownForRematch(oldDefaultNetwork, newDefaultNetwork, nais); @@ -6753,7 +6817,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // and became unneeded due to another network improving its score to the // point where this network will no longer be able to satisfy any requests // even if it validates. - updateLingerState(nai, now); + if (updateLingerState(nai, now)) { + notifyNetworkLosing(nai, now); + } } else { if (DBG) log("Reaping " + nai.name()); teardownUnneededNetwork(nai); @@ -7006,6 +7072,12 @@ public class ConnectivityService extends IConnectivityManager.Stub callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0); } + // Notify the requests on this NAI that the network is now lingered. + private void notifyNetworkLosing(@NonNull final NetworkAgentInfo nai, final long now) { + final int lingerTime = (int) (nai.getLingerExpiry() - now); + notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime); + } + /** * Notify of the blocked state apps with a registered callback matching a given NAI. * @@ -7519,6 +7591,8 @@ public class ConnectivityService extends IConnectivityManager.Stub */ @VisibleForTesting class ConnectivityDiagnosticsHandler extends Handler { + private final String mTag = ConnectivityDiagnosticsHandler.class.getSimpleName(); + /** * Used to handle ConnectivityDiagnosticsCallback registration events from {@link * android.net.ConnectivityDiagnosticsManager}. @@ -7546,6 +7620,26 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private static final int EVENT_NETWORK_TESTED = ConnectivityService.EVENT_NETWORK_TESTED; + /** + * Event for NetworkMonitor to inform ConnectivityService that a potential data stall has + * been detected on the network. + * obj = Long the timestamp (in millis) for when the suspected data stall was detected. + * arg1 = {@link DataStallReport#DetectionMethod} indicating the detection method. + * arg2 = NetID. + * data = PersistableBundle of extras passed from NetworkMonitor. + */ + private static final int EVENT_DATA_STALL_SUSPECTED = 4; + + /** + * Event for ConnectivityDiagnosticsHandler to handle network connectivity being reported to + * the platform. This event will invoke {@link + * IConnectivityDiagnosticsCallback#onNetworkConnectivityReported} for permissioned + * callbacks. + * obj = Network that was reported on + * arg1 = boolint for the quality reported + */ + private static final int EVENT_NETWORK_CONNECTIVITY_REPORTED = 5; + private ConnectivityDiagnosticsHandler(Looper looper) { super(looper); } @@ -7576,6 +7670,24 @@ public class ConnectivityService extends IConnectivityManager.Stub handleNetworkTestedWithExtras(reportEvent, extras); break; } + case EVENT_DATA_STALL_SUSPECTED: { + final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2); + if (nai == null) break; + + // This is safe because NetworkMonitorCallbacks#notifyDataStallSuspected + // receives a PersistableBundle and converts it to the Bundle in the incoming + // Message. + final PersistableBundle extras = new PersistableBundle(msg.getData()); + handleDataStallSuspected(nai, (long) msg.obj, msg.arg1, extras); + break; + } + case EVENT_NETWORK_CONNECTIVITY_REPORTED: { + handleNetworkConnectivityReported((NetworkAgentInfo) msg.obj, toBool(msg.arg1)); + break; + } + default: { + Log.e(mTag, "Unrecognized event in ConnectivityDiagnostics: " + msg.what); + } } } } @@ -7705,6 +7817,35 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + private void handleDataStallSuspected( + @NonNull NetworkAgentInfo nai, long timestampMillis, int detectionMethod, + @NonNull PersistableBundle extras) { + final DataStallReport report = + new DataStallReport(nai.network, timestampMillis, detectionMethod, extras); + final List<IConnectivityDiagnosticsCallback> results = + getMatchingPermissionedCallbacks(nai); + for (final IConnectivityDiagnosticsCallback cb : results) { + try { + cb.onDataStallSuspected(report); + } catch (RemoteException ex) { + loge("Error invoking onDataStallSuspected", ex); + } + } + } + + private void handleNetworkConnectivityReported( + @NonNull NetworkAgentInfo nai, boolean connectivity) { + final List<IConnectivityDiagnosticsCallback> results = + getMatchingPermissionedCallbacks(nai); + for (final IConnectivityDiagnosticsCallback cb : results) { + try { + cb.onNetworkConnectivityReported(nai.network, connectivity); + } catch (RemoteException ex) { + loge("Error invoking onNetworkConnectivityReported", ex); + } + } + } + private List<IConnectivityDiagnosticsCallback> getMatchingPermissionedCallbacks( @NonNull NetworkAgentInfo nai) { final List<IConnectivityDiagnosticsCallback> results = new ArrayList<>(); @@ -7757,12 +7898,13 @@ public class ConnectivityService extends IConnectivityManager.Stub throw new IllegalArgumentException("ConnectivityManager.TYPE_* are deprecated." + " Please use NetworkCapabilities instead."); } - mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackageName); + final int callingUid = Binder.getCallingUid(); + mAppOpsManager.checkPackage(callingUid, callingPackageName); // This NetworkCapabilities is only used for matching to Networks. Clear out its owner uid // and administrator uids to be safe. final NetworkCapabilities nc = new NetworkCapabilities(request.networkCapabilities); - restrictRequestUidsForCaller(nc); + restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName); final NetworkRequest requestWithId = new NetworkRequest( diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 50843b0999a1..63cddac0bfba 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -30,6 +30,7 @@ import static android.os.PowerManager.locationPowerSaveModeToString; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -305,11 +306,7 @@ public class LocationManagerService extends ILocationManager.Stub { public void onOpChanged(int op, String packageName) { // onOpChanged invoked on ui thread, move to our thread to reduce risk // of blocking ui thread - mHandler.post(() -> { - synchronized (mLock) { - onAppOpChangedLocked(); - } - }); + mHandler.post(() -> onAppOpChanged(packageName)); } }); mPackageManager.addOnPermissionsChangeListener( @@ -392,13 +389,26 @@ public class LocationManagerService extends ILocationManager.Stub { } } - @GuardedBy("mLock") - private void onAppOpChangedLocked() { - for (Receiver receiver : mReceivers.values()) { - receiver.updateMonitoring(true); - } - for (LocationProviderManager manager : mProviderManagers) { - applyRequirementsLocked(manager); + private void onAppOpChanged(String packageName) { + synchronized (mLock) { + for (Receiver receiver : mReceivers.values()) { + if (receiver.mCallerIdentity.mPackageName.equals(packageName)) { + receiver.updateMonitoring(true); + } + } + + HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size()); + for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) { + String provider = entry.getKey(); + for (UpdateRecord record : entry.getValue()) { + if (record.mReceiver.mCallerIdentity.mPackageName.equals(packageName)) { + affectedProviders.add(provider); + } + } + } + for (String provider : affectedProviders) { + applyRequirementsLocked(provider); + } } } @@ -2161,10 +2171,10 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean injectLocation(Location location) { - mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, - "Location Hardware permission not granted to inject location"); - mContext.enforceCallingPermission(ACCESS_FINE_LOCATION, - "Access Fine Location permission not granted to inject Location"); + mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, null); + mContext.enforceCallingPermission(ACCESS_FINE_LOCATION, null); + + Preconditions.checkArgument(location.isComplete()); synchronized (mLock) { LocationProviderManager manager = getLocationProviderManager(location.getProvider()); @@ -2410,20 +2420,15 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean isLocationEnabledForUser(int userId) { - if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS, - null); - } - + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + userId, false, false, "isLocationEnabledForUser", null); return mSettingsHelper.isLocationEnabled(userId); } @Override public boolean isProviderEnabledForUser(String providerName, int userId) { - if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS, - null); - } + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + userId, false, false, "isProviderEnabledForUser", null); // Fused provider is accessed indirectly via criteria rather than the provider-based APIs, // so we discourage its use @@ -2732,6 +2737,9 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void setTestProviderLocation(String provider, Location location, String packageName) { + Preconditions.checkArgument(location.isComplete(), + "incomplete location object, missing timestamp or accuracy?"); + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) != AppOpsManager.MODE_ALLOWED) { return; diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index 135f6f3ea125..6fb7b267c285 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -272,10 +272,11 @@ public final class PinnerService extends SystemService { private void handlePinOnStart() { final String bootImage = SystemProperties.get("dalvik.vm.boot-image", ""); String[] filesToPin = null; - if (bootImage.endsWith("apex.art")) { - // Use the files listed for that specific boot image + if (bootImage.endsWith("boot-image.prof")) { + // Use the files listed for that specific boot image. + // TODO: find a better way to know we're using the JIT zygote configuration. filesToPin = mContext.getResources().getStringArray( - com.android.internal.R.array.config_apexBootImagePinnerServiceFiles); + com.android.internal.R.array.config_jitzygoteBootImagePinnerServiceFiles); } else { // Files to pin come from the overlay and can be specified per-device config filesToPin = mContext.getResources().getStringArray( diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index cbf6c274e865..0e5a6bb8bd1c 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -284,16 +284,16 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { static final int ENFORCE_PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR - | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST - | PhoneStateListener.LISTEN_REGISTRATION_FAILURE - | PhoneStateListener.LISTEN_BARRING_INFO; + | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST; - static final int PRECISE_PHONE_STATE_PERMISSION_MASK = + static final int ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_PRECISE_CALL_STATE - | PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE - | PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES - | PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED - | PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES; + | PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE + | PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES + | PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED + | PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES + | PhoneStateListener.LISTEN_REGISTRATION_FAILURE + | PhoneStateListener.LISTEN_BARRING_INFO; static final int READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK = PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL @@ -2535,7 +2535,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - if ((events & PRECISE_PHONE_STATE_PERMISSION_MASK) != 0) { + if ((events & ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK) != 0) { // check if calling app has either permission READ_PRECISE_PHONE_STATE // or with carrier privileges try { diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 4a65a96e57a1..fabe92dbcb8a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -287,7 +287,7 @@ final class ActivityManagerConstants extends ContentObserver { // When the restriction is enabled, foreground service started from background will not have // while-in-use permissions like location, camera and microphone. (The foreground service can be // started, the restriction is on while-in-use permissions.) - volatile boolean mFlagBackgroundFgsStartRestrictionEnabled; + volatile boolean mFlagBackgroundFgsStartRestrictionEnabled = true; private final ActivityManagerService mService; private ContentResolver mResolver; @@ -304,18 +304,19 @@ final class ActivityManagerConstants extends ContentObserver { // we have no limit on the number of service, visible, foreground, or other such // processes and the number of those processes does not count against the cached // process limit. - public int CUR_MAX_CACHED_PROCESSES; + public int CUR_MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; // The maximum number of empty app processes we will let sit around. - public int CUR_MAX_EMPTY_PROCESSES; + public int CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES); // The number of empty apps at which we don't consider it necessary to do // memory trimming. - public int CUR_TRIM_EMPTY_PROCESSES; + public int CUR_TRIM_EMPTY_PROCESSES = computeEmptyProcessLimit(MAX_CACHED_PROCESSES) / 2; // The number of cached at which we don't consider it necessary to do // memory trimming. - public int CUR_TRIM_CACHED_PROCESSES; + public int CUR_TRIM_CACHED_PROCESSES = + (MAX_CACHED_PROCESSES - computeEmptyProcessLimit(MAX_CACHED_PROCESSES)) / 3; /** * Packages that can't be killed even if it's requested to be killed on imperceptible. @@ -419,6 +420,8 @@ final class ActivityManagerConstants extends ContentObserver { context.getResources().getIntArray( com.android.internal.R.array.config_defaultImperceptibleKillingExemptionProcStates)) .boxed().collect(Collectors.toList()); + IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages); + IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.addAll(mDefaultImperceptibleKillExemptProcStates); } public void start(ContentResolver resolver) { @@ -550,8 +553,6 @@ final class ActivityManagerConstants extends ContentObserver { // For new flags that are intended for server-side experiments, please use the new // DeviceConfig package. - - updateMaxCachedProcesses(); } } @@ -580,6 +581,9 @@ final class ActivityManagerConstants extends ContentObserver { } private void updateOomAdjUpdatePolicy() { + + + OOMADJ_UPDATE_QUICK = DeviceConfig.getInt( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_OOMADJ_UPDATE_POLICY, diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index b19a37e0d840..f872c6bd1b8b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -50,7 +50,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false; static final boolean DEBUG_BROADCAST_DEFERRAL = DEBUG_BROADCAST || false; static final boolean DEBUG_COMPACTION = DEBUG_ALL || false; - static final boolean DEBUG_FREEZER = DEBUG_ALL || false; + static final boolean DEBUG_FREEZER = DEBUG_ALL || true; static final boolean DEBUG_LRU = DEBUG_ALL || false; static final boolean DEBUG_MU = DEBUG_ALL || false; static final boolean DEBUG_NETWORK = DEBUG_ALL || false; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 148f7de4449f..6bff76c15edd 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -466,18 +466,9 @@ public class ActivityManagerService extends IActivityManager.Stub // How long we wait for a launched process to attach to the activity manager // before we decide it's never going to come up for real. static final int PROC_START_TIMEOUT = 10*1000; - // How long we wait for an attached process to publish its content providers - // before we decide it must be hung. - static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000; - // How long we wait to kill an application zygote, after the last process using // it has gone away. static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000; - /** - * How long we wait for an provider to be published. Should be longer than - * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT}. - */ - static final int CONTENT_PROVIDER_WAIT_TIMEOUT = 20 * 1000; // How long we wait for a launched process to attach to the activity manager // before we decide it's never going to come up for real, when the process was @@ -2617,7 +2608,7 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessCpuThread.start(); mBatteryStatsService.publish(); - mAppOpsService.publish(mContext); + mAppOpsService.publish(); Slog.d("AppOps", "AppOpsService published"); LocalServices.addService(ActivityManagerInternal.class, mInternal); mActivityTaskManager.onActivityManagerInternalAdded(); @@ -4934,7 +4925,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (providers != null && checkAppInLaunchingProvidersLocked(app)) { Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG); msg.obj = app; - mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT); + mHandler.sendMessageDelayed(msg, + ContentResolver.CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS); } checkTime(startTime, "attachApplicationLocked: before bindApplication"); @@ -6843,7 +6835,8 @@ public class ActivityManagerService extends IActivityManager.Stub cpi = cpr.info; if (isSingleton(cpi.processName, cpi.applicationInfo, cpi.name, cpi.flags) - && isValidSingletonCall(r.uid, cpi.applicationInfo.uid)) { + && isValidSingletonCall(r == null ? callingUid : r.uid, + cpi.applicationInfo.uid)) { userId = UserHandle.USER_SYSTEM; checkCrossUser = false; } else { @@ -6931,7 +6924,8 @@ public class ActivityManagerService extends IActivityManager.Stub conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag, stable); if (conn != null && (conn.stableCount+conn.unstableCount) == 1) { - if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) { + if (cpr.proc != null + && r != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) { // If this is a perceptible app accessing the provider, // make sure to count it as being accessed and thus // back up on the LRU list. This is good because @@ -7003,7 +6997,8 @@ public class ActivityManagerService extends IActivityManager.Stub // Then allow connecting to the singleton provider boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo, cpi.name, cpi.flags) - && isValidSingletonCall(r.uid, cpi.applicationInfo.uid); + && isValidSingletonCall(r == null ? callingUid : r.uid, + cpi.applicationInfo.uid); if (singleton) { userId = UserHandle.USER_SYSTEM; } @@ -7198,7 +7193,8 @@ public class ActivityManagerService extends IActivityManager.Stub } // Wait for the provider to be published... - final long timeout = SystemClock.uptimeMillis() + CONTENT_PROVIDER_WAIT_TIMEOUT; + final long timeout = + SystemClock.uptimeMillis() + ContentResolver.CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS; boolean timedOut = false; synchronized (cpr) { while (cpr.provider == null) { @@ -7235,12 +7231,14 @@ public class ActivityManagerService extends IActivityManager.Stub } } if (timedOut) { - // Note we do it afer releasing the lock. + // Note we do it after releasing the lock. String callerName = "unknown"; - synchronized (this) { - final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller); - if (record != null) { - callerName = record.processName; + if (caller != null) { + synchronized (this) { + final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller); + if (record != null) { + callerName = record.processName; + } } } @@ -19507,7 +19505,7 @@ public class ActivityManagerService extends IActivityManager.Stub } public AppOpsService getAppOpsService(File file, Handler handler) { - return new AppOpsService(file, handler); + return new AppOpsService(file, handler, getContext()); } public Handler getUiHandler(ActivityManagerService service) { diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 313c185a84c6..eec68dc7353e 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -442,7 +442,7 @@ public final class CachedAppOptimizer { */ @GuardedBy("mPhenotypeFlagLock") private void updateUseFreezer() { - if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) { mUseFreezer = isFreezerSupported(); } @@ -946,6 +946,15 @@ public final class CachedAppOptimizer { } EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name); + + // See above for why we're not taking mPhenotypeFlagLock here + if (mRandom.nextFloat() < mFreezerStatsdSampleRate) { + FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED, + FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP, + pid, + name, + unfrozenDuration); + } } } @@ -994,6 +1003,16 @@ public final class CachedAppOptimizer { } EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, name); + + // See above for why we're not taking mPhenotypeFlagLock here + if (mRandom.nextFloat() < mFreezerStatsdSampleRate) { + FrameworkStatsLog.write( + FrameworkStatsLog.APP_FREEZE_CHANGED, + FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__UNFREEZE_APP, + pid, + name, + frozenDuration); + } } } } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index b107626e3bec..a651d9dc4992 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -330,6 +330,7 @@ public final class OomAdjuster { // If this proc state is changed, need to update its uid record here if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT && (uidRec.setProcState != uidRec.getCurProcState() + || uidRec.setCapability != uidRec.curCapability || uidRec.setWhitelist != uidRec.curWhitelist)) { ActiveUids uids = mTmpUidRecords; uids.clear(); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 71486d3686f3..dcada89bb04a 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -3271,6 +3271,9 @@ public final class ProcessList { } final ProcessRecord getLRURecordForAppLocked(IApplicationThread thread) { + if (thread == null) { + return null; + } final IBinder threadBinder = thread.asBinder(); // Find the application record. for (int i = mLruProcesses.size() - 1; i >= 0; i--) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 784ce4a7d53b..06561f57f495 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -19,11 +19,15 @@ package com.android.server.appop; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; +import static android.app.AppOpsManager.CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE; import static android.app.AppOpsManager.FILTER_BY_FEATURE_ID; import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; import static android.app.AppOpsManager.FILTER_BY_UID; import static android.app.AppOpsManager.HistoricalOpsRequestFilter; +import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME; +import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME; +import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.NoteOpEvent; import static android.app.AppOpsManager.OP_CAMERA; @@ -41,6 +45,7 @@ import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; import static android.app.AppOpsManager.UID_STATE_PERSISTENT; import static android.app.AppOpsManager.UID_STATE_TOP; +import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES; import static android.app.AppOpsManager._NUM_OP; import static android.app.AppOpsManager.extractFlagsFromKey; import static android.app.AppOpsManager.extractUidStateFromKey; @@ -53,6 +58,10 @@ import static android.content.Intent.EXTRA_REPLACING; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; import static android.os.Process.STATSD_UID; +import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; + +import static java.lang.Long.max; + import android.Manifest; import android.annotation.IntRange; import android.annotation.NonNull; @@ -70,6 +79,7 @@ import android.app.AppOpsManager.OpFlags; import android.app.AppOpsManagerInternal; import android.app.AppOpsManagerInternal.CheckOpsDelegate; import android.app.AsyncNotedAppOp; +import android.compat.Compatibility; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -129,7 +139,6 @@ import com.android.internal.app.IAppOpsNotedCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.os.Zygote; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; @@ -216,7 +225,7 @@ public class AppOpsService extends IAppOpsService.Stub { //TODO: remove this when development is done. private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31; - Context mContext; + final Context mContext; final AtomicFile mFile; final Handler mHandler; @@ -291,8 +300,11 @@ public class AppOpsService extends IAppOpsService.Stub { @GuardedBy("this") private CheckOpsDelegate mCheckOpsDelegate; - @GuardedBy("this") - private SparseArray<List<Integer>> mSwitchOpToOps; + /** + * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never + * changed + */ + private final SparseArray<int[]> mSwitchedOps = new SparseArray<>(); private ActivityManagerInternal mActivityManagerInternal; @@ -343,30 +355,25 @@ public class AppOpsService extends IAppOpsService.Stub { */ @VisibleForTesting final class Constants extends ContentObserver { - // Key names stored in the settings value. - private static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time"; - private static final String KEY_FG_SERVICE_STATE_SETTLE_TIME - = "fg_service_state_settle_time"; - private static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time"; /** * How long we want for a drop in uid state from top to settle before applying it. * @see Settings.Global#APP_OPS_CONSTANTS - * @see #KEY_TOP_STATE_SETTLE_TIME + * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME */ public long TOP_STATE_SETTLE_TIME; /** * How long we want for a drop in uid state from foreground to settle before applying it. * @see Settings.Global#APP_OPS_CONSTANTS - * @see #KEY_FG_SERVICE_STATE_SETTLE_TIME + * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME */ public long FG_SERVICE_STATE_SETTLE_TIME; /** * How long we want for a drop in uid state from background to settle before applying it. * @see Settings.Global#APP_OPS_CONSTANTS - * @see #KEY_BG_STATE_SETTLE_TIME + * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME */ public long BG_STATE_SETTLE_TIME; @@ -1191,17 +1198,22 @@ public class AppOpsService extends IAppOpsService.Stub { final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager(); final class ModeCallback implements DeathRecipient { + /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */ + public static final int ALL_OPS = -2; + final IAppOpsCallback mCallback; final int mWatchingUid; final int mFlags; + final int mWatchedOpCode; final int mCallingUid; final int mCallingPid; - ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int callingUid, - int callingPid) { + ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOp, + int callingUid, int callingPid) { mCallback = callback; mWatchingUid = watchingUid; mFlags = flags; + mWatchedOpCode = watchedOp; mCallingUid = callingUid; mCallingPid = callingPid; try { @@ -1224,6 +1236,10 @@ public class AppOpsService extends IAppOpsService.Stub { UserHandle.formatUid(sb, mWatchingUid); sb.append(" flags=0x"); sb.append(Integer.toHexString(mFlags)); + if (mWatchedOpCode != OP_NONE) { + sb.append(" op="); + sb.append(opToName(mWatchedOpCode)); + } sb.append(" from uid="); UserHandle.formatUid(sb, mCallingUid); sb.append(" pid="); @@ -1337,16 +1353,23 @@ public class AppOpsService extends IAppOpsService.Stub { featureOp.onClientDeath(clientId); } - public AppOpsService(File storagePath, Handler handler) { + public AppOpsService(File storagePath, Handler handler, Context context) { + mContext = context; + LockGuard.installLock(this, LockGuard.INDEX_APP_OPS); mFile = new AtomicFile(storagePath, "appops"); mHandler = handler; mConstants = new Constants(mHandler); readState(); + + for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) { + int switchCode = AppOpsManager.opToSwitch(switchedCode); + mSwitchedOps.put(switchCode, + ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode)); + } } - public void publish(Context context) { - mContext = context; + public void publish() { ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder()); LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal); } @@ -1616,6 +1639,19 @@ public class AppOpsService extends IAppOpsService.Stub { } } + /** + * Update the pending state for the uid + * + * @param currentTime The current elapsed real time + * @param uid The uid that has a pending state + */ + private void updatePendingState(long currentTime, int uid) { + synchronized (this) { + mLastRealtime = max(currentTime, mLastRealtime); + updatePendingStateIfNeededLocked(mUidStates.get(uid)); + } + } + public void updateUidProcState(int uid, int procState, @ActivityManager.ProcessCapability int capability) { synchronized (this) { @@ -1647,7 +1683,12 @@ public class AppOpsService extends IAppOpsService.Stub { } else { settleTime = mConstants.BG_STATE_SETTLE_TIME; } - uidState.pendingStateCommitTime = SystemClock.elapsedRealtime() + settleTime; + final long commitTime = SystemClock.elapsedRealtime() + settleTime; + uidState.pendingStateCommitTime = commitTime; + + mHandler.sendMessageDelayed( + PooledLambda.obtainMessage(AppOpsService::updatePendingState, this, + commitTime + 1, uid), settleTime + 1); } if (uidState.pkgOps != null) { @@ -2014,6 +2055,19 @@ public class AppOpsService extends IAppOpsService.Stub { uidState.evalForegroundOps(mOpModeWatchers); } + notifyOpChangedForAllPkgsInUid(code, uid, false, callbackToIgnore); + notifyOpChangedSync(code, uid, null, mode); + } + + /** + * Notify that an op changed for all packages in an uid. + * + * @param code The op that changed + * @param uid The uid the op was changed for + * @param onlyForeground Only notify watchers that watch for foreground changes + */ + private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground, + @Nullable IAppOpsCallback callbackToIgnore) { String[] uidPackageNames = getPackagesForUid(uid); ArrayMap<ModeCallback, ArraySet<String>> callbackSpecs = null; @@ -2023,6 +2077,10 @@ public class AppOpsService extends IAppOpsService.Stub { final int callbackCount = callbacks.size(); for (int i = 0; i < callbackCount; i++) { ModeCallback callback = callbacks.valueAt(i); + if (onlyForeground && (callback.mFlags & WATCH_FOREGROUND_CHANGES) == 0) { + continue; + } + ArraySet<String> changedPackages = new ArraySet<>(); Collections.addAll(changedPackages, uidPackageNames); if (callbackSpecs == null) { @@ -2041,6 +2099,10 @@ public class AppOpsService extends IAppOpsService.Stub { final int callbackCount = callbacks.size(); for (int i = 0; i < callbackCount; i++) { ModeCallback callback = callbacks.valueAt(i); + if (onlyForeground && (callback.mFlags & WATCH_FOREGROUND_CHANGES) == 0) { + continue; + } + ArraySet<String> changedPackages = callbackSpecs.get(callback); if (changedPackages == null) { changedPackages = new ArraySet<>(); @@ -2057,7 +2119,6 @@ public class AppOpsService extends IAppOpsService.Stub { } if (callbackSpecs == null) { - notifyOpChangedSync(code, uid, null, mode); return; } @@ -2079,23 +2140,24 @@ public class AppOpsService extends IAppOpsService.Stub { } } } - - notifyOpChangedSync(code, uid, null, mode); } private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) { PackageManager packageManager = mContext.getPackageManager(); + if (packageManager == null) { + // This can only happen during early boot. At this time the permission state and appop + // state are in sync + return; + } + String[] packageNames = packageManager.getPackagesForUid(uid); if (ArrayUtils.isEmpty(packageNames)) { return; } String packageName = packageNames[0]; - List<Integer> ops = getSwitchOpToOps().get(switchCode); - int opsSize = CollectionUtils.size(ops); - for (int i = 0; i < opsSize; i++) { - int code = ops.get(i); - + int[] ops = mSwitchedOps.get(switchCode); + for (int code : ops) { String permissionName = AppOpsManager.opToPermission(code); if (permissionName == null) { continue; @@ -2126,24 +2188,27 @@ public class AppOpsService extends IAppOpsService.Stub { UserHandle user = UserHandle.getUserHandleForUid(uid); boolean isRevokedCompat; if (permissionInfo.backgroundPermission != null) { - boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED; - - if (isBackgroundRevokedCompat && supportsRuntimePermissions) { - Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime" - + " permission state, this is discouraged and you should revoke the" - + " runtime permission instead: uid=" + uid + ", switchCode=" - + switchCode + ", mode=" + mode + ", permission=" - + permissionInfo.backgroundPermission); - } + if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName) + == PackageManager.PERMISSION_GRANTED) { + boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED; + + if (isBackgroundRevokedCompat && supportsRuntimePermissions) { + Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime" + + " permission state, this is discouraged and you should revoke the" + + " runtime permission instead: uid=" + uid + ", switchCode=" + + switchCode + ", mode=" + mode + ", permission=" + + permissionInfo.backgroundPermission); + } - long identity = Binder.clearCallingIdentity(); - try { - packageManager.updatePermissionFlags(permissionInfo.backgroundPermission, - packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, - isBackgroundRevokedCompat - ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user); - } finally { - Binder.restoreCallingIdentity(identity); + long identity = Binder.clearCallingIdentity(); + try { + packageManager.updatePermissionFlags(permissionInfo.backgroundPermission, + packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, + isBackgroundRevokedCompat + ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user); + } finally { + Binder.restoreCallingIdentity(identity); + } } isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED @@ -2170,25 +2235,6 @@ public class AppOpsService extends IAppOpsService.Stub { } } - @NonNull - private SparseArray<List<Integer>> getSwitchOpToOps() { - synchronized (this) { - if (mSwitchOpToOps == null) { - mSwitchOpToOps = new SparseArray<>(); - for (int op = 0; op < _NUM_OP; op++) { - int switchOp = AppOpsManager.opToSwitch(op); - List<Integer> ops = mSwitchOpToOps.get(switchOp); - if (ops == null) { - ops = new ArrayList<>(); - mSwitchOpToOps.put(switchOp, ops); - } - ops.add(op); - } - } - return mSwitchOpToOps; - } - } - private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode) { final StorageManagerInternal storageManagerInternal = LocalServices.getService(StorageManagerInternal.class); @@ -2289,16 +2335,29 @@ public class AppOpsService extends IAppOpsService.Stub { if (uid != UID_ANY && callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { return; } - // There are features watching for mode changes such as window manager - // and location manager which are in our process. The callbacks in these - // features may require permissions our remote caller does not have. - final long identity = Binder.clearCallingIdentity(); - try { - callback.mCallback.opChanged(code, uid, packageName); - } catch (RemoteException e) { - /* ignore */ - } finally { - Binder.restoreCallingIdentity(identity); + + // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE + int[] switchedCodes; + if (callback.mWatchedOpCode == ALL_OPS) { + switchedCodes = mSwitchedOps.get(code); + } else if (callback.mWatchedOpCode == OP_NONE) { + switchedCodes = new int[]{code}; + } else { + switchedCodes = new int[]{callback.mWatchedOpCode}; + } + + for (int switchedCode : switchedCodes) { + // There are features watching for mode changes such as window manager + // and location manager which are in our process. The callbacks in these + // features may require permissions our remote caller does not have. + final long identity = Binder.clearCallingIdentity(); + try { + callback.mCallback.opChanged(switchedCode, uid, packageName); + } catch (RemoteException e) { + /* ignore */ + } finally { + Binder.restoreCallingIdentity(identity); + } } } @@ -2493,17 +2552,32 @@ public class AppOpsService extends IAppOpsService.Stub { return; } synchronized (this) { - op = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op; + int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op; + + // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE + int notifiedOps; + if (Compatibility.isChangeEnabled( + CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE)) { + if (op == OP_NONE) { + notifiedOps = ALL_OPS; + } else { + notifiedOps = op; + } + } else { + notifiedOps = switchOp; + } + ModeCallback cb = mModeWatchers.get(callback.asBinder()); if (cb == null) { - cb = new ModeCallback(callback, watchedUid, flags, callingUid, callingPid); + cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid, + callingPid); mModeWatchers.put(callback.asBinder(), cb); } - if (op != AppOpsManager.OP_NONE) { - ArraySet<ModeCallback> cbs = mOpModeWatchers.get(op); + if (switchOp != AppOpsManager.OP_NONE) { + ArraySet<ModeCallback> cbs = mOpModeWatchers.get(switchOp); if (cbs == null) { cbs = new ArraySet<>(); - mOpModeWatchers.put(op, cbs); + mOpModeWatchers.put(switchOp, cbs); } cbs.add(cb); } @@ -3322,6 +3396,18 @@ public class AppOpsService extends IAppOpsService.Stub { uidState = new UidState(uid); mUidStates.put(uid, uidState); } else { + updatePendingStateIfNeededLocked(uidState); + } + return uidState; + } + + /** + * Check if the pending state should be updated and do so if needed + * + * @param uidState The uidState that might have a pending state + */ + private void updatePendingStateIfNeededLocked(@NonNull UidState uidState) { + if (uidState != null) { if (uidState.pendingStateCommitTime != 0) { if (uidState.pendingStateCommitTime < mLastRealtime) { commitUidPendingStateLocked(uidState); @@ -3333,7 +3419,6 @@ public class AppOpsService extends IAppOpsService.Stub { } } } - return uidState; } private void commitUidPendingStateLocked(UidState uidState) { @@ -3353,24 +3438,28 @@ public class AppOpsService extends IAppOpsService.Stub { && uidState.appWidgetVisible == uidState.pendingAppWidgetVisible) { continue; } - final ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code); - if (callbacks != null) { - for (int cbi = callbacks.size() - 1; cbi >= 0; cbi--) { - final ModeCallback callback = callbacks.valueAt(cbi); - if ((callback.mFlags & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0 - || !callback.isWatchingUid(uidState.uid)) { - continue; - } - boolean doAllPackages = uidState.opModes != null - && uidState.opModes.indexOfKey(code) >= 0 - && uidState.opModes.get(code) == AppOpsManager.MODE_FOREGROUND; - if (uidState.pkgOps != null) { + + if (uidState.opModes != null + && uidState.opModes.indexOfKey(code) >= 0 + && uidState.opModes.get(code) == AppOpsManager.MODE_FOREGROUND) { + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsService::notifyOpChangedForAllPkgsInUid, + this, code, uidState.uid, true, null)); + } else { + final ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code); + if (callbacks != null) { + for (int cbi = callbacks.size() - 1; cbi >= 0; cbi--) { + final ModeCallback callback = callbacks.valueAt(cbi); + if ((callback.mFlags & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0 + || !callback.isWatchingUid(uidState.uid)) { + continue; + } for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) { final Op op = uidState.pkgOps.valueAt(pkgi).get(code); if (op == null) { continue; } - if (doAllPackages || op.mode == AppOpsManager.MODE_FOREGROUND) { + if (op.mode == AppOpsManager.MODE_FOREGROUND) { mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpChanged, this, callback, code, uidState.uid, @@ -3795,11 +3884,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (tagName.equals("op")) { final int code = Integer.parseInt(parser.getAttributeValue(null, "n")); final int mode = Integer.parseInt(parser.getAttributeValue(null, "m")); - UidState uidState = getUidStateLocked(uid, true); - if (uidState.opModes == null) { - uidState.opModes = new SparseIntArray(); - } - uidState.opModes.put(code, mode); + setUidMode(code, uid, mode); } else { Slog.w(TAG, "Unknown element under <uid-ops>: " + parser.getName()); 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 b2548af0f576..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(); @@ -3406,13 +3406,11 @@ public class AudioService extends IAudioService.Stub } public void binderDied() { - int oldModeOwnerPid = 0; + int oldModeOwnerPid; int newModeOwnerPid = 0; synchronized (mDeviceBroker.mSetModeLock) { Log.w(TAG, "setMode() client died"); - if (!mSetModeDeathHandlers.isEmpty()) { - oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); - } + oldModeOwnerPid = getModeOwnerPid(); int index = mSetModeDeathHandlers.indexOf(this); if (index < 0) { Log.w(TAG, "unregistered setMode() client died"); @@ -3446,17 +3444,19 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#setMode(int) */ public void setMode(int mode, IBinder cb, String callingPackage) { - if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); } + if (DEBUG_MODE) { + Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); + } if (!checkAudioSettingsPermission("setMode()")) { return; } - - if ( (mode == AudioSystem.MODE_IN_CALL) && - (mContext.checkCallingOrSelfPermission( + final boolean hasModifyPhoneStatePermission = mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) - != PackageManager.PERMISSION_GRANTED)) { + == PackageManager.PERMISSION_GRANTED; + final int callingPid = Binder.getCallingPid(); + if ((mode == AudioSystem.MODE_IN_CALL) && !hasModifyPhoneStatePermission) { Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode(MODE_IN_CALL) from pid=" - + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); + + callingPid + ", uid=" + Binder.getCallingUid()); return; } @@ -3470,16 +3470,25 @@ public class AudioService extends IAudioService.Stub return; } - int oldModeOwnerPid = 0; - int newModeOwnerPid = 0; + int oldModeOwnerPid; + int newModeOwnerPid; synchronized (mDeviceBroker.mSetModeLock) { - if (!mSetModeDeathHandlers.isEmpty()) { - oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); - } if (mode == AudioSystem.MODE_CURRENT) { mode = mMode; } - newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage); + oldModeOwnerPid = getModeOwnerPid(); + // Do not allow changing mode if a call is active and the requester + // does not have permission to modify phone state or is not the mode owner. + if (((mMode == AudioSystem.MODE_IN_CALL) + || (mMode == AudioSystem.MODE_IN_COMMUNICATION)) + && !(hasModifyPhoneStatePermission || (oldModeOwnerPid == callingPid))) { + Log.w(TAG, "setMode(" + mode + ") from pid=" + callingPid + + ", uid=" + Binder.getCallingUid() + + ", cannot change mode from " + mMode + + " without permission or being mode owner"); + return; + } + newModeOwnerPid = setModeInt(mode, cb, callingPid, callingPackage); } // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // SCO connections not started by the application changing the mode when pid changes @@ -3569,10 +3578,9 @@ public class AudioService extends IAudioService.Stub if (status == AudioSystem.AUDIO_STATUS_OK) { if (actualMode != AudioSystem.MODE_NORMAL) { - if (mSetModeDeathHandlers.isEmpty()) { + newModeOwnerPid = getModeOwnerPid(); + if (newModeOwnerPid == 0) { Log.e(TAG, "setMode() different from MODE_NORMAL with empty mode client stack"); - } else { - newModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); } } // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL 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/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java index 766e5c4d638f..7bbda9f3f732 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java @@ -20,11 +20,14 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.IBiometricNativeHandle; import android.os.IBinder; +import android.os.NativeHandle; import android.os.RemoteException; import android.security.KeyStore; import android.util.Slog; +import java.io.IOException; import java.util.ArrayList; /** @@ -41,6 +44,7 @@ public abstract class AuthenticationClient extends ClientMonitor { public static final int LOCKOUT_PERMANENT = 2; private final boolean mRequireConfirmation; + private final NativeHandle mWindowId; // We need to track this state since it's possible for applications to request for // authentication while the device is already locked out. In that case, the client is created @@ -69,11 +73,25 @@ public abstract class AuthenticationClient extends ClientMonitor { public AuthenticationClient(Context context, Constants constants, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId, - boolean restricted, String owner, int cookie, boolean requireConfirmation) { + boolean restricted, String owner, int cookie, boolean requireConfirmation, + IBiometricNativeHandle windowId) { super(context, constants, daemon, halDeviceId, token, listener, targetUserId, groupId, restricted, owner, cookie); mOpId = opId; mRequireConfirmation = requireConfirmation; + mWindowId = Utils.dupNativeHandle(windowId); + } + + @Override + public void destroy() { + if (mWindowId != null && mWindowId.getFileDescriptors() != null) { + try { + mWindowId.close(); + } catch (IOException e) { + Slog.e(getLogTag(), "Failed to close windowId NativeHandle: ", e); + } + } + super.destroy(); } protected long getStartTimeMs() { @@ -233,7 +251,7 @@ public abstract class AuthenticationClient extends ClientMonitor { onStart(); try { mStartTimeMs = System.currentTimeMillis(); - final int result = getDaemonWrapper().authenticate(mOpId, getGroupId()); + final int result = getDaemonWrapper().authenticate(mOpId, getGroupId(), mWindowId); if (result != 0) { Slog.w(getLogTag(), "startAuthentication failed, result=" + result); mMetricsLogger.histogram(mConstants.tagAuthStartError(), result); diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index 687d9353ebd1..0e709944b03f 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -32,6 +32,7 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IBiometricServiceReceiverInternal; @@ -43,6 +44,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IHwBinder; import android.os.IRemoteCallback; +import android.os.NativeHandle; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -220,9 +222,10 @@ public abstract class BiometricServiceBase extends SystemService public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, - boolean restricted, String owner, int cookie, boolean requireConfirmation) { + boolean restricted, String owner, int cookie, boolean requireConfirmation, + IBiometricNativeHandle windowId) { super(context, getConstants(), daemon, halDeviceId, token, listener, targetUserId, - groupId, opId, restricted, owner, cookie, requireConfirmation); + groupId, opId, restricted, owner, cookie, requireConfirmation, windowId); } @Override @@ -283,10 +286,10 @@ public abstract class BiometricServiceBase extends SystemService public EnrollClientImpl(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int userId, int groupId, byte[] cryptoToken, boolean restricted, String owner, - final int[] disabledFeatures, int timeoutSec) { + final int[] disabledFeatures, int timeoutSec, IBiometricNativeHandle windowId) { super(context, getConstants(), daemon, halDeviceId, token, listener, userId, groupId, cryptoToken, restricted, owner, getBiometricUtils(), - disabledFeatures, timeoutSec); + disabledFeatures, timeoutSec, windowId); } @Override @@ -472,12 +475,13 @@ public abstract class BiometricServiceBase extends SystemService */ protected interface DaemonWrapper { int ERROR_ESRCH = 3; // Likely HAL is dead. see errno.h. - int authenticate(long operationId, int groupId) throws RemoteException; + int authenticate(long operationId, int groupId, NativeHandle windowId) + throws RemoteException; int cancel() throws RemoteException; int remove(int groupId, int biometricId) throws RemoteException; int enumerate() throws RemoteException; int enroll(byte[] token, int groupId, int timeout, - ArrayList<Integer> disabledFeatures) throws RemoteException; + ArrayList<Integer> disabledFeatures, NativeHandle windowId) throws RemoteException; void resetLockout(byte[] token) throws RemoteException; } diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java index 7ebb7c059b4c..684795ec66b5 100644 --- a/services/core/java/com/android/server/biometrics/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/EnrollClient.java @@ -20,10 +20,13 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.IBiometricNativeHandle; import android.os.IBinder; +import android.os.NativeHandle; import android.os.RemoteException; import android.util.Slog; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -35,6 +38,7 @@ public abstract class EnrollClient extends ClientMonitor { private final BiometricUtils mBiometricUtils; private final int[] mDisabledFeatures; private final int mTimeoutSec; + private final NativeHandle mWindowId; private long mEnrollmentStartTimeMs; @@ -44,13 +48,26 @@ public abstract class EnrollClient extends ClientMonitor { BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int userId, int groupId, byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils, - final int[] disabledFeatures, int timeoutSec) { + final int[] disabledFeatures, int timeoutSec, IBiometricNativeHandle windowId) { super(context, constants, daemon, halDeviceId, token, listener, userId, groupId, restricted, owner, 0 /* cookie */); mBiometricUtils = utils; mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length); mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length); mTimeoutSec = timeoutSec; + mWindowId = Utils.dupNativeHandle(windowId); + } + + @Override + public void destroy() { + if (mWindowId != null && mWindowId.getFileDescriptors() != null) { + try { + mWindowId.close(); + } catch (IOException e) { + Slog.e(getLogTag(), "Failed to close windowId NativeHandle: ", e); + } + } + super.destroy(); } @Override @@ -102,7 +119,7 @@ public abstract class EnrollClient extends ClientMonitor { } final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), mTimeoutSec, - disabledFeatures); + disabledFeatures, mWindowId); if (result != 0) { Slog.w(getLogTag(), "startEnroll failed, result=" + result); mMetricsLogger.histogram(mConstants.tagEnrollStartError(), result); diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 389763b5377a..2d4ab6308a8b 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -23,12 +23,17 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType; +import android.hardware.biometrics.IBiometricNativeHandle; import android.os.Build; import android.os.Bundle; +import android.os.NativeHandle; import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; +import java.io.FileDescriptor; +import java.io.IOException; + public class Utils { public static boolean isDebugEnabled(Context context, int targetUserId) { if (targetUserId == UserHandle.USER_NULL) { @@ -237,4 +242,31 @@ public class Utils { throw new IllegalArgumentException("Unsupported dismissal reason: " + reason); } } + + /** + * Converts an {@link IBiometricNativeHandle} to a {@link NativeHandle} by duplicating the + * the underlying file descriptors. + * + * Both the original and new handle must be closed after use. + * + * @param h {@link IBiometricNativeHandle} received as a binder call argument. Usually used to + * identify a WindowManager window. Can be null. + * @return A {@link NativeHandle} representation of {@code h}. Will be null if either {@code h} + * or its contents are null. + */ + public static NativeHandle dupNativeHandle(IBiometricNativeHandle h) { + NativeHandle handle = null; + if (h != null && h.fds != null && h.ints != null) { + FileDescriptor[] fds = new FileDescriptor[h.fds.length]; + for (int i = 0; i < h.fds.length; ++i) { + try { + fds[i] = h.fds[i].dup().getFileDescriptor(); + } catch (IOException e) { + return null; + } + } + handle = new NativeHandle(fds, h.ints, true /* own */); + } + return handle; + } } diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index b512475e7971..31c3d4d7b24e 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -34,6 +34,7 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.face.V1_0.IBiometricsFace; @@ -214,9 +215,10 @@ public class FaceService extends BiometricServiceBase { public FaceAuthClient(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, - boolean restricted, String owner, int cookie, boolean requireConfirmation) { + boolean restricted, String owner, int cookie, boolean requireConfirmation, + IBiometricNativeHandle windowId) { super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId, - restricted, owner, cookie, requireConfirmation); + restricted, owner, cookie, requireConfirmation, windowId); } @Override @@ -373,7 +375,7 @@ public class FaceService extends BiometricServiceBase { @Override // Binder call public void enroll(int userId, final IBinder token, final byte[] cryptoToken, final IFaceServiceReceiver receiver, final String opPackageName, - final int[] disabledFeatures) { + final int[] disabledFeatures, IBiometricNativeHandle windowId) { checkPermission(MANAGE_BIOMETRIC); updateActiveGroup(userId, opPackageName); @@ -384,7 +386,7 @@ public class FaceService extends BiometricServiceBase { final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, 0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures, - ENROLL_TIMEOUT_SEC) { + ENROLL_TIMEOUT_SEC, windowId) { @Override public int[] getAcquireIgnorelist() { @@ -411,6 +413,14 @@ public class FaceService extends BiometricServiceBase { } @Override // Binder call + public void enrollRemotely(int userId, final IBinder token, final byte[] cryptoToken, + final IFaceServiceReceiver receiver, final String opPackageName, + final int[] disabledFeatures) { + checkPermission(MANAGE_BIOMETRIC); + // TODO(b/145027036): Implement this. + } + + @Override // Binder call public void cancelEnrollment(final IBinder token) { checkPermission(MANAGE_BIOMETRIC); cancelEnrollmentInternal(token); @@ -426,7 +436,7 @@ public class FaceService extends BiometricServiceBase { final AuthenticationClientImpl client = new FaceAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, - 0 /* cookie */, false /* requireConfirmation */); + 0 /* cookie */, false /* requireConfirmation */, null /* windowId */); authenticateInternal(client, opId, opPackageName); } @@ -442,7 +452,7 @@ public class FaceService extends BiometricServiceBase { mDaemonWrapper, mHalDeviceId, token, new BiometricPromptServiceListenerImpl(wrapperReceiver), mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, cookie, - requireConfirmation); + requireConfirmation, null /* windowId */); authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); } @@ -985,7 +995,8 @@ public class FaceService extends BiometricServiceBase { */ private final DaemonWrapper mDaemonWrapper = new DaemonWrapper() { @Override - public int authenticate(long operationId, int groupId) throws RemoteException { + public int authenticate(long operationId, int groupId, NativeHandle windowId) + throws RemoteException { IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { Slog.w(TAG, "authenticate(): no face HAL!"); @@ -1026,7 +1037,7 @@ public class FaceService extends BiometricServiceBase { @Override public int enroll(byte[] cryptoToken, int groupId, int timeout, - ArrayList<Integer> disabledFeatures) throws RemoteException { + ArrayList<Integer> disabledFeatures, NativeHandle windowId) throws RemoteException { IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { Slog.w(TAG, "enroll(): no face HAL!"); @@ -1036,7 +1047,17 @@ public class FaceService extends BiometricServiceBase { for (int i = 0; i < cryptoToken.length; i++) { token.add(cryptoToken[i]); } - return daemon.enroll(token, timeout, disabledFeatures); + android.hardware.biometrics.face.V1_1.IBiometricsFace daemon11 = + android.hardware.biometrics.face.V1_1.IBiometricsFace.castFrom( + daemon); + if (daemon11 != null) { + return daemon11.enroll_1_1(token, timeout, disabledFeatures, windowId); + } else if (windowId == null) { + return daemon.enroll(token, timeout, disabledFeatures); + } else { + Slog.e(TAG, "enroll(): windowId is only supported in @1.1 HAL"); + return ERROR_ESRCH; + } } @Override diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java index 6150de151ccc..7a4e62e18474 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java @@ -38,7 +38,7 @@ public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId) throws RemoteException { mFingerprintService.prepareForAuthentication(token, sessionId, userId, wrapperReceiver, - opPackageName, cookie, callingUid, callingPid, callingUserId); + opPackageName, cookie, callingUid, callingPid, callingUserId, null /* windowId */); } @Override diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index 44797ad97b37..57d1867b3aca 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -37,6 +37,7 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; @@ -50,6 +51,7 @@ import android.os.Binder; import android.os.Build; import android.os.Environment; import android.os.IBinder; +import android.os.NativeHandle; import android.os.RemoteException; import android.os.SELinux; import android.os.SystemClock; @@ -132,9 +134,9 @@ public class FingerprintService extends BiometricServiceBase { DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId, boolean restricted, String owner, int cookie, - boolean requireConfirmation) { + boolean requireConfirmation, IBiometricNativeHandle windowId) { super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId, - restricted, owner, cookie, requireConfirmation); + restricted, owner, cookie, requireConfirmation, windowId); } @Override @@ -198,7 +200,7 @@ public class FingerprintService extends BiometricServiceBase { @Override // Binder call public void enroll(final IBinder token, final byte[] cryptoToken, final int userId, final IFingerprintServiceReceiver receiver, final int flags, - final String opPackageName) { + final String opPackageName, IBiometricNativeHandle windowId) { checkPermission(MANAGE_FINGERPRINT); final boolean restricted = isRestricted(); @@ -206,7 +208,7 @@ public class FingerprintService extends BiometricServiceBase { final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId, cryptoToken, restricted, opPackageName, new int[0] /* disabledFeatures */, - ENROLL_TIMEOUT_SEC) { + ENROLL_TIMEOUT_SEC, windowId) { @Override public boolean shouldVibrate() { return true; @@ -230,20 +232,22 @@ public class FingerprintService extends BiometricServiceBase { @Override // Binder call public void authenticate(final IBinder token, final long opId, final int groupId, final IFingerprintServiceReceiver receiver, final int flags, - final String opPackageName) { + final String opPackageName, IBiometricNativeHandle windowId) { updateActiveGroup(groupId, opPackageName); final boolean restricted = isRestricted(); final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId, opId, restricted, opPackageName, - 0 /* cookie */, false /* requireConfirmation */); + 0 /* cookie */, false /* requireConfirmation */, + windowId); authenticateInternal(client, opId, opPackageName); } @Override // Binder call public void prepareForAuthentication(IBinder token, long opId, int groupId, IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, - int cookie, int callingUid, int callingPid, int callingUserId) { + int cookie, int callingUid, int callingPid, int callingUserId, + IBiometricNativeHandle windowId) { checkPermission(MANAGE_BIOMETRIC); updateActiveGroup(groupId, opPackageName); final boolean restricted = true; // BiometricPrompt is always restricted @@ -251,7 +255,8 @@ public class FingerprintService extends BiometricServiceBase { mDaemonWrapper, mHalDeviceId, token, new BiometricPromptServiceListenerImpl(wrapperReceiver), mCurrentUserId, groupId, opId, restricted, opPackageName, cookie, - false /* requireConfirmation */); + false /* requireConfirmation */, + windowId); authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); } @@ -654,13 +659,24 @@ public class FingerprintService extends BiometricServiceBase { */ private final DaemonWrapper mDaemonWrapper = new DaemonWrapper() { @Override - public int authenticate(long operationId, int groupId) throws RemoteException { + public int authenticate(long operationId, int groupId, NativeHandle windowId) + throws RemoteException { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { Slog.w(TAG, "authenticate(): no fingerprint HAL!"); return ERROR_ESRCH; } - return daemon.authenticate(operationId, groupId); + android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint daemon22 = + android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint.castFrom( + daemon); + if (daemon22 != null) { + return daemon22.authenticate_2_2(operationId, groupId, windowId); + } else if (windowId == null) { + return daemon.authenticate(operationId, groupId); + } else { + Slog.e(TAG, "authenticate(): windowId is only supported in @2.2 HAL"); + return ERROR_ESRCH; + } } @Override @@ -695,13 +711,27 @@ public class FingerprintService extends BiometricServiceBase { @Override public int enroll(byte[] cryptoToken, int groupId, int timeout, - ArrayList<Integer> disabledFeatures) throws RemoteException { + ArrayList<Integer> disabledFeatures, NativeHandle windowId) throws RemoteException { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { Slog.w(TAG, "enroll(): no fingerprint HAL!"); return ERROR_ESRCH; } - return daemon.enroll(cryptoToken, groupId, timeout); + android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint daemon22 = + android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprint.castFrom( + daemon); + if (daemon22 != null) { + ArrayList<Byte> cryptoTokenAsList = new ArrayList<>(cryptoToken.length); + for (byte b : cryptoToken) { + cryptoTokenAsList.add(b); + } + return daemon22.enroll_2_2(cryptoTokenAsList, groupId, timeout, windowId); + } else if (windowId == null) { + return daemon.enroll(cryptoToken, groupId, timeout); + } else { + Slog.e(TAG, "enroll(): windowId is only supported in @2.2 HAL"); + return ERROR_ESRCH; + } } @Override diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 2fc9d04623f8..af474304c76e 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -16,6 +16,12 @@ package com.android.server.compat; +import static android.Manifest.permission.LOG_COMPAT_CHANGE; +import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG; +import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.IActivityManager; import android.content.Context; @@ -67,12 +73,14 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void reportChange(long changeId, ApplicationInfo appInfo) { + checkCompatChangeLogPermission(); reportChange(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED); } @Override public void reportChangeByPackageName(long changeId, String packageName, int userId) { + checkCompatChangeLogPermission(); ApplicationInfo appInfo = getApplicationInfo(packageName, userId); if (appInfo == null) { return; @@ -82,11 +90,13 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void reportChangeByUid(long changeId, int uid) { + checkCompatChangeLogPermission(); reportChange(changeId, uid, ChangeReporter.STATE_LOGGED); } @Override public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) { + checkCompatChangeReadAndLogPermission(); if (mCompatConfig.isChangeEnabled(changeId, appInfo)) { reportChange(changeId, appInfo.uid, ChangeReporter.STATE_ENABLED); @@ -98,7 +108,9 @@ public class PlatformCompat extends IPlatformCompat.Stub { } @Override - public boolean isChangeEnabledByPackageName(long changeId, String packageName, int userId) { + public boolean isChangeEnabledByPackageName(long changeId, String packageName, + @UserIdInt int userId) { + checkCompatChangeReadAndLogPermission(); ApplicationInfo appInfo = getApplicationInfo(packageName, userId); if (appInfo == null) { return true; @@ -108,6 +120,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public boolean isChangeEnabledByUid(long changeId, int uid) { + checkCompatChangeReadAndLogPermission(); String[] packages = mContext.getPackageManager().getPackagesForUid(uid); if (packages == null || packages.length == 0) { return true; @@ -140,6 +153,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void setOverrides(CompatibilityChangeConfig overrides, String packageName) throws RemoteException, SecurityException { + checkCompatChangeOverridePermission(); mCompatConfig.addOverrides(overrides, packageName); killPackage(packageName); } @@ -147,11 +161,13 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) throws RemoteException, SecurityException { + checkCompatChangeOverridePermission(); mCompatConfig.addOverrides(overrides, packageName); } @Override public void clearOverrides(String packageName) throws RemoteException, SecurityException { + checkCompatChangeOverridePermission(); mCompatConfig.removePackageOverrides(packageName); killPackage(packageName); } @@ -159,12 +175,14 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void clearOverridesForTest(String packageName) throws RemoteException, SecurityException { + checkCompatChangeOverridePermission(); mCompatConfig.removePackageOverrides(packageName); } @Override public boolean clearOverride(long changeId, String packageName) throws RemoteException, SecurityException { + checkCompatChangeOverridePermission(); boolean existed = mCompatConfig.removeOverride(changeId, packageName); killPackage(packageName); return existed; @@ -172,11 +190,13 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) { + checkCompatChangeReadAndLogPermission(); return mCompatConfig.getAppConfig(appInfo); } @Override public CompatibilityChangeInfo[] listAllChanges() { + checkCompatChangeReadPermission(); return mCompatConfig.dumpChanges(); } @@ -215,6 +235,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + checkCompatChangeReadAndLogPermission(); if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return; mCompatConfig.dumpConfig(pw); } @@ -272,4 +293,30 @@ public class PlatformCompat extends IPlatformCompat.Stub { Binder.restoreCallingIdentity(identity); } } + + private void checkCompatChangeLogPermission() throws SecurityException { + if (mContext.checkCallingOrSelfPermission(LOG_COMPAT_CHANGE) + != PERMISSION_GRANTED) { + throw new SecurityException("Cannot log compat change usage"); + } + } + + private void checkCompatChangeReadPermission() throws SecurityException { + if (mContext.checkCallingOrSelfPermission(READ_COMPAT_CHANGE_CONFIG) + != PERMISSION_GRANTED) { + throw new SecurityException("Cannot read compat change"); + } + } + + private void checkCompatChangeOverridePermission() throws SecurityException { + if (mContext.checkCallingOrSelfPermission(OVERRIDE_COMPAT_CHANGE_CONFIG) + != PERMISSION_GRANTED) { + throw new SecurityException("Cannot override compat change"); + } + } + + private void checkCompatChangeReadAndLogPermission() throws SecurityException { + checkCompatChangeReadPermission(); + checkCompatChangeLogPermission(); + } } diff --git a/services/core/java/com/android/server/compat/PlatformCompatNative.java b/services/core/java/com/android/server/compat/PlatformCompatNative.java index 85dfbf411667..5d7af650db0b 100644 --- a/services/core/java/com/android/server/compat/PlatformCompatNative.java +++ b/services/core/java/com/android/server/compat/PlatformCompatNative.java @@ -16,6 +16,8 @@ package com.android.server.compat; +import android.annotation.UserIdInt; + import com.android.internal.compat.IPlatformCompatNative; /** @@ -39,7 +41,8 @@ public class PlatformCompatNative extends IPlatformCompatNative.Stub { } @Override - public boolean isChangeEnabledByPackageName(long changeId, String packageName, int userId) { + public boolean isChangeEnabledByPackageName(long changeId, String packageName, + @UserIdInt int userId) { return mPlatformCompat.isChangeEnabledByPackageName(changeId, packageName, userId); } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 89af5b58ff72..cb88c4e8a739 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -49,6 +49,7 @@ import android.content.pm.UserInfo; import android.net.ConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.IpPrefix; +import android.net.IpSecManager; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.LocalSocket; @@ -180,7 +181,10 @@ public class Vpn { private boolean mIsPackageTargetingAtLeastQ; private String mInterface; private Connection mConnection; - private LegacyVpnRunner mLegacyVpnRunner; + + /** Tracks the runners for all VPN types managed by the platform (eg. LegacyVpn, PlatformVpn) */ + private VpnRunner mVpnRunner; + private PendingIntent mStatusIntent; private volatile boolean mEnableTeardown = true; private final INetworkManagementService mNetd; @@ -762,7 +766,7 @@ public class Vpn { mNetworkCapabilities.setUids(null); } - // Revoke the connection or stop LegacyVpnRunner. + // Revoke the connection or stop the VpnRunner. if (mConnection != null) { try { mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION, @@ -772,9 +776,9 @@ public class Vpn { } mContext.unbindService(mConnection); mConnection = null; - } else if (mLegacyVpnRunner != null) { - mLegacyVpnRunner.exit(); - mLegacyVpnRunner = null; + } else if (mVpnRunner != null) { + mVpnRunner.exit(); + mVpnRunner = null; } try { @@ -1510,8 +1514,8 @@ public class Vpn { @Override public void interfaceStatusChanged(String interfaze, boolean up) { synchronized (Vpn.this) { - if (!up && mLegacyVpnRunner != null) { - mLegacyVpnRunner.check(interfaze); + if (!up && mVpnRunner != null && mVpnRunner instanceof LegacyVpnRunner) { + ((LegacyVpnRunner) mVpnRunner).exitIfOuterInterfaceIs(interfaze); } } } @@ -1528,9 +1532,10 @@ public class Vpn { mContext.unbindService(mConnection); mConnection = null; agentDisconnect(); - } else if (mLegacyVpnRunner != null) { - mLegacyVpnRunner.exit(); - mLegacyVpnRunner = null; + } else if (mVpnRunner != null) { + // agentDisconnect must be called from mVpnRunner.exit() + mVpnRunner.exit(); + mVpnRunner = null; } } } @@ -1913,23 +1918,40 @@ public class Vpn { private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) { - stopLegacyVpnPrivileged(); + stopVpnRunnerPrivileged(); // Prepare for the new request. prepareInternal(VpnConfig.LEGACY_VPN); updateState(DetailedState.CONNECTING, "startLegacyVpn"); // Start a new LegacyVpnRunner and we are done! - mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile); - mLegacyVpnRunner.start(); + mVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile); + mVpnRunner.start(); + } + + /** + * Checks if this the currently running VPN (if any) was started by the Settings app + * + * <p>This includes both Legacy VPNs and Platform VPNs. + */ + private boolean isSettingsVpnLocked() { + return mVpnRunner != null && VpnConfig.LEGACY_VPN.equals(mPackage); } - /** Stop legacy VPN. Permissions must be checked by callers. */ - public synchronized void stopLegacyVpnPrivileged() { - if (mLegacyVpnRunner != null) { - mLegacyVpnRunner.exit(); - mLegacyVpnRunner = null; + /** Stop VPN runner. Permissions must be checked by callers. */ + public synchronized void stopVpnRunnerPrivileged() { + if (!isSettingsVpnLocked()) { + return; + } + + final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner; + mVpnRunner.exit(); + mVpnRunner = null; + + // LegacyVpn uses daemons that must be shut down before new ones are brought up. + // The same limitation does not apply to Platform VPNs. + if (isLegacyVpn) { synchronized (LegacyVpnRunner.TAG) { // wait for old thread to completely finish before spinning up // new instance, otherwise state updates can be out of order. @@ -1951,7 +1973,7 @@ public class Vpn { * Callers are responsible for checking permissions if needed. */ private synchronized LegacyVpnInfo getLegacyVpnInfoPrivileged() { - if (mLegacyVpnRunner == null) return null; + if (!isSettingsVpnLocked()) return null; final LegacyVpnInfo info = new LegacyVpnInfo(); info.key = mConfig.user; @@ -1962,14 +1984,53 @@ public class Vpn { return info; } - public VpnConfig getLegacyVpnConfig() { - if (mLegacyVpnRunner != null) { + public synchronized VpnConfig getLegacyVpnConfig() { + if (isSettingsVpnLocked()) { return mConfig; } else { return null; } } + /** This class represents the common interface for all VPN runners. */ + private abstract class VpnRunner extends Thread { + + protected VpnRunner(String name) { + super(name); + } + + public abstract void run(); + + protected abstract void exit(); + } + + private class IkeV2VpnRunner extends VpnRunner { + private static final String TAG = "IkeV2VpnRunner"; + + private final IpSecManager mIpSecManager; + private final VpnProfile mProfile; + + IkeV2VpnRunner(VpnProfile profile) { + super(TAG); + mProfile = profile; + + // TODO: move this to startVpnRunnerPrivileged() + mConfig = new VpnConfig(); + mIpSecManager = mContext.getSystemService(IpSecManager.class); + } + + @Override + public void run() { + // TODO: Build IKE config, start IKE session + } + + @Override + public void exit() { + // TODO: Teardown IKE session & any resources. + agentDisconnect(); + } + } + /** * Bringing up a VPN connection takes time, and that is all this thread * does. Here we have plenty of time. The only thing we need to take @@ -1977,7 +2038,7 @@ public class Vpn { * requests will pile up. This could be done in a Handler as a state * machine, but it is much easier to read in the current form. */ - private class LegacyVpnRunner extends Thread { + private class LegacyVpnRunner extends VpnRunner { private static final String TAG = "LegacyVpnRunner"; private final String[] mDaemons; @@ -2047,13 +2108,21 @@ public class Vpn { mContext.registerReceiver(mBroadcastReceiver, filter); } - public void check(String interfaze) { + /** + * Checks if the parameter matches the underlying interface + * + * <p>If the underlying interface is torn down, the LegacyVpnRunner also should be. It has + * no ability to migrate between interfaces (or Networks). + */ + public void exitIfOuterInterfaceIs(String interfaze) { if (interfaze.equals(mOuterInterface)) { Log.i(TAG, "Legacy VPN is going down with " + interfaze); exit(); } } + /** Tears down this LegacyVpn connection */ + @Override public void exit() { // We assume that everything is reset after stopping the daemons. interrupt(); diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java index 6ba25a53248d..838dc84d4a0a 100644 --- a/services/core/java/com/android/server/display/DisplayAdapter.java +++ b/services/core/java/com/android/server/display/DisplayAdapter.java @@ -109,24 +109,14 @@ abstract class DisplayAdapter { */ protected final void sendDisplayDeviceEventLocked( final DisplayDevice device, final int event) { - mHandler.post(new Runnable() { - @Override - public void run() { - mListener.onDisplayDeviceEvent(device, event); - } - }); + mHandler.post(() -> mListener.onDisplayDeviceEvent(device, event)); } /** * Sends a request to perform traversals. */ protected final void sendTraversalRequestLocked() { - mHandler.post(new Runnable() { - @Override - public void run() { - mListener.onTraversalRequested(); - } - }); + mHandler.post(() -> mListener.onTraversalRequested()); } public static Display.Mode createMode(int width, int height, float refreshRate) { @@ -135,7 +125,7 @@ abstract class DisplayAdapter { } public interface Listener { - public void onDisplayDeviceEvent(DisplayDevice device, int event); - public void onTraversalRequested(); + void onDisplayDeviceEvent(DisplayDevice device, int event); + void onTraversalRequested(); } } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 704cbff44388..fc9542a33a78 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -65,8 +65,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular"; - private final LongSparseArray<LocalDisplayDevice> mDevices = - new LongSparseArray<LocalDisplayDevice>(); + private static final int NO_DISPLAY_MODE_ID = 0; + + private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>(); @SuppressWarnings("unused") // Becomes active at instantiation time. private PhysicalDisplayEventReceiver mPhysicalDisplayEventReceiver; @@ -133,14 +134,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { hdrCapabilities, isInternal); mDevices.put(physicalDisplayId, device); sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED); - } else { - boolean changed = device.updateDisplayConfigsLocked(configs, activeConfig, - configSpecs); - changed |= device.updateColorModesLocked(colorModes, activeColorMode); - changed |= device.updateHdrCapabilitiesLocked(hdrCapabilities); - if (changed) { - sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED); - } + } else if (device.updateDisplayProperties(configs, activeConfig, + configSpecs, colorModes, activeColorMode, hdrCapabilities)) { + sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED); } } else { // The display is no longer available. Ignore the attempt to add it. @@ -215,10 +211,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { mPhysicalDisplayId = physicalDisplayId; mIsInternal = isInternal; mDisplayInfo = info; - - updateDisplayConfigsLocked(configs, activeConfigId, configSpecs); - updateColorModesLocked(colorModes, activeColorMode); - updateHdrCapabilitiesLocked(hdrCapabilities); + updateDisplayProperties(configs, activeConfigId, configSpecs, colorModes, + activeColorMode, hdrCapabilities); mSidekickInternal = LocalServices.getService(SidekickInternal.class); if (mIsInternal) { LightsManager lights = LocalServices.getService(LightsManager.class); @@ -240,13 +234,25 @@ final class LocalDisplayAdapter extends DisplayAdapter { return true; } + /** + * Returns true if there is a change. + **/ + public boolean updateDisplayProperties(SurfaceControl.DisplayConfig[] configs, + int activeConfigId, SurfaceControl.DesiredDisplayConfigSpecs configSpecs, + int[] colorModes, int activeColorMode, Display.HdrCapabilities hdrCapabilities) { + boolean changed = updateDisplayConfigsLocked(configs, activeConfigId, configSpecs); + changed |= updateColorModesLocked(colorModes, activeColorMode); + changed |= updateHdrCapabilitiesLocked(hdrCapabilities); + return changed; + } + public boolean updateDisplayConfigsLocked( SurfaceControl.DisplayConfig[] configs, int activeConfigId, SurfaceControl.DesiredDisplayConfigSpecs configSpecs) { mDisplayConfigs = Arrays.copyOf(configs, configs.length); mActiveConfigId = activeConfigId; // Build an updated list of all existing modes. - ArrayList<DisplayModeRecord> records = new ArrayList<DisplayModeRecord>(); + ArrayList<DisplayModeRecord> records = new ArrayList<>(); boolean modesAdded = false; for (int i = 0; i < configs.length; i++) { SurfaceControl.DisplayConfig config = configs[i]; @@ -286,7 +292,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { // Check whether surface flinger spontaneously changed modes out from under us. // Schedule traversals to ensure that the correct state is reapplied if necessary. - if (mActiveModeId != 0 + if (mActiveModeId != NO_DISPLAY_MODE_ID && mActiveModeId != activeRecord.mMode.getModeId()) { mActiveModeInvalid = true; sendTraversalRequestLocked(); @@ -294,12 +300,12 @@ final class LocalDisplayAdapter extends DisplayAdapter { // Check whether surface flinger spontaneously changed display config specs out from // under us. If so, schedule a traversal to reapply our display config specs. - if (mDisplayModeSpecs.baseModeId != 0) { + if (mDisplayModeSpecs.baseModeId != NO_DISPLAY_MODE_ID) { int activeBaseMode = findMatchingModeIdLocked(configSpecs.defaultConfig); // If we can't map the defaultConfig index to a mode, then the physical display // configs must have changed, and the code below for handling changes to the // list of available modes will take care of updating display config specs. - if (activeBaseMode != 0) { + if (activeBaseMode != NO_DISPLAY_MODE_ID) { if (mDisplayModeSpecs.baseModeId != activeBaseMode || mDisplayModeSpecs.refreshRateRange.min != configSpecs.minRefreshRate || mDisplayModeSpecs.refreshRateRange.max @@ -323,18 +329,23 @@ final class LocalDisplayAdapter extends DisplayAdapter { mSupportedModes.put(record.mMode.getModeId(), record); } - // Update the default mode, if needed. - if (findDisplayConfigIdLocked(mDefaultModeId) < 0) { - if (mDefaultModeId != 0) { - Slog.w(TAG, "Default display mode no longer available, using currently" - + " active mode as default."); - } + // For a new display, we need to initialize the default mode ID. + if (mDefaultModeId == NO_DISPLAY_MODE_ID) { + mDefaultModeId = activeRecord.mMode.getModeId(); + } else if (modesAdded && mActiveModeId != activeRecord.mMode.getModeId()) { + Slog.d(TAG, "New display modes are added and the active mode has changed, " + + "use active mode as default mode."); + mActiveModeId = activeRecord.mMode.getModeId(); + mDefaultModeId = activeRecord.mMode.getModeId(); + } else if (findDisplayConfigIdLocked(mDefaultModeId) < 0) { + Slog.w(TAG, "Default display mode no longer available, using currently" + + " active mode as default."); mDefaultModeId = activeRecord.mMode.getModeId(); } // Determine whether the display mode specs' base mode is still there. if (mSupportedModes.indexOfKey(mDisplayModeSpecs.baseModeId) < 0) { - if (mDisplayModeSpecs.baseModeId != 0) { + if (mDisplayModeSpecs.baseModeId != NO_DISPLAY_MODE_ID) { Slog.w(TAG, "DisplayModeSpecs base mode no longer available, using currently" + " active mode."); @@ -345,7 +356,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { // Determine whether the active mode is still there. if (mSupportedModes.indexOfKey(mActiveModeId) < 0) { - if (mActiveModeId != 0) { + if (mActiveModeId != NO_DISPLAY_MODE_ID) { Slog.w(TAG, "Active display mode no longer available, reverting to default" + " mode."); } @@ -797,7 +808,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { } mActiveConfigId = activeConfigId; mActiveModeId = findMatchingModeIdLocked(activeConfigId); - mActiveModeInvalid = mActiveModeId == 0; + mActiveModeInvalid = mActiveModeId == NO_DISPLAY_MODE_ID; if (mActiveModeInvalid) { Slog.w(TAG, "In unknown mode after setting allowed configs" + ", activeConfigId=" + mActiveConfigId); @@ -922,7 +933,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { return record.mMode.getModeId(); } } - return 0; + return NO_DISPLAY_MODE_ID; } private void updateDeviceInfoLocked() { 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/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java index 5161a77e4ede..b0e2e6432c77 100644 --- a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java +++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java @@ -113,11 +113,27 @@ public final class IncrementalManagerShellCommand extends ShellCommand { return ERROR_COMMAND_EXECUTION; } - final Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = getDataLoaderDynamicArgs(); - if (dataLoaderDynamicArgs == null) { + final Map<String, ParcelFileDescriptor> fds = getShellFileDescriptors(); + if (fds == null) { pw.println("File names and sizes don't match."); return ERROR_DATA_LOADER_INIT; } + // dup FDs before closing them + final Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = new HashMap<>(); + for (Map.Entry<String, ParcelFileDescriptor> nfd : fds.entrySet()) { + try { + dataLoaderDynamicArgs.put(nfd.getKey(), nfd.getValue().dup()); + } catch (IOException ignored) { + pw.println("Failed to dup shell file descriptor"); + return ERROR_DATA_LOADER_INIT; + } finally { + try { + nfd.getValue().close(); + } catch (IOException ignored) { + } + } + } + final DataLoaderParams params = DataLoaderParams.forIncremental( new ComponentName(LOADER_PACKAGE_NAME, LOADER_CLASS_NAME), "", dataLoaderDynamicArgs); @@ -131,17 +147,9 @@ public final class IncrementalManagerShellCommand extends ShellCommand { try { int sessionId = packageInstaller.createSession(sessionParams); pw.println("Successfully opened session: sessionId = " + sessionId); - } catch (Exception ex) { + } catch (IOException ex) { pw.println("Failed to create session."); return ERROR_COMMAND_EXECUTION; - } finally { - try { - for (Map.Entry<String, ParcelFileDescriptor> nfd - : dataLoaderDynamicArgs.entrySet()) { - nfd.getValue().close(); - } - } catch (IOException ignored) { - } } return 0; } @@ -177,7 +185,8 @@ public final class IncrementalManagerShellCommand extends ShellCommand { InstallationFile file = installationFiles.get(i); final int location = file.getFileType() == FILE_TYPE_OBB ? LOCATION_MEDIA_OBB : LOCATION_DATA_APP; - session.addFile(location, file.getName(), file.getSize(), file.getMetadata(), null); + session.addFile(location, file.getName(), file.getSize(), file.getMetadata(), + null); } session.commit(localReceiver.getIntentSender()); final Intent result = localReceiver.getResult(); @@ -212,7 +221,8 @@ public final class IncrementalManagerShellCommand extends ShellCommand { private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { @Override - public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, + public void send(int code, Intent intent, String resolvedType, + IBinder whitelistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { try { @@ -237,14 +247,14 @@ public final class IncrementalManagerShellCommand extends ShellCommand { } /** Helpers. */ - private Map<String, ParcelFileDescriptor> getDataLoaderDynamicArgs() { - Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = new HashMap<>(); + private Map<String, ParcelFileDescriptor> getShellFileDescriptors() { + Map<String, ParcelFileDescriptor> fds = new HashMap<>(); final FileDescriptor outFd = getOutFileDescriptor(); final FileDescriptor inFd = getInFileDescriptor(); try { - dataLoaderDynamicArgs.put("inFd", ParcelFileDescriptor.dup(inFd)); - dataLoaderDynamicArgs.put("outFd", ParcelFileDescriptor.dup(outFd)); - return dataLoaderDynamicArgs; + fds.put("inFd", ParcelFileDescriptor.dup(inFd)); + fds.put("outFd", ParcelFileDescriptor.dup(outFd)); + return fds; } catch (Exception ex) { Slog.e(TAG, "Failed to dup FDs"); return null; @@ -292,7 +302,8 @@ public final class IncrementalManagerShellCommand extends ShellCommand { pw.println("Invalid file index in: " + fileArgs); return null; } - final byte[] metadata = String.valueOf(index).getBytes(StandardCharsets.UTF_8); + final byte[] metadata = String.valueOf(index).getBytes( + StandardCharsets.UTF_8); fileList.add(new InstallationFile(name, size, metadata)); break; } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 90358ca1f133..a8f706cdd629 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -223,7 +223,7 @@ public class InputManagerService extends IInputManager.Stub int displayId, InputApplicationHandle application); private static native void nativeSetFocusedDisplay(long ptr, int displayId); private static native boolean nativeTransferTouchFocus(long ptr, - InputChannel fromChannel, InputChannel toChannel); + IBinder fromChannelToken, IBinder toChannelToken); private static native void nativeSetPointerSpeed(long ptr, int speed); private static native void nativeSetShowTouches(long ptr, boolean enabled); private static native void nativeSetInteractive(long ptr, boolean interactive); @@ -1554,14 +1554,29 @@ public class InputManagerService extends IInputManager.Stub * @return True if the transfer was successful. False if the window with the * specified channel did not actually have touch focus at the time of the request. */ - public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) { - if (fromChannel == null) { - throw new IllegalArgumentException("fromChannel must not be null."); - } - if (toChannel == null) { - throw new IllegalArgumentException("toChannel must not be null."); - } - return nativeTransferTouchFocus(mPtr, fromChannel, toChannel); + public boolean transferTouchFocus(@NonNull InputChannel fromChannel, + @NonNull InputChannel toChannel) { + return nativeTransferTouchFocus(mPtr, fromChannel.getToken(), toChannel.getToken()); + } + + /** + * Atomically transfers touch focus from one window to another as identified by + * their input channels. It is possible for multiple windows to have + * touch focus if they support split touch dispatch + * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this + * method only transfers touch focus of the specified window without affecting + * other windows that may also have touch focus at the same time. + * @param fromChannelToken The channel token of a window that currently has touch focus. + * @param toChannelToken The channel token of the window that should receive touch focus in + * place of the first. + * @return True if the transfer was successful. False if the window with the + * specified channel did not actually have touch focus at the time of the request. + */ + public boolean transferTouchFocus(@NonNull IBinder fromChannelToken, + @NonNull IBinder toChannelToken) { + Objects.nonNull(fromChannelToken); + Objects.nonNull(toChannelToken); + return nativeTransferTouchFocus(mPtr, fromChannelToken, toChannelToken); } @Override // Binder call diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index 9754b6d4db02..68ced79b3e38 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,19 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { return; } - String appCert = getCertificateFingerprint(packageInfo); + List<String> appCertificates = getCertificateFingerprint(packageInfo); + List<String> installerCertificates = + getInstallerCertificateFingerprint(installerPackageName); + + Slog.w(TAG, appCertificates.toString()); AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder(); builder.setPackageName(getPackageNameNormalized(packageName)); - builder.setAppCertificate(appCert == null ? "" : appCert); + builder.setAppCertificates(appCertificates); builder.setVersionCode(intent.getLongExtra(EXTRA_LONG_VERSION_CODE, -1)); builder.setInstallerName(getPackageNameNormalized(installerPackageName)); - builder.setInstallerCertificate( - getInstallerCertificateFingerprint(installerPackageName)); + builder.setInstallerCertificates(installerCertificates); builder.setIsPreInstalled(isSystemApp(packageName)); AppInstallMetadata appInstallMetadata = builder.build(); @@ -290,7 +301,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { FrameworkStatsLog.write( FrameworkStatsLog.INTEGRITY_CHECK_RESULT_REPORTED, packageName, - appCert, + appCertificates.toString(), appInstallMetadata.getVersionCode(), installerPackageName, result.getLoggingResponse(), @@ -320,7 +331,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,25 +410,29 @@ 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. */ private Map<String, String> getAllowedInstallers(@NonNull PackageInfo packageInfo) { Map<String, String> packageCertMap = new HashMap<>(); @@ -445,12 +460,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 +507,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 +527,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/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java index 87eee4ea7be4..60e8cce55730 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java +++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java @@ -63,10 +63,12 @@ public class RuleIndexingController { searchIndexingKeysRangeContainingKey( sPackageNameBasedIndexes, appInstallMetadata.getPackageName())); - // Add the range for app certificate indexes rules. - indexRanges.add( - searchIndexingKeysRangeContainingKey( - sAppCertificateBasedIndexes, appInstallMetadata.getAppCertificate())); + // Add the range for app certificate indexes rules of all certificates. + for (String appCertificate : appInstallMetadata.getAppCertificates()) { + indexRanges.add( + searchIndexingKeysRangeContainingKey( + sAppCertificateBasedIndexes, appCertificate)); + } // Add the range for unindexed rules. indexRanges.add( 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/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java new file mode 100644 index 000000000000..58c2707a1f19 --- /dev/null +++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java @@ -0,0 +1,360 @@ +/* + * 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. + */ + +package com.android.server.media; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ComponentInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Handler; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; +import android.view.KeyEvent; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; + +/** + * Holds the media button receiver, and also provides helper methods around it. + */ +final class MediaButtonReceiverHolder { + public static final int COMPONENT_TYPE_INVALID = 0; + public static final int COMPONENT_TYPE_BROADCAST = 1; + public static final int COMPONENT_TYPE_ACTIVITY = 2; + public static final int COMPONENT_TYPE_SERVICE = 3; + + @IntDef(value = { + COMPONENT_TYPE_INVALID, + COMPONENT_TYPE_BROADCAST, + COMPONENT_TYPE_ACTIVITY, + COMPONENT_TYPE_SERVICE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ComponentType {} + + private static final String TAG = "PendingIntentHolder"; + private static final boolean DEBUG_KEY_EVENT = MediaSessionService.DEBUG_KEY_EVENT; + private static final String COMPONENT_NAME_USER_ID_DELIM = ","; + + private final int mUserId; + private final PendingIntent mPendingIntent; + private final ComponentName mComponentName; + private final String mPackageName; + @ComponentType + private final int mComponentType; + + /** + * Unflatten from string which is previously flattened string via flattenToString(). + * <p> + * It's used to store and restore media button receiver across the boot, by keeping the intent's + * component name to the persistent storage. + * + * @param mediaButtonReceiverInfo previously flattened string via flattenToString() + * @return new instance if the string was valid. {@code null} otherwise. + */ + public static MediaButtonReceiverHolder unflattenFromString( + Context context, String mediaButtonReceiverInfo) { + if (TextUtils.isEmpty(mediaButtonReceiverInfo)) { + return null; + } + String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM); + if (tokens == null || (tokens.length != 2 && tokens.length != 3)) { + return null; + } + ComponentName componentName = ComponentName.unflattenFromString(tokens[0]); + int userId = Integer.parseInt(tokens[1]); + // Guess component type if the OS version is updated from the older version. + int componentType = (tokens.length == 3) + ? Integer.parseInt(tokens[2]) + : getComponentType(context, componentName); + return new MediaButtonReceiverHolder(userId, null, componentName, componentType); + } + + /** + * Creates a new instance. + * + * @param context context + * @param userId userId + * @param pendingIntent pending intent + * @return Can be {@code null} if pending intent was null. + */ + public static MediaButtonReceiverHolder create(Context context, int userId, + PendingIntent pendingIntent) { + if (pendingIntent == null) { + return null; + } + ComponentName componentName = (pendingIntent != null && pendingIntent.getIntent() != null) + ? pendingIntent.getIntent().getComponent() : null; + if (componentName != null) { + // Explicit intent, where component name is in the PendingIntent. + return new MediaButtonReceiverHolder(userId, pendingIntent, componentName, + getComponentType(context, componentName)); + } + + // Implicit intent, where component name isn't in the PendingIntent. Try resolve. + PackageManager pm = context.getPackageManager(); + Intent intent = pendingIntent.getIntent(); + if ((componentName = resolveImplicitServiceIntent(pm, intent)) != null) { + return new MediaButtonReceiverHolder( + userId, pendingIntent, componentName, COMPONENT_TYPE_SERVICE); + } else if ((componentName = resolveManifestDeclaredBroadcastReceiverIntent(pm, intent)) + != null) { + return new MediaButtonReceiverHolder( + userId, pendingIntent, componentName, COMPONENT_TYPE_BROADCAST); + } else if ((componentName = resolveImplicitActivityIntent(pm, intent)) != null) { + return new MediaButtonReceiverHolder( + userId, pendingIntent, componentName, COMPONENT_TYPE_ACTIVITY); + } + + // Failed to resolve target component for the pending intent. It's unlikely to be usable. + // However, the pending intent would be still used, just to follow the legacy behavior. + Log.w(TAG, "Unresolvable implicit intent is set, pi=" + pendingIntent); + String packageName = (pendingIntent != null && pendingIntent.getIntent() != null) + ? pendingIntent.getIntent().getPackage() : null; + return new MediaButtonReceiverHolder(userId, pendingIntent, + packageName != null ? packageName : ""); + } + + private MediaButtonReceiverHolder(int userId, PendingIntent pendingIntent, + ComponentName componentName, @ComponentType int componentType) { + mUserId = userId; + mPendingIntent = pendingIntent; + mComponentName = componentName; + mPackageName = componentName.getPackageName(); + mComponentType = componentType; + } + + private MediaButtonReceiverHolder(int userId, PendingIntent pendingIntent, String packageName) { + mUserId = userId; + mPendingIntent = pendingIntent; + mComponentName = null; + mPackageName = packageName; + mComponentType = COMPONENT_TYPE_INVALID; + } + + /** + * @return the user id + */ + public int getUserId() { + return mUserId; + } + + /** + * @return package name that the media button receiver would be sent to. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * Sends the media key event to the media button receiver. + * <p> + * This prioritizes using use pending intent for sending media key event. + * + * @param context context to be used to call PendingIntent#send + * @param keyEvent keyEvent to send + * @param resultCode result code to be used to call PendingIntent#send + * Ignored if there's no valid pending intent. + * @param onFinishedListener callback to be used to get result of PendingIntent#send. + * Ignored if there's no valid pending intent. + * @param handler handler to be used to call onFinishedListener + * Ignored if there's no valid pending intent. + * @see PendingIntent#send(Context, int, Intent, PendingIntent.OnFinished, Handler) + */ + public boolean send(Context context, KeyEvent keyEvent, String callingPackageName, + int resultCode, PendingIntent.OnFinished onFinishedListener, Handler handler) { + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); + // TODO: Find a way to also send PID/UID in secure way. + mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackageName); + + if (mPendingIntent != null) { + if (DEBUG_KEY_EVENT) { + Log.d(TAG, "Sending " + keyEvent + " to the last known PendingIntent " + + mPendingIntent); + } + try { + mPendingIntent.send( + context, resultCode, mediaButtonIntent, onFinishedListener, handler); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Error sending key event to media button receiver " + mPendingIntent, e); + return false; + } + } else if (mComponentName != null) { + if (DEBUG_KEY_EVENT) { + Log.d(TAG, "Sending " + keyEvent + " to the restored intent " + + mComponentName + ", type=" + mComponentType); + } + mediaButtonIntent.setComponent(mComponentName); + UserHandle userHandle = UserHandle.of(mUserId); + try { + switch (mComponentType) { + case COMPONENT_TYPE_ACTIVITY: + context.startActivityAsUser(mediaButtonIntent, userHandle); + break; + case COMPONENT_TYPE_SERVICE: + context.startForegroundServiceAsUser(mediaButtonIntent, + userHandle); + break; + default: + // Legacy behavior for other cases. + context.sendBroadcastAsUser(mediaButtonIntent, userHandle); + } + } catch (Exception e) { + Log.w(TAG, "Error sending media button to the restored intent " + + mComponentName + ", type=" + mComponentType, e); + return false; + } + } else { + // Leave log, just in case. + Log.e(TAG, "Shouldn't be happen -- pending intent or component name must be set"); + return false; + } + return true; + } + + + @Override + public String toString() { + if (mPendingIntent != null) { + return "MBR {pi=" + mPendingIntent + ", type=" + mComponentType + "}"; + } + return "Restored MBR {component=" + mComponentName + ", type=" + mComponentType + "}"; + } + + /** + * @return flattened string. Can be empty string if the MBR is created with implicit intent. + */ + public String flattenToString() { + if (mComponentName == null) { + // We don't know which component would receive the key event. + return ""; + } + return String.join(COMPONENT_NAME_USER_ID_DELIM, + mComponentName.toString(), + String.valueOf(mUserId), + String.valueOf(mComponentType)); + } + + /** + * Gets the type of the component + * + * @param context context + * @param componentName component name + * @return A component type + */ + @ComponentType + private static int getComponentType(Context context, ComponentName componentName) { + if (componentName == null) { + return COMPONENT_TYPE_INVALID; + } + PackageManager pm = context.getPackageManager(); + try { + ActivityInfo activityInfo = pm.getActivityInfo(componentName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_ACTIVITIES); + if (activityInfo != null) { + return COMPONENT_TYPE_ACTIVITY; + } + } catch (PackageManager.NameNotFoundException e) { + } + try { + ServiceInfo serviceInfo = pm.getServiceInfo(componentName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_SERVICES); + if (serviceInfo != null) { + return COMPONENT_TYPE_SERVICE; + } + } catch (PackageManager.NameNotFoundException e) { + } + // Pick legacy behavior for BroadcastReceiver or unknown. + return COMPONENT_TYPE_BROADCAST; + } + + private static ComponentName resolveImplicitServiceIntent(PackageManager pm, Intent intent) { + // Flag explanations. + // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE: + // filter apps regardless of the phone's locked/unlocked state. + // - GET_SERVICES: Return service + return createComponentName(pm.resolveService(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_SERVICES)); + } + + private static ComponentName resolveManifestDeclaredBroadcastReceiverIntent( + PackageManager pm, Intent intent) { + // Flag explanations. + // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE: + // filter apps regardless of the phone's locked/unlocked state. + List<ResolveInfo> resolveInfos = pm.queryBroadcastReceivers(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + return (resolveInfos != null && !resolveInfos.isEmpty()) + ? createComponentName(resolveInfos.get(0)) : null; + } + + private static ComponentName resolveImplicitActivityIntent(PackageManager pm, Intent intent) { + // Flag explanations. + // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE: + // Filter apps regardless of the phone's locked/unlocked state. + // - MATCH_DEFAULT_ONLY: + // Implicit intent receiver should be set as default. Only needed for activity. + // - GET_ACTIVITIES: Return activity + return createComponentName(pm.resolveActivity(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DEFAULT_ONLY + | PackageManager.GET_ACTIVITIES)); + } + + private static ComponentName createComponentName(ResolveInfo resolveInfo) { + if (resolveInfo == null) { + return null; + } + ComponentInfo componentInfo; + // Code borrowed from ResolveInfo#getComponentInfo(). + if (resolveInfo.activityInfo != null) { + componentInfo = resolveInfo.activityInfo; + } else if (resolveInfo.serviceInfo != null) { + componentInfo = resolveInfo.serviceInfo; + } else { + // We're not interested in content provider. + return null; + } + // Code borrowed from ComponentInfo#getComponentName(). + try { + return new ComponentName(componentInfo.packageName, componentInfo.name); + } catch (IllegalArgumentException | NullPointerException e) { + // This may be happen if resolveActivity() end up with matching multiple activities. + // see PackageManager#resolveActivity(). + return null; + } + } +} diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 7b17b4ed6219..83588846ade8 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -63,7 +63,6 @@ abstract class MediaRoute2Provider { public abstract void sendControlRequest(String routeId, Intent request); public abstract void requestSetVolume(String routeId, int volume); - public abstract void requestUpdateVolume(String routeId, int delta); @NonNull public String getUniqueId() { diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index e22ea4681428..dd536ecd19fa 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -138,14 +138,6 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - @Override - public void requestUpdateVolume(String routeId, int delta) { - if (mConnectionReady) { - mActiveConnection.requestUpdateVolume(routeId, delta); - updateBinding(); - } - } - public boolean hasComponentName(String packageName, String className) { return mComponentName.getPackageName().equals(packageName) && mComponentName.getClassName().equals(className); @@ -419,6 +411,12 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv mActiveConnection.dispose(); mActiveConnection = null; setAndNotifyProviderState(null); + synchronized (mLock) { + for (RoutingSessionInfo sessionInfo : mSessionInfos) { + mCallback.onSessionReleased(this, sessionInfo); + } + mSessionInfos.clear(); + } } } @@ -518,14 +516,6 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - public void requestUpdateVolume(String routeId, int delta) { - try { - mProvider.requestUpdateVolume(routeId, delta); - } catch (RemoteException ex) { - Slog.e(TAG, "Failed to deliver request to request update volume.", ex); - } - } - @Override public void binderDied() { mHandler.post(() -> onConnectionDied(Connection.this)); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 358cc29e3d64..b1133223e59a 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -334,20 +334,6 @@ class MediaRouter2ServiceImpl { } } - public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) { - Objects.requireNonNull(client, "client must not be null"); - Objects.requireNonNull(route, "route must not be null"); - - final long token = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - requestUpdateVolumeLocked(client, route, delta); - } - } finally { - Binder.restoreCallingIdentity(token); - } - } - public void requestCreateClientSession(IMediaRouter2Manager manager, String packageName, MediaRoute2Info route, int requestId) { final long token = Binder.clearCallingIdentity(); @@ -375,21 +361,6 @@ class MediaRouter2ServiceImpl { } } - public void requestUpdateVolume2Manager(IMediaRouter2Manager manager, - MediaRoute2Info route, int delta) { - Objects.requireNonNull(manager, "manager must not be null"); - Objects.requireNonNull(route, "route must not be null"); - - final long token = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - requestUpdateVolumeLocked(manager, route, delta); - } - } finally { - Binder.restoreCallingIdentity(token); - } - } - @NonNull public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) { final long token = Binder.clearCallingIdentity(); @@ -628,18 +599,6 @@ class MediaRouter2ServiceImpl { } } - private void requestUpdateVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route, - int delta) { - final IBinder binder = client.asBinder(); - Client2Record clientRecord = mAllClientRecords.get(binder); - - if (clientRecord != null) { - clientRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::requestUpdateVolume, - clientRecord.mUserRecord.mHandler, route, delta)); - } - } - private void registerManagerLocked(IMediaRouter2Manager manager, int uid, int pid, String packageName, int userId, boolean trusted) { final IBinder binder = manager.asBinder(); @@ -713,18 +672,6 @@ class MediaRouter2ServiceImpl { } } - private void requestUpdateVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route, - int delta) { - final IBinder binder = manager.asBinder(); - ManagerRecord managerRecord = mAllManagerRecords.get(binder); - - if (managerRecord != null) { - managerRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::requestUpdateVolume, - managerRecord.mUserRecord.mHandler, route, delta)); - } - } - private List<RoutingSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) { final IBinder binder = manager.asBinder(); ManagerRecord managerRecord = mAllManagerRecords.get(binder); @@ -1459,13 +1406,6 @@ class MediaRouter2ServiceImpl { } } - private void requestUpdateVolume(MediaRoute2Info route, int delta) { - final MediaRoute2Provider provider = findProvider(route.getProviderId()); - if (provider != null) { - provider.requestUpdateVolume(route.getOriginalId(), delta); - } - } - private List<IMediaRouter2Client> getClients() { final List<IMediaRouter2Client> clients = new ArrayList<>(); MediaRouter2ServiceImpl service = mServiceRef.get(); diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index e1d38039b955..57f0328f1c00 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -539,12 +539,6 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) { - mService2.requestUpdateVolume2(client, route, delta); - } - - // Binder call - @Override public void requestSetVolume2Manager(IMediaRouter2Manager manager, MediaRoute2Info route, int volume) { mService2.requestSetVolume2Manager(manager, route, volume); @@ -552,13 +546,6 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public void requestUpdateVolume2Manager(IMediaRouter2Manager manager, - MediaRoute2Info route, int delta) { - mService2.requestUpdateVolume2Manager(manager, route, delta); - } - - // Binder call - @Override public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) { return mService2.getActiveSessions(manager); } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 7bcbcd4a3d09..9f47b349d3fc 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -127,7 +127,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR new ArrayList<>(); private long mFlags; - private PendingIntent mMediaButtonReceiver; + private MediaButtonReceiverHolder mMediaButtonReceiverHolder; private PendingIntent mLaunchIntent; // TransportPerformer fields @@ -220,8 +220,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR * * @return The pending intent set by the app or null. */ - public PendingIntent getMediaButtonReceiver() { - return mMediaButtonReceiver; + public MediaButtonReceiverHolder getMediaButtonReceiver() { + return mMediaButtonReceiverHolder; } /** @@ -471,7 +471,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR + ", userId=" + mUserId); pw.println(indent + "package=" + mPackageName); pw.println(indent + "launchIntent=" + mLaunchIntent); - pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiver); + pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiverHolder); pw.println(indent + "active=" + mIsActive); pw.println(indent + "flags=" + mFlags); pw.println(indent + "rating type=" + mRatingType); @@ -833,12 +833,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR @Override public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException { - if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) == 1) { - return; - } - mMediaButtonReceiver = pi; final long token = Binder.clearCallingIdentity(); try { + if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) + != 0) { + return; + } + mMediaButtonReceiverHolder = + MediaButtonReceiverHolder.create(mContext, mUserId, pi); mService.onMediaButtonReceiverChanged(MediaSessionRecord.this); } finally { Binder.restoreCallingIdentity(token); @@ -1529,5 +1531,4 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR msg.sendToTarget(); } } - } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 88b884efce83..7ffac062b7f5 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -18,22 +18,17 @@ package com.android.server.media; import static android.os.UserHandle.USER_ALL; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.INotificationManager; import android.app.KeyguardManager; import android.app.PendingIntent; -import android.app.PendingIntent.CanceledException; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; -import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.media.AudioManager; @@ -99,7 +94,7 @@ public class MediaSessionService extends SystemService implements Monitor { private static final String TAG = "MediaSessionService"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); // Leave log for key event always. - private static final boolean DEBUG_KEY_EVENT = true; + static final boolean DEBUG_KEY_EVENT = true; private static final int WAKELOCK_TIMEOUT = 5000; private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000; @@ -760,12 +755,6 @@ public class MediaSessionService extends SystemService implements Monitor { * <p>The contents of this object is guarded by {@link #mLock}. */ final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener { - public static final int COMPONENT_TYPE_INVALID = 0; - public static final int COMPONENT_TYPE_BROADCAST = 1; - public static final int COMPONENT_TYPE_ACTIVITY = 2; - public static final int COMPONENT_TYPE_SERVICE = 3; - private static final String COMPONENT_NAME_USER_ID_DELIM = ","; - private final int mFullUserId; private final MediaSessionStack mPriorityStack; private final HashMap<IBinder, OnMediaKeyEventDispatchedListenerRecord> @@ -774,10 +763,7 @@ public class MediaSessionService extends SystemService implements Monitor { mOnMediaKeyEventSessionChangedListeners = new HashMap<>(); private final SparseIntArray mUidToSessionCount = new SparseIntArray(); - private PendingIntent mLastMediaButtonReceiver; - private ComponentName mRestoredMediaButtonReceiver; - private int mRestoredMediaButtonReceiverComponentType; - private int mRestoredMediaButtonReceiverUserId; + private MediaButtonReceiverHolder mLastMediaButtonReceiverHolder; private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener; private int mOnVolumeKeyLongPressListenerUid; @@ -794,21 +780,9 @@ public class MediaSessionService extends SystemService implements Monitor { // Restore the remembered media button receiver before the boot. String mediaButtonReceiverInfo = Settings.Secure.getStringForUser(mContentResolver, Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId); - if (mediaButtonReceiverInfo == null) { - return; - } - String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM); - if (tokens == null || (tokens.length != 2 && tokens.length != 3)) { - return; - } - mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]); - mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]); - if (tokens.length == 3) { - mRestoredMediaButtonReceiverComponentType = Integer.parseInt(tokens[2]); - } else { - mRestoredMediaButtonReceiverComponentType = - getComponentType(mRestoredMediaButtonReceiver); - } + mLastMediaButtonReceiverHolder = + MediaButtonReceiverHolder.unflattenFromString( + mContext, mediaButtonReceiverInfo); } public void destroySessionsForUserLocked(int userId) { @@ -892,10 +866,7 @@ public class MediaSessionService extends SystemService implements Monitor { : mOnMediaKeyEventSessionChangedListeners.values()) { pw.println(indent + " from " + getCallingPackageName(cr.uid)); } - pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver); - pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver); - pw.println(indent + "Restored MediaButtonReceiverComponentType: " - + mRestoredMediaButtonReceiverComponentType); + pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiverHolder); mPriorityStack.dump(pw, indent); } @@ -924,25 +895,12 @@ public class MediaSessionService extends SystemService implements Monitor { return; } MediaSessionRecord sessionRecord = (MediaSessionRecord) record; - PendingIntent receiver = sessionRecord.getMediaButtonReceiver(); - mLastMediaButtonReceiver = receiver; - mRestoredMediaButtonReceiver = null; - mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID; - - String mediaButtonReceiverInfo = ""; - if (receiver != null) { - ComponentName component = receiver.getIntent().getComponent(); - if (component != null - && record.getPackageName().equals(component.getPackageName())) { - String componentName = component.flattenToString(); - int componentType = getComponentType(component); - mediaButtonReceiverInfo = String.join(COMPONENT_NAME_USER_ID_DELIM, - componentName, String.valueOf(record.getUserId()), - String.valueOf(componentType)); - } - } + mLastMediaButtonReceiverHolder = sessionRecord.getMediaButtonReceiver(); + String mediaButtonReceiverInfo = (mLastMediaButtonReceiverHolder == null) + ? "" : mLastMediaButtonReceiverHolder.flattenToString(); Settings.Secure.putStringForUser(mContentResolver, - Settings.System.MEDIA_BUTTON_RECEIVER, mediaButtonReceiverInfo, + Settings.System.MEDIA_BUTTON_RECEIVER, + mediaButtonReceiverInfo, mFullUserId); } @@ -958,15 +916,9 @@ public class MediaSessionService extends SystemService implements Monitor { } else { // TODO(jaewan): Implement } - } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) { - callback.onMediaKeyEventSessionChanged( - mCurrentFullUserRecord.mLastMediaButtonReceiver - .getIntent().getComponent().getPackageName(), - null); - } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) { - callback.onMediaKeyEventSessionChanged( - mCurrentFullUserRecord.mRestoredMediaButtonReceiver.getPackageName(), - null); + } else if (mCurrentFullUserRecord.mLastMediaButtonReceiverHolder != null) { + String packageName = mLastMediaButtonReceiverHolder.getPackageName(); + callback.onMediaKeyEventSessionChanged(packageName, null); } } catch (RemoteException e) { Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e); @@ -985,35 +937,6 @@ public class MediaSessionService extends SystemService implements Monitor { ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession(); } - private int getComponentType(@Nullable ComponentName componentName) { - if (componentName == null) { - return COMPONENT_TYPE_INVALID; - } - PackageManager pm = mContext.getPackageManager(); - try { - ActivityInfo activityInfo = pm.getActivityInfo(componentName, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.GET_ACTIVITIES); - if (activityInfo != null) { - return COMPONENT_TYPE_ACTIVITY; - } - } catch (NameNotFoundException e) { - } - try { - ServiceInfo serviceInfo = pm.getServiceInfo(componentName, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.GET_SERVICES); - if (serviceInfo != null) { - return COMPONENT_TYPE_SERVICE; - } - } catch (NameNotFoundException e) { - } - // Pick legacy behavior for BroadcastReceiver or unknown. - return COMPONENT_TYPE_BROADCAST; - } - final class OnMediaKeyEventDispatchedListenerRecord implements IBinder.DeathRecipient { public final IOnMediaKeyEventDispatchedListener callback; public final int uid; @@ -2166,79 +2089,31 @@ public class MediaSessionService extends SystemService implements Monitor { } catch (RemoteException e) { Log.w(TAG, "Failed to send callback", e); } - } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null - || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) { + } else if (mCurrentFullUserRecord.mLastMediaButtonReceiverHolder != null) { if (needWakeLock) { mKeyEventReceiver.acquireWakeLockLocked(); } - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); - // TODO: Find a way to also send PID/UID in secure way. - String callerPackageName = + String callingPackageName = (asSystemService) ? mContext.getPackageName() : packageName; - mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callerPackageName); - try { - if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) { - PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver; - if (DEBUG_KEY_EVENT) { - Log.d(TAG, "Sending " + keyEvent - + " to the last known PendingIntent " + receiver); - } - receiver.send(mContext, - needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, - mediaButtonIntent, mKeyEventReceiver, mHandler); - ComponentName componentName = mCurrentFullUserRecord - .mLastMediaButtonReceiver.getIntent().getComponent(); - if (componentName != null) { - for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr - : mCurrentFullUserRecord - .mOnMediaKeyEventDispatchedListeners.values()) { - cr.callback.onMediaKeyEventDispatched(keyEvent, - componentName.getPackageName(), null); - } - } - } else { - ComponentName receiver = - mCurrentFullUserRecord.mRestoredMediaButtonReceiver; - int componentType = mCurrentFullUserRecord - .mRestoredMediaButtonReceiverComponentType; - UserHandle userHandle = UserHandle.of(mCurrentFullUserRecord - .mRestoredMediaButtonReceiverUserId); - if (DEBUG_KEY_EVENT) { - Log.d(TAG, "Sending " + keyEvent + " to the restored intent " - + receiver + ", type=" + componentType); - } - mediaButtonIntent.setComponent(receiver); + + MediaButtonReceiverHolder mediaButtonReceiverHolder = + mCurrentFullUserRecord.mLastMediaButtonReceiverHolder; + + boolean sent = mediaButtonReceiverHolder.send( + mContext, keyEvent, callingPackageName, + needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mKeyEventReceiver, + mHandler); + if (sent) { + String pkgName = mediaButtonReceiverHolder.getPackageName(); + for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr + : mCurrentFullUserRecord + .mOnMediaKeyEventDispatchedListeners.values()) { try { - switch (componentType) { - case FullUserRecord.COMPONENT_TYPE_ACTIVITY: - mContext.startActivityAsUser(mediaButtonIntent, userHandle); - break; - case FullUserRecord.COMPONENT_TYPE_SERVICE: - mContext.startForegroundServiceAsUser(mediaButtonIntent, - userHandle); - break; - default: - // Legacy behavior for other cases. - mContext.sendBroadcastAsUser(mediaButtonIntent, userHandle); - } - } catch (Exception e) { - Log.w(TAG, "Error sending media button to the restored intent " - + receiver + ", type=" + componentType, e); - } - for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr - : mCurrentFullUserRecord - .mOnMediaKeyEventDispatchedListeners.values()) { - cr.callback.onMediaKeyEventDispatched(keyEvent, - receiver.getPackageName(), null); + cr.callback.onMediaKeyEventDispatched(keyEvent, pkgName, null); + } catch (RemoteException e) { + Log.w(TAG, "Failed notify key event dispatch, uid=" + cr.uid, e); } } - } catch (CanceledException e) { - Log.i(TAG, "Error sending key event to media button receiver " - + mCurrentFullUserRecord.mLastMediaButtonReceiver, e); - } catch (RemoteException e) { - Log.w(TAG, "Failed to send callback", e); } } } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 798a781a9d6c..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 @@ -153,11 +158,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { public void requestSetVolume(String routeId, int volume) { } - //TODO: implement method - @Override - public void requestUpdateVolume(String routeId, int delta) { - } - private void initializeDefaultRoute() { mDefaultRoute = new MediaRoute2Info.Builder( DEFAULT_ROUTE_ID, @@ -283,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/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index ef8f6479cb3d..3cafafffc62a 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -138,7 +138,7 @@ public class LockdownVpnTracker { if (egressDisconnected || egressChanged) { mAcceptedEgressIface = null; - mVpn.stopLegacyVpnPrivileged(); + mVpn.stopVpnRunnerPrivileged(); } if (egressDisconnected) { hideNotification(); @@ -218,7 +218,7 @@ public class LockdownVpnTracker { mAcceptedEgressIface = null; mErrorCount = 0; - mVpn.stopLegacyVpnPrivileged(); + mVpn.stopVpnRunnerPrivileged(); mVpn.setLockdown(false); hideNotification(); diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java index e8cb1632bdc3..061cbd8a1a40 100644 --- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java +++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java @@ -170,6 +170,11 @@ public class NotificationHistoryDatabase { mFileWriteHandler.post(rpr); } + public void deleteNotificationHistoryItem(String pkg, long postedTime) { + RemoveNotificationRunnable rnr = new RemoveNotificationRunnable(pkg, postedTime); + mFileWriteHandler.post(rnr); + } + public void addNotification(final HistoricalNotification notification) { synchronized (mLock) { mBuffer.addNewNotificationToWrite(notification); @@ -367,6 +372,49 @@ public class NotificationHistoryDatabase { } } + final class RemoveNotificationRunnable implements Runnable { + private String mPkg; + private long mPostedTime; + private NotificationHistory mNotificationHistory; + + public RemoveNotificationRunnable(String pkg, long postedTime) { + mPkg = pkg; + mPostedTime = postedTime; + } + + @VisibleForTesting + void setNotificationHistory(NotificationHistory nh) { + mNotificationHistory = nh; + } + + @Override + public void run() { + if (DEBUG) Slog.d(TAG, "RemovePackageRunnable"); + synchronized (mLock) { + // Remove from pending history + mBuffer.removeNotificationFromWrite(mPkg, mPostedTime); + + Iterator<AtomicFile> historyFileItr = mHistoryFiles.iterator(); + while (historyFileItr.hasNext()) { + final AtomicFile af = historyFileItr.next(); + try { + NotificationHistory notificationHistory = mNotificationHistory != null + ? mNotificationHistory + : new NotificationHistory(); + readLocked(af, notificationHistory, + new NotificationHistoryFilter.Builder().build()); + if(notificationHistory.removeNotificationFromWrite(mPkg, mPostedTime)) { + writeLocked(af, notificationHistory); + } + } catch (Exception e) { + Slog.e(TAG, "Cannot clean up file on notification removal " + + af.getBaseFile().getName(), e); + } + } + } + } + } + public static final class NotificationHistoryFileAttrProvider implements NotificationHistoryDatabase.FileAttrProvider { final static String TAG = "NotifHistoryFileDate"; diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java index 41bc29f7a82e..9aab0fd912e8 100644 --- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java +++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java @@ -130,7 +130,7 @@ public class NotificationHistoryManager { } } - public void onPackageRemoved(int userId, String packageName) { + public void onPackageRemoved(@UserIdInt int userId, String packageName) { synchronized (mLock) { if (!mUserUnlockedStates.get(userId, false)) { if (mHistoryEnabled.get(userId, false)) { @@ -150,6 +150,22 @@ public class NotificationHistoryManager { } } + public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) { + synchronized (mLock) { + int userId = UserHandle.getUserId(uid); + final NotificationHistoryDatabase userHistory = + getUserHistoryAndInitializeIfNeededLocked(userId); + // TODO: it shouldn't be possible to delete a notification entry while the user is + // locked but we should handle it + if (userHistory == null) { + Slog.w(TAG, "Attempted to remove notif for locked/gone/disabled user " + + userId); + return; + } + userHistory.deleteNotificationHistoryItem(pkg, postedTime); + } + } + // TODO: wire this up to AMS when power button is long pressed public void triggerWriteToDisk() { synchronized (mLock) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f6276fbf21a1..7ffd899215de 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -193,6 +193,7 @@ import android.provider.DeviceConfig; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.Condition; +import android.service.notification.ConversationChannelWrapper; import android.service.notification.IConditionProvider; import android.service.notification.INotificationListener; import android.service.notification.IStatusBarNotificationHolder; @@ -2681,18 +2682,24 @@ public class NotificationManagerService extends SystemService { @Override public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, int displayId, @Nullable ITransientNotificationCallback callback) { - enqueueToast(pkg, token, text, null, duration, displayId, callback); + enqueueToast(pkg, token, text, null, duration, displayId, callback, false); } @Override public void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, int displayId) { - enqueueToast(pkg, token, null, callback, duration, displayId, null); + enqueueToast(pkg, token, null, callback, duration, displayId, null, true); + } + + @Override + public void enqueueTextOrCustomToast(String pkg, IBinder token, + ITransientNotification callback, int duration, int displayId, boolean isCustom) { + enqueueToast(pkg, token, null, callback, duration, displayId, null, isCustom); } private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback, int duration, int displayId, - @Nullable ITransientNotificationCallback textCallback) { + @Nullable ITransientNotificationCallback textCallback, boolean isCustom) { if (DBG) { Slog.i(TAG, "enqueueToast pkg=" + pkg + " token=" + token + " duration=" + duration + " displayId=" + displayId); @@ -2730,7 +2737,7 @@ public class NotificationManagerService extends SystemService { return; } - if (callback != null && !appIsForeground && !isSystemToast) { + if (callback != null && !appIsForeground && !isSystemToast && isCustom) { boolean block; long id = Binder.clearCallingIdentity(); try { @@ -2774,7 +2781,7 @@ public class NotificationManagerService extends SystemService { if (!isSystemToast) { int count = 0; final int N = mToastQueue.size(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { final ToastRecord r = mToastQueue.get(i); if (r.pkg.equals(pkg)) { count++; @@ -2814,7 +2821,7 @@ public class NotificationManagerService extends SystemService { if (pkg == null || token == null) { Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " token=" + token); - return ; + return; } synchronized (mToastQueue) { @@ -2927,14 +2934,14 @@ public class NotificationManagerService extends SystemService { /** * Updates the enabled state for notifications for the given package (and uid). - * Additionally, this method marks the app importance as locked by the user, which means + * Additionally, this method marks the app importance as locked by the user, which + * means * that notifications from the app will <b>not</b> be considered for showing a * blocking helper. * - * @param pkg package that owns the notifications to update - * @param uid uid of the app providing notifications + * @param pkg package that owns the notifications to update + * @param uid uid of the app providing notifications * @param enabled whether notifications should be enabled for the app - * * @see #setNotificationsEnabledForPackage(String, int, boolean) */ @Override @@ -3025,6 +3032,12 @@ public class NotificationManagerService extends SystemService { } @Override + public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) { + checkCallerIsSystem(); + mHistoryManager.deleteNotificationHistoryItem(pkg, uid, postedTime); + } + + @Override public int getPackageImportance(String pkg) { checkCallerIsSystemOrSameApp(pkg); return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid()); @@ -3209,9 +3222,10 @@ public class NotificationManagerService extends SystemService { @Override public NotificationChannel getNotificationChannelForPackage(String pkg, int uid, - String channelId, boolean includeDeleted) { + String channelId, String conversationId, boolean includeDeleted) { checkCallerIsSystem(); - return mPreferencesHelper.getNotificationChannel(pkg, uid, channelId, includeDeleted); + return mPreferencesHelper.getConversationNotificationChannel( + pkg, uid, channelId, conversationId, true, includeDeleted); } @Override @@ -3348,6 +3362,27 @@ public class NotificationManagerService extends SystemService { } @Override + public ParceledListSlice<ConversationChannelWrapper> getConversationsForPackage(String pkg, + int uid) { + enforceSystemOrSystemUI("getConversationsForPackage"); + ArrayList<ConversationChannelWrapper> conversations = + mPreferencesHelper.getConversations(pkg, uid); + for (ConversationChannelWrapper conversation : conversations) { + LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery() + .setPackage(pkg) + .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED) + .setShortcutIds(Arrays.asList( + conversation.getNotificationChannel().getConversationId())); + List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts( + query, UserHandle.of(UserHandle.getUserId(uid))); + if (shortcuts != null && !shortcuts.isEmpty()) { + conversation.setShortcutInfo(shortcuts.get(0)); + } + } + return new ParceledListSlice<>(conversations); + } + + @Override public NotificationChannelGroup getPopulatedNotificationChannelGroupForPackage( String pkg, int uid, String groupId, boolean includeDeleted) { enforceSystemOrSystemUI("getPopulatedNotificationChannelGroupForPackage"); @@ -5387,8 +5422,8 @@ public class NotificationManagerService extends SystemService { try { fixNotification(notification, pkg, tag, id, userId); - } catch (NameNotFoundException e) { - Slog.e(TAG, "Cannot create a context for sending app", e); + } catch (Exception e) { + Slog.e(TAG, "Cannot fix notification", e); return; } @@ -7428,13 +7463,15 @@ public class NotificationManagerService extends SystemService { } @GuardedBy("mNotificationLock") - private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason, + private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, + @NotificationListenerService.NotificationCancelReason int reason, boolean wasPosted, String listenerName) { cancelNotificationLocked(r, sendDelete, reason, -1, -1, wasPosted, listenerName); } @GuardedBy("mNotificationLock") - private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason, + private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, + @NotificationListenerService.NotificationCancelReason int reason, int rank, int count, boolean wasPosted, String listenerName) { final String canceledKey = r.getKey(); @@ -7552,6 +7589,10 @@ public class NotificationManagerService extends SystemService { EventLogTags.writeNotificationCanceled(canceledKey, reason, r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now), rank, count, listenerName); + if (wasPosted) { + mNotificationRecordLogger.logNotificationCancelled(r, reason, + r.getStats().getDismissalSurface()); + } } @VisibleForTesting @@ -7594,7 +7635,7 @@ public class NotificationManagerService extends SystemService { for (int i = 0; i < newUris.size(); i++) { final Uri uri = newUris.valueAt(i); if (oldUris == null || !oldUris.contains(uri)) { - if (DBG) Slog.d(TAG, key + ": granting " + uri); + Slog.d(TAG, key + ": granting " + uri); grantUriPermission(permissionOwner, uri, newRecord.getUid(), targetPkg, targetUserId); } @@ -7631,6 +7672,8 @@ public class NotificationManagerService extends SystemService { targetUserId); } catch (RemoteException ignored) { // Ignored because we're in same process + } catch (SecurityException e) { + Slog.e(TAG, "Cannot grant uri access; " + sourceUid + " does not own " + uri); } finally { Binder.restoreCallingIdentity(ident); } @@ -9010,59 +9053,64 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") private void notifyPostedLocked(NotificationRecord r, NotificationRecord old, boolean notifyAllListeners) { - // Lazily initialized snapshots of the notification. - StatusBarNotification sbn = r.getSbn(); - StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null; - TrimCache trimCache = new TrimCache(sbn); + try { + // Lazily initialized snapshots of the notification. + StatusBarNotification sbn = r.getSbn(); + StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null; + TrimCache trimCache = new TrimCache(sbn); + + for (final ManagedServiceInfo info : getServices()) { + boolean sbnVisible = isVisibleToListener(sbn, info); + boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) + : false; + // This notification hasn't been and still isn't visible -> ignore. + if (!oldSbnVisible && !sbnVisible) { + continue; + } + // If the notification is hidden, don't notifyPosted listeners targeting < P. + // Instead, those listeners will receive notifyPosted when the notification is + // unhidden. + if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) { + continue; + } - for (final ManagedServiceInfo info : getServices()) { - boolean sbnVisible = isVisibleToListener(sbn, info); - boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false; - // This notification hasn't been and still isn't visible -> ignore. - if (!oldSbnVisible && !sbnVisible) { - continue; - } - // If the notification is hidden, don't notifyPosted listeners targeting < P. - // Instead, those listeners will receive notifyPosted when the notification is - // unhidden. - if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) { - continue; - } + // If we shouldn't notify all listeners, this means the hidden state of + // a notification was changed. Don't notifyPosted listeners targeting >= P. + // Instead, those listeners will receive notifyRankingUpdate. + if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) { + continue; + } - // If we shouldn't notify all listeners, this means the hidden state of - // a notification was changed. Don't notifyPosted listeners targeting >= P. - // Instead, those listeners will receive notifyRankingUpdate. - if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) { - continue; - } + final NotificationRankingUpdate update = makeRankingUpdateLocked(info); - final NotificationRankingUpdate update = makeRankingUpdateLocked(info); + // This notification became invisible -> remove the old one. + if (oldSbnVisible && !sbnVisible) { + final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight(); + mHandler.post(new Runnable() { + @Override + public void run() { + notifyRemoved( + info, oldSbnLightClone, update, null, REASON_USER_STOPPED); + } + }); + continue; + } + + // Grant access before listener is notified + final int targetUserId = (info.userid == UserHandle.USER_ALL) + ? UserHandle.USER_SYSTEM : info.userid; + updateUriPermissions(r, old, info.component.getPackageName(), targetUserId); - // This notification became invisible -> remove the old one. - if (oldSbnVisible && !sbnVisible) { - final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight(); + final StatusBarNotification sbnToPost = trimCache.ForListener(info); mHandler.post(new Runnable() { @Override public void run() { - notifyRemoved( - info, oldSbnLightClone, update, null, REASON_USER_STOPPED); + notifyPosted(info, sbnToPost, update); } }); - continue; } - - // Grant access before listener is notified - final int targetUserId = (info.userid == UserHandle.USER_ALL) - ? UserHandle.USER_SYSTEM : info.userid; - updateUriPermissions(r, old, info.component.getPackageName(), targetUserId); - - final StatusBarNotification sbnToPost = trimCache.ForListener(info); - mHandler.post(new Runnable() { - @Override - public void run() { - notifyPosted(info, sbnToPost, update); - } - }); + } catch (Exception e) { + Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e); } } @@ -9243,7 +9291,7 @@ public class NotificationManagerService extends SystemService { } BackgroundThread.getHandler().post(() -> { - if (hasCompanionDevice(serviceInfo)) { + if (serviceInfo.isSystem || hasCompanionDevice(serviceInfo)) { notifyNotificationChannelChanged( serviceInfo, pkg, user, channel, modificationType); } @@ -9263,7 +9311,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/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java index 8d8511f030f0..fc2d9e775149 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java @@ -16,10 +16,16 @@ package com.android.server.notification; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; +import static android.service.notification.NotificationListenerService.REASON_CLICK; +import static android.service.notification.NotificationListenerService.REASON_TIMEOUT; + import android.annotation.Nullable; import android.app.Notification; import android.app.Person; import android.os.Bundle; +import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationStats; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; @@ -44,22 +50,144 @@ public interface NotificationRecordLogger { int position, int buzzBeepBlink); /** + * Logs a notification cancel / dismiss event using UiEventReported (event ids from the + * NotificationCancelledEvents enum). + * @param r The NotificationRecord. If null, no action is taken. + * @param reason The reason the notification was canceled. + * @param dismissalSurface The surface the notification was dismissed from. + */ + void logNotificationCancelled(@Nullable NotificationRecord r, + @NotificationListenerService.NotificationCancelReason int reason, + @NotificationStats.DismissalSurface int dismissalSurface); + + /** * The UiEvent enums that this class can log. */ - enum NotificationReportedEvents implements UiEventLogger.UiEventEnum { - INVALID(0), + enum NotificationReportedEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "New notification enqueued to post") NOTIFICATION_POSTED(162), - @UiEvent(doc = "Notification substantially updated") + @UiEvent(doc = "Notification substantially updated, or alerted again.") NOTIFICATION_UPDATED(163); private final int mId; - NotificationReportedEvents(int id) { + NotificationReportedEvent(int id) { + mId = id; + } + @Override public int getId() { + return mId; + } + + public static NotificationReportedEvent fromRecordPair(NotificationRecordPair p) { + return (p.old != null) ? NotificationReportedEvent.NOTIFICATION_UPDATED : + NotificationReportedEvent.NOTIFICATION_POSTED; + } + } + + enum NotificationCancelledEvent implements UiEventLogger.UiEventEnum { + INVALID(0), + @UiEvent(doc = "Notification was canceled due to a notification click.") + NOTIFICATION_CANCEL_CLICK(164), + @UiEvent(doc = "Notification was canceled due to a user dismissal, surface not specified.") + NOTIFICATION_CANCEL_USER_OTHER(165), + @UiEvent(doc = "Notification was canceled due to a user dismiss-all (from the notification" + + " shade).") + NOTIFICATION_CANCEL_USER_CANCEL_ALL(166), + @UiEvent(doc = "Notification was canceled due to an inflation error.") + NOTIFICATION_CANCEL_ERROR(167), + @UiEvent(doc = "Notification was canceled by the package manager modifying the package.") + NOTIFICATION_CANCEL_PACKAGE_CHANGED(168), + @UiEvent(doc = "Notification was canceled by the owning user context being stopped.") + NOTIFICATION_CANCEL_USER_STOPPED(169), + @UiEvent(doc = "Notification was canceled by the user banning the package.") + NOTIFICATION_CANCEL_PACKAGE_BANNED(170), + @UiEvent(doc = "Notification was canceled by the app canceling this specific notification.") + NOTIFICATION_CANCEL_APP_CANCEL(171), + @UiEvent(doc = "Notification was canceled by the app cancelling all its notifications.") + NOTIFICATION_CANCEL_APP_CANCEL_ALL(172), + @UiEvent(doc = "Notification was canceled by a listener reporting a user dismissal.") + NOTIFICATION_CANCEL_LISTENER_CANCEL(173), + @UiEvent(doc = "Notification was canceled by a listener reporting a user dismiss all.") + NOTIFICATION_CANCEL_LISTENER_CANCEL_ALL(174), + @UiEvent(doc = "Notification was canceled because it was a member of a canceled group.") + NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED(175), + @UiEvent(doc = "Notification was canceled because it was an invisible member of a group.") + NOTIFICATION_CANCEL_GROUP_OPTIMIZATION(176), + @UiEvent(doc = "Notification was canceled by the device administrator suspending the " + + "package.") + NOTIFICATION_CANCEL_PACKAGE_SUSPENDED(177), + @UiEvent(doc = "Notification was canceled by the owning managed profile being turned off.") + NOTIFICATION_CANCEL_PROFILE_TURNED_OFF(178), + @UiEvent(doc = "Autobundled summary notification was canceled because its group was " + + "unbundled") + NOTIFICATION_CANCEL_UNAUTOBUNDLED(179), + @UiEvent(doc = "Notification was canceled by the user banning the channel.") + NOTIFICATION_CANCEL_CHANNEL_BANNED(180), + @UiEvent(doc = "Notification was snoozed.") + NOTIFICATION_CANCEL_SNOOZED(181), + @UiEvent(doc = "Notification was canceled due to timeout") + NOTIFICATION_CANCEL_TIMEOUT(182), + // Values 183-189 reserved for future system dismissal reasons + @UiEvent(doc = "Notification was canceled due to user dismissal of a peeking notification.") + NOTIFICATION_CANCEL_USER_PEEK(190), + @UiEvent(doc = "Notification was canceled due to user dismissal from the always-on display") + NOTIFICATION_CANCEL_USER_AOD(191), + @UiEvent(doc = "Notification was canceled due to user dismissal from the notification" + + " shade.") + NOTIFICATION_CANCEL_USER_SHADE(192), + @UiEvent(doc = "Notification was canceled due to user dismissal from the lockscreen") + NOTIFICATION_CANCEL_USER_LOCKSCREEN(193); + + private final int mId; + NotificationCancelledEvent(int id) { mId = id; } @Override public int getId() { return mId; } + public static NotificationCancelledEvent fromCancelReason( + @NotificationListenerService.NotificationCancelReason int reason, + @NotificationStats.DismissalSurface int surface) { + // Shouldn't be possible to get a non-dismissed notification here. + if (surface == NotificationStats.DISMISSAL_NOT_DISMISSED) { + if (NotificationManagerService.DBG) { + throw new IllegalArgumentException("Unexpected surface " + surface); + } + return INVALID; + } + // Most cancel reasons do not have a meaningful surface. Reason codes map directly + // to NotificationCancelledEvent codes. + if (surface == NotificationStats.DISMISSAL_OTHER) { + if ((REASON_CLICK <= reason) && (reason <= REASON_TIMEOUT)) { + return NotificationCancelledEvent.values()[reason]; + } + if (NotificationManagerService.DBG) { + throw new IllegalArgumentException("Unexpected cancel reason " + reason); + } + return INVALID; + } + // User cancels have a meaningful surface, which we differentiate by. See b/149038335 + // for caveats. + if (reason != REASON_CANCEL) { + if (NotificationManagerService.DBG) { + throw new IllegalArgumentException("Unexpected cancel with surface " + reason); + } + return INVALID; + } + switch (surface) { + case NotificationStats.DISMISSAL_PEEK: + return NOTIFICATION_CANCEL_USER_PEEK; + case NotificationStats.DISMISSAL_AOD: + return NOTIFICATION_CANCEL_USER_AOD; + case NotificationStats.DISMISSAL_SHADE: + return NOTIFICATION_CANCEL_USER_SHADE; + default: + if (NotificationManagerService.DBG) { + throw new IllegalArgumentException("Unexpected surface for user-dismiss " + + reason); + } + return INVALID; + } + } } /** @@ -88,7 +216,8 @@ public interface NotificationRecordLogger { return true; } - return !(Objects.equals(r.getSbn().getChannelIdLogTag(), old.getSbn().getChannelIdLogTag()) + return !(Objects.equals(r.getSbn().getChannelIdLogTag(), + old.getSbn().getChannelIdLogTag()) && Objects.equals(r.getSbn().getGroupLogTag(), old.getSbn().getGroupLogTag()) && (r.getSbn().getNotification().isGroupSummary() == old.getSbn().getNotification().isGroupSummary()) @@ -97,11 +226,6 @@ public interface NotificationRecordLogger { && (r.getImportance() == old.getImportance())); } - NotificationReportedEvents getUiEvent() { - return (old != null) ? NotificationReportedEvents.NOTIFICATION_UPDATED : - NotificationReportedEvents.NOTIFICATION_POSTED; - } - /** * @return hash code for the notification style class, or 0 if none exists. */ diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java index 4974c3001b9b..015d280535e6 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java @@ -16,6 +16,8 @@ package com.android.server.notification; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.util.FrameworkStatsLog; /** @@ -24,6 +26,8 @@ import com.android.internal.util.FrameworkStatsLog; */ public class NotificationRecordLoggerImpl implements NotificationRecordLogger { + UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + @Override public void logNotificationReported(NotificationRecord r, NotificationRecord old, int position, int buzzBeepBlink) { @@ -32,7 +36,7 @@ public class NotificationRecordLoggerImpl implements NotificationRecordLogger { return; } FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_REPORTED, - /* int32 event_id = 1 */ p.getUiEvent().getId(), + /* int32 event_id = 1 */ NotificationReportedEvent.fromRecordPair(p).getId(), /* int32 uid = 2 */ r.getUid(), /* string package_name = 3 */ r.getSbn().getPackageName(), /* int32 instance_id = 4 */ p.getInstanceId(), @@ -61,9 +65,10 @@ public class NotificationRecordLoggerImpl implements NotificationRecordLogger { ); } - - - - - + @Override + public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) { + mUiEventLogger.logWithInstanceId( + NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), + r.getUid(), r.getSbn().getPackageName(), r.getSbn().getInstanceId()); + } } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index fe39322edb42..a244c48fab6a 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -36,6 +36,7 @@ import android.metrics.LogMaker; import android.os.Build; import android.os.UserHandle; import android.provider.Settings; +import android.service.notification.ConversationChannelWrapper; import android.service.notification.NotificationListenerService; import android.service.notification.RankingHelperProto; import android.text.TextUtils; @@ -1176,6 +1177,43 @@ public class PreferencesHelper implements RankingConfig { return groups; } + public ArrayList<ConversationChannelWrapper> getConversations(String pkg, int uid) { + Objects.requireNonNull(pkg); + synchronized (mPackagePreferences) { + PackagePreferences r = getPackagePreferencesLocked(pkg, uid); + if (r == null) { + return new ArrayList<>(); + } + ArrayList<ConversationChannelWrapper> conversations = new ArrayList<>(); + int N = r.channels.size(); + for (int i = 0; i < N; i++) { + final NotificationChannel nc = r.channels.valueAt(i); + if (!TextUtils.isEmpty(nc.getConversationId()) && !nc.isDeleted()) { + ConversationChannelWrapper conversation = new ConversationChannelWrapper(); + conversation.setNotificationChannel(nc); + conversation.setParentChannelLabel( + r.channels.get(nc.getParentChannelId()).getName()); + boolean blockedByGroup = false; + if (nc.getGroup() != null) { + NotificationChannelGroup group = r.groups.get(nc.getGroup()); + if (group != null) { + if (group.isBlocked()) { + blockedByGroup = true; + } else { + conversation.setGroupLabel(group.getName()); + } + } + } + if (!blockedByGroup) { + conversations.add(conversation); + } + } + } + + return conversations; + } + } + @Override public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, boolean includeDeleted) { diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java index 639cc70fa275..90fc59a893a6 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -38,6 +38,8 @@ import android.util.Log; import android.util.LruCache; import android.util.Slog; +import libcore.util.EmptyArray; + import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; @@ -301,7 +303,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { for (String person: second) { people.add(person); } - return (String[]) people.toArray(); + return people.toArray(EmptyArray.STRING); } @Nullable 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/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 3ad120705eb3..d629b547992b 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -33,7 +33,7 @@ import android.content.pm.parsing.ComponentParseUtils.ParsedComponent; import android.content.pm.parsing.ComponentParseUtils.ParsedIntentInfo; import android.content.pm.parsing.ComponentParseUtils.ParsedProvider; import android.content.pm.parsing.ComponentParseUtils.ParsedService; -import android.net.Uri; +import android.os.Binder; import android.os.Process; import android.os.Trace; import android.os.UserHandle; @@ -86,10 +86,10 @@ public class AppsFilter { private final SparseSetArray<Integer> mQueriesViaPackage = new SparseSetArray<>(); /** - * A mapping from the set of App IDs that query others via intent to the list - * of packages that the intents resolve to. + * A mapping from the set of App IDs that query others via component match to the list + * of packages that the they resolve to. */ - private final SparseSetArray<Integer> mQueriesViaIntent = new SparseSetArray<>(); + private final SparseSetArray<Integer> mQueriesViaComponent = new SparseSetArray<>(); /** * A set of App IDs that are always queryable by any package, regardless of their manifest @@ -171,11 +171,13 @@ public class AppsFilter { @Override public boolean packageIsEnabled(AndroidPackage pkg) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "packageIsEnabled"); + final long token = Binder.clearCallingIdentity(); try { // TODO(b/135203078): Do not use toAppInfo return mInjector.getCompatibility().isChangeEnabled( PackageManager.FILTER_APPLICATION_QUERY, pkg.toAppInfoWithoutState()); } finally { + Binder.restoreCallingIdentity(token); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } @@ -203,16 +205,19 @@ public class AppsFilter { } /** Returns true if the querying package may query for the potential target package */ - private static boolean canQueryViaIntent(AndroidPackage querying, + private static boolean canQueryViaComponents(AndroidPackage querying, AndroidPackage potentialTarget) { - if (querying.getQueriesIntents() == null) { - return false; - } - for (Intent intent : querying.getQueriesIntents()) { - if (matches(intent, potentialTarget)) { - return true; + if (querying.getQueriesIntents() != null) { + for (Intent intent : querying.getQueriesIntents()) { + if (matchesIntentFilters(intent, potentialTarget)) { + return true; + } } } + if (querying.getQueriesProviders() != null + && matchesProviders(querying.getQueriesProviders(), potentialTarget)) { + return true; + } return false; } @@ -235,24 +240,25 @@ public class AppsFilter { return false; } - private static boolean matches(Intent intent, AndroidPackage potentialTarget) { + private static boolean matchesProviders( + Set<String> queriesAuthorities, AndroidPackage potentialTarget) { for (int p = ArrayUtils.size(potentialTarget.getProviders()) - 1; p >= 0; p--) { ParsedProvider provider = potentialTarget.getProviders().get(p); if (!provider.isExported()) { continue; } - final Uri data = intent.getData(); - if (!"content".equalsIgnoreCase(intent.getScheme()) || data == null - || provider.getAuthority() == null) { - continue; - } - StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";", false); + StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";", + false); while (authorities.hasMoreElements()) { - if (Objects.equals(authorities.nextElement(), data.getAuthority())) { + if (queriesAuthorities.contains(authorities.nextToken())) { return true; } } } + return false; + } + + private static boolean matchesIntentFilters(Intent intent, AndroidPackage potentialTarget) { for (int s = ArrayUtils.size(potentialTarget.getServices()) - 1; s >= 0; s--) { ParsedService service = potentialTarget.getServices().get(s); if (!service.exported) { @@ -365,8 +371,8 @@ public class AppsFilter { final AndroidPackage existingPkg = existingSetting.pkg; // let's evaluate the ability of already added packages to see this new package if (!newIsForceQueryable) { - if (canQueryViaIntent(existingPkg, newPkg)) { - mQueriesViaIntent.add(existingSetting.appId, newPkgSetting.appId); + if (canQueryViaComponents(existingPkg, newPkg)) { + mQueriesViaComponent.add(existingSetting.appId, newPkgSetting.appId); } if (canQueryViaPackage(existingPkg, newPkg) || canQueryAsInstaller(existingSetting, newPkg)) { @@ -375,8 +381,8 @@ public class AppsFilter { } // now we'll evaluate our new package's ability to see existing packages if (!mForceQueryable.contains(existingSetting.appId)) { - if (canQueryViaIntent(newPkg, existingPkg)) { - mQueriesViaIntent.add(newPkgSetting.appId, existingSetting.appId); + if (canQueryViaComponents(newPkg, existingPkg)) { + mQueriesViaComponent.add(newPkgSetting.appId, existingSetting.appId); } if (canQueryViaPackage(newPkg, existingPkg) || canQueryAsInstaller(newPkgSetting, existingPkg)) { @@ -424,9 +430,9 @@ public class AppsFilter { } } - mQueriesViaIntent.remove(setting.appId); - for (int i = mQueriesViaIntent.size() - 1; i >= 0; i--) { - mQueriesViaIntent.remove(mQueriesViaIntent.keyAt(i), setting.appId); + mQueriesViaComponent.remove(setting.appId); + for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) { + mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i), setting.appId); } mQueriesViaPackage.remove(setting.appId); for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) { @@ -591,10 +597,10 @@ public class AppsFilter { Trace.endSection(); } try { - Trace.beginSection("mQueriesViaIntent"); - if (mQueriesViaIntent.contains(callingAppId, targetAppId)) { + Trace.beginSection("mQueriesViaComponent"); + if (mQueriesViaComponent.contains(callingAppId, targetAppId)) { if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "queries intent"); + log(callingSetting, targetPkgSetting, "queries component"); } return false; } @@ -729,7 +735,7 @@ public class AppsFilter { pw.println(" queries via package name:"); dumpQueriesMap(pw, filteringAppId, mQueriesViaPackage, " ", expandPackages); pw.println(" queries via intent:"); - dumpQueriesMap(pw, filteringAppId, mQueriesViaIntent, " ", expandPackages); + dumpQueriesMap(pw, filteringAppId, mQueriesViaComponent, " ", expandPackages); pw.println(" queryable via interaction:"); for (int user : users) { pw.append(" User ").append(Integer.toString(user)).println(":"); diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 40ea6cfb6d4a..b98bb0831b0e 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -234,12 +234,12 @@ public class Installer extends SystemService { } public void moveCompleteApp(String fromUuid, String toUuid, String packageName, - String dataAppName, int appId, String seInfo, int targetSdkVersion) - throws InstallerException { + String dataAppName, int appId, String seInfo, int targetSdkVersion, + String fromCodePath) throws InstallerException { if (!checkBeforeRemote()) return; try { mInstalld.moveCompleteApp(fromUuid, toUuid, packageName, dataAppName, appId, seInfo, - targetSdkVersion); + targetSdkVersion, fromCodePath); } catch (Exception e) { throw InstallerException.from(e); } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 7bb782b9fadc..0cd1c259dd9e 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -29,12 +29,14 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ILauncherApps; import android.content.pm.IOnAppsChangedListener; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageManager; +import android.content.pm.IShortcutChangeCallback; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.ShortcutQuery; import android.content.pm.PackageInfo; @@ -659,10 +661,27 @@ public class LauncherAppsService extends SystemService { } } + private void ensureStrictAccessShortcutsPermission(@NonNull String callingPackage) { + verifyCallingPackage(callingPackage); + if (!injectHasAccessShortcutsPermission(injectBinderCallingPid(), + injectBinderCallingUid())) { + throw new SecurityException("Caller can't access shortcut information"); + } + } + + /** + * Returns true if the caller has the "ACCESS_SHORTCUTS" permission. + */ + @VisibleForTesting + boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) { + return mContext.checkPermission(android.Manifest.permission.ACCESS_SHORTCUTS, + callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; + } + @Override public ParceledListSlice getShortcuts(String callingPackage, long changedSince, - String packageName, List shortcutIds, ComponentName componentName, int flags, - UserHandle targetUser) { + String packageName, List shortcutIds, List<LocusId> locusIds, + ComponentName componentName, int flags, UserHandle targetUser) { ensureShortcutPermission(callingPackage); if (!canAccessProfile(targetUser.getIdentifier(), "Cannot get shortcuts")) { return new ParceledListSlice<>(Collections.EMPTY_LIST); @@ -671,16 +690,31 @@ public class LauncherAppsService extends SystemService { throw new IllegalArgumentException( "To query by shortcut ID, package name must also be set"); } + if (locusIds != null && packageName == null) { + throw new IllegalArgumentException( + "To query by locus ID, package name must also be set"); + } // TODO(b/29399275): Eclipse compiler requires explicit List<ShortcutInfo> cast below. return new ParceledListSlice<>((List<ShortcutInfo>) mShortcutServiceInternal.getShortcuts(getCallingUserId(), - callingPackage, changedSince, packageName, shortcutIds, + callingPackage, changedSince, packageName, shortcutIds, locusIds, componentName, flags, targetUser.getIdentifier(), injectBinderCallingPid(), injectBinderCallingUid())); } @Override + public void registerShortcutChangeCallback(String callingPackage, long changedSince, + String packageName, List shortcutIds, List<LocusId> locusIds, + ComponentName componentName, int flags, IShortcutChangeCallback callback, + int callbackId) { + } + + @Override + public void unregisterShortcutChangeCallback(String callingPackage, int callbackId) { + } + + @Override public void pinShortcuts(String callingPackage, String packageName, List<String> ids, UserHandle targetUser) { ensureShortcutPermission(callingPackage); @@ -693,6 +727,30 @@ public class LauncherAppsService extends SystemService { } @Override + public void cacheShortcuts(String callingPackage, String packageName, List<String> ids, + UserHandle targetUser) { + ensureStrictAccessShortcutsPermission(callingPackage); + if (!canAccessProfile(targetUser.getIdentifier(), "Cannot cache shortcuts")) { + return; + } + + mShortcutServiceInternal.cacheShortcuts(getCallingUserId(), + callingPackage, packageName, ids, targetUser.getIdentifier()); + } + + @Override + public void uncacheShortcuts(String callingPackage, String packageName, List<String> ids, + UserHandle targetUser) { + ensureStrictAccessShortcutsPermission(callingPackage); + if (!canAccessProfile(targetUser.getIdentifier(), "Cannot uncache shortcuts")) { + return; + } + + mShortcutServiceInternal.uncacheShortcuts(getCallingUserId(), + callingPackage, packageName, ids, targetUser.getIdentifier()); + } + + @Override public int getShortcutIconResId(String callingPackage, String packageName, String id, int targetUserId) { ensureShortcutPermission(callingPackage); @@ -1137,7 +1195,7 @@ public class LauncherAppsService extends SystemService { mShortcutServiceInternal.getShortcuts(launcherUserId, cookie.packageName, /* changedSince= */ 0, packageName, /* shortcutIds=*/ null, - /* component= */ null, + /* locusIds=*/ null, /* component= */ null, ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY | ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED , userId, cookie.callingPid, cookie.callingUid); 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 bf7bebd10a13..944280d6db88 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -129,6 +129,7 @@ import com.android.server.pm.dex.DexManager; import com.android.server.security.VerityUtils; import libcore.io.IoUtils; +import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -212,7 +213,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_SIGNATURE = "signature"; private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill"; - private static final int[] EMPTY_CHILD_SESSION_ARRAY = {}; + private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT; + private static final FileInfo[] EMPTY_FILE_INFO_ARRAY = {}; private static final String SYSTEM_DATA_LOADER_PACKAGE = "android"; @@ -375,8 +377,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // TODO(b/146080380): merge file list with Callback installation. private IncrementalFileStorages mIncrementalFileStorages; - private static final String[] EMPTY_STRING_ARRAY = new String[]{}; - private static final FileFilter sAddedApkFilter = new FileFilter() { @Override public boolean accept(File file) { @@ -558,10 +558,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionErrorMessage = stagedSessionErrorMessage != null ? stagedSessionErrorMessage : ""; - if (isStreamingInstallation() - && this.params.dataLoaderParams.getComponentName().getPackageName() - == SYSTEM_DATA_LOADER_PACKAGE) { - assertShellOrSystemCalling("System data loaders"); + if (isDataLoaderInstallation()) { + if (isApexInstallation()) { + throw new IllegalArgumentException( + "DataLoader installation of APEX modules is not allowed."); + } + } + + if (isStreamingInstallation()) { + if (!isIncrementalInstallationAllowed(mPackageName)) { + throw new IllegalArgumentException( + "Incremental installation of this package is not allowed."); + } + if (this.params.dataLoaderParams.getComponentName().getPackageName() + == SYSTEM_DATA_LOADER_PACKAGE) { + assertShellOrSystemCalling("System data loaders"); + } } } @@ -719,7 +731,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!isDataLoaderInstallation()) { String[] result = stageDir.list(); if (result == null) { - result = EMPTY_STRING_ARRAY; + result = EmptyArray.STRING; } return result; } @@ -1174,6 +1186,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } /** + * Checks if the package can be installed on IncFs. + */ + private static boolean isIncrementalInstallationAllowed(String packageName) { + final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); + final AndroidPackage existingPackage = pmi.getPackage(packageName); + if (existingPackage == null) { + return true; + } + + return !PackageManagerService.isSystemApp(existingPackage); + } + + /** * If this was not already called, the session will be sealed. * * This method may be called multiple times to update the status receiver validate caller @@ -1362,14 +1387,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return false; } - final PackageInfo pkgInfo = mPm.getPackageInfo( - params.appPackageName, PackageManager.GET_SIGNATURES - | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId); - if (isApexInstallation()) { validateApexInstallLocked(); } else { - validateApkInstallLocked(pkgInfo); + validateApkInstallLocked(); } } @@ -1660,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. @@ -1786,8 +1804,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * {@link PackageManagerService}. */ @GuardedBy("mLock") - private void validateApkInstallLocked(@Nullable PackageInfo pkgInfo) - throws PackageManagerException { + private void validateApkInstallLocked() throws PackageManagerException { ApkLite baseApk = null; mPackageName = null; mVersionCode = -1; @@ -1797,6 +1814,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mResolvedStagedFiles.clear(); mResolvedInheritedFiles.clear(); + final PackageInfo pkgInfo = mPm.getPackageInfo( + params.appPackageName, PackageManager.GET_SIGNATURES + | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId); + // Partial installs must be consistent with existing install if (params.mode == SessionParams.MODE_INHERIT_EXISTING && (pkgInfo == null || pkgInfo.applicationInfo == null)) { @@ -2236,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) { @@ -2248,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); @@ -3096,7 +3117,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } if (grantedRuntimePermissions.size() > 0) { - params.grantedRuntimePermissions = (String[]) grantedRuntimePermissions.toArray(); + params.grantedRuntimePermissions = + grantedRuntimePermissions.toArray(EmptyArray.STRING); } if (whitelistedRestrictedPermissions.size() > 0) { @@ -3115,7 +3137,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { FileInfo[] fileInfosArray = null; if (!files.isEmpty()) { - fileInfosArray = (FileInfo[]) files.toArray(); + fileInfosArray = files.toArray(EMPTY_FILE_INFO_ARRAY); } InstallSource installSource = InstallSource.create(installInitiatingPackageName, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8e5ff66a7561..88c048cea267 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -150,6 +150,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.AuxiliaryResolveInfo; import android.content.pm.ChangedPackages; import android.content.pm.ComponentInfo; +import android.content.pm.DataLoaderType; import android.content.pm.FallbackCategoryProvider; import android.content.pm.FeatureInfo; import android.content.pm.IDexModuleRegisterCallback; @@ -1658,7 +1659,8 @@ public class PackageManagerService extends IPackageManager.Stub handlePackagePostInstall(parentRes, grantPermissions, killApp, virtualPreload, grantedPermissions, whitelistedRestrictedPermissions, didRestore, - args.installSource.installerPackageName, args.observer); + args.installSource.installerPackageName, args.observer, + args.mDataLoaderType); // Handle the child packages final int childCount = (parentRes.addedChildPackages != null) @@ -1668,7 +1670,8 @@ public class PackageManagerService extends IPackageManager.Stub handlePackagePostInstall(childRes, grantPermissions, killApp, virtualPreload, grantedPermissions, whitelistedRestrictedPermissions, false /*didRestore*/, - args.installSource.installerPackageName, args.observer); + args.installSource.installerPackageName, args.observer, + args.mDataLoaderType); } // Log tracing if needed @@ -1995,7 +1998,7 @@ public class PackageManagerService extends IPackageManager.Stub boolean killApp, boolean virtualPreload, String[] grantedPermissions, List<String> whitelistedRestrictedPermissions, boolean launchedForRestore, String installerPackage, - IPackageInstallObserver2 installObserver) { + IPackageInstallObserver2 installObserver, int dataLoaderType) { final boolean succeeded = res.returnCode == PackageManager.INSTALL_SUCCEEDED; final boolean update = res.removedInfo != null && res.removedInfo.removedPackage != null; @@ -2096,11 +2099,14 @@ public class PackageManagerService extends IPackageManager.Stub if (update) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } + extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType); + // Send to all running apps. sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, extras, 0 /*flags*/, null /*targetPackage*/, null /*finishedReceiver*/, updateUserIds, instantUserIds); if (installerPackageName != null) { + // Send to the installer, even if it's not running. sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, extras, 0 /*flags*/, installerPackageName, null /*finishedReceiver*/, @@ -3621,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); @@ -13344,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; } /** @@ -13978,9 +13995,11 @@ public class PackageManagerService extends IPackageManager.Stub final int appId; final String seinfo; final int targetSdkVersion; + final String fromCodePath; public MoveInfo(int moveId, String fromUuid, String toUuid, String packageName, - String dataAppName, int appId, String seinfo, int targetSdkVersion) { + String dataAppName, int appId, String seinfo, int targetSdkVersion, + String fromCodePath) { this.moveId = moveId; this.fromUuid = fromUuid; this.toUuid = toUuid; @@ -13989,6 +14008,7 @@ public class PackageManagerService extends IPackageManager.Stub this.appId = appId; this.seinfo = seinfo; this.targetSdkVersion = targetSdkVersion; + this.fromCodePath = fromCodePath; } } @@ -14114,13 +14134,14 @@ public class PackageManagerService extends IPackageManager.Stub MultiPackageInstallParams mParentInstallParams; final long requiredInstalledVersionCode; final boolean forceQueryableOverride; + final int mDataLoaderType; InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer, int installFlags, InstallSource installSource, String volumeUuid, VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride, String[] grantedPermissions, List<String> whitelistedRestrictedPermissions, SigningDetails signingDetails, int installReason, - long requiredInstalledVersionCode) { + long requiredInstalledVersionCode, int dataLoaderType) { super(user); this.origin = origin; this.move = move; @@ -14136,40 +14157,42 @@ public class PackageManagerService extends IPackageManager.Stub this.installReason = installReason; this.requiredInstalledVersionCode = requiredInstalledVersionCode; this.forceQueryableOverride = false; + this.mDataLoaderType = dataLoaderType; } InstallParams(ActiveInstallSession activeInstallSession) { super(activeInstallSession.getUser()); + final PackageInstaller.SessionParams sessionParams = + activeInstallSession.getSessionParams(); if (DEBUG_INSTANT) { - if ((activeInstallSession.getSessionParams().installFlags + if ((sessionParams.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { Slog.d(TAG, "Ephemeral install of " + activeInstallSession.getPackageName()); } } verificationInfo = new VerificationInfo( - activeInstallSession.getSessionParams().originatingUri, - activeInstallSession.getSessionParams().referrerUri, - activeInstallSession.getSessionParams().originatingUid, + sessionParams.originatingUri, + sessionParams.referrerUri, + sessionParams.originatingUid, activeInstallSession.getInstallerUid()); origin = OriginInfo.fromStagedFile(activeInstallSession.getStagedDir()); move = null; installReason = fixUpInstallReason( activeInstallSession.getInstallSource().installerPackageName, activeInstallSession.getInstallerUid(), - activeInstallSession.getSessionParams().installReason); + sessionParams.installReason); observer = activeInstallSession.getObserver(); - installFlags = activeInstallSession.getSessionParams().installFlags; + installFlags = sessionParams.installFlags; installSource = activeInstallSession.getInstallSource(); - volumeUuid = activeInstallSession.getSessionParams().volumeUuid; - packageAbiOverride = activeInstallSession.getSessionParams().abiOverride; - grantedRuntimePermissions = activeInstallSession.getSessionParams() - .grantedRuntimePermissions; - whitelistedRestrictedPermissions = activeInstallSession.getSessionParams() - .whitelistedRestrictedPermissions; + volumeUuid = sessionParams.volumeUuid; + packageAbiOverride = sessionParams.abiOverride; + grantedRuntimePermissions = sessionParams.grantedRuntimePermissions; + whitelistedRestrictedPermissions = sessionParams.whitelistedRestrictedPermissions; signingDetails = activeInstallSession.getSigningDetails(); - requiredInstalledVersionCode = activeInstallSession.getSessionParams() - .requiredInstalledVersionCode; - forceQueryableOverride = activeInstallSession.getSessionParams().forceQueryableOverride; + requiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode; + forceQueryableOverride = sessionParams.forceQueryableOverride; + mDataLoaderType = (sessionParams.dataLoaderParams != null) + ? sessionParams.dataLoaderParams.getType() : DataLoaderType.NONE; } @Override @@ -14537,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); @@ -14772,6 +14795,7 @@ public class PackageManagerService extends IPackageManager.Stub final int installReason; final boolean forceQueryableOverride; @Nullable final MultiPackageInstallParams mMultiPackageInstallParams; + final int mDataLoaderType; // The list of instruction sets supported by this app. This is currently // only used during the rmdex() phase to clean up resources. We can get rid of this @@ -14785,7 +14809,7 @@ public class PackageManagerService extends IPackageManager.Stub List<String> whitelistedRestrictedPermissions, String traceMethod, int traceCookie, SigningDetails signingDetails, int installReason, boolean forceQueryableOverride, - MultiPackageInstallParams multiPackageInstallParams) { + MultiPackageInstallParams multiPackageInstallParams, int dataLoaderType) { this.origin = origin; this.move = move; this.installFlags = installFlags; @@ -14803,6 +14827,7 @@ public class PackageManagerService extends IPackageManager.Stub this.installReason = installReason; this.forceQueryableOverride = forceQueryableOverride; this.mMultiPackageInstallParams = multiPackageInstallParams; + this.mDataLoaderType = dataLoaderType; } /** New install */ @@ -14812,7 +14837,8 @@ public class PackageManagerService extends IPackageManager.Stub params.getUser(), null /*instructionSets*/, params.packageAbiOverride, params.grantedRuntimePermissions, params.whitelistedRestrictedPermissions, params.traceMethod, params.traceCookie, params.signingDetails, - params.installReason, params.forceQueryableOverride, params.mParentInstallParams); + params.installReason, params.forceQueryableOverride, + params.mParentInstallParams, params.mDataLoaderType); } abstract int copyApk(); @@ -14903,7 +14929,8 @@ public class PackageManagerService extends IPackageManager.Stub super(OriginInfo.fromNothing(), null, null, 0, InstallSource.EMPTY, null, null, instructionSets, null, null, null, null, 0, PackageParser.SigningDetails.UNKNOWN, - PackageManager.INSTALL_REASON_UNKNOWN, false, null /* parent */); + PackageManager.INSTALL_REASON_UNKNOWN, false, null /* parent */, + DataLoaderType.NONE); this.codeFile = (codePath != null) ? new File(codePath) : null; this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null; } @@ -14943,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; @@ -14983,9 +15011,7 @@ public class PackageManagerService extends IPackageManager.Stub try { makeDirRecursive(afterCodeFile.getParentFile(), 0775); if (onIncremental) { - // TODO(b/147371381): fix incremental installation - mIncrementalManager.rename(beforeCodeFile.getAbsolutePath(), - afterCodeFile.getAbsolutePath()); + mIncrementalManager.renameCodePath(beforeCodeFile, afterCodeFile); } else { Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath()); } @@ -15104,7 +15130,8 @@ public class PackageManagerService extends IPackageManager.Stub synchronized (mInstaller) { try { mInstaller.moveCompleteApp(move.fromUuid, move.toUuid, move.packageName, - move.dataAppName, move.appId, move.seinfo, move.targetSdkVersion); + move.dataAppName, move.appId, move.seinfo, move.targetSdkVersion, + move.fromCodePath); } catch (InstallerException e) { Slog.w(TAG, "Failed to move app", e); return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; @@ -22054,6 +22081,7 @@ public class PackageManagerService extends IPackageManager.Stub final PackageFreezer freezer; final int[] installedUserIds; final boolean isCurrentLocationExternal; + final String fromCodePath; // reader synchronized (mLock) { @@ -22110,6 +22138,7 @@ public class PackageManagerService extends IPackageManager.Stub targetSdkVersion = pkg.getTargetSdkVersion(); freezer = freezePackage(packageName, "movePackageInternal"); installedUserIds = ps.queryInstalledUsers(mUserManager.getUserIds(), true); + fromCodePath = pkg.getCodePath(); } final Bundle extras = new Bundle(); @@ -22238,7 +22267,7 @@ public class PackageManagerService extends IPackageManager.Stub final String dataAppName = codeFile.getName(); move = new MoveInfo(moveId, currentVolumeUuid, volumeUuid, packageName, - dataAppName, appId, seinfo, targetSdkVersion); + dataAppName, appId, seinfo, targetSdkVersion, fromCodePath); } else { move = null; } @@ -22251,7 +22280,8 @@ public class PackageManagerService extends IPackageManager.Stub installSource, volumeUuid, null /*verificationInfo*/, user, packageAbiOverride, null /*grantedPermissions*/, null /*whitelistedRestrictedPermissions*/, PackageParser.SigningDetails.UNKNOWN, - PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.VERSION_CODE_HIGHEST); + PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.VERSION_CODE_HIGHEST, + DataLoaderType.NONE); params.setTraceMethod("movePackage").setTraceCookie(System.identityHashCode(params)); msg.obj = params; @@ -22757,6 +22787,11 @@ public class PackageManagerService extends IPackageManager.Stub private class PackageManagerNative extends IPackageManagerNative.Stub { @Override + public String[] getAllPackages() { + return PackageManagerService.this.getAllPackages().toArray(new String[0]); + } + + @Override public String[] getNamesForUids(int[] uids) throws RemoteException { final String[] results = PackageManagerService.this.getNamesForUids(uids); // massage results so they can be parsed by the native binder 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/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index f7889ea6141c..377fd16d4e19 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -33,6 +33,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; +import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; @@ -1703,7 +1704,7 @@ public class ShortcutService extends IShortcutService.Stub { ShortcutInfo.validateIcon(shortcut.getIcon()); } - shortcut.replaceFlags(0); + shortcut.replaceFlags(shortcut.getFlags() & ShortcutInfo.FLAG_LONG_LIVED); } private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) { @@ -1985,7 +1986,6 @@ public class ShortcutService extends IShortcutService.Stub { // Verify if caller is the shortcut owner, only if caller doesn't have ACCESS_SHORTCUTS. verifyShortcutInfoPackage(callingPackage, shortcut); } - final String shortcutPackage = shortcut.getPackage(); final boolean ret; synchronized (mLock) { @@ -1999,6 +1999,7 @@ public class ShortcutService extends IShortcutService.Stub { // someone already), then we just replace the existing one with this new one, // and then proceed the rest of the process. if (shortcut != null) { + final String shortcutPackage = shortcut.getPackage(); final ShortcutPackage ps = getPackageShortcutsForPublisherLocked( shortcutPackage, userId); final String id = shortcut.getId(); @@ -2155,48 +2156,6 @@ public class ShortcutService extends IShortcutService.Stub { } @Override - public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName, - @UserIdInt int userId) { - verifyCaller(packageName, userId); - - synchronized (mLock) { - throwIfUserLockedL(userId); - - return getShortcutsWithQueryLocked( - packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, - ShortcutInfo::isDynamicVisible); - } - } - - @Override - public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName, - @UserIdInt int userId) { - verifyCaller(packageName, userId); - - synchronized (mLock) { - throwIfUserLockedL(userId); - - return getShortcutsWithQueryLocked( - packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, - ShortcutInfo::isManifestVisible); - } - } - - @Override - public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName, - @UserIdInt int userId) { - verifyCaller(packageName, userId); - - synchronized (mLock) { - throwIfUserLockedL(userId); - - return getShortcutsWithQueryLocked( - packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, - ShortcutInfo::isPinnedVisible); - } - } - - @Override public ParceledListSlice<ShortcutInfo> getShortcuts(String packageName, @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId) { verifyCaller(packageName, userId); @@ -2631,7 +2590,7 @@ public class ShortcutService extends IShortcutService.Stub { public List<ShortcutInfo> getShortcuts(int launcherUserId, @NonNull String callingPackage, long changedSince, @Nullable String packageName, @Nullable List<String> shortcutIds, - @Nullable ComponentName componentName, + @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName, int queryFlags, int userId, int callingPid, int callingUid) { final ArrayList<ShortcutInfo> ret = new ArrayList<>(); @@ -2652,15 +2611,16 @@ public class ShortcutService extends IShortcutService.Stub { if (packageName != null) { getShortcutsInnerLocked(launcherUserId, - callingPackage, packageName, shortcutIds, changedSince, + callingPackage, packageName, shortcutIds, locusIds, changedSince, componentName, queryFlags, userId, ret, cloneFlag, callingPid, callingUid); } else { final List<String> shortcutIdsF = shortcutIds; + final List<LocusId> locusIdsF = locusIds; getUserShortcutsLocked(userId).forAllPackages(p -> { getShortcutsInnerLocked(launcherUserId, - callingPackage, p.getPackageName(), shortcutIdsF, changedSince, - componentName, queryFlags, userId, ret, cloneFlag, + callingPackage, p.getPackageName(), shortcutIdsF, locusIdsF, + changedSince, componentName, queryFlags, userId, ret, cloneFlag, callingPid, callingUid); }); } @@ -2670,12 +2630,15 @@ public class ShortcutService extends IShortcutService.Stub { @GuardedBy("ShortcutService.this.mLock") private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage, - @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince, + @Nullable String packageName, @Nullable List<String> shortcutIds, + @Nullable List<LocusId> locusIds, long changedSince, @Nullable ComponentName componentName, int queryFlags, int userId, ArrayList<ShortcutInfo> ret, int cloneFlag, int callingPid, int callingUid) { final ArraySet<String> ids = shortcutIds == null ? null : new ArraySet<>(shortcutIds); + final ArraySet<LocusId> locIds = locusIds == null ? null + : new ArraySet<>(locusIds); final ShortcutUser user = getUserShortcutsLocked(userId); final ShortcutPackage p = user.getPackageShortcutsIfExists(packageName); @@ -2702,6 +2665,9 @@ public class ShortcutService extends IShortcutService.Stub { if (ids != null && !ids.contains(si.getId())) { return false; } + if (locIds != null && !locIds.contains(si.getLocusId())) { + return false; + } if (componentName != null) { if (si.getActivity() != null && !si.getActivity().equals(componentName)) { @@ -2792,6 +2758,68 @@ public class ShortcutService extends IShortcutService.Stub { } @Override + public void cacheShortcuts(int launcherUserId, + @NonNull String callingPackage, @NonNull String packageName, + @NonNull List<String> shortcutIds, int userId) { + updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds, + userId, /* doCache= */ true); + } + + @Override + public void uncacheShortcuts(int launcherUserId, + @NonNull String callingPackage, @NonNull String packageName, + @NonNull List<String> shortcutIds, int userId) { + updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds, + userId, /* doCache= */ false); + } + + private void updateCachedShortcutsInternal(int launcherUserId, + @NonNull String callingPackage, @NonNull String packageName, + @NonNull List<String> shortcutIds, int userId, boolean doCache) { + // Calling permission must be checked by LauncherAppsImpl. + Preconditions.checkStringNotEmpty(packageName, "packageName"); + Objects.requireNonNull(shortcutIds, "shortcutIds"); + + synchronized (mLock) { + throwIfUserLockedL(userId); + throwIfUserLockedL(launcherUserId); + + final int idSize = shortcutIds.size(); + final ShortcutPackage sp = getUserShortcutsLocked(userId) + .getPackageShortcutsIfExists(packageName); + if (idSize == 0 || sp == null) { + return; + } + + for (int i = 0; i < idSize; i++) { + final String id = Preconditions.checkStringNotEmpty(shortcutIds.get(i)); + final ShortcutInfo si = sp.findShortcutById(id); + if (si == null || doCache == si.isCached()) { + continue; + } + + if (doCache) { + if (si.isDynamic() && si.isLongLived()) { + si.addFlags(ShortcutInfo.FLAG_CACHED); + } else { + Log.w(TAG, "Only dynamic long lived shortcuts can get cached. Ignoring" + + "shortcut " + si.getId()); + } + } else { + if (si.isDynamic()) { + si.clearFlags(ShortcutInfo.FLAG_CACHED); + } else { + sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true); + } + } + } + } + packageShortcutsChanged(packageName, userId); + + verifyStates(); + } + + @Override public Intent[] createShortcutIntents(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId, 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/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java index 6daf5162ebad..6eba59acbc94 100644 --- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java +++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java @@ -402,7 +402,7 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn public String getStatus() { return mContext.getString( com.android.internal.R.string.bugreport_status, - Build.VERSION.RELEASE, + Build.VERSION.RELEASE_OR_CODENAME, Build.ID); } } diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 2c7795a6274b..43d45967f67b 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -39,9 +39,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManagerInternal.PackageListObserver; import android.content.pm.PermissionInfo; -import android.content.pm.parsing.AndroidPackage; import android.os.Build; -import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -52,14 +50,15 @@ import android.telecom.TelecomManager; import android.util.ArrayMap; import android.util.ArraySet; import android.util.LongSparseLongArray; -import android.util.Pair; import android.util.Slog; +import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.infra.AndroidFuture; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.IntPair; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; @@ -70,6 +69,7 @@ import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.ExecutionException; /** @@ -100,7 +100,7 @@ public final class PermissionPolicyService extends SystemService { * scheduled for a package/user. */ @GuardedBy("mLock") - private final ArraySet<Pair<String, Integer>> mIsPackageSyncsScheduled = new ArraySet<>(); + private final ArraySet<Integer> mIsPackageSyncsScheduled = new ArraySet<>(); public PermissionPolicyService(@NonNull Context context) { super(context); @@ -125,10 +125,8 @@ public final class PermissionPolicyService extends SystemService { @Override public void onPackageChanged(String packageName, int uid) { - final int userId = UserHandle.getUserId(uid); - - if (isStarted(userId)) { - synchronizePackagePermissionsAndAppOpsForUser(packageName, userId); + if (isStarted(UserHandle.getUserId(uid))) { + synchronizePackagePermissionsAndAppOpsForUser(uid); } } @@ -139,12 +137,21 @@ public final class PermissionPolicyService extends SystemService { }); permManagerInternal.addOnRuntimePermissionStateChangedListener( - this::synchronizePackagePermissionsAndAppOpsAsyncForUser); + (packageName, userId) -> { + int uid; + try { + uid = getContext().getPackageManager().getPackageUidAsUser(packageName, 0, + userId); + } catch (NameNotFoundException e) { + Slog.e(LOG_TAG, "Cannot synchronize changed package " + packageName, e); + return; + } + synchronizeUidPermissionsAndAppOpsAsync(uid); + }); mAppOpsCallback = new IAppOpsCallback.Stub() { public void opChanged(int op, int uid, String packageName) { - synchronizePackagePermissionsAndAppOpsAsyncForUser(packageName, - UserHandle.getUserId(uid)); + synchronizeUidPermissionsAndAppOpsAsync(uid); } }; @@ -194,19 +201,17 @@ public final class PermissionPolicyService extends SystemService { return AppOpsManager.opToSwitch(op); } - private void synchronizePackagePermissionsAndAppOpsAsyncForUser(@NonNull String packageName, - @UserIdInt int changedUserId) { - if (isStarted(changedUserId)) { + private void synchronizeUidPermissionsAndAppOpsAsync(int uid) { + if (isStarted(UserHandle.getUserId(uid))) { synchronized (mLock) { - if (mIsPackageSyncsScheduled.add(new Pair<>(packageName, changedUserId))) { + if (mIsPackageSyncsScheduled.add(uid)) { FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( PermissionPolicyService ::synchronizePackagePermissionsAndAppOpsForUser, - this, packageName, changedUserId)); + this, uid)); } else { if (DEBUG) { - Slog.v(LOG_TAG, "sync for " + packageName + "/" + changedUserId - + " already scheduled"); + Slog.v(LOG_TAG, "sync for " + uid + " already scheduled"); } } } @@ -335,39 +340,20 @@ public final class PermissionPolicyService extends SystemService { /** * Synchronize a single package. */ - private void synchronizePackagePermissionsAndAppOpsForUser(@NonNull String packageName, - @UserIdInt int userId) { + private void synchronizePackagePermissionsAndAppOpsForUser(int uid) { synchronized (mLock) { - mIsPackageSyncsScheduled.remove(new Pair<>(packageName, userId)); + mIsPackageSyncsScheduled.remove(uid); } if (DEBUG) { Slog.v(LOG_TAG, - "synchronizePackagePermissionsAndAppOpsForUser(" + packageName + ", " - + userId + ")"); + "synchronizePackagePermissionsAndAppOpsForUser(" + uid + ")"); } - final PackageManagerInternal packageManagerInternal = LocalServices.getService( - PackageManagerInternal.class); - final PackageInfo pkg = packageManagerInternal.getPackageInfo(packageName, 0, - Process.SYSTEM_UID, userId); - if (pkg == null) { - return; - } final PermissionToOpSynchroniser synchroniser = new PermissionToOpSynchroniser( - getUserContext(getContext(), UserHandle.of(userId))); - synchroniser.addPackage(pkg.packageName); - final String[] sharedPkgNames = packageManagerInternal.getSharedUserPackagesForPackage( - pkg.packageName, userId); - - for (String sharedPkgName : sharedPkgNames) { - final AndroidPackage sharedPkg = packageManagerInternal - .getPackage(sharedPkgName); - if (sharedPkg != null) { - synchroniser.addPackage(sharedPkg.getPackageName()); - } - } - synchroniser.syncPackages(); + getUserContext(getContext(), UserHandle.getUserHandleForUid(uid))); + synchroniser.addUid(uid); + synchroniser.syncUids(); } /** @@ -381,8 +367,8 @@ public final class PermissionPolicyService extends SystemService { final PermissionToOpSynchroniser synchronizer = new PermissionToOpSynchroniser( getUserContext(getContext(), UserHandle.of(userId))); packageManagerInternal.forEachPackage( - (pkg) -> synchronizer.addPackage(pkg.getPackageName())); - synchronizer.syncPackages(); + (pkg) -> synchronizer.addUid(pkg.getUid())); + synchronizer.syncUids(); } /** @@ -397,37 +383,51 @@ public final class PermissionPolicyService extends SystemService { private final @NonNull ArrayMap<String, PermissionInfo> mRuntimePermissionInfos; + // Cache uid -> packageNames + private SparseArray<String[]> mUidToPkg = new SparseArray<>(); + /** * All ops that need to be flipped to allow. * - * @see #syncPackages + * @see #syncUids */ - private final @NonNull ArrayList<OpToChange> mOpsToAllow = new ArrayList<>(); + private final @NonNull ArraySet<OpToChange> mOpsToAllow = new ArraySet<>(); /** * All ops that need to be flipped to ignore. * - * @see #syncPackages + * @see #syncUids */ - private final @NonNull ArrayList<OpToChange> mOpsToIgnore = new ArrayList<>(); + private final @NonNull ArraySet<OpToChange> mOpsToIgnore = new ArraySet<>(); /** * All ops that need to be flipped to ignore if not allowed. * * Currently, only used by soft restricted permissions logic. * - * @see #syncPackages + * @see #syncUids */ - private final @NonNull ArrayList<OpToChange> mOpsToIgnoreIfNotAllowed = new ArrayList<>(); + private final @NonNull ArraySet<OpToChange> mOpsToIgnoreIfNotAllowed = new ArraySet<>(); /** * All ops that need to be flipped to foreground. * * Currently, only used by the foreground/background permissions logic. * - * @see #syncPackages + * @see #syncUids */ - private final @NonNull ArrayList<OpToChange> mOpsToForeground = new ArrayList<>(); + private final @NonNull ArraySet<OpToChange> mOpsToForeground = new ArraySet<>(); + + private @Nullable String[] getPackageNamesForUid(int uid) { + String[] pkgs = mUidToPkg.get(uid); + if (pkgs != null) { + return pkgs; + } + + pkgs = mPackageManager.getPackagesForUid(uid); + mUidToPkg.put(uid, pkgs); + return pkgs; + } PermissionToOpSynchroniser(@NonNull Context context) { mContext = context; @@ -449,11 +449,11 @@ public final class PermissionPolicyService extends SystemService { } /** - * Set app ops that were added in {@link #addPackage}. + * Set app ops that were added in {@link #addUid}. * * <p>This processes ops previously added by {@link #addAppOps(PackageInfo, String)} */ - private void syncPackages() { + private void syncUids() { // Remember which ops were already set. This makes sure that we always set the most // permissive mode if two OpChanges are scheduled. This can e.g. happen if two // permissions change the same op. See {@link #getSwitchOp}. @@ -461,42 +461,42 @@ public final class PermissionPolicyService extends SystemService { final int allowCount = mOpsToAllow.size(); for (int i = 0; i < allowCount; i++) { - final OpToChange op = mOpsToAllow.get(i); + final OpToChange op = mOpsToAllow.valueAt(i); - setUidModeAllowed(op.code, op.uid, op.packageName); + setUidModeAllowed(op.code, op.uid); alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); } final int foregroundCount = mOpsToForeground.size(); for (int i = 0; i < foregroundCount; i++) { - final OpToChange op = mOpsToForeground.get(i); + final OpToChange op = mOpsToForeground.valueAt(i); if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) { continue; } - setUidModeForeground(op.code, op.uid, op.packageName); + setUidModeForeground(op.code, op.uid); alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); } final int ignoreCount = mOpsToIgnore.size(); for (int i = 0; i < ignoreCount; i++) { - final OpToChange op = mOpsToIgnore.get(i); + final OpToChange op = mOpsToIgnore.valueAt(i); if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) { continue; } - setUidModeIgnored(op.code, op.uid, op.packageName); + setUidModeIgnored(op.code, op.uid); alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); } final int ignoreIfNotAllowedCount = mOpsToIgnoreIfNotAllowed.size(); for (int i = 0; i < ignoreIfNotAllowedCount; i++) { - final OpToChange op = mOpsToIgnoreIfNotAllowed.get(i); + final OpToChange op = mOpsToIgnoreIfNotAllowed.valueAt(i); if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) { continue; } - boolean wasSet = setUidModeIgnoredIfNotAllowed(op.code, op.uid, op.packageName); + boolean wasSet = setUidModeIgnoredIfNotAllowed(op.code, op.uid); if (wasSet) { alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); } @@ -555,7 +555,7 @@ public final class PermissionPolicyService extends SystemService { } int uid = packageInfo.applicationInfo.uid; - OpToChange opToChange = new OpToChange(uid, packageName, appOpCode); + OpToChange opToChange = new OpToChange(uid, appOpCode); switch (appOpMode) { case MODE_ALLOWED: mOpsToAllow.add(opToChange); @@ -618,8 +618,7 @@ public final class PermissionPolicyService extends SystemService { } int uid = packageInfo.applicationInfo.uid; - String packageName = packageInfo.packageName; - OpToChange extraOpToChange = new OpToChange(uid, packageName, extraOpCode); + OpToChange extraOpToChange = new OpToChange(uid, extraOpCode); if (policy.mayAllowExtraAppOp()) { mOpsToAllow.add(extraOpToChange); } else { @@ -632,45 +631,56 @@ public final class PermissionPolicyService extends SystemService { } /** - * Add a package for {@link #syncPackages() processing} later. + * Add a Uid for {@link #syncUids() processing} later. * * <p>Note: Called with the package lock held. Do <u>not</u> call into app-op manager. * - * @param pkgName The package to add for later processing. + * @param uid The uid to add for later processing. */ - void addPackage(@NonNull String pkgName) { - final PackageInfo pkg; - try { - pkg = mPackageManager.getPackageInfo(pkgName, GET_PERMISSIONS); - } catch (NameNotFoundException e) { + void addUid(int uid) { + String[] pkgNames = getPackageNamesForUid(uid); + if (pkgNames == null) { return; } - if (pkg.requestedPermissions == null) { - return; - } + for (String pkgName : pkgNames) { + final PackageInfo pkg; + try { + pkg = mPackageManager.getPackageInfo(pkgName, GET_PERMISSIONS); + } catch (NameNotFoundException e) { + continue; + } + + if (pkg.requestedPermissions == null) { + continue; + } - for (String permission : pkg.requestedPermissions) { - addAppOps(pkg, permission); + for (String permission : pkg.requestedPermissions) { + addAppOps(pkg, permission); + } } } - private void setUidModeAllowed(int opCode, int uid, @NonNull String packageName) { - setUidMode(opCode, uid, MODE_ALLOWED, packageName); + private void setUidModeAllowed(int opCode, int uid) { + setUidMode(opCode, uid, MODE_ALLOWED); } - private void setUidModeForeground(int opCode, int uid, @NonNull String packageName) { - setUidMode(opCode, uid, MODE_FOREGROUND, packageName); + private void setUidModeForeground(int opCode, int uid) { + setUidMode(opCode, uid, MODE_FOREGROUND); } - private void setUidModeIgnored(int opCode, int uid, @NonNull String packageName) { - setUidMode(opCode, uid, MODE_IGNORED, packageName); + private void setUidModeIgnored(int opCode, int uid) { + setUidMode(opCode, uid, MODE_IGNORED); } - private boolean setUidModeIgnoredIfNotAllowed(int opCode, int uid, - @NonNull String packageName) { + private boolean setUidModeIgnoredIfNotAllowed(int opCode, int uid) { + String[] pkgsOfUid = getPackageNamesForUid(uid); + if (ArrayUtils.isEmpty(pkgsOfUid)) { + return false; + } + final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName( - opCode), uid, packageName); + opCode), uid, pkgsOfUid[0]); if (currentMode != MODE_ALLOWED) { if (currentMode != MODE_IGNORED) { mAppOpsManagerInternal.setUidModeIgnoringCallback(opCode, uid, MODE_IGNORED, @@ -681,20 +691,24 @@ public final class PermissionPolicyService extends SystemService { return false; } - private void setUidMode(int opCode, int uid, int mode, - @NonNull String packageName) { + private void setUidMode(int opCode, int uid, int mode) { + String[] pkgsOfUid = getPackageNamesForUid(uid); + if (ArrayUtils.isEmpty(pkgsOfUid)) { + return; + } + final int oldMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName( - opCode), uid, packageName); + opCode), uid, pkgsOfUid[0]); if (oldMode != mode) { mAppOpsManagerInternal.setUidModeIgnoringCallback(opCode, uid, mode, mAppOpsCallback); final int newMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName( - opCode), uid, packageName); + opCode), uid, pkgsOfUid[0]); if (newMode != mode) { // Work around incorrectly-set package mode. It never makes sense for app ops // related to runtime permissions, but can get in the way and we have to reset // it. - mAppOpsManagerInternal.setModeIgnoringCallback(opCode, uid, packageName, + mAppOpsManagerInternal.setModeIgnoringCallback(opCode, uid, pkgsOfUid[0], AppOpsManager.opToDefaultMode(opCode), mAppOpsCallback); } } @@ -702,14 +716,30 @@ public final class PermissionPolicyService extends SystemService { private class OpToChange { final int uid; - final @NonNull String packageName; final int code; - OpToChange(int uid, @NonNull String packageName, int code) { + OpToChange(int uid, int code) { this.uid = uid; - this.packageName = packageName; this.code = code; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + OpToChange other = (OpToChange) o; + return uid == other.uid && code == other.code; + } + + @Override + public int hashCode() { + return Objects.hash(uid, code); + } } } diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java index 81ec46634e8a..d7543805c2c6 100644 --- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java +++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java @@ -122,8 +122,9 @@ public abstract class SoftRestrictedPermissionPolicy { PackageManager pm = context.getPackageManager(); int flags = pm.getPermissionFlags(permission, appInfo.packageName, user); isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; - shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; targetSDK = getMinimumTargetSDK(context, appInfo, user); + shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0 + || targetSDK > Build.VERSION_CODES.Q; hasRequestedLegacyExternalStorage = hasUidRequestedLegacyExternalStorage( appInfo.uid, context); hasWriteMediaStorageGrantedForUid = hasWriteMediaStorageGrantedForUid( diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java index e77839c4e5b5..524ae54972e5 100644 --- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java +++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java @@ -224,6 +224,13 @@ public class AppDataRollbackHelper { } /** + * Deletes all device-encrypted apex data snapshots for the given rollback id. + */ + public void destroyApexDeSnapshots(int rollbackId) { + mApexManager.destroyDeSnapshots(rollbackId); + } + + /** * Commits the pending backups and restores for a given {@code userId} and {@code rollback}. If * the rollback has a pending backup, it is updated with a mapping from {@code userId} to inode * of the CE user data snapshot. diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index 4d7af9cc0d44..5abd9f0698d3 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -44,7 +44,6 @@ import android.util.SparseLongArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; -import com.android.server.pm.ApexManager; import java.io.File; import java.io.IOException; @@ -182,6 +181,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. @@ -662,7 +670,7 @@ class Rollback { } } if (containsApex) { - ApexManager.getInstance().destroyDeSnapshots(info.getRollbackId()); + dataHelper.destroyApexDeSnapshots(info.getRollbackId()); } RollbackStore.deleteRollback(this); @@ -829,6 +837,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..1be6f227b44b 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; @@ -33,7 +38,6 @@ import android.content.rollback.PackageRollbackInfo; import android.content.rollback.RollbackInfo; import android.util.ArraySet; import android.util.Slog; -import android.util.StatsLog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; @@ -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. @@ -169,12 +206,25 @@ public final class WatchdogRollbackLogger { + " rollbackReason: " + rollbackReasonToString(rollbackReason) + " failedPackageName: " + failingPackageName); if (logPackage != null) { - StatsLog.logWatchdogRollbackOccurred(type, logPackage.getPackageName(), - logPackage.getVersionCode(), rollbackReason, failingPackageName); + FrameworkStatsLog.write( + FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED, + type, + logPackage.getPackageName(), + logPackage.getVersionCode(), + rollbackReason, + failingPackageName, + new byte[]{}); } else { // In the case that the log package is null, still log an empty string as an // indication that retrieving the logging parent failed. - StatsLog.logWatchdogRollbackOccurred(type, "", 0, rollbackReason, failingPackageName); + FrameworkStatsLog.write( + FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED, + type, + "", + 0, + rollbackReason, + failingPackageName, + new byte[]{}); } } @@ -196,14 +246,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 +261,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/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 96f1219861ec..5c79f6e6391d 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -2480,7 +2480,7 @@ public class StatsPullAtomService extends SystemService { .writeString(Build.BRAND) .writeString(Build.PRODUCT) .writeString(Build.DEVICE) - .writeString(Build.VERSION.RELEASE) + .writeString(Build.VERSION.RELEASE_OR_CODENAME) .writeString(Build.ID) .writeString(Build.VERSION.INCREMENTAL) .writeString(Build.TYPE) diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index b7d63609cff9..ed6424cb4a30 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -21,7 +21,7 @@ import android.annotation.Nullable; import android.app.timedetector.ITimeDetectorService; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; -import android.app.timedetector.PhoneTimeSuggestion; +import android.app.timedetector.TelephonyTimeSuggestion; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; @@ -37,6 +37,9 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Objects; +/** + * The implementation of ITimeDetectorService.aidl. + */ public final class TimeDetectorService extends ITimeDetectorService.Stub { private static final String TAG = "TimeDetectorService"; @@ -75,7 +78,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true, new ContentObserver(handler) { public void onChange(boolean selfChange) { - timeDetectorService.handleAutoTimeDetectionToggle(); + timeDetectorService.handleAutoTimeDetectionChanged(); } }); @@ -91,11 +94,11 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { } @Override - public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSignal) { - enforceSuggestPhoneTimePermission(); + public void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSignal) { + enforceSuggestTelephonyTimePermission(); Objects.requireNonNull(timeSignal); - mHandler.post(() -> mTimeDetectorStrategy.suggestPhoneTime(timeSignal)); + mHandler.post(() -> mTimeDetectorStrategy.suggestTelephonyTime(timeSignal)); } @Override @@ -114,8 +117,9 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(timeSignal)); } + /** Internal method for handling the auto time setting being changed. */ @VisibleForTesting - public void handleAutoTimeDetectionToggle() { + public void handleAutoTimeDetectionChanged() { mHandler.post(mTimeDetectorStrategy::handleAutoTimeDetectionChanged); } @@ -127,10 +131,10 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { mTimeDetectorStrategy.dump(pw, args); } - private void enforceSuggestPhoneTimePermission() { + private void enforceSuggestTelephonyTimePermission() { mContext.enforceCallingPermission( - android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE, - "suggest phone time and time zone"); + android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE, + "suggest telephony time and time zone"); } private void enforceSuggestManualTimePermission() { diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java index 468b806d6dce..a5fba4e6ba49 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -20,14 +20,14 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; -import android.app.timedetector.PhoneTimeSuggestion; +import android.app.timedetector.TelephonyTimeSuggestion; import android.os.TimestampedValue; import java.io.PrintWriter; /** - * The interface for classes that implement the time detection algorithm used by the - * TimeDetectorService. + * The interface for the class that implements the time detection algorithm used by the + * {@link TimeDetectorService}. * * <p>Most calls will be handled by a single thread but that is not true for all calls. For example * {@link #dump(PrintWriter, String[])}) may be called on a different thread so implementations must @@ -78,7 +78,7 @@ public interface TimeDetectorStrategy { void initialize(@NonNull Callback callback); /** Process the suggested time from telephony sources. */ - void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion); + void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion); /** Process the suggested manually entered time. */ void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index a1e643f15a8e..88de34027bb9 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -22,7 +22,7 @@ import android.annotation.Nullable; import android.app.AlarmManager; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; -import android.app.timedetector.PhoneTimeSuggestion; +import android.app.timedetector.TelephonyTimeSuggestion; import android.os.TimestampedValue; import android.util.LocalLog; import android.util.Slog; @@ -38,9 +38,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * An implementation of TimeDetectorStrategy that passes phone and manual suggestions to - * {@link AlarmManager}. When there are multiple phone sources, the one with the lowest ID is used - * unless the data becomes too stale. + * An implementation of {@link TimeDetectorStrategy} that passes telephony and manual suggestions to + * {@link AlarmManager}. When there are multiple telephony sources, the one with the lowest ID is + * used unless the data becomes too stale. * * <p>Most public methods are marked synchronized to ensure thread safety around internal state. */ @@ -50,23 +50,26 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { private static final String LOG_TAG = "SimpleTimeDetectorStrategy"; /** A score value used to indicate "no score", either due to validation failure or age. */ - private static final int PHONE_INVALID_SCORE = -1; - /** The number of buckets phone suggestions can be put in by age. */ - private static final int PHONE_BUCKET_COUNT = 24; + private static final int TELEPHONY_INVALID_SCORE = -1; + /** The number of buckets telephony suggestions can be put in by age. */ + private static final int TELEPHONY_BUCKET_COUNT = 24; /** Each bucket is this size. All buckets are equally sized. */ @VisibleForTesting - static final int PHONE_BUCKET_SIZE_MILLIS = 60 * 60 * 1000; - /** Phone and network suggestions older than this value are considered too old to be used. */ + static final int TELEPHONY_BUCKET_SIZE_MILLIS = 60 * 60 * 1000; + /** + * Telephony and network suggestions older than this value are considered too old to be used. + */ @VisibleForTesting - static final long MAX_UTC_TIME_AGE_MILLIS = PHONE_BUCKET_COUNT * PHONE_BUCKET_SIZE_MILLIS; + static final long MAX_UTC_TIME_AGE_MILLIS = + TELEPHONY_BUCKET_COUNT * TELEPHONY_BUCKET_SIZE_MILLIS; - @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL, ORIGIN_NETWORK }) + @IntDef({ ORIGIN_TELEPHONY, ORIGIN_MANUAL, ORIGIN_NETWORK }) @Retention(RetentionPolicy.SOURCE) public @interface Origin {} /** Used when a time value originated from a telephony signal. */ @Origin - private static final int ORIGIN_PHONE = 1; + private static final int ORIGIN_TELEPHONY = 1; /** Used when a time value originated from a user / manual settings. */ @Origin @@ -83,10 +86,16 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { */ private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000; - /** The number of previous phone suggestions to keep for each ID (for use during debugging). */ - private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30; + /** + * The number of suggestions to keep. These are logged in bug reports to assist when debugging + * issues with detection. + */ + private static final int KEEP_SUGGESTION_HISTORY_SIZE = 10; - // A log for changes made to the system clock and why. + /** + * A log that records the decisions / decision metadata that affected the device's system clock + * time. This is logged in bug reports to assist with debugging issues with detection. + */ @NonNull private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */); @@ -106,7 +115,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { * stable. */ @GuardedBy("this") - private final ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionBySlotIndex = + private final ArrayMapWithHistory<Integer, TelephonyTimeSuggestion> mSuggestionBySlotIndex = new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); @GuardedBy("this") @@ -135,7 +144,18 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { if (!validateSuggestionTime(timeSuggestion.getUtcTime(), timeSuggestion)) { return; } - mLastNetworkSuggestion.set(timeSuggestion); + + // The caller submits suggestions with the best available information when there are network + // changes. The best available information may have been cached and if they were all stored + // this would lead to duplicates showing up in the suggestion history. The suggestions may + // be made for different reasons but there is not a significant benefit to storing the same + // suggestion information again. doAutoTimeDetection() should still be called: this ensures + // the suggestion and device state are always re-evaluated, which might produce a different + // detected time if, for example, the age of all suggestions are considered. + NetworkTimeSuggestion lastNetworkSuggestion = mLastNetworkSuggestion.get(); + if (lastNetworkSuggestion == null || !lastNetworkSuggestion.equals(timeSuggestion)) { + mLastNetworkSuggestion.set(timeSuggestion); + } // Now perform auto time detection. The new suggestion may be used to modify the system // clock. @@ -144,7 +164,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } @Override - public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) { + public synchronized void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion) { // Empty time suggestion means that telephony network connectivity has been lost. // The passage of time is relentless, and we don't expect our users to use a time machine, // so we can continue relying on previous suggestions when we lose connectivity. This is @@ -157,13 +177,13 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { // Perform validation / input filtering and record the validated suggestion against the // slotIndex. - if (!validateAndStorePhoneSuggestion(timeSuggestion)) { + if (!validateAndStoreTelephonySuggestion(timeSuggestion)) { return; } // Now perform auto time detection. The new suggestion may be used to modify the system // clock. - String reason = "New phone time suggested. timeSuggestion=" + timeSuggestion; + String reason = "New telephony time suggested. timeSuggestion=" + timeSuggestion; doAutoTimeDetection(reason); } @@ -201,7 +221,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { mTimeChangesLog.dump(ipw); ipw.decreaseIndent(); // level 2 - ipw.println("Phone suggestion history:"); + ipw.println("Telephony suggestion history:"); ipw.increaseIndent(); // level 2 mSuggestionBySlotIndex.dump(ipw); ipw.decreaseIndent(); // level 2 @@ -216,7 +236,8 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } @GuardedBy("this") - private boolean validateAndStorePhoneSuggestion(@NonNull PhoneTimeSuggestion suggestion) { + private boolean validateAndStoreTelephonySuggestion( + @NonNull TelephonyTimeSuggestion suggestion) { TimestampedValue<Long> newUtcTime = suggestion.getUtcTime(); if (!validateSuggestionTime(newUtcTime, suggestion)) { // There's probably nothing useful we can do: elsewhere we assume that reference @@ -225,7 +246,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } int slotIndex = suggestion.getSlotIndex(); - PhoneTimeSuggestion previousSuggestion = mSuggestionBySlotIndex.get(slotIndex); + TelephonyTimeSuggestion previousSuggestion = mSuggestionBySlotIndex.get(slotIndex); if (previousSuggestion != null) { // We can log / discard suggestions with obvious issues with the reference time clock. if (previousSuggestion.getUtcTime() == null @@ -241,7 +262,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { newUtcTime, previousSuggestion.getUtcTime()); if (referenceTimeDifference < 0) { // The reference time is before the previously received suggestion. Ignore it. - Slog.w(LOG_TAG, "Out of order phone suggestion received." + Slog.w(LOG_TAG, "Out of order telephony suggestion received." + " referenceTimeDifference=" + referenceTimeDifference + " previousSuggestion=" + previousSuggestion + " suggestion=" + suggestion); @@ -282,18 +303,18 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { // Android devices currently prioritize any telephony over network signals. There are // carrier compliance tests that would need to be changed before we could ignore NITZ or - // prefer NTP generally. This check is cheap on devices without phone hardware. - PhoneTimeSuggestion bestPhoneSuggestion = findBestPhoneSuggestion(); - if (bestPhoneSuggestion != null) { - final TimestampedValue<Long> newUtcTime = bestPhoneSuggestion.getUtcTime(); - String cause = "Found good phone suggestion." - + ", bestPhoneSuggestion=" + bestPhoneSuggestion + // prefer NTP generally. This check is cheap on devices without telephony hardware. + TelephonyTimeSuggestion bestTelephonySuggestion = findBestTelephonySuggestion(); + if (bestTelephonySuggestion != null) { + final TimestampedValue<Long> newUtcTime = bestTelephonySuggestion.getUtcTime(); + String cause = "Found good telephony suggestion." + + ", bestTelephonySuggestion=" + bestTelephonySuggestion + ", detectionReason=" + detectionReason; - setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, cause); + setSystemClockIfRequired(ORIGIN_TELEPHONY, newUtcTime, cause); return; } - // There is no good phone suggestion, try network. + // There is no good telephony suggestion, try network. NetworkTimeSuggestion networkSuggestion = findLatestValidNetworkSuggestion(); if (networkSuggestion != null) { final TimestampedValue<Long> newUtcTime = networkSuggestion.getUtcTime(); @@ -305,18 +326,18 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } if (DBG) { - Slog.d(LOG_TAG, "Could not determine time: No best phone or network suggestion." + Slog.d(LOG_TAG, "Could not determine time: No best telephony or network suggestion." + " detectionReason=" + detectionReason); } } @GuardedBy("this") @Nullable - private PhoneTimeSuggestion findBestPhoneSuggestion() { + private TelephonyTimeSuggestion findBestTelephonySuggestion() { long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis(); - // Phone time suggestions are assumed to be derived from NITZ or NITZ-like signals. These - // have a number of limitations: + // Telephony time suggestions are assumed to be derived from NITZ or NITZ-like signals. + // These have a number of limitations: // 1) No guarantee of accuracy ("accuracy of the time information is in the order of // minutes") [1] // 2) No guarantee of regular signals ("dependent on the handset crossing radio network @@ -335,8 +356,8 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { // For simplicity, we try to value recency, then consistency of slotIndex. // // The heuristic works as follows: - // Recency: The most recent suggestion from each phone is scored. The score is based on a - // discrete age bucket, i.e. so signals received around the same time will be in the same + // Recency: The most recent suggestion from each slotIndex is scored. The score is based on + // a discrete age bucket, i.e. so signals received around the same time will be in the same // bucket, thus applying a loose reference time ordering. The suggestion with the highest // score is used. // Consistency: If there a multiple suggestions with the same score, the suggestion with the @@ -345,11 +366,11 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { // In the trivial case with a single ID this will just mean that the latest received // suggestion is used. - PhoneTimeSuggestion bestSuggestion = null; - int bestScore = PHONE_INVALID_SCORE; + TelephonyTimeSuggestion bestSuggestion = null; + int bestScore = TELEPHONY_INVALID_SCORE; for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) { Integer slotIndex = mSuggestionBySlotIndex.keyAt(i); - PhoneTimeSuggestion candidateSuggestion = mSuggestionBySlotIndex.valueAt(i); + TelephonyTimeSuggestion candidateSuggestion = mSuggestionBySlotIndex.valueAt(i); if (candidateSuggestion == null) { // Unexpected - null suggestions should never be stored. Slog.w(LOG_TAG, "Latest suggestion unexpectedly null for slotIndex." @@ -362,8 +383,9 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { continue; } - int candidateScore = scorePhoneSuggestion(elapsedRealtimeMillis, candidateSuggestion); - if (candidateScore == PHONE_INVALID_SCORE) { + int candidateScore = + scoreTelephonySuggestion(elapsedRealtimeMillis, candidateSuggestion); + if (candidateScore == TELEPHONY_INVALID_SCORE) { // Expected: This means the suggestion is obviously invalid or just too old. continue; } @@ -384,8 +406,8 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { return bestSuggestion; } - private static int scorePhoneSuggestion( - long elapsedRealtimeMillis, @NonNull PhoneTimeSuggestion timeSuggestion) { + private static int scoreTelephonySuggestion( + long elapsedRealtimeMillis, @NonNull TelephonyTimeSuggestion timeSuggestion) { // Validate first. TimestampedValue<Long> utcTime = timeSuggestion.getUtcTime(); @@ -393,21 +415,21 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { Slog.w(LOG_TAG, "Existing suggestion found to be invalid " + " elapsedRealtimeMillis=" + elapsedRealtimeMillis + ", timeSuggestion=" + timeSuggestion); - return PHONE_INVALID_SCORE; + return TELEPHONY_INVALID_SCORE; } // The score is based on the age since receipt. Suggestions are bucketed so two // suggestions in the same bucket from different slotIndexs are scored the same. long ageMillis = elapsedRealtimeMillis - utcTime.getReferenceTimeMillis(); - // Turn the age into a discrete value: 0 <= bucketIndex < PHONE_BUCKET_COUNT. - int bucketIndex = (int) (ageMillis / PHONE_BUCKET_SIZE_MILLIS); - if (bucketIndex >= PHONE_BUCKET_COUNT) { - return PHONE_INVALID_SCORE; + // Turn the age into a discrete value: 0 <= bucketIndex < TELEPHONY_BUCKET_COUNT. + int bucketIndex = (int) (ageMillis / TELEPHONY_BUCKET_SIZE_MILLIS); + if (bucketIndex >= TELEPHONY_BUCKET_COUNT) { + return TELEPHONY_INVALID_SCORE; } // We want the lowest bucket index to have the highest score. 0 > score >= BUCKET_COUNT. - return PHONE_BUCKET_COUNT - bucketIndex; + return TELEPHONY_BUCKET_COUNT - bucketIndex; } /** Returns the latest, valid, network suggestion. Returns {@code null} if there isn't one. */ @@ -537,13 +559,13 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { } /** - * Returns the current best phone suggestion. Not intended for general use: it is used during - * tests to check strategy behavior. + * Returns the current best telephony suggestion. Not intended for general use: it is used + * during tests to check strategy behavior. */ @VisibleForTesting @Nullable - public synchronized PhoneTimeSuggestion findBestPhoneSuggestionForTests() { - return findBestPhoneSuggestion(); + public synchronized TelephonyTimeSuggestion findBestTelephonySuggestionForTests() { + return findBestTelephonySuggestion(); } /** @@ -561,7 +583,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { */ @VisibleForTesting @Nullable - public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int slotIndex) { + public synchronized TelephonyTimeSuggestion getLatestTelephonySuggestion(int slotIndex) { return mSuggestionBySlotIndex.get(slotIndex); } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java index adf6d7e51f4f..2520316b5d54 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java @@ -24,9 +24,9 @@ import android.os.SystemProperties; import android.provider.Settings; /** - * The real implementation of {@link TimeZoneDetectorStrategy.Callback}. + * The real implementation of {@link TimeZoneDetectorStrategyImpl.Callback}. */ -public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategy.Callback { +public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategyImpl.Callback { private static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index 9a1fe6501221..57b6ec9062a8 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.timezonedetector.ITimeZoneDetectorService; import android.app.timezonedetector.ManualTimeZoneSuggestion; -import android.app.timezonedetector.PhoneTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; @@ -67,19 +67,21 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub private static TimeZoneDetectorService create(@NonNull Context context) { final TimeZoneDetectorStrategy timeZoneDetectorStrategy = - TimeZoneDetectorStrategy.create(context); + TimeZoneDetectorStrategyImpl.create(context); Handler handler = FgThread.getHandler(); + TimeZoneDetectorService service = + new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy); + ContentResolver contentResolver = context.getContentResolver(); contentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true, new ContentObserver(handler) { public void onChange(boolean selfChange) { - timeZoneDetectorStrategy.handleAutoTimeZoneDetectionChange(); + service.handleAutoTimeZoneDetectionChanged(); } }); - - return new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy); + return service; } @VisibleForTesting @@ -99,11 +101,11 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub } @Override - public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) { - enforceSuggestPhoneTimeZonePermission(); + public void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion timeZoneSuggestion) { + enforceSuggestTelephonyTimeZonePermission(); Objects.requireNonNull(timeZoneSuggestion); - mHandler.post(() -> mTimeZoneDetectorStrategy.suggestPhoneTimeZone(timeZoneSuggestion)); + mHandler.post(() -> mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion)); } @Override @@ -111,17 +113,25 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub @Nullable String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; - mTimeZoneDetectorStrategy.dumpState(pw, args); + mTimeZoneDetectorStrategy.dump(pw, args); + } + + /** Internal method for handling the auto time zone setting being changed. */ + @VisibleForTesting + public void handleAutoTimeZoneDetectionChanged() { + mHandler.post(mTimeZoneDetectorStrategy::handleAutoTimeZoneDetectionChanged); } - private void enforceSuggestPhoneTimeZonePermission() { + private void enforceSuggestTelephonyTimeZonePermission() { mContext.enforceCallingPermission( - android.Manifest.permission.SET_TIME_ZONE, "set time zone"); + android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE, + "suggest telephony time and time zone"); } private void enforceSuggestManualTimeZonePermission() { mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.SET_TIME_ZONE, "set time zone"); + android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE, + "suggest manual time and time zone"); } } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index b0e006908231..0eb27cc5cff0 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -15,507 +15,44 @@ */ package com.android.server.timezonedetector; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE; - -import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.timezonedetector.ManualTimeZoneSuggestion; -import android.app.timezonedetector.PhoneTimeZoneSuggestion; -import android.content.Context; -import android.util.LocalLog; -import android.util.Slog; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.IndentingPrintWriter; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; /** - * A singleton, stateful time zone detection strategy that is aware of user (manual) suggestions and - * suggestions from multiple phone devices. Suggestions are acted on or ignored as needed, dependent - * on the current "auto time zone detection" setting. + * The interface for the class that implement the time detection algorithm used by the + * {@link TimeZoneDetectorService}. + * + * <p>Most calls will be handled by a single thread but that is not true for all calls. For example + * {@link #dump(PrintWriter, String[])}) may be called on a different thread so implementations must + * handle thread safety. * - * <p>For automatic detection it keeps track of the most recent suggestion from each phone it uses - * the best suggestion based on a scoring algorithm. If several phones provide the same score then - * the phone with the lowest numeric ID "wins". If the situation changes and it is no longer - * possible to be confident about the time zone, phones must submit an empty suggestion in order to - * "withdraw" their previous suggestion. + * @hide */ -public class TimeZoneDetectorStrategy { - - /** - * Used by {@link TimeZoneDetectorStrategy} to interact with the surrounding service. It can be - * faked for tests. - * - * <p>Note: Because the system properties-derived values like - * {@link #isAutoTimeZoneDetectionEnabled()}, {@link #isAutoTimeZoneDetectionEnabled()}, - * {@link #getDeviceTimeZone()} can be modified independently and from different threads (and - * processes!), their use are prone to race conditions. That will be true until the - * responsibility for setting their values is moved to {@link TimeZoneDetectorStrategy}. - */ - @VisibleForTesting - public interface Callback { - - /** - * Returns true if automatic time zone detection is enabled in settings. - */ - boolean isAutoTimeZoneDetectionEnabled(); - - /** - * Returns true if the device has had an explicit time zone set. - */ - boolean isDeviceTimeZoneInitialized(); - - /** - * Returns the device's currently configured time zone. - */ - String getDeviceTimeZone(); - - /** - * Sets the device's time zone. - */ - void setDeviceTimeZone(@NonNull String zoneId); - } - - private static final String LOG_TAG = "TimeZoneDetectorStrategy"; - private static final boolean DBG = false; +public interface TimeZoneDetectorStrategy { - @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL }) - @Retention(RetentionPolicy.SOURCE) - public @interface Origin {} - - /** Used when a time value originated from a telephony signal. */ - @Origin - private static final int ORIGIN_PHONE = 1; - - /** Used when a time value originated from a user / manual settings. */ - @Origin - private static final int ORIGIN_MANUAL = 2; - - /** - * The abstract score for an empty or invalid phone suggestion. - * - * Used to score phone suggestions where there is no zone. - */ - @VisibleForTesting - public static final int PHONE_SCORE_NONE = 0; - - /** - * The abstract score for a low quality phone suggestion. - * - * Used to score suggestions where: - * The suggested zone ID is one of several possibilities, and the possibilities have different - * offsets. - * - * You would have to be quite desperate to want to use this choice. - */ - @VisibleForTesting - public static final int PHONE_SCORE_LOW = 1; - - /** - * The abstract score for a medium quality phone suggestion. - * - * Used for: - * The suggested zone ID is one of several possibilities but at least the possibilities have the - * same offset. Users would get the correct time but for the wrong reason. i.e. their device may - * switch to DST at the wrong time and (for example) their calendar events. - */ - @VisibleForTesting - public static final int PHONE_SCORE_MEDIUM = 2; - - /** - * The abstract score for a high quality phone suggestion. - * - * Used for: - * The suggestion was for one zone ID and the answer was unambiguous and likely correct given - * the info available. - */ - @VisibleForTesting - public static final int PHONE_SCORE_HIGH = 3; - - /** - * The abstract score for a highest quality phone suggestion. - * - * Used for: - * Suggestions that must "win" because they constitute test or emulator zone ID. - */ - @VisibleForTesting - public static final int PHONE_SCORE_HIGHEST = 4; - - /** - * The threshold at which phone suggestions are good enough to use to set the device's time - * zone. - */ - @VisibleForTesting - public static final int PHONE_SCORE_USAGE_THRESHOLD = PHONE_SCORE_MEDIUM; - - /** The number of previous phone suggestions to keep for each ID (for use during debugging). */ - private static final int KEEP_PHONE_SUGGESTION_HISTORY_SIZE = 30; - - @NonNull - private final Callback mCallback; - - /** - * A log that records the decisions / decision metadata that affected the device's time zone - * (for use during debugging). - */ - @NonNull - private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */); - - /** - * A mapping from slotIndex to a phone time zone suggestion. We typically expect one or two - * mappings: devices will have a small number of telephony devices and slotIndexs are assumed to - * be stable. - */ - @GuardedBy("this") - private ArrayMapWithHistory<Integer, QualifiedPhoneTimeZoneSuggestion> mSuggestionBySlotIndex = - new ArrayMapWithHistory<>(KEEP_PHONE_SUGGESTION_HISTORY_SIZE); - - /** - * Creates a new instance of {@link TimeZoneDetectorStrategy}. - */ - public static TimeZoneDetectorStrategy create(Context context) { - Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context); - return new TimeZoneDetectorStrategy(timeZoneDetectionServiceHelper); - } - - @VisibleForTesting - public TimeZoneDetectorStrategy(Callback callback) { - mCallback = Objects.requireNonNull(callback); - } - - /** Process the suggested manually- / user-entered time zone. */ - public synchronized void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion) { - Objects.requireNonNull(suggestion); - - String timeZoneId = suggestion.getZoneId(); - String cause = "Manual time suggestion received: suggestion=" + suggestion; - setDeviceTimeZoneIfRequired(ORIGIN_MANUAL, timeZoneId, cause); - } + /** Process the suggested manually-entered (i.e. user sourced) time zone. */ + void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion); /** * Suggests a time zone for the device, or withdraws a previous suggestion if - * {@link PhoneTimeZoneSuggestion#getZoneId()} is {@code null}. The suggestion is scoped to a - * specific {@link PhoneTimeZoneSuggestion#getSlotIndex() phone}. - * See {@link PhoneTimeZoneSuggestion} for an explanation of the metadata associated with a + * {@link TelephonyTimeZoneSuggestion#getZoneId()} is {@code null}. The suggestion is scoped to + * a specific {@link TelephonyTimeZoneSuggestion#getSlotIndex() slotIndex}. + * See {@link TelephonyTimeZoneSuggestion} for an explanation of the metadata associated with a * suggestion. The strategy uses suggestions to decide whether to modify the device's time zone * setting and what to set it to. */ - public synchronized void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion) { - if (DBG) { - Slog.d(LOG_TAG, "Phone suggestion received. newSuggestion=" + suggestion); - } - Objects.requireNonNull(suggestion); - - // Score the suggestion. - int score = scorePhoneSuggestion(suggestion); - QualifiedPhoneTimeZoneSuggestion scoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(suggestion, score); - - // Store the suggestion against the correct slotIndex. - mSuggestionBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion); - - // Now perform auto time zone detection. The new suggestion may be used to modify the time - // zone setting. - String reason = "New phone time suggested. suggestion=" + suggestion; - doAutoTimeZoneDetection(reason); - } - - private static int scorePhoneSuggestion(@NonNull PhoneTimeZoneSuggestion suggestion) { - int score; - if (suggestion.getZoneId() == null) { - score = PHONE_SCORE_NONE; - } else if (suggestion.getMatchType() == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY - || suggestion.getMatchType() == MATCH_TYPE_EMULATOR_ZONE_ID) { - // Handle emulator / test cases : These suggestions should always just be used. - score = PHONE_SCORE_HIGHEST; - } else if (suggestion.getQuality() == QUALITY_SINGLE_ZONE) { - score = PHONE_SCORE_HIGH; - } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET) { - // The suggestion may be wrong, but at least the offset should be correct. - score = PHONE_SCORE_MEDIUM; - } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS) { - // The suggestion has a good chance of being wrong. - score = PHONE_SCORE_LOW; - } else { - throw new AssertionError(); - } - return score; - } - - /** - * Finds the best available time zone suggestion from all phones. If it is high-enough quality - * and automatic time zone detection is enabled then it will be set on the device. The outcome - * can be that this strategy becomes / remains un-opinionated and nothing is set. - */ - @GuardedBy("this") - private void doAutoTimeZoneDetection(@NonNull String detectionReason) { - if (!mCallback.isAutoTimeZoneDetectionEnabled()) { - // Avoid doing unnecessary work with this (race-prone) check. - return; - } - - QualifiedPhoneTimeZoneSuggestion bestPhoneSuggestion = findBestPhoneSuggestion(); - - // Work out what to do with the best suggestion. - if (bestPhoneSuggestion == null) { - // There is no phone suggestion available at all. Become un-opinionated. - if (DBG) { - Slog.d(LOG_TAG, "Could not determine time zone: No best phone suggestion." - + " detectionReason=" + detectionReason); - } - return; - } - - // Special case handling for uninitialized devices. This should only happen once. - String newZoneId = bestPhoneSuggestion.suggestion.getZoneId(); - if (newZoneId != null && !mCallback.isDeviceTimeZoneInitialized()) { - String cause = "Device has no time zone set. Attempting to set the device to the best" - + " available suggestion." - + " bestPhoneSuggestion=" + bestPhoneSuggestion - + ", detectionReason=" + detectionReason; - Slog.i(LOG_TAG, cause); - setDeviceTimeZoneIfRequired(ORIGIN_PHONE, newZoneId, cause); - return; - } - - boolean suggestionGoodEnough = bestPhoneSuggestion.score >= PHONE_SCORE_USAGE_THRESHOLD; - if (!suggestionGoodEnough) { - if (DBG) { - Slog.d(LOG_TAG, "Best suggestion not good enough." - + " bestPhoneSuggestion=" + bestPhoneSuggestion - + ", detectionReason=" + detectionReason); - } - return; - } - - // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time - // zone ID. - if (newZoneId == null) { - Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:" - + " bestPhoneSuggestion=" + bestPhoneSuggestion - + " detectionReason=" + detectionReason); - return; - } - - String zoneId = bestPhoneSuggestion.suggestion.getZoneId(); - String cause = "Found good suggestion." - + ", bestPhoneSuggestion=" + bestPhoneSuggestion - + ", detectionReason=" + detectionReason; - setDeviceTimeZoneIfRequired(ORIGIN_PHONE, zoneId, cause); - } - - @GuardedBy("this") - private void setDeviceTimeZoneIfRequired( - @Origin int origin, @NonNull String newZoneId, @NonNull String cause) { - Objects.requireNonNull(newZoneId); - Objects.requireNonNull(cause); - - boolean isOriginAutomatic = isOriginAutomatic(origin); - if (isOriginAutomatic) { - if (!mCallback.isAutoTimeZoneDetectionEnabled()) { - if (DBG) { - Slog.d(LOG_TAG, "Auto time zone detection is not enabled." - + " origin=" + origin - + ", newZoneId=" + newZoneId - + ", cause=" + cause); - } - return; - } - } else { - if (mCallback.isAutoTimeZoneDetectionEnabled()) { - if (DBG) { - Slog.d(LOG_TAG, "Auto time zone detection is enabled." - + " origin=" + origin - + ", newZoneId=" + newZoneId - + ", cause=" + cause); - } - return; - } - } - - String currentZoneId = mCallback.getDeviceTimeZone(); - - // Avoid unnecessary changes / intents. - if (newZoneId.equals(currentZoneId)) { - // No need to set the device time zone - the setting is already what we would be - // suggesting. - if (DBG) { - Slog.d(LOG_TAG, "No need to change the time zone;" - + " device is already set to the suggested zone." - + " origin=" + origin - + ", newZoneId=" + newZoneId - + ", cause=" + cause); - } - return; - } - - mCallback.setDeviceTimeZone(newZoneId); - String msg = "Set device time zone." - + " origin=" + origin - + ", currentZoneId=" + currentZoneId - + ", newZoneId=" + newZoneId - + ", cause=" + cause; - if (DBG) { - Slog.d(LOG_TAG, msg); - } - mTimeZoneChangesLog.log(msg); - } - - private static boolean isOriginAutomatic(@Origin int origin) { - return origin != ORIGIN_MANUAL; - } - - @GuardedBy("this") - @Nullable - private QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestion() { - QualifiedPhoneTimeZoneSuggestion bestSuggestion = null; - - // Iterate over the latest QualifiedPhoneTimeZoneSuggestion objects received for each phone - // and find the best. Note that we deliberately do not look at age: the caller can - // rate-limit so age is not a strong indicator of confidence. Instead, the callers are - // expected to withdraw suggestions they no longer have confidence in. - for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) { - QualifiedPhoneTimeZoneSuggestion candidateSuggestion = - mSuggestionBySlotIndex.valueAt(i); - if (candidateSuggestion == null) { - // Unexpected - continue; - } - - if (bestSuggestion == null) { - bestSuggestion = candidateSuggestion; - } else if (candidateSuggestion.score > bestSuggestion.score) { - bestSuggestion = candidateSuggestion; - } else if (candidateSuggestion.score == bestSuggestion.score) { - // Tie! Use the suggestion with the lowest slotIndex. - int candidateSlotIndex = candidateSuggestion.suggestion.getSlotIndex(); - int bestSlotIndex = bestSuggestion.suggestion.getSlotIndex(); - if (candidateSlotIndex < bestSlotIndex) { - bestSuggestion = candidateSuggestion; - } - } - } - return bestSuggestion; - } - - /** - * Returns the current best phone suggestion. Not intended for general use: it is used during - * tests to check strategy behavior. - */ - @VisibleForTesting - @Nullable - public synchronized QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestionForTests() { - return findBestPhoneSuggestion(); - } + void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion); /** * Called when there has been a change to the automatic time zone detection setting. */ - @VisibleForTesting - public synchronized void handleAutoTimeZoneDetectionChange() { - if (DBG) { - Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called"); - } - if (mCallback.isAutoTimeZoneDetectionEnabled()) { - // When the user enabled time zone detection, run the time zone detection and change the - // device time zone if possible. - String reason = "Auto time zone detection setting enabled."; - doAutoTimeZoneDetection(reason); - } - } + void handleAutoTimeZoneDetectionChanged(); /** * Dumps internal state such as field values. */ - public synchronized void dumpState(PrintWriter pw, String[] args) { - IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); - ipw.println("TimeZoneDetectorStrategy:"); - - ipw.increaseIndent(); // level 1 - ipw.println("mCallback.isTimeZoneDetectionEnabled()=" - + mCallback.isAutoTimeZoneDetectionEnabled()); - ipw.println("mCallback.isDeviceTimeZoneInitialized()=" - + mCallback.isDeviceTimeZoneInitialized()); - ipw.println("mCallback.getDeviceTimeZone()=" - + mCallback.getDeviceTimeZone()); - - ipw.println("Time zone change log:"); - ipw.increaseIndent(); // level 2 - mTimeZoneChangesLog.dump(ipw); - ipw.decreaseIndent(); // level 2 - - ipw.println("Phone suggestion history:"); - ipw.increaseIndent(); // level 2 - mSuggestionBySlotIndex.dump(ipw); - ipw.decreaseIndent(); // level 2 - ipw.decreaseIndent(); // level 1 - ipw.flush(); - } - - /** - * A method used to inspect strategy state during tests. Not intended for general use. - */ - @VisibleForTesting - public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int slotIndex) { - return mSuggestionBySlotIndex.get(slotIndex); - } - - /** - * A {@link PhoneTimeZoneSuggestion} with additional qualifying metadata. - */ - @VisibleForTesting - public static class QualifiedPhoneTimeZoneSuggestion { - - @VisibleForTesting - public final PhoneTimeZoneSuggestion suggestion; - - /** - * The score the suggestion has been given. This can be used to rank against other - * suggestions of the same type. - */ - @VisibleForTesting - public final int score; - - @VisibleForTesting - public QualifiedPhoneTimeZoneSuggestion(PhoneTimeZoneSuggestion suggestion, int score) { - this.suggestion = suggestion; - this.score = score; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - QualifiedPhoneTimeZoneSuggestion that = (QualifiedPhoneTimeZoneSuggestion) o; - return score == that.score - && suggestion.equals(that.suggestion); - } - - @Override - public int hashCode() { - return Objects.hash(score, suggestion); - } - - @Override - public String toString() { - return "QualifiedPhoneTimeZoneSuggestion{" - + "suggestion=" + suggestion - + ", score=" + score - + '}'; - } - } + void dump(PrintWriter pw, String[] args); } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java new file mode 100644 index 000000000000..cc33fb0f5008 --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -0,0 +1,522 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.timezonedetector; + +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.timezonedetector.ManualTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; +import android.content.Context; +import android.util.LocalLog; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; + +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * An implementation of {@link TimeZoneDetectorStrategy} that handle telephony and manual + * suggestions. Suggestions are acted on or ignored as needed, dependent on the current "auto time + * zone detection" setting. + * + * <p>For automatic detection, it keeps track of the most recent telephony suggestion from each + * slotIndex and it uses the best suggestion based on a scoring algorithm. If several slotIndexes + * provide the same score then the slotIndex with the lowest numeric value "wins". If the situation + * changes and it is no longer possible to be confident about the time zone, slotIndexes must have + * an empty suggestion submitted in order to "withdraw" their previous suggestion. + * + * <p>Most public methods are marked synchronized to ensure thread safety around internal state. + */ +public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrategy { + + /** + * Used by {@link TimeZoneDetectorStrategyImpl} to interact with the surrounding service. It can + * be faked for tests. + * + * <p>Note: Because the system properties-derived values like + * {@link #isAutoTimeZoneDetectionEnabled()}, {@link #isAutoTimeZoneDetectionEnabled()}, + * {@link #getDeviceTimeZone()} can be modified independently and from different threads (and + * processes!), their use are prone to race conditions. That will be true until the + * responsibility for setting their values is moved to {@link TimeZoneDetectorStrategyImpl}. + */ + @VisibleForTesting + public interface Callback { + + /** + * Returns true if automatic time zone detection is enabled in settings. + */ + boolean isAutoTimeZoneDetectionEnabled(); + + /** + * Returns true if the device has had an explicit time zone set. + */ + boolean isDeviceTimeZoneInitialized(); + + /** + * Returns the device's currently configured time zone. + */ + String getDeviceTimeZone(); + + /** + * Sets the device's time zone. + */ + void setDeviceTimeZone(@NonNull String zoneId); + } + + private static final String LOG_TAG = "TimeZoneDetectorStrategy"; + private static final boolean DBG = false; + + @IntDef({ ORIGIN_TELEPHONY, ORIGIN_MANUAL }) + @Retention(RetentionPolicy.SOURCE) + public @interface Origin {} + + /** Used when a time value originated from a telephony signal. */ + @Origin + private static final int ORIGIN_TELEPHONY = 1; + + /** Used when a time value originated from a user / manual settings. */ + @Origin + private static final int ORIGIN_MANUAL = 2; + + /** + * The abstract score for an empty or invalid telephony suggestion. + * + * Used to score telephony suggestions where there is no zone. + */ + @VisibleForTesting + public static final int TELEPHONY_SCORE_NONE = 0; + + /** + * The abstract score for a low quality telephony suggestion. + * + * Used to score suggestions where: + * The suggested zone ID is one of several possibilities, and the possibilities have different + * offsets. + * + * You would have to be quite desperate to want to use this choice. + */ + @VisibleForTesting + public static final int TELEPHONY_SCORE_LOW = 1; + + /** + * The abstract score for a medium quality telephony suggestion. + * + * Used for: + * The suggested zone ID is one of several possibilities but at least the possibilities have the + * same offset. Users would get the correct time but for the wrong reason. i.e. their device may + * switch to DST at the wrong time and (for example) their calendar events. + */ + @VisibleForTesting + public static final int TELEPHONY_SCORE_MEDIUM = 2; + + /** + * The abstract score for a high quality telephony suggestion. + * + * Used for: + * The suggestion was for one zone ID and the answer was unambiguous and likely correct given + * the info available. + */ + @VisibleForTesting + public static final int TELEPHONY_SCORE_HIGH = 3; + + /** + * The abstract score for a highest quality telephony suggestion. + * + * Used for: + * Suggestions that must "win" because they constitute test or emulator zone ID. + */ + @VisibleForTesting + public static final int TELEPHONY_SCORE_HIGHEST = 4; + + /** + * The threshold at which telephony suggestions are good enough to use to set the device's time + * zone. + */ + @VisibleForTesting + public static final int TELEPHONY_SCORE_USAGE_THRESHOLD = TELEPHONY_SCORE_MEDIUM; + + /** + * The number of suggestions to keep. These are logged in bug reports to assist when debugging + * issues with detection. + */ + private static final int KEEP_SUGGESTION_HISTORY_SIZE = 10; + + @NonNull + private final Callback mCallback; + + /** + * A log that records the decisions / decision metadata that affected the device's time zone. + * This is logged in bug reports to assist with debugging issues with detection. + */ + @NonNull + private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */); + + /** + * A mapping from slotIndex to a telephony time zone suggestion. We typically expect one or two + * mappings: devices will have a small number of telephony devices and slotIndexes are assumed + * to be stable. + */ + @GuardedBy("this") + private ArrayMapWithHistory<Integer, QualifiedTelephonyTimeZoneSuggestion> + mSuggestionBySlotIndex = new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); + + /** + * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}. + */ + public static TimeZoneDetectorStrategyImpl create(Context context) { + Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context); + return new TimeZoneDetectorStrategyImpl(timeZoneDetectionServiceHelper); + } + + @VisibleForTesting + public TimeZoneDetectorStrategyImpl(Callback callback) { + mCallback = Objects.requireNonNull(callback); + } + + @Override + public synchronized void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion) { + Objects.requireNonNull(suggestion); + + String timeZoneId = suggestion.getZoneId(); + String cause = "Manual time suggestion received: suggestion=" + suggestion; + setDeviceTimeZoneIfRequired(ORIGIN_MANUAL, timeZoneId, cause); + } + + @Override + public synchronized void suggestTelephonyTimeZone( + @NonNull TelephonyTimeZoneSuggestion suggestion) { + if (DBG) { + Slog.d(LOG_TAG, "Telephony suggestion received. newSuggestion=" + suggestion); + } + Objects.requireNonNull(suggestion); + + // Score the suggestion. + int score = scoreTelephonySuggestion(suggestion); + QualifiedTelephonyTimeZoneSuggestion scoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(suggestion, score); + + // Store the suggestion against the correct slotIndex. + mSuggestionBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion); + + // Now perform auto time zone detection. The new suggestion may be used to modify the time + // zone setting. + String reason = "New telephony time suggested. suggestion=" + suggestion; + doAutoTimeZoneDetection(reason); + } + + private static int scoreTelephonySuggestion(@NonNull TelephonyTimeZoneSuggestion suggestion) { + int score; + if (suggestion.getZoneId() == null) { + score = TELEPHONY_SCORE_NONE; + } else if (suggestion.getMatchType() == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY + || suggestion.getMatchType() == MATCH_TYPE_EMULATOR_ZONE_ID) { + // Handle emulator / test cases : These suggestions should always just be used. + score = TELEPHONY_SCORE_HIGHEST; + } else if (suggestion.getQuality() == QUALITY_SINGLE_ZONE) { + score = TELEPHONY_SCORE_HIGH; + } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET) { + // The suggestion may be wrong, but at least the offset should be correct. + score = TELEPHONY_SCORE_MEDIUM; + } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS) { + // The suggestion has a good chance of being wrong. + score = TELEPHONY_SCORE_LOW; + } else { + throw new AssertionError(); + } + return score; + } + + /** + * Finds the best available time zone suggestion from all slotIndexes. If it is high-enough + * quality and automatic time zone detection is enabled then it will be set on the device. The + * outcome can be that this strategy becomes / remains un-opinionated and nothing is set. + */ + @GuardedBy("this") + private void doAutoTimeZoneDetection(@NonNull String detectionReason) { + if (!mCallback.isAutoTimeZoneDetectionEnabled()) { + // Avoid doing unnecessary work with this (race-prone) check. + return; + } + + QualifiedTelephonyTimeZoneSuggestion bestTelephonySuggestion = + findBestTelephonySuggestion(); + + // Work out what to do with the best suggestion. + if (bestTelephonySuggestion == null) { + // There is no telephony suggestion available at all. Become un-opinionated. + if (DBG) { + Slog.d(LOG_TAG, "Could not determine time zone: No best telephony suggestion." + + " detectionReason=" + detectionReason); + } + return; + } + + // Special case handling for uninitialized devices. This should only happen once. + String newZoneId = bestTelephonySuggestion.suggestion.getZoneId(); + if (newZoneId != null && !mCallback.isDeviceTimeZoneInitialized()) { + String cause = "Device has no time zone set. Attempting to set the device to the best" + + " available suggestion." + + " bestTelephonySuggestion=" + bestTelephonySuggestion + + ", detectionReason=" + detectionReason; + Slog.i(LOG_TAG, cause); + setDeviceTimeZoneIfRequired(ORIGIN_TELEPHONY, newZoneId, cause); + return; + } + + boolean suggestionGoodEnough = + bestTelephonySuggestion.score >= TELEPHONY_SCORE_USAGE_THRESHOLD; + if (!suggestionGoodEnough) { + if (DBG) { + Slog.d(LOG_TAG, "Best suggestion not good enough." + + " bestTelephonySuggestion=" + bestTelephonySuggestion + + ", detectionReason=" + detectionReason); + } + return; + } + + // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time + // zone ID. + if (newZoneId == null) { + Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:" + + " bestTelephonySuggestion=" + bestTelephonySuggestion + + " detectionReason=" + detectionReason); + return; + } + + String zoneId = bestTelephonySuggestion.suggestion.getZoneId(); + String cause = "Found good suggestion." + + ", bestTelephonySuggestion=" + bestTelephonySuggestion + + ", detectionReason=" + detectionReason; + setDeviceTimeZoneIfRequired(ORIGIN_TELEPHONY, zoneId, cause); + } + + @GuardedBy("this") + private void setDeviceTimeZoneIfRequired( + @Origin int origin, @NonNull String newZoneId, @NonNull String cause) { + Objects.requireNonNull(newZoneId); + Objects.requireNonNull(cause); + + boolean isOriginAutomatic = isOriginAutomatic(origin); + if (isOriginAutomatic) { + if (!mCallback.isAutoTimeZoneDetectionEnabled()) { + if (DBG) { + Slog.d(LOG_TAG, "Auto time zone detection is not enabled." + + " origin=" + origin + + ", newZoneId=" + newZoneId + + ", cause=" + cause); + } + return; + } + } else { + if (mCallback.isAutoTimeZoneDetectionEnabled()) { + if (DBG) { + Slog.d(LOG_TAG, "Auto time zone detection is enabled." + + " origin=" + origin + + ", newZoneId=" + newZoneId + + ", cause=" + cause); + } + return; + } + } + + String currentZoneId = mCallback.getDeviceTimeZone(); + + // Avoid unnecessary changes / intents. + if (newZoneId.equals(currentZoneId)) { + // No need to set the device time zone - the setting is already what we would be + // suggesting. + if (DBG) { + Slog.d(LOG_TAG, "No need to change the time zone;" + + " device is already set to the suggested zone." + + " origin=" + origin + + ", newZoneId=" + newZoneId + + ", cause=" + cause); + } + return; + } + + mCallback.setDeviceTimeZone(newZoneId); + String msg = "Set device time zone." + + " origin=" + origin + + ", currentZoneId=" + currentZoneId + + ", newZoneId=" + newZoneId + + ", cause=" + cause; + if (DBG) { + Slog.d(LOG_TAG, msg); + } + mTimeZoneChangesLog.log(msg); + } + + private static boolean isOriginAutomatic(@Origin int origin) { + return origin != ORIGIN_MANUAL; + } + + @GuardedBy("this") + @Nullable + private QualifiedTelephonyTimeZoneSuggestion findBestTelephonySuggestion() { + QualifiedTelephonyTimeZoneSuggestion bestSuggestion = null; + + // Iterate over the latest QualifiedTelephonyTimeZoneSuggestion objects received for each + // slotIndex and find the best. Note that we deliberately do not look at age: the caller can + // rate-limit so age is not a strong indicator of confidence. Instead, the callers are + // expected to withdraw suggestions they no longer have confidence in. + for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) { + QualifiedTelephonyTimeZoneSuggestion candidateSuggestion = + mSuggestionBySlotIndex.valueAt(i); + if (candidateSuggestion == null) { + // Unexpected + continue; + } + + if (bestSuggestion == null) { + bestSuggestion = candidateSuggestion; + } else if (candidateSuggestion.score > bestSuggestion.score) { + bestSuggestion = candidateSuggestion; + } else if (candidateSuggestion.score == bestSuggestion.score) { + // Tie! Use the suggestion with the lowest slotIndex. + int candidateSlotIndex = candidateSuggestion.suggestion.getSlotIndex(); + int bestSlotIndex = bestSuggestion.suggestion.getSlotIndex(); + if (candidateSlotIndex < bestSlotIndex) { + bestSuggestion = candidateSuggestion; + } + } + } + return bestSuggestion; + } + + /** + * Returns the current best telephony suggestion. Not intended for general use: it is used + * during tests to check strategy behavior. + */ + @VisibleForTesting + @Nullable + public synchronized QualifiedTelephonyTimeZoneSuggestion findBestTelephonySuggestionForTests() { + return findBestTelephonySuggestion(); + } + + @Override + public synchronized void handleAutoTimeZoneDetectionChanged() { + if (DBG) { + Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called"); + } + if (mCallback.isAutoTimeZoneDetectionEnabled()) { + // When the user enabled time zone detection, run the time zone detection and change the + // device time zone if possible. + String reason = "Auto time zone detection setting enabled."; + doAutoTimeZoneDetection(reason); + } + } + + /** + * Dumps internal state such as field values. + */ + @Override + public synchronized void dump(PrintWriter pw, String[] args) { + IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + ipw.println("TimeZoneDetectorStrategy:"); + + ipw.increaseIndent(); // level 1 + ipw.println("mCallback.isTimeZoneDetectionEnabled()=" + + mCallback.isAutoTimeZoneDetectionEnabled()); + ipw.println("mCallback.isDeviceTimeZoneInitialized()=" + + mCallback.isDeviceTimeZoneInitialized()); + ipw.println("mCallback.getDeviceTimeZone()=" + + mCallback.getDeviceTimeZone()); + + ipw.println("Time zone change log:"); + ipw.increaseIndent(); // level 2 + mTimeZoneChangesLog.dump(ipw); + ipw.decreaseIndent(); // level 2 + + ipw.println("Telephony suggestion history:"); + ipw.increaseIndent(); // level 2 + mSuggestionBySlotIndex.dump(ipw); + ipw.decreaseIndent(); // level 2 + ipw.decreaseIndent(); // level 1 + ipw.flush(); + } + + /** + * A method used to inspect strategy state during tests. Not intended for general use. + */ + @VisibleForTesting + public synchronized QualifiedTelephonyTimeZoneSuggestion getLatestTelephonySuggestion( + int slotIndex) { + return mSuggestionBySlotIndex.get(slotIndex); + } + + /** + * A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata. + */ + @VisibleForTesting + public static class QualifiedTelephonyTimeZoneSuggestion { + + @VisibleForTesting + public final TelephonyTimeZoneSuggestion suggestion; + + /** + * The score the suggestion has been given. This can be used to rank against other + * suggestions of the same type. + */ + @VisibleForTesting + public final int score; + + @VisibleForTesting + public QualifiedTelephonyTimeZoneSuggestion( + TelephonyTimeZoneSuggestion suggestion, int score) { + this.suggestion = suggestion; + this.score = score; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QualifiedTelephonyTimeZoneSuggestion that = (QualifiedTelephonyTimeZoneSuggestion) o; + return score == that.score + && suggestion.equals(that.suggestion); + } + + @Override + public int hashCode() { + return Objects.hash(score, suggestion); + } + + @Override + public String toString() { + return "QualifiedTelephonyTimeZoneSuggestion{" + + "suggestion=" + suggestion + + ", score=" + score + + '}'; + } + } +} diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 4f010d5e0f2c..e3b1152cd7b7 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -383,36 +383,38 @@ public final class TvInputManagerService extends SystemService { if (mCurrentUserId == userId) { return; } - UserState userState = mUserStates.get(mCurrentUserId); - List<SessionState> sessionStatesToRelease = new ArrayList<>(); - for (SessionState sessionState : userState.sessionStateMap.values()) { - if (sessionState.session != null && !sessionState.isRecordingSession) { - sessionStatesToRelease.add(sessionState); + if (mUserStates.contains(mCurrentUserId)) { + UserState userState = mUserStates.get(mCurrentUserId); + List<SessionState> sessionStatesToRelease = new ArrayList<>(); + for (SessionState sessionState : userState.sessionStateMap.values()) { + if (sessionState.session != null && !sessionState.isRecordingSession) { + sessionStatesToRelease.add(sessionState); + } } - } - for (SessionState sessionState : sessionStatesToRelease) { - try { - sessionState.session.release(); - } catch (RemoteException e) { - Slog.e(TAG, "error in release", e); + for (SessionState sessionState : sessionStatesToRelease) { + try { + sessionState.session.release(); + } catch (RemoteException e) { + Slog.e(TAG, "error in release", e); + } + clearSessionAndNotifyClientLocked(sessionState); } - clearSessionAndNotifyClientLocked(sessionState); - } - for (Iterator<ComponentName> it = userState.serviceStateMap.keySet().iterator(); - it.hasNext(); ) { - ComponentName component = it.next(); - ServiceState serviceState = userState.serviceStateMap.get(component); - if (serviceState != null && serviceState.sessionTokens.isEmpty()) { - if (serviceState.callback != null) { - try { - serviceState.service.unregisterCallback(serviceState.callback); - } catch (RemoteException e) { - Slog.e(TAG, "error in unregisterCallback", e); + for (Iterator<ComponentName> it = userState.serviceStateMap.keySet().iterator(); + it.hasNext(); ) { + ComponentName component = it.next(); + ServiceState serviceState = userState.serviceStateMap.get(component); + if (serviceState != null && serviceState.sessionTokens.isEmpty()) { + if (serviceState.callback != null) { + try { + serviceState.service.unregisterCallback(serviceState.callback); + } catch (RemoteException e) { + Slog.e(TAG, "error in unregisterCallback", e); + } } + mContext.unbindService(serviceState.connection); + it.remove(); } - mContext.unbindService(serviceState.connection); - it.remove(); } } @@ -490,6 +492,10 @@ public final class TvInputManagerService extends SystemService { userState.mainSessionToken = null; mUserStates.remove(userId); + + if (userId == mCurrentUserId) { + switchUser(UserHandle.USER_SYSTEM); + } } } diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index d5961a881c36..d380f8cd7337 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -356,6 +356,8 @@ class ActivityStack extends Task implements BoundsAnimationTarget { // TODO(task-hierarchy): remove when tiles can be actual parents TaskTile mTile = null; + private int mLastTaskOrganizerWindowingMode = -1; + private final Handler mHandler; private class ActivityStackHandler extends Handler { @@ -794,6 +796,13 @@ class ActivityStack extends Task implements BoundsAnimationTarget { } final int windowingMode = getWindowingMode(); + if (windowingMode == mLastTaskOrganizerWindowingMode) { + // If our windowing mode hasn't actually changed, then just stick + // with our old organizer. This lets us implement the semantic + // where SysUI can continue to manage it's old tasks + // while CTS temporarily takes over the registration. + return; + } /* * Different windowing modes may be managed by different task organizers. If * getTaskOrganizer returns null, we still call setTaskOrganizer to @@ -802,6 +811,7 @@ class ActivityStack extends Task implements BoundsAnimationTarget { final ITaskOrganizer org = mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); setTaskOrganizer(org); + mLastTaskOrganizerWindowingMode = windowingMode; } @Override diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 75d87edbc437..f35ba9e69ed7 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -386,6 +386,8 @@ public class ActivityStartController { } else { callingPid = callingUid = -1; } + final int filterCallingUid = ActivityStarter.computeResolveFilterUid( + callingUid, realCallingUid, UserHandle.USER_NULL); final SparseArray<String> startingUidPkgs = new SparseArray<>(); final long origId = Binder.clearCallingIdentity(); try { @@ -408,9 +410,7 @@ public class ActivityStartController { // Collect information about the target of the Intent. ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i], - 0 /* startFlags */, null /* profilerInfo */, userId, - ActivityStarter.computeResolveFilterUid( - callingUid, realCallingUid, UserHandle.USER_NULL)); + 0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid); aInfo = mService.mAmInternal.getActivityInfoForUser(aInfo, userId); if (aInfo != null) { @@ -457,6 +457,7 @@ public class ActivityStartController { Slog.wtf(TAG, sb.toString()); } + final IBinder sourceResultTo = resultTo; final ActivityRecord[] outActivity = new ActivityRecord[1]; // Lock the loop to ensure the activities launched in a sequence. synchronized (mService.mGlobalLock) { @@ -470,7 +471,18 @@ public class ActivityStartController { } return startResult; } - resultTo = outActivity[0] != null ? outActivity[0].appToken : null; + final ActivityRecord started = outActivity[0]; + if (started != null && started.getUid() == filterCallingUid) { + // Only the started activity which has the same uid as the source caller can + // be the caller of next activity. + resultTo = started.appToken; + } else { + resultTo = sourceResultTo; + // Different apps not adjacent to the caller are forced to be new task. + if (i < starters.length - 1) { + starters[i + 1].getIntent().addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + } } } } finally { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 9e3292b59402..c7270f257923 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2493,7 +2493,6 @@ class ActivityStarter { return this; } - @VisibleForTesting Intent getIntent() { return mRequest.intent; } diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java new file mode 100644 index 000000000000..94decc792fd3 --- /dev/null +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -0,0 +1,122 @@ +/* + * 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 android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; +import android.view.ITaskOrganizer; +import android.view.SurfaceControl; + +import java.util.HashMap; + +/** + * Utility class for collecting and merging transactions from various sources asynchronously. + * For example to use to synchronously resize all the children of a window container + * 1. Open a new sync set, and pass the listener that will be invoked + * int id startSyncSet(TransactionReadyListener) + * the returned ID will be eventually passed to the TransactionReadyListener in combination + * with the prepared transaction. You also use it to refer to the operation in future steps. + * 2. Ask each child to participate: + * addToSyncSet(int id, WindowContainer wc) + * if the child thinks it will be affected by a configuration change (a.k.a. has a visible + * window in its sub hierarchy, then we will increment a counter of expected callbacks + * At this point the containers hierarchy will redirect pendingTransaction and sub hierarchy + * updates in to the sync engine. + * 3. Apply your configuration changes to the window containers. + * 4. Tell the engine that the sync set is ready + * setReady(int id) + * 5. If there were no sub windows anywhere in the hierarchy to wait on, then + * transactionReady is immediately invoked, otherwise all the windows are poked + * to redraw and to deliver a buffer to WMS#finishDrawing. + * Once all this drawing is complete the combined transaction of all the buffers + * and pending transaction hierarchy changes will be delivered to the TransactionReadyListener + */ +class BLASTSyncEngine { + private static final String TAG = "BLASTSyncEngine"; + + interface TransactionReadyListener { + void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction); + }; + + // Holds state associated with a single synchronous set of operations. + class SyncState implements TransactionReadyListener { + int mSyncId; + SurfaceControl.Transaction mMergedTransaction; + int mRemainingTransactions; + TransactionReadyListener mListener; + boolean mReady = false; + + private void tryFinish() { + if (mRemainingTransactions == 0 && mReady) { + mListener.transactionReady(mSyncId, mMergedTransaction); + mPendingSyncs.remove(mSyncId); + } + } + + public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) { + mRemainingTransactions--; + mMergedTransaction.merge(mergedTransaction); + tryFinish(); + } + + void setReady() { + mReady = true; + tryFinish(); + } + + boolean addToSync(WindowContainer wc) { + if (wc.prepareForSync(this, mSyncId)) { + mRemainingTransactions++; + return true; + } + return false; + } + + SyncState(TransactionReadyListener l, int id) { + mListener = l; + mSyncId = id; + mMergedTransaction = new SurfaceControl.Transaction(); + mRemainingTransactions = 0; + } + }; + + int mNextSyncId = 0; + + final HashMap<Integer, SyncState> mPendingSyncs = new HashMap(); + + BLASTSyncEngine() { + } + + int startSyncSet(TransactionReadyListener listener) { + final int id = mNextSyncId++; + final SyncState s = new SyncState(listener, id); + mPendingSyncs.put(id, s); + return id; + } + + boolean addToSyncSet(int id, WindowContainer wc) { + final SyncState st = mPendingSyncs.get(id); + return st.addToSync(wc); + } + + // TODO(b/148476626): TIMEOUTS! + void setReady(int id) { + final SyncState st = mPendingSyncs.get(id); + st.setReady(); + } +} diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index efe79b36d645..e71371a477f7 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -526,7 +526,9 @@ public class DisplayRotation { mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout); mIsWaitingForRemoteRotation = false; mDisplayContent.sendNewConfiguration(); - mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); + if (t != null) { + mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null); + } } } 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/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2fdcbc9083f6..2196d899406d 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -3323,6 +3323,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent> * @return {@code true} if the top activity looks like it belongs to {@param userId}. */ private void taskTopActivityIsUser(Task task, @UserIdInt int userId) { + // TODO(b/80414790): having utilities to loop for all leaf tasks from caller vs. checking + // leaf tasks here. + if (!task.isLeafTask()) { + // No op if not a leaf task since we don't want to report root tasks to + // TaskStackListeners. + return; + } + // To handle the case that work app is in the task but just is not the top one. final ActivityRecord activityRecord = task.getTopNonFinishingActivity(); final ActivityRecord resultTo = (activityRecord != null ? activityRecord.resultTo : null); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 87c91ef6b96c..302e7596dec4 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; @@ -1433,18 +1427,27 @@ class Task extends WindowContainer<WindowContainer> { } /** - * @return whether or not there are ONLY task overlay activities in the stack. + * @return whether or not there are ONLY task overlay activities in the task. * If {@param includeFinishing} is set, then don't ignore finishing activities in the * check. If there are no task overlay activities, this call returns false. */ boolean onlyHasTaskOverlayActivities(boolean includeFinishing) { - if (getChildCount() == 0) { - return false; - } - if (includeFinishing) { - return getActivity((r) -> r.isTaskOverlay()) != null; + int count = 0; + for (int i = getChildCount() - 1; i >= 0; i--) { + final ActivityRecord r = getChildAt(i).asActivityRecord(); + if (r == null) { + // Has a child that is other than Activity. + return false; + } + if (!includeFinishing && r.finishing) { + continue; + } + if (!r.isTaskOverlay()) { + return false; + } + count++; } - return getActivity((r) -> !r.finishing && r.isTaskOverlay()) != null; + return count > 0; } private boolean autoRemoveFromRecents() { @@ -2371,6 +2374,15 @@ class Task extends WindowContainer<WindowContainer> { return getRootTask() == this; } + boolean isLeafTask() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + if (mChildren.get(i).asTask() != null) { + return false; + } + } + return true; + } + int getDescendantTaskCount() { final int[] currentCount = {0}; final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; }, @@ -3195,12 +3207,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 44a6fc936961..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; @@ -38,6 +40,7 @@ import android.util.ArraySet; import android.util.Slog; import android.view.ITaskOrganizer; import android.view.IWindowContainer; +import android.view.SurfaceControl; import android.view.WindowContainerTransaction; import com.android.internal.util.function.pooled.PooledConsumer; @@ -46,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; @@ -53,7 +57,8 @@ import java.util.WeakHashMap; * Stores the TaskOrganizers associated with a given windowing mode and * their associated state. */ -class TaskOrganizerController extends ITaskOrganizerController.Stub { +class TaskOrganizerController extends ITaskOrganizerController.Stub + implements BLASTSyncEngine.TransactionReadyListener { private static final String TAG = "TaskOrganizerController"; /** Flag indicating that an applied transaction may have effected lifecycle */ @@ -74,11 +79,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { @Override public void binderDied() { synchronized (mGlobalLock) { - final TaskOrganizerState state = mTaskOrganizerStates.get(mTaskOrganizer); - for (int i = 0; i < state.mOrganizedTasks.size(); i++) { - state.mOrganizedTasks.get(i).taskOrganizerDied(); - } - mTaskOrganizerStates.remove(mTaskOrganizer); + final TaskOrganizerState state = + mTaskOrganizerStates.get(mTaskOrganizer.asBinder()); + state.releaseTasks(); + mTaskOrganizerStates.remove(mTaskOrganizer.asBinder()); if (mTaskOrganizersForWindowingMode.get(mWindowingMode) == mTaskOrganizer) { mTaskOrganizersForWindowingMode.remove(mWindowingMode); } @@ -89,32 +93,84 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { class TaskOrganizerState { ITaskOrganizer mOrganizer; DeathRecipient mDeathRecipient; + int mWindowingMode; ArrayList<Task> mOrganizedTasks = new ArrayList<>(); + // Save the TaskOrganizer which we replaced registration for + // so it can be re-registered if we unregister. + TaskOrganizerState mReplacementFor; + boolean mDisposed = false; + + + TaskOrganizerState(ITaskOrganizer organizer, int windowingMode, + TaskOrganizerState replacing) { + mOrganizer = organizer; + mDeathRecipient = new DeathRecipient(organizer, windowingMode); + try { + organizer.asBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + Slog.e(TAG, "TaskOrganizer failed to register death recipient"); + } + mWindowingMode = windowingMode; + mReplacementFor = replacing; + } + void addTask(Task t) { mOrganizedTasks.add(t); + try { + mOrganizer.taskAppeared(t.getTaskInfo()); + } catch (Exception e) { + Slog.e(TAG, "Exception sending taskAppeared callback" + e); + } } void removeTask(Task t) { + try { + mOrganizer.taskVanished(t.getRemoteToken()); + } catch (Exception e) { + Slog.e(TAG, "Exception sending taskVanished callback" + e); + } mOrganizedTasks.remove(t); } - TaskOrganizerState(ITaskOrganizer organizer, DeathRecipient deathRecipient) { - mOrganizer = organizer; - mDeathRecipient = deathRecipient; + void dispose() { + mDisposed = true; + releaseTasks(); + handleReplacement(); + } + + void releaseTasks() { + for (int i = mOrganizedTasks.size() - 1; i >= 0; i--) { + final Task t = mOrganizedTasks.get(i); + t.taskOrganizerDied(); + removeTask(t); + } + } + + void handleReplacement() { + if (mReplacementFor != null && !mReplacementFor.mDisposed) { + mTaskOrganizersForWindowingMode.put(mWindowingMode, mReplacementFor); + } + } + + void unlinkDeath() { + mDisposed = true; + mOrganizer.asBinder().unlinkToDeath(mDeathRecipient, 0); } }; final HashMap<Integer, TaskOrganizerState> mTaskOrganizersForWindowingMode = new HashMap(); - final HashMap<ITaskOrganizer, TaskOrganizerState> mTaskOrganizerStates = new HashMap(); + final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap(); final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap(); private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>(); private final ArrayList<Task> mPendingTaskInfoChanges = new ArrayList<>(); + private final BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine(); + final ActivityTaskManagerService mService; RunningTaskInfo mTmpTaskInfo; @@ -128,17 +184,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func); } - private void clearIfNeeded(int windowingMode) { - final TaskOrganizerState oldState = mTaskOrganizersForWindowingMode.get(windowingMode); - if (oldState != null) { - oldState.mOrganizer.asBinder().unlinkToDeath(oldState.mDeathRecipient, 0); - } - } - /** * Register a TaskOrganizer to manage tasks as they enter the given windowing mode. * If there was already a TaskOrganizer for this windowing mode it will be evicted - * and receive taskVanished callbacks in the process. + * but will continue to organize it's existing tasks. */ @Override public void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) { @@ -153,24 +202,25 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - clearIfNeeded(windowingMode); - DeathRecipient dr = new DeathRecipient(organizer, windowingMode); - try { - organizer.asBinder().linkToDeath(dr, 0); - } catch (RemoteException e) { - Slog.e(TAG, "TaskOrganizer failed to register death recipient"); - } - - final TaskOrganizerState state = new TaskOrganizerState(organizer, dr); + final TaskOrganizerState state = new TaskOrganizerState(organizer, windowingMode, + mTaskOrganizersForWindowingMode.get(windowingMode)); mTaskOrganizersForWindowingMode.put(windowingMode, state); - - mTaskOrganizerStates.put(organizer, state); + mTaskOrganizerStates.put(organizer.asBinder(), state); } } finally { Binder.restoreCallingIdentity(origId); } } + void unregisterTaskOrganizer(ITaskOrganizer organizer) { + final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); + state.unlinkDeath(); + if (mTaskOrganizersForWindowingMode.get(state.mWindowingMode) == state) { + mTaskOrganizersForWindowingMode.remove(state.mWindowingMode); + } + state.dispose(); + } + ITaskOrganizer getTaskOrganizer(int windowingMode) { final TaskOrganizerState state = mTaskOrganizersForWindowingMode.get(windowingMode); if (state == null) { @@ -179,35 +229,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return state.mOrganizer; } - private void sendTaskAppeared(ITaskOrganizer organizer, Task task) { - try { - organizer.taskAppeared(task.getTaskInfo()); - } catch (Exception e) { - Slog.e(TAG, "Exception sending taskAppeared callback" + e); - } - } - - private void sendTaskVanished(ITaskOrganizer organizer, Task task) { - try { - organizer.taskVanished(task.getRemoteToken()); - } catch (Exception e) { - Slog.e(TAG, "Exception sending taskVanished callback" + e); - } - } - void onTaskAppeared(ITaskOrganizer organizer, Task task) { - TaskOrganizerState state = mTaskOrganizerStates.get(organizer); - + final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); state.addTask(task); - sendTaskAppeared(organizer, task); } void onTaskVanished(ITaskOrganizer organizer, Task task) { - final TaskOrganizerState state = mTaskOrganizerStates.get(organizer); - sendTaskVanished(organizer, task); - - // This could trigger TaskAppeared for other tasks in the same stack so make sure - // we do this AFTER sending taskVanished. + final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); state.removeTask(task); } @@ -350,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)) { @@ -380,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) @@ -408,15 +523,35 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } @Override - public void applyContainerTransaction(WindowContainerTransaction t) { + public int applyContainerTransaction(WindowContainerTransaction t, ITaskOrganizer organizer) { enforceStackPermission("applyContainerTransaction()"); + int syncId = -1; if (t == null) { - return; + throw new IllegalArgumentException( + "Null transaction passed to applyContainerTransaction"); } long ident = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { int effects = 0; + + /** + * If organizer is non-null we are looking to synchronize this transaction + * by collecting all the results in to a SurfaceFlinger transaction and + * then delivering that to the given organizers transaction ready callback. + * See {@link BLASTSyncEngine} for the details of the operation. But at + * a high level we create a sync operation with a given ID and an associated + * organizer. Then we notify each WindowContainer in this WindowContainer + * transaction that it is participating in a sync operation with that + * ID. Once everything is notified we tell the BLASTSyncEngine + * "setSyncReady" which means that we have added everything + * to the set. At any point after this, all the WindowContainers + * will eventually finish applying their changes and notify the + * BLASTSyncEngine which will deliver the Transaction to the organizer. + */ + if (organizer != null) { + syncId = startSyncWithOrganizer(organizer); + } mService.deferWindowLayout(); try { ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>(); @@ -425,15 +560,25 @@ 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; + // Lifecycle changes will trigger ensureConfig for everything. if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0 && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { haveConfigChanges.add(wc); } + if (syncId >= 0) { + 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 @@ -454,10 +599,38 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } } finally { mService.continueWindowLayout(); + if (syncId >= 0) { + setSyncReady(syncId); + } } } } finally { Binder.restoreCallingIdentity(ident); } + return syncId; + } + + @Override + public void transactionReady(int id, SurfaceControl.Transaction sc) { + final ITaskOrganizer organizer = mTaskOrganizersByPendingSyncId.get(id); + if (organizer == null) { + Slog.e(TAG, "Got transaction complete for unexpected ID"); + } + try { + organizer.transactionReady(id, sc); + } catch (RemoteException e) { + } + + mTaskOrganizersByPendingSyncId.remove(id); + } + + int startSyncWithOrganizer(ITaskOrganizer organizer) { + int id = mBLASTSyncEngine.startSyncSet(this); + mTaskOrganizersByPendingSyncId.put(id, organizer); + return id; + } + + void setSyncReady(int id) { + mBLASTSyncEngine.setReady(id); } } diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java index 8bbb0d7da4b6..b5892b9b4dc6 100644 --- a/services/core/java/com/android/server/wm/TaskPositioner.java +++ b/services/core/java/com/android/server/wm/TaskPositioner.java @@ -20,6 +20,11 @@ import static android.app.ActivityTaskManager.RESIZE_MODE_USER; import static android.app.ActivityTaskManager.RESIZE_MODE_USER_FORCED; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING; @@ -29,7 +34,6 @@ import static com.android.server.wm.WindowManagerService.dipToPixel; import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP; -import android.annotation.IntDef; import android.annotation.NonNull; import android.app.IActivityTaskManager; import android.graphics.Point; @@ -55,11 +59,10 @@ import android.view.SurfaceControl; import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.policy.TaskResizingAlgorithm; +import com.android.internal.policy.TaskResizingAlgorithm.CtrlType; import com.android.server.protolog.common.ProtoLog; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - class TaskPositioner implements IBinder.DeathRecipient { private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false; private static final String TAG_LOCAL = "TaskPositioner"; @@ -67,33 +70,10 @@ class TaskPositioner implements IBinder.DeathRecipient { private static Factory sFactory; - @IntDef(flag = true, - value = { - CTRL_NONE, - CTRL_LEFT, - CTRL_RIGHT, - CTRL_TOP, - CTRL_BOTTOM - }) - @Retention(RetentionPolicy.SOURCE) - @interface CtrlType {} - - private static final int CTRL_NONE = 0x0; - private static final int CTRL_LEFT = 0x1; - private static final int CTRL_RIGHT = 0x2; - private static final int CTRL_TOP = 0x4; - private static final int CTRL_BOTTOM = 0x8; - public static final float RESIZING_HINT_ALPHA = 0.5f; public static final int RESIZING_HINT_DURATION_MS = 0; - // The minimal aspect ratio which needs to be met to count as landscape (or 1/.. for portrait). - // Note: We do not use the 1.33 from the CDD here since the user is allowed to use what ever - // aspect he desires. - @VisibleForTesting - static final float MIN_ASPECT = 1.2f; - private final WindowManagerService mService; private final IActivityTaskManager mActivityManager; private WindowPositionerEventReceiver mInputEventReceiver; @@ -477,122 +457,13 @@ class TaskPositioner implements IBinder.DeathRecipient { */ @VisibleForTesting void resizeDrag(float x, float y) { - // This is a resizing operation. - // We need to keep various constraints: - // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y] - // 2. The orientation is kept - if required. - final int deltaX = Math.round(x - mStartDragX); - final int deltaY = Math.round(y - mStartDragY); - int left = mWindowOriginalBounds.left; - int top = mWindowOriginalBounds.top; - int right = mWindowOriginalBounds.right; - int bottom = mWindowOriginalBounds.bottom; - - // Calculate the resulting width and height of the drag operation. - int width = right - left; - int height = bottom - top; - if ((mCtrlType & CTRL_LEFT) != 0) { - width = Math.max(mMinVisibleWidth, width - deltaX); - } else if ((mCtrlType & CTRL_RIGHT) != 0) { - width = Math.max(mMinVisibleWidth, width + deltaX); - } - if ((mCtrlType & CTRL_TOP) != 0) { - height = Math.max(mMinVisibleHeight, height - deltaY); - } else if ((mCtrlType & CTRL_BOTTOM) != 0) { - height = Math.max(mMinVisibleHeight, height + deltaY); - } - - // If we have to preserve the orientation - check that we are doing so. - final float aspect = (float) width / (float) height; - if (mPreserveOrientation && ((mStartOrientationWasLandscape && aspect < MIN_ASPECT) - || (!mStartOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) { - // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major - // drag axis. What ever is producing the bigger rectangle will be chosen. - int width1; - int width2; - int height1; - int height2; - if (mStartOrientationWasLandscape) { - // Assuming that the width is our target we calculate the height. - width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width)); - height1 = Math.min(height, Math.round((float)width1 / MIN_ASPECT)); - if (height1 < mMinVisibleHeight) { - // If the resulting height is too small we adjust to the minimal size. - height1 = mMinVisibleHeight; - width1 = Math.max(mMinVisibleWidth, - Math.min(mMaxVisibleSize.x, Math.round((float)height1 * MIN_ASPECT))); - } - // Assuming that the height is our target we calculate the width. - height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height)); - width2 = Math.max(width, Math.round((float)height2 * MIN_ASPECT)); - if (width2 < mMinVisibleWidth) { - // If the resulting width is too small we adjust to the minimal size. - width2 = mMinVisibleWidth; - height2 = Math.max(mMinVisibleHeight, - Math.min(mMaxVisibleSize.y, Math.round((float)width2 / MIN_ASPECT))); - } - } else { - // Assuming that the width is our target we calculate the height. - width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width)); - height1 = Math.max(height, Math.round((float)width1 * MIN_ASPECT)); - if (height1 < mMinVisibleHeight) { - // If the resulting height is too small we adjust to the minimal size. - height1 = mMinVisibleHeight; - width1 = Math.max(mMinVisibleWidth, - Math.min(mMaxVisibleSize.x, Math.round((float)height1 / MIN_ASPECT))); - } - // Assuming that the height is our target we calculate the width. - height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height)); - width2 = Math.min(width, Math.round((float)height2 / MIN_ASPECT)); - if (width2 < mMinVisibleWidth) { - // If the resulting width is too small we adjust to the minimal size. - width2 = mMinVisibleWidth; - height2 = Math.max(mMinVisibleHeight, - Math.min(mMaxVisibleSize.y, Math.round((float)width2 * MIN_ASPECT))); - } - } - - // Use the bigger of the two rectangles if the major change was positive, otherwise - // do the opposite. - final boolean grows = width > (right - left) || height > (bottom - top); - if (grows == (width1 * height1 > width2 * height2)) { - width = width1; - height = height1; - } else { - width = width2; - height = height2; - } - } - - // Update mWindowDragBounds to the new drag size. - updateDraggedBounds(left, top, right, bottom, width, height); + updateDraggedBounds(TaskResizingAlgorithm.resizeDrag(x, y, mStartDragX, mStartDragY, + mWindowOriginalBounds, mCtrlType, mMinVisibleWidth, mMinVisibleHeight, + mMaxVisibleSize, mPreserveOrientation, mStartOrientationWasLandscape)); } - /** - * Given the old coordinates and the new width and height, update the mWindowDragBounds. - * - * @param left The original left bound before the user started dragging. - * @param top The original top bound before the user started dragging. - * @param right The original right bound before the user started dragging. - * @param bottom The original bottom bound before the user started dragging. - * @param newWidth The new dragged width. - * @param newHeight The new dragged height. - */ - void updateDraggedBounds(int left, int top, int right, int bottom, int newWidth, - int newHeight) { - // Generate the final bounds by keeping the opposite drag edge constant. - if ((mCtrlType & CTRL_LEFT) != 0) { - left = right - newWidth; - } else { // Note: The right might have changed - if we pulled at the right or not. - right = left + newWidth; - } - if ((mCtrlType & CTRL_TOP) != 0) { - top = bottom - newHeight; - } else { // Note: The height might have changed - if we pulled at the bottom or not. - bottom = top + newHeight; - } - - mWindowDragBounds.set(left, top, right, bottom); + private void updateDraggedBounds(Rect newBounds) { + mWindowDragBounds.set(newBounds); checkBoundsForOrientationViolations(mWindowDragBounds); } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 86723153c5f6..504aa2dc7b4c 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -94,7 +94,8 @@ import java.util.function.Predicate; * changes are made to this class. */ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E> - implements Comparable<WindowContainer>, Animatable { + implements Comparable<WindowContainer>, Animatable, + BLASTSyncEngine.TransactionReadyListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM; @@ -260,6 +261,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< */ RemoteToken mRemoteToken = null; + BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine(); + SurfaceControl.Transaction mBLASTSyncTransaction = new SurfaceControl.Transaction(); + boolean mUsingBLASTSyncTransaction = false; + BLASTSyncEngine.TransactionReadyListener mWaitingListener; + int mWaitingSyncId; + WindowContainer(WindowManagerService wms) { mWmService = wms; mPendingTransaction = wms.mTransactionFactory.get(); @@ -1837,6 +1844,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Override public Transaction getPendingTransaction() { + if (mUsingBLASTSyncTransaction) { + return mBLASTSyncTransaction; + } + final DisplayContent displayContent = getDisplayContent(); if (displayContent != null && displayContent != this) { return displayContent.getPendingTransaction(); @@ -2285,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; @@ -2316,4 +2331,38 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return sb.toString(); } } + + @Override + public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) { + mergedTransaction.merge(mBLASTSyncTransaction); + mUsingBLASTSyncTransaction = false; + + mWaitingListener.transactionReady(mWaitingSyncId, mergedTransaction); + + mWaitingListener = null; + mWaitingSyncId = -1; + } + + boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener, + int waitingId) { + boolean willSync = false; + if (!isVisible()) { + return willSync; + } + mUsingBLASTSyncTransaction = true; + + int localId = mBLASTSyncEngine.startSyncSet(this); + for (int i = 0; i < mChildren.size(); i++) { + final WindowContainer child = mChildren.get(i); + willSync = mBLASTSyncEngine.addToSyncSet(localId, child) | willSync; + } + + // Make sure to set these before we call setReady in case the sync was a no-op + mWaitingSyncId = waitingId; + mWaitingListener = waitingListener; + + mBLASTSyncEngine.setReady(localId); + + return willSync; + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 6e243f0b937a..59eee9cc9404 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -562,4 +562,14 @@ public abstract class WindowManagerInternal { */ public abstract void setAccessibilityIdToSurfaceMetadata( IBinder windowToken, int accessibilityWindowId); + + /** + * Transfers input focus from a given input token to that of the IME window. + * + * @param sourceInputToken The source token. + * @param displayId The display hosting the IME window. + * @return Whether transfer was successful. + */ + public abstract boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, + int displayId); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a5b99b0a26a3..a370093a3907 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2505,7 +2505,7 @@ public class WindowManagerService extends IWindowManager.Stub WindowState win = windowForClientLocked(session, client, false); ProtoLog.d(WM_DEBUG_ADD_REMOVE, "finishDrawingWindow: %s mDrawState=%s", win, (win != null ? win.mWinAnimator.drawStateToString() : "null")); - if (win != null && win.mWinAnimator.finishDrawingLocked(postDrawTransaction)) { + if (win != null && win.finishDrawing(postDrawTransaction)) { if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) { win.getDisplayContent().pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; @@ -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 " @@ -7558,6 +7564,29 @@ public class WindowManagerService extends IWindowManager.Stub } } } + + @Override + public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, + int displayId) { + final IBinder destinationInputToken; + + synchronized (mGlobalLock) { + final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + if (displayContent == null) { + return false; + } + final WindowState imeWindow = displayContent.mInputMethodWindow; + if (imeWindow == null) { + return false; + } + if (imeWindow.mInputChannel == null) { + return false; + } + destinationInputToken = imeWindow.mInputChannel.getToken(); + } + + return mInputManager.transferTouchFocus(sourceInputToken, destinationInputToken); + } } void registerAppFreezeListener(AppFreezeListener listener) { @@ -7992,7 +8021,11 @@ public class WindowManagerService extends IWindowManager.Stub int displayId, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper displayCutout) { synchronized (mGlobalLock) { - final DisplayContent dc = mRoot.getDisplayContent(displayId); + final DisplayContent dc = mRoot.getDisplayContentOrCreate(displayId); + if (dc == null) { + throw new WindowManager.InvalidDisplayException("Display#" + displayId + + "could not be found!"); + } final WindowToken windowToken = dc.getWindowToken(attrs.token); final ActivityRecord activity; if (windowToken != null && windowToken.asActivityRecord() != null) { 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/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 42e5bbc1c1ee..b336f8d1a1ac 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5689,4 +5689,30 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP SurfaceControl getDeferTransactionBarrier() { return mWinAnimator.getDeferTransactionBarrier(); } + + @Override + boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener, + int waitingId) { + // TODO(b/148871522): Support child window + mWaitingListener = waitingListener; + mWaitingSyncId = waitingId; + mUsingBLASTSyncTransaction = true; + return true; + } + + boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction) { + if (!mUsingBLASTSyncTransaction) { + return mWinAnimator.finishDrawingLocked(postDrawTransaction); + } + if (postDrawTransaction == null) { + postDrawTransaction = new SurfaceControl.Transaction(); + } + postDrawTransaction.merge(mBLASTSyncTransaction); + mWaitingListener.transactionReady(mWaitingSyncId, postDrawTransaction); + mUsingBLASTSyncTransaction = false; + + mWaitingSyncId = 0; + mWaitingListener = null; + return mWinAnimator.finishDrawingLocked(null); + } } diff --git a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java b/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java index 9f307bb0f98f..59abaab0f795 100644 --- a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java +++ b/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java @@ -59,7 +59,7 @@ public class DisplayRotationUtil { } /** - * Compute bounds after rotating teh screen. + * Compute bounds after rotating the screen. * * @param bounds Bounds before the rotation. The array must contain exactly 4 non-null elements. * @param rotation rotation constant defined in android.view.Surface. diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 390068e1fa75..49c7e0a3b242 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -13,7 +13,6 @@ cc_library_static { ], srcs: [ - ":graphicsstats_proto", ":lib_alarmManagerService_native", "BroadcastRadio/JavaRef.cpp", "BroadcastRadio/NativeCallbackThread.cpp", @@ -53,7 +52,6 @@ cc_library_static { "com_android_server_UsbHostManager.cpp", "com_android_server_VibratorService.cpp", "com_android_server_PersistentDataBlockService.cpp", - "com_android_server_GraphicsStatsService.cpp", "com_android_server_am_CachedAppOptimizer.cpp", "com_android_server_am_LowMemDetector.cpp", "com_android_server_incremental_IncrementalManagerService.cpp", @@ -107,8 +105,7 @@ cc_defaults { "libinputflinger", "libinputflinger_base", "libinputservice", - "libprotobuf-cpp-lite", - "libprotoutil", + "libstatshidl", "libstatspull", "libstatssocket", "libstatslog", @@ -155,6 +152,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_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp deleted file mode 100644 index 7644adebb10a..000000000000 --- a/services/core/jni/com_android_server_GraphicsStatsService.cpp +++ /dev/null @@ -1,290 +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. - */ - -#define LOG_TAG "GraphicsStatsService" - -#include <jni.h> -#include <log/log.h> -#include <nativehelper/JNIHelp.h> -#include <nativehelper/ScopedPrimitiveArray.h> -#include <nativehelper/ScopedUtfChars.h> -#include <JankTracker.h> -#include <service/GraphicsStatsService.h> -#include <stats_pull_atom_callback.h> -#include <stats_event.h> -#include <statslog.h> -#include <google/protobuf/io/zero_copy_stream_impl_lite.h> -#include <android/util/ProtoOutputStream.h> -#include "android/graphics/Utils.h" -#include "core_jni_helpers.h" -#include "protos/graphicsstats.pb.h" -#include <cstring> -#include <memory> - -namespace android { - -using namespace android::uirenderer; - -static jint getAshmemSize(JNIEnv*, jobject) { - return sizeof(ProfileData); -} - -static jlong createDump(JNIEnv*, jobject, jint fd, jboolean isProto) { - GraphicsStatsService::Dump* dump = GraphicsStatsService::createDump(fd, isProto - ? GraphicsStatsService::DumpType::Protobuf : GraphicsStatsService::DumpType::Text); - return reinterpret_cast<jlong>(dump); -} - -static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage, - jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { - std::string path; - const ProfileData* data = nullptr; - LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null"); - ScopedByteArrayRO buffer{env}; - if (jdata != nullptr) { - buffer.reset(jdata); - LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData), - "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData)); - data = reinterpret_cast<const ProfileData*>(buffer.get()); - } - if (jpath != nullptr) { - ScopedUtfChars pathChars(env, jpath); - LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); - path.assign(pathChars.c_str(), pathChars.size()); - } - ScopedUtfChars packageChars(env, jpackage); - LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), "Failed to get path chars"); - GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); - LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer"); - - const std::string package(packageChars.c_str(), packageChars.size()); - GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data); -} - -static void addFileToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath) { - ScopedUtfChars pathChars(env, jpath); - LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); - const std::string path(pathChars.c_str(), pathChars.size()); - GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); - GraphicsStatsService::addToDump(dump, path); -} - -static void finishDump(JNIEnv*, jobject, jlong dumpPtr) { - GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); - GraphicsStatsService::finishDump(dump); -} - -static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) { - GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); - std::vector<uint8_t>* result = new std::vector<uint8_t>(); - GraphicsStatsService::finishDumpInMemory(dump, - [](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) { - std::vector<uint8_t>* outBuffer = reinterpret_cast<std::vector<uint8_t>*>(param2); - if (outBuffer->size() < totalSize) { - outBuffer->resize(totalSize); - } - std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize); - }, env, result); - return reinterpret_cast<jlong>(result); -} - -static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage, - jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { - ScopedByteArrayRO buffer(env, jdata); - LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData), - "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData)); - ScopedUtfChars pathChars(env, jpath); - LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); - ScopedUtfChars packageChars(env, jpackage); - LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), "Failed to get path chars"); - - const std::string path(pathChars.c_str(), pathChars.size()); - const std::string package(packageChars.c_str(), packageChars.size()); - const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer.get()); - GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data); -} - -static jobject gGraphicsStatsServiceObject = nullptr; -static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID; - -static JNIEnv* getJNIEnv() { - JavaVM* vm = AndroidRuntime::getJavaVM(); - JNIEnv* env = nullptr; - if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { - if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { - LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!"); - } - } - return env; -} - -using namespace google::protobuf; - -// Field ids taken from FrameTimingHistogram message in atoms.proto -#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1 -#define FRAME_COUNTS_FIELD_NUMBER 2 - -static void writeCpuHistogram(stats_event* event, - const uirenderer::protos::GraphicsStatsProto& stat) { - util::ProtoOutputStream proto; - for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { - auto& bucket = stat.histogram(bucketIndex); - proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | - TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, - (int)bucket.render_millis()); - } - for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { - auto& bucket = stat.histogram(bucketIndex); - proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | - FRAME_COUNTS_FIELD_NUMBER /* field id */, - (long long)bucket.frame_count()); - } - std::vector<uint8_t> outVector; - proto.serializeToVector(&outVector); - stats_event_write_byte_array(event, outVector.data(), outVector.size()); -} - -static void writeGpuHistogram(stats_event* event, - const uirenderer::protos::GraphicsStatsProto& stat) { - util::ProtoOutputStream proto; - for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { - auto& bucket = stat.gpu_histogram(bucketIndex); - proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | - TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, - (int)bucket.render_millis()); - } - for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { - auto& bucket = stat.gpu_histogram(bucketIndex); - proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | - FRAME_COUNTS_FIELD_NUMBER /* field id */, - (long long)bucket.frame_count()); - } - std::vector<uint8_t> outVector; - proto.serializeToVector(&outVector); - stats_event_write_byte_array(event, outVector.data(), outVector.size()); -} - -// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom. -static status_pull_atom_return_t graphicsStatsPullCallback(int32_t atom_tag, - pulled_stats_event_list* data, - void* cookie) { - JNIEnv* env = getJNIEnv(); - if (!env) { - return false; - } - if (gGraphicsStatsServiceObject == nullptr) { - ALOGE("Failed to get graphicsstats service"); - return STATS_PULL_SKIP; - } - - for (bool lastFullDay : {true, false}) { - jlong jdata = (jlong) env->CallLongMethod( - gGraphicsStatsServiceObject, - gGraphicsStatsService_pullGraphicsStatsMethodID, - (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE)); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - ALOGE("Failed to invoke graphicsstats service"); - return STATS_PULL_SKIP; - } - if (!jdata) { - // null means data is not available for that day. - continue; - } - android::uirenderer::protos::GraphicsStatsServiceDumpProto serviceDump; - std::vector<uint8_t>* buffer = reinterpret_cast<std::vector<uint8_t>*>(jdata); - std::unique_ptr<std::vector<uint8_t>> bufferRelease(buffer); - int dataSize = buffer->size(); - if (!dataSize) { - // Data is not available for that day. - continue; - } - io::ArrayInputStream input{buffer->data(), dataSize}; - bool success = serviceDump.ParseFromZeroCopyStream(&input); - if (!success) { - ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'", - serviceDump.InitializationErrorString().c_str(), dataSize); - return STATS_PULL_SKIP; - } - - for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) { - auto& stat = serviceDump.stats(stat_index); - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, android::util::GRAPHICS_STATS); - stats_event_write_string8(event, stat.package_name().c_str()); - stats_event_write_int64(event, (int64_t)stat.version_code()); - stats_event_write_int64(event, (int64_t)stat.stats_start()); - stats_event_write_int64(event, (int64_t)stat.stats_end()); - stats_event_write_int32(event, (int32_t)stat.pipeline()); - stats_event_write_int32(event, (int32_t)stat.summary().total_frames()); - stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count()); - stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count()); - stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count()); - stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count()); - stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count()); - stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count()); - writeCpuHistogram(event, stat); - writeGpuHistogram(event, stat); - // TODO: fill in UI mainline module version, when the feature is available. - stats_event_write_int64(event, (int64_t)0); - stats_event_write_bool(event, !lastFullDay); - stats_event_build(event); - } - } - return STATS_PULL_SUCCESS; -} - -// Register a puller for GRAPHICS_STATS atom with the statsd service. -static void nativeInit(JNIEnv* env, jobject javaObject) { - gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject); - pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds - .timeout_ns = 2 * NS_PER_SEC, // 2 seconds - .additive_fields = nullptr, - .additive_fields_size = 0}; - register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback, - &metadata, nullptr); -} - -static void nativeDestructor(JNIEnv* env, jobject javaObject) { - //TODO: Unregister the puller callback when a new API is available. - env->DeleteGlobalRef(gGraphicsStatsServiceObject); - gGraphicsStatsServiceObject = nullptr; -} - -static const JNINativeMethod sMethods[] = { - { "nGetAshmemSize", "()I", (void*) getAshmemSize }, - { "nCreateDump", "(IZ)J", (void*) createDump }, - { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) addToDump }, - { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump }, - { "nFinishDump", "(J)V", (void*) finishDump }, - { "nFinishDumpInMemory", "(J)J", (void*) finishDumpInMemory }, - { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) saveBuffer }, - { "nativeInit", "()V", (void*) nativeInit }, - { "nativeDestructor", "()V", (void*)nativeDestructor } -}; - -int register_android_server_GraphicsStatsService(JNIEnv* env) -{ - jclass graphicsStatsService_class = FindClassOrDie(env, - "com/android/server/GraphicsStatsService"); - gGraphicsStatsService_pullGraphicsStatsMethodID = GetMethodIDOrDie(env, - graphicsStatsService_class, "pullGraphicsStats", "(Z)J"); - return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService", - sMethods, NELEM(sMethods)); -} - -} // namespace android 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/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 212a3a638634..49db3d5b2b64 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -1563,20 +1563,17 @@ static void nativeSetSystemUiVisibility(JNIEnv* /* env */, } static jboolean nativeTransferTouchFocus(JNIEnv* env, - jclass /* clazz */, jlong ptr, jobject fromChannelObj, jobject toChannelObj) { - NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); - - sp<InputChannel> fromChannel = - android_view_InputChannel_getInputChannel(env, fromChannelObj); - sp<InputChannel> toChannel = - android_view_InputChannel_getInputChannel(env, toChannelObj); - - if (fromChannel == nullptr || toChannel == nullptr) { + jclass /* clazz */, jlong ptr, jobject fromChannelTokenObj, jobject toChannelTokenObj) { + if (fromChannelTokenObj == nullptr || toChannelTokenObj == nullptr) { return JNI_FALSE; } + sp<IBinder> fromChannelToken = ibinderForJavaObject(env, fromChannelTokenObj); + sp<IBinder> toChannelToken = ibinderForJavaObject(env, toChannelTokenObj); + + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); if (im->getInputManager()->getDispatcher()->transferTouchFocus( - fromChannel->getConnectionToken(), toChannel->getConnectionToken())) { + fromChannelToken, toChannelToken)) { return JNI_TRUE; } else { return JNI_FALSE; @@ -1784,7 +1781,7 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*) nativeSetInputDispatchMode }, { "nativeSetSystemUiVisibility", "(JI)V", (void*) nativeSetSystemUiVisibility }, - { "nativeTransferTouchFocus", "(JLandroid/view/InputChannel;Landroid/view/InputChannel;)Z", + { "nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)Z", (void*) nativeTransferTouchFocus }, { "nativeSetPointerSpeed", "(JI)V", (void*) nativeSetPointerSpeed }, diff --git a/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp b/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp index f5b778e85e5c..43cd0a2d2474 100644 --- a/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp +++ b/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp @@ -31,32 +31,32 @@ namespace android { static server::stats::PowerStatsPuller gPowerStatsPuller; static server::stats::SubsystemSleepStatePuller gSubsystemSleepStatePuller; -static status_pull_atom_return_t onDevicePowerMeasurementCallback(int32_t atom_tag, - pulled_stats_event_list* data, - void* cookie) { +static AStatsManager_PullAtomCallbackReturn onDevicePowerMeasurementCallback(int32_t atom_tag, + AStatsEventList* data, + void* cookie) { return gPowerStatsPuller.Pull(atom_tag, data); } -static status_pull_atom_return_t subsystemSleepStateCallback(int32_t atom_tag, - pulled_stats_event_list* data, - void* cookie) { +static AStatsManager_PullAtomCallbackReturn subsystemSleepStateCallback(int32_t atom_tag, + AStatsEventList* data, + void* cookie) { return gSubsystemSleepStatePuller.Pull(atom_tag, data); } static void nativeInit(JNIEnv* env, jobject javaObject) { // on device power measurement gPowerStatsPuller = server::stats::PowerStatsPuller(); - register_stats_pull_atom_callback(android::util::ON_DEVICE_POWER_MEASUREMENT, - onDevicePowerMeasurementCallback, - /* metadata= */ nullptr, - /* cookie= */ nullptr); + AStatsManager_registerPullAtomCallback(android::util::ON_DEVICE_POWER_MEASUREMENT, + onDevicePowerMeasurementCallback, + /* metadata= */ nullptr, + /* cookie= */ nullptr); // subsystem sleep state gSubsystemSleepStatePuller = server::stats::SubsystemSleepStatePuller(); - register_stats_pull_atom_callback(android::util::SUBSYSTEM_SLEEP_STATE, - subsystemSleepStateCallback, - /* metadata= */ nullptr, - /* cookie= */ nullptr); + AStatsManager_registerPullAtomCallback(android::util::SUBSYSTEM_SLEEP_STATE, + subsystemSleepStateCallback, + /* metadata= */ nullptr, + /* cookie= */ nullptr); } static const JNINativeMethod sMethods[] = {{"nativeInit", "()V", (void*)nativeInit}}; diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 1202ad33996d..c1864945f921 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -49,7 +49,7 @@ int register_android_server_PersistentDataBlockService(JNIEnv* env); int register_android_server_Watchdog(JNIEnv* env); int register_android_server_HardwarePropertiesManagerService(JNIEnv* env); int register_android_server_SyntheticPasswordManager(JNIEnv* env); -int register_android_server_GraphicsStatsService(JNIEnv* env); +int register_android_graphics_GraphicsStatsService(JNIEnv* env); int register_android_hardware_display_DisplayViewport(JNIEnv* env); int register_android_server_net_NetworkStatsFactory(JNIEnv* env); int register_android_server_net_NetworkStatsService(JNIEnv* env); @@ -102,7 +102,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_HardwarePropertiesManagerService(env); register_android_server_storage_AppFuse(env); register_android_server_SyntheticPasswordManager(env); - register_android_server_GraphicsStatsService(env); + register_android_graphics_GraphicsStatsService(env); register_android_hardware_display_DisplayViewport(env); register_android_server_net_NetworkStatsFactory(env); register_android_server_net_NetworkStatsService(env); diff --git a/services/core/jni/stats/PowerStatsPuller.cpp b/services/core/jni/stats/PowerStatsPuller.cpp index e80b5cfc4a71..d8f6faac0ffb 100644 --- a/services/core/jni/stats/PowerStatsPuller.cpp +++ b/services/core/jni/stats/PowerStatsPuller.cpp @@ -78,11 +78,12 @@ static bool getPowerStatsHalLocked() { PowerStatsPuller::PowerStatsPuller() {} -status_pull_atom_return_t PowerStatsPuller::Pull(int32_t atomTag, pulled_stats_event_list* data) { +AStatsManager_PullAtomCallbackReturn PowerStatsPuller::Pull(int32_t atomTag, + AStatsEventList* data) { std::lock_guard<std::mutex> lock(gPowerStatsHalMutex); if (!getPowerStatsHalLocked()) { - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } // Pull getRailInfo if necessary @@ -100,14 +101,14 @@ status_pull_atom_return_t PowerStatsPuller::Pull(int32_t atomTag, pulled_stats_e if (!resultSuccess || !ret.isOk()) { ALOGE("power.stats getRailInfo() failed. Description: %s", ret.description().c_str()); gPowerStatsHal = nullptr; - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } // If SUCCESS but empty, or if NOT_SUPPORTED, then never try again. if (gRailInfo.empty()) { ALOGE("power.stats has no rail information"); gPowerStatsExist = false; // No rail info, so never try again. gPowerStatsHal = nullptr; - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } } @@ -134,15 +135,16 @@ status_pull_atom_return_t PowerStatsPuller::Pull(int32_t atomTag, pulled_stats_e } const RailInfo& rail = gRailInfo[energyData.index]; - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, - android::util::ON_DEVICE_POWER_MEASUREMENT); - stats_event_write_string8(event, - rail.subsysName.c_str()); - stats_event_write_string8(event, rail.railName.c_str()); - stats_event_write_int64(event, energyData.timestamp); - stats_event_write_int64(event, energyData.energy); - stats_event_build(event); + AStatsEvent* event = + AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId( + event, + android::util::ON_DEVICE_POWER_MEASUREMENT); + AStatsEvent_writeString(event, rail.subsysName.c_str()); + AStatsEvent_writeString(event, rail.railName.c_str()); + AStatsEvent_writeInt64(event, energyData.timestamp); + AStatsEvent_writeInt64(event, energyData.energy); + AStatsEvent_build(event); ALOGV("power.stat: %s.%s: %llu, %llu", rail.subsysName.c_str(), rail.railName.c_str(), @@ -153,9 +155,9 @@ status_pull_atom_return_t PowerStatsPuller::Pull(int32_t atomTag, pulled_stats_e if (!resultSuccess || !ret.isOk()) { ALOGE("power.stats getEnergyData() failed. Description: %s", ret.description().c_str()); gPowerStatsHal = nullptr; - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } - return STATS_PULL_SUCCESS; + return AStatsManager_PULL_SUCCESS; } } // namespace stats diff --git a/services/core/jni/stats/PowerStatsPuller.h b/services/core/jni/stats/PowerStatsPuller.h index 048dbb933f52..db07d600d251 100644 --- a/services/core/jni/stats/PowerStatsPuller.h +++ b/services/core/jni/stats/PowerStatsPuller.h @@ -29,7 +29,7 @@ namespace stats { class PowerStatsPuller { public: PowerStatsPuller(); - status_pull_atom_return_t Pull(int32_t atomTag, pulled_stats_event_list* data); + AStatsManager_PullAtomCallbackReturn Pull(int32_t atomTag, AStatsEventList* data); }; } // namespace stats diff --git a/services/core/jni/stats/SubsystemSleepStatePuller.cpp b/services/core/jni/stats/SubsystemSleepStatePuller.cpp index c6a836cfb6d5..45afb5eb7aa9 100644 --- a/services/core/jni/stats/SubsystemSleepStatePuller.cpp +++ b/services/core/jni/stats/SubsystemSleepStatePuller.cpp @@ -55,7 +55,7 @@ namespace android { namespace server { namespace stats { -static std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list* data)> +static std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList* data)> gPuller = {}; static sp<android::hardware::power::V1_0::IPower> gPowerHalV1_0 = nullptr; @@ -176,12 +176,12 @@ static bool getPowerStatsHalLocked() { } // The caller must be holding gPowerHalMutex. -static status_pull_atom_return_t getIPowerStatsDataLocked(int32_t atomTag, - pulled_stats_event_list* data) { +static AStatsManager_PullAtomCallbackReturn getIPowerStatsDataLocked(int32_t atomTag, + AStatsEventList* data) { using android::hardware::power::stats::V1_0::Status; if(!getPowerStatsHalLocked()) { - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } // Get power entity state residency data bool success = false; @@ -194,17 +194,17 @@ static status_pull_atom_return_t getIPowerStatsDataLocked(int32_t atomTag, } for (auto result : results) { for (auto stateResidency : result.stateResidencyData) { - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE); - stats_event_write_string8(event, - gEntityNames.at(result.powerEntityId).c_str()); - stats_event_write_string8(event, - gStateNames.at(result.powerEntityId) - .at(stateResidency.powerEntityStateId) - .c_str()); - stats_event_write_int64(event, stateResidency.totalStateEntryCount); - stats_event_write_int64(event, stateResidency.totalTimeInStateMs); - stats_event_build(event); + AStatsEvent* event = AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId(event, android::util::SUBSYSTEM_SLEEP_STATE); + AStatsEvent_writeString(event, + gEntityNames.at(result.powerEntityId).c_str()); + AStatsEvent_writeString(event, + gStateNames.at(result.powerEntityId) + .at(stateResidency.powerEntityStateId) + .c_str()); + AStatsEvent_writeInt64(event, stateResidency.totalStateEntryCount); + AStatsEvent_writeInt64(event, stateResidency.totalTimeInStateMs); + AStatsEvent_build(event); } } success = true; @@ -213,9 +213,9 @@ static status_pull_atom_return_t getIPowerStatsDataLocked(int32_t atomTag, // bool success determines if this succeeded or not. checkResultLocked(ret, __func__); if (!success) { - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } - return STATS_PULL_SUCCESS; + return AStatsManager_PULL_SUCCESS; } // The caller must be holding gPowerHalMutex. @@ -244,12 +244,12 @@ static bool getPowerHalLocked() { } // The caller must be holding gPowerHalMutex. -static status_pull_atom_return_t getIPowerDataLocked(int32_t atomTag, - pulled_stats_event_list* data) { +static AStatsManager_PullAtomCallbackReturn getIPowerDataLocked(int32_t atomTag, + AStatsEventList* data) { using android::hardware::power::V1_0::Status; if(!getPowerHalLocked()) { - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } Return<void> ret; @@ -259,26 +259,26 @@ static status_pull_atom_return_t getIPowerDataLocked(int32_t atomTag, for (size_t i = 0; i < states.size(); i++) { const PowerStatePlatformSleepState& state = states[i]; - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE); - stats_event_write_string8(event, state.name.c_str()); - stats_event_write_string8(event, ""); - stats_event_write_int64(event, state.totalTransitions); - stats_event_write_int64(event, state.residencyInMsecSinceBoot); - stats_event_build(event); + AStatsEvent* event = AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId(event, android::util::SUBSYSTEM_SLEEP_STATE); + AStatsEvent_writeString(event, state.name.c_str()); + AStatsEvent_writeString(event, ""); + AStatsEvent_writeInt64(event, state.totalTransitions); + AStatsEvent_writeInt64(event, state.residencyInMsecSinceBoot); + AStatsEvent_build(event); ALOGV("powerstate: %s, %lld, %lld, %d", state.name.c_str(), (long long)state.residencyInMsecSinceBoot, (long long)state.totalTransitions, state.supportedOnlyInSuspend ? 1 : 0); for (const auto& voter : state.voters) { - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE); - stats_event_write_string8(event, state.name.c_str()); - stats_event_write_string8(event, voter.name.c_str()); - stats_event_write_int64(event, voter.totalNumberOfTimesVotedSinceBoot); - stats_event_write_int64(event, voter.totalTimeInMsecVotedForSinceBoot); - stats_event_build(event); + AStatsEvent* event = AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId(event, android::util::SUBSYSTEM_SLEEP_STATE); + AStatsEvent_writeString(event, state.name.c_str()); + AStatsEvent_writeString(event, voter.name.c_str()); + AStatsEvent_writeInt64(event, voter.totalNumberOfTimesVotedSinceBoot); + AStatsEvent_writeInt64(event, voter.totalTimeInMsecVotedForSinceBoot); + AStatsEvent_build(event); ALOGV("powerstatevoter: %s, %s, %lld, %lld", state.name.c_str(), voter.name.c_str(), @@ -288,7 +288,7 @@ static status_pull_atom_return_t getIPowerDataLocked(int32_t atomTag, } }); if (!checkResultLocked(ret, __func__)) { - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } // Trying to cast to IPower 1.1, this will succeed only for devices supporting 1.1 @@ -305,14 +305,14 @@ static status_pull_atom_return_t getIPowerDataLocked(int32_t atomTag, for (size_t j = 0; j < subsystem.states.size(); j++) { const PowerStateSubsystemSleepState& state = subsystem.states[j]; - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, - android::util::SUBSYSTEM_SLEEP_STATE); - stats_event_write_string8(event, subsystem.name.c_str()); - stats_event_write_string8(event, state.name.c_str()); - stats_event_write_int64(event, state.totalTransitions); - stats_event_write_int64(event, state.residencyInMsecSinceBoot); - stats_event_build(event); + AStatsEvent* event = AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId(event, + android::util::SUBSYSTEM_SLEEP_STATE); + AStatsEvent_writeString(event, subsystem.name.c_str()); + AStatsEvent_writeString(event, state.name.c_str()); + AStatsEvent_writeInt64(event, state.totalTransitions); + AStatsEvent_writeInt64(event, state.residencyInMsecSinceBoot); + AStatsEvent_build(event); ALOGV("subsystemstate: %s, %s, %lld, %lld, %lld", subsystem.name.c_str(), state.name.c_str(), @@ -324,14 +324,14 @@ static status_pull_atom_return_t getIPowerDataLocked(int32_t atomTag, } }); } - return STATS_PULL_SUCCESS; + return AStatsManager_PULL_SUCCESS; } // The caller must be holding gPowerHalMutex. -std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list* data)> +std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList* data)> getPullerLocked() { - std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list * data)> ret = - {}; + std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList * data)> + ret = {}; // First see if power.stats HAL is available. Fall back to power HAL if // power.stats HAL is unavailable. @@ -346,8 +346,8 @@ getPullerLocked() { return ret; } -status_pull_atom_return_t SubsystemSleepStatePuller::Pull(int32_t atomTag, - pulled_stats_event_list* data) { +AStatsManager_PullAtomCallbackReturn SubsystemSleepStatePuller::Pull(int32_t atomTag, + AStatsEventList* data) { std::lock_guard<std::mutex> lock(gPowerHalMutex); if(!gPuller) { @@ -359,7 +359,7 @@ status_pull_atom_return_t SubsystemSleepStatePuller::Pull(int32_t atomTag, } ALOGE("Unable to load Power Hal or power.stats HAL"); - return STATS_PULL_SKIP; + return AStatsManager_PULL_SKIP; } } // namespace stats diff --git a/services/core/jni/stats/SubsystemSleepStatePuller.h b/services/core/jni/stats/SubsystemSleepStatePuller.h index 59dbbd258401..da9679c68a64 100644 --- a/services/core/jni/stats/SubsystemSleepStatePuller.h +++ b/services/core/jni/stats/SubsystemSleepStatePuller.h @@ -29,7 +29,7 @@ namespace stats { class SubsystemSleepStatePuller { public: SubsystemSleepStatePuller(); - status_pull_atom_return_t Pull(int32_t atomTag, pulled_stats_event_list* data); + AStatsManager_PullAtomCallbackReturn Pull(int32_t atomTag, AStatsEventList* data); }; } // namespace stats diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 3bd0297501fd..109f39d9dbcf 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -458,8 +458,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // A collection of user restrictions that are deprecated and should simply be ignored. private static final Set<String> DEPRECATED_USER_RESTRICTIONS; private static final String AB_DEVICE_KEY = "ro.build.ab_update"; - // Permissions related to location which must not be granted automatically - private static final Set<String> LOCATION_PERMISSIONS; static { SECURE_SETTINGS_WHITELIST = new ArraySet<>(); @@ -504,11 +502,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { DEPRECATED_USER_RESTRICTIONS = Sets.newHashSet( UserManager.DISALLOW_ADD_MANAGED_PROFILE, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE); - - LOCATION_PERMISSIONS = Sets.newHashSet( - permission.ACCESS_FINE_LOCATION, - permission.ACCESS_BACKGROUND_LOCATION, - permission.ACCESS_COARSE_LOCATION); } /** @@ -11601,6 +11594,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { millis, "DevicePolicyManagerService: setTime"); mInjector.binderWithCleanCallingIdentity( () -> mInjector.getTimeDetector().suggestManualTime(manualTimeSuggestion)); + + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_TIME) + .setAdmin(who) + .write(); return true; } @@ -11617,6 +11615,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { timeZone, "DevicePolicyManagerService: setTimeZone"); mInjector.binderWithCleanCallingIdentity(() -> mInjector.getTimeZoneDetector().suggestManualTimeZone(manualTimeZoneSuggestion)); + + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_TIME_ZONE) + .setAdmin(who) + .write(); return true; } @@ -12534,14 +12537,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { true); } - // Prevent granting location-related permissions without user consent. - if (LOCATION_PERMISSIONS.contains(permission) - && grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED - && !isUnattendedManagedKioskUnchecked()) { - callback.sendResult(null); - return; - } - if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) { diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index 0941831f5299..b2c316a25e7f 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -16,6 +16,7 @@ #include "BinderIncrementalService.h" +#include <android-base/logging.h> #include <binder/IResultReceiver.h> #include <binder/PermissionCache.h> #include <incfs.h> @@ -24,7 +25,6 @@ #include "jni.h" #include "nativehelper/JNIHelp.h" #include "path.h" -#include <android-base/logging.h> using namespace std::literals; using namespace android::incremental; @@ -277,6 +277,13 @@ binder::Status BinderIncrementalService::startLoading(int32_t storageId, bool* _ return ok(); } +binder::Status BinderIncrementalService::configureNativeBinaries( + int32_t storageId, const std::string& apkFullPath, const std::string& libDirRelativePath, + const std::string& abi, bool* _aidl_return) { + *_aidl_return = mImpl.configureNativeBinaries(storageId, apkFullPath, libDirRelativePath, abi); + return ok(); +} + } // namespace android::os::incremental jlong Incremental_IncrementalService_Start() { diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h index 8a099776b54b..51d7de3d9adf 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -28,11 +28,11 @@ namespace android::os::incremental { class BinderIncrementalService : public BnIncrementalService, public BinderService<BinderIncrementalService> { public: - BinderIncrementalService(const sp<IServiceManager> &sm); + BinderIncrementalService(const sp<IServiceManager>& sm); - static BinderIncrementalService *start(); - static const char16_t *getServiceName() { return u"incremental_service"; } - status_t dump(int fd, const Vector<String16> &args) final; + static BinderIncrementalService* start(); + static const char16_t* getServiceName() { return u"incremental_service"; } + status_t dump(int fd, const Vector<String16>& args) final; void onSystemReady(); void onInvalidStorage(int mountId); @@ -70,6 +70,9 @@ public: std::vector<uint8_t>* _aidl_return) final; binder::Status startLoading(int32_t storageId, bool* _aidl_return) final; binder::Status deleteStorage(int32_t storageId) final; + binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath, + const std::string& libDirRelativePath, + const std::string& abi, bool* _aidl_return) final; private: android::incremental::IncrementalService mImpl; diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index dbd97cfa039a..3b513774b615 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -60,6 +60,9 @@ struct Constants { static constexpr auto storagePrefix = "st"sv; static constexpr auto mountpointMdPrefix = ".mountpoint."sv; static constexpr auto infoMdName = ".info"sv; + static constexpr auto libDir = "lib"sv; + static constexpr auto libSuffix = ".so"sv; + static constexpr auto blockSize = 4096; }; static const Constants& constants() { @@ -259,16 +262,18 @@ IncrementalService::~IncrementalService() = default; inline const char* toString(TimePoint t) { using SystemClock = std::chrono::system_clock; - time_t time = SystemClock::to_time_t(SystemClock::now() + std::chrono::duration_cast<SystemClock::duration>(t - Clock::now())); + time_t time = SystemClock::to_time_t( + SystemClock::now() + + std::chrono::duration_cast<SystemClock::duration>(t - Clock::now())); return std::ctime(&time); } inline const char* toString(IncrementalService::BindKind kind) { switch (kind) { - case IncrementalService::BindKind::Temporary: - return "Temporary"; - case IncrementalService::BindKind::Permanent: - return "Permanent"; + case IncrementalService::BindKind::Temporary: + return "Temporary"; + case IncrementalService::BindKind::Permanent: + return "Permanent"; } } @@ -1124,6 +1129,122 @@ bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs, return true; } +// Extract lib filse from zip, create new files in incfs and write data to them +bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_view apkFullPath, + std::string_view libDirRelativePath, + std::string_view abi) { + const auto ifs = getIfs(storage); + // First prepare target directories if they don't exist yet + if (auto res = makeDirs(storage, libDirRelativePath, 0755)) { + LOG(ERROR) << "Failed to prepare target lib directory " << libDirRelativePath + << " errno: " << res; + return false; + } + + std::unique_ptr<ZipFileRO> zipFile(ZipFileRO::open(apkFullPath.data())); + if (!zipFile) { + LOG(ERROR) << "Failed to open zip file at " << apkFullPath; + return false; + } + void* cookie = nullptr; + const auto libFilePrefix = path::join(constants().libDir, abi); + if (!zipFile.get()->startIteration(&cookie, libFilePrefix.c_str() /* prefix */, + constants().libSuffix.data() /* suffix */)) { + LOG(ERROR) << "Failed to start zip iteration for " << apkFullPath; + return false; + } + ZipEntryRO entry = nullptr; + bool success = true; + while ((entry = zipFile.get()->nextEntry(cookie)) != nullptr) { + char fileName[PATH_MAX]; + if (zipFile.get()->getEntryFileName(entry, fileName, sizeof(fileName))) { + continue; + } + const auto libName = path::basename(fileName); + const auto targetLibPath = path::join(libDirRelativePath, libName); + const auto targetLibPathAbsolute = normalizePathToStorage(ifs, storage, targetLibPath); + // If the extract file already exists, skip + struct stat st; + if (stat(targetLibPathAbsolute.c_str(), &st) == 0) { + LOG(INFO) << "Native lib file already exists: " << targetLibPath + << "; skipping extraction"; + continue; + } + + uint32_t uncompressedLen; + if (!zipFile.get()->getEntryInfo(entry, nullptr, &uncompressedLen, nullptr, nullptr, + nullptr, nullptr)) { + LOG(ERROR) << "Failed to read native lib entry: " << fileName; + success = false; + break; + } + + // Create new lib file without signature info + incfs::NewFileParams libFileParams; + libFileParams.size = uncompressedLen; + libFileParams.verification.hashAlgorithm = INCFS_HASH_NONE; + // Metadata of the new lib file is its relative path + IncFsSpan libFileMetadata; + libFileMetadata.data = targetLibPath.c_str(); + libFileMetadata.size = targetLibPath.size(); + libFileParams.metadata = libFileMetadata; + incfs::FileId libFileId = idFromMetadata(targetLibPath); + if (auto res = makeFile(storage, targetLibPath, 0777, libFileId, libFileParams)) { + LOG(ERROR) << "Failed to make file for: " << targetLibPath << " errno: " << res; + success = false; + // If one lib file fails to be created, abort others as well + break; + } + + // Write extracted data to new file + std::vector<uint8_t> libData(uncompressedLen); + if (!zipFile.get()->uncompressEntry(entry, &libData[0], uncompressedLen)) { + LOG(ERROR) << "Failed to extract native lib zip entry: " << fileName; + success = false; + break; + } + android::base::unique_fd writeFd(mIncFs->openWrite(ifs->control, libFileId)); + if (writeFd < 0) { + LOG(ERROR) << "Failed to open write fd for: " << targetLibPath << " errno: " << writeFd; + success = false; + break; + } + const int numBlocks = uncompressedLen / constants().blockSize + 1; + std::vector<IncFsDataBlock> instructions; + auto remainingData = std::span(libData); + for (int i = 0; i < numBlocks - 1; i++) { + auto inst = IncFsDataBlock{ + .fileFd = writeFd, + .pageIndex = static_cast<IncFsBlockIndex>(i), + .compression = INCFS_COMPRESSION_KIND_NONE, + .kind = INCFS_BLOCK_KIND_DATA, + .dataSize = static_cast<uint16_t>(constants().blockSize), + .data = reinterpret_cast<const char*>(remainingData.data()), + }; + instructions.push_back(inst); + remainingData = remainingData.subspan(constants().blockSize); + } + // Last block + auto inst = IncFsDataBlock{ + .fileFd = writeFd, + .pageIndex = static_cast<IncFsBlockIndex>(numBlocks - 1), + .compression = INCFS_COMPRESSION_KIND_NONE, + .kind = INCFS_BLOCK_KIND_DATA, + .dataSize = static_cast<uint16_t>(remainingData.size()), + .data = reinterpret_cast<const char*>(remainingData.data()), + }; + instructions.push_back(inst); + size_t res = mIncFs->writeBlocks(instructions); + if (res != instructions.size()) { + LOG(ERROR) << "Failed to write data into: " << targetLibPath; + success = false; + } + instructions.clear(); + } + zipFile.get()->endIteration(cookie); + return success; +} + binder::Status IncrementalService::IncrementalDataLoaderListener::onStatusChanged(MountId mountId, int newStatus) { std::unique_lock l(incrementalService.mLock); diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index dec9f64f2084..2444ddecdb80 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -126,7 +126,8 @@ public: std::vector<std::string> listFiles(StorageId storage) const; bool startLoading(StorageId storage) const; - + bool configureNativeBinaries(StorageId storage, std::string_view apkFullPath, + std::string_view libDirRelativePath, std::string_view abi); class IncrementalDataLoaderListener : public android::content::pm::BnDataLoaderStatusListener { public: IncrementalDataLoaderListener(IncrementalService& incrementalService) diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 036335ca9011..eef6c63d29c9 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -41,6 +41,7 @@ import android.content.res.Configuration; import android.content.res.Resources.Theme; import android.database.sqlite.SQLiteCompatibilityWalFlags; import android.database.sqlite.SQLiteGlobal; +import android.graphics.GraphicsStatsService; import android.hardware.display.DisplayManagerInternal; import android.net.ConnectivityModuleConnector; import android.net.ITetheringConnector; 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 79503f797318..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. } @@ -293,13 +292,14 @@ public class DataManager { | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER; return mShortcutServiceInternal.getShortcuts( mInjector.getCallingUserId(), /*callingPackage=*/ PLATFORM_PACKAGE_NAME, - /*changedSince=*/ 0, packageName, shortcutIds, /*componentName=*/ null, queryFlags, - userId, MY_PID, MY_UID); + /*changedSince=*/ 0, packageName, shortcutIds, /*locusIds=*/ null, + /*componentName=*/ null, queryFlags, userId, MY_PID, MY_UID); } private void forAllUnlockedUsers(Consumer<UserData> consumer) { for (int i = 0; i < mUserDataArray.size(); i++) { - UserData userData = mUserDataArray.get(i); + int userId = mUserDataArray.keyAt(i); + UserData userData = mUserDataArray.get(userId); if (userData.isUnlocked()) { consumer.accept(userData); } @@ -385,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); } @@ -603,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. + } } /** @@ -611,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(); + } } } @@ -679,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/Android.bp b/services/tests/mockingservicestests/Android.bp index 3d9f11ff6d2f..339ff6b8b526 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -22,6 +22,7 @@ android_test { "services.net", "service-jobscheduler", "service-permission", + "service-blobstore", "androidx.test.runner", "mockito-target-extended-minus-junit4", "platform-test-annotations", diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml index 0e24b0314b2d..44eb8285c7db 100644 --- a/services/tests/mockingservicestests/AndroidManifest.xml +++ b/services/tests/mockingservicestests/AndroidManifest.xml @@ -17,6 +17,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.frameworks.mockingservicestests"> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/> <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> <uses-permission android:name="android.permission.HARDWARE_TEST"/> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> diff --git a/services/tests/servicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml index a37d84f7a08c..a37d84f7a08c 100644 --- a/services/tests/servicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml +++ b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml 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/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java index 155de3bc0266..2d5fa237f6b7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java @@ -101,9 +101,8 @@ public class AppOpsServiceTest { private StaticMockitoSession mMockingSession; private void setupAppOpsService() { - mAppOpsService = new AppOpsService(mAppOpsFile, mHandler); + mAppOpsService = new AppOpsService(mAppOpsFile, mHandler, spy(sContext)); mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver()); - mAppOpsService.mContext = spy(sContext); // Always approve all permission checks doNothing().when(mAppOpsService.mContext).enforcePermission(anyString(), anyInt(), diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java index 66d2baba2909..e48b67167cdf 100644 --- a/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -19,9 +19,17 @@ package com.android.server.appop; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.app.AppOpsManager; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.os.Handler; import android.os.HandlerThread; @@ -133,10 +141,24 @@ public class AppOpsUpgradeTest { AppOpsDataParser parser = new AppOpsDataParser(mAppOpsFile); assertTrue(parser.parse()); assertEquals(AppOpsDataParser.NO_VERSION, parser.mVersion); - AppOpsService testService = new AppOpsService(mAppOpsFile, mHandler); // trigger upgrade + + // Use mock context and package manager to fake permision package manager calls. + Context testContext = spy(mContext); + + // Pretent everybody has all permissions + doNothing().when(testContext).enforcePermission(anyString(), anyInt(), anyInt(), + nullable(String.class)); + + PackageManager testPM = mock(PackageManager.class); + when(testContext.getPackageManager()).thenReturn(testPM); + + // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat + when(testPM.getPackagesForUid(anyInt())).thenReturn(null); + + AppOpsService testService = spy( + new AppOpsService(mAppOpsFile, mHandler, testContext)); // trigger upgrade assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND, AppOpsManager.OP_RUN_ANY_IN_BACKGROUND); - testService.mContext = mContext; mHandler.removeCallbacks(testService.mWriteRunner); testService.writeState(); assertTrue(parser.parse()); diff --git a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java new file mode 100644 index 000000000000..3778e17f4320 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java @@ -0,0 +1,342 @@ +/* + * 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. + */ +package com.android.server.blob; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.blob.BlobHandle; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; +import android.util.ArrayMap; +import android.util.LongSparseArray; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.blob.BlobStoreManagerService.Injector; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +import java.io.File; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class BlobStoreManagerServiceTest { + private Context mContext; + private Handler mHandler; + private BlobStoreManagerService mService; + + private MockitoSession mMockitoSession; + + @Mock + private File mBlobsDir; + + private LongSparseArray<BlobStoreSession> mUserSessions; + private ArrayMap<BlobHandle, BlobMetadata> mUserBlobs; + + private static final String TEST_PKG1 = "com.example1"; + private static final String TEST_PKG2 = "com.example2"; + private static final String TEST_PKG3 = "com.example3"; + + private static final int TEST_UID1 = 10001; + private static final int TEST_UID2 = 10002; + private static final int TEST_UID3 = 10003; + + @Before + public void setUp() { + // Share classloader to allow package private access. + System.setProperty("dexmaker.share_classloader", "true"); + + mMockitoSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .mockStatic(BlobStoreConfig.class) + .startMocking(); + + doReturn(mBlobsDir).when(() -> BlobStoreConfig.getBlobsDir()); + doReturn(true).when(mBlobsDir).exists(); + doReturn(new File[0]).when(mBlobsDir).listFiles(); + + mContext = InstrumentationRegistry.getTargetContext(); + mHandler = new TestHandler(Looper.getMainLooper()); + mService = new BlobStoreManagerService(mContext, new TestInjector()); + mUserSessions = new LongSparseArray<>(); + mUserBlobs = new ArrayMap<>(); + + mService.addUserSessionsForTest(mUserSessions, UserHandle.myUserId()); + mService.addUserBlobsForTest(mUserBlobs, UserHandle.myUserId()); + } + + @After + public void tearDown() { + if (mMockitoSession != null) { + mMockitoSession.finishMocking(); + } + } + + @Test + public void testHandlePackageRemoved() throws Exception { + // Setup sessions + final File sessionFile1 = mock(File.class); + final long sessionId1 = 11; + final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, + sessionId1, sessionFile1); + mUserSessions.append(sessionId1, session1); + + final File sessionFile2 = mock(File.class); + final long sessionId2 = 25; + final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2, + sessionId2, sessionFile2); + mUserSessions.append(sessionId2, session2); + + final File sessionFile3 = mock(File.class); + final long sessionId3 = 37; + final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3, + sessionId3, sessionFile3); + mUserSessions.append(sessionId3, session3); + + final File sessionFile4 = mock(File.class); + final long sessionId4 = 48; + final BlobStoreSession session4 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, + sessionId4, sessionFile4); + mUserSessions.append(sessionId4, session4); + + // Setup blobs + final long blobId1 = 978; + final File blobFile1 = mock(File.class); + final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(), + "label1", System.currentTimeMillis(), "tag1"); + final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, true); + mUserBlobs.put(blobHandle1, blobMetadata1); + + final long blobId2 = 347; + final File blobFile2 = mock(File.class); + final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(), + "label2", System.currentTimeMillis(), "tag2"); + final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, false); + mUserBlobs.put(blobHandle2, blobMetadata2); + + mService.addKnownIdsForTest(sessionId1, sessionId2, sessionId3, sessionId4, + blobId1, blobId2); + + // Invoke test method + mService.handlePackageRemoved(TEST_PKG1, TEST_UID1); + + // Verify sessions are removed + verify(sessionFile1).delete(); + verify(sessionFile2, never()).delete(); + verify(sessionFile3, never()).delete(); + verify(sessionFile4).delete(); + + assertThat(mUserSessions.size()).isEqualTo(2); + assertThat(mUserSessions.get(sessionId1)).isNull(); + assertThat(mUserSessions.get(sessionId2)).isNotNull(); + assertThat(mUserSessions.get(sessionId3)).isNotNull(); + assertThat(mUserSessions.get(sessionId4)).isNull(); + + // Verify blobs are removed + verify(blobMetadata1).removeCommitter(TEST_PKG1, TEST_UID1); + verify(blobMetadata1).removeLeasee(TEST_PKG1, TEST_UID1); + verify(blobMetadata2).removeCommitter(TEST_PKG1, TEST_UID1); + verify(blobMetadata2).removeLeasee(TEST_PKG1, TEST_UID1); + + verify(blobFile1, never()).delete(); + verify(blobFile2).delete(); + + assertThat(mUserBlobs.size()).isEqualTo(1); + assertThat(mUserBlobs.get(blobHandle1)).isNotNull(); + assertThat(mUserBlobs.get(blobHandle2)).isNull(); + + assertThat(mService.getKnownIdsForTest()).containsExactly( + sessionId2, sessionId3, blobId1); + } + + @Test + public void testHandleIdleMaintenance_deleteUnknownBlobs() throws Exception { + // Setup blob files + final long testId1 = 286; + final File file1 = mock(File.class); + doReturn(String.valueOf(testId1)).when(file1).getName(); + final long testId2 = 349; + final File file2 = mock(File.class); + doReturn(String.valueOf(testId2)).when(file2).getName(); + final long testId3 = 7355; + final File file3 = mock(File.class); + doReturn(String.valueOf(testId3)).when(file3).getName(); + + doReturn(new File[] {file1, file2, file3}).when(mBlobsDir).listFiles(); + mService.addKnownIdsForTest(testId1, testId3); + + // Invoke test method + mService.handleIdleMaintenanceLocked(); + + // Verify unknown blobs are delete + verify(file1, never()).delete(); + verify(file2).delete(); + verify(file3, never()).delete(); + } + + @Test + public void testHandleIdleMaintenance_deleteStaleSessions() throws Exception { + // Setup sessions + final File sessionFile1 = mock(File.class); + doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS + 1000) + .when(sessionFile1).lastModified(); + final long sessionId1 = 342; + final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(), + "label1", System.currentTimeMillis() - 1000, "tag1"); + final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, + sessionId1, sessionFile1, blobHandle1); + mUserSessions.append(sessionId1, session1); + + final File sessionFile2 = mock(File.class); + doReturn(System.currentTimeMillis() - 20000) + .when(sessionFile2).lastModified(); + final long sessionId2 = 4597; + final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(), + "label2", System.currentTimeMillis() + 20000, "tag2"); + final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2, + sessionId2, sessionFile2, blobHandle2); + mUserSessions.append(sessionId2, session2); + + final File sessionFile3 = mock(File.class); + doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS - 2000) + .when(sessionFile3).lastModified(); + final long sessionId3 = 9484; + final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(), + "label3", System.currentTimeMillis() + 30000, "tag3"); + final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3, + sessionId3, sessionFile3, blobHandle3); + mUserSessions.append(sessionId3, session3); + + mService.addKnownIdsForTest(sessionId1, sessionId2, sessionId3); + + // Invoke test method + mService.handleIdleMaintenanceLocked(); + + // Verify stale sessions are removed + verify(sessionFile1).delete(); + verify(sessionFile2, never()).delete(); + verify(sessionFile3).delete(); + + assertThat(mUserSessions.size()).isEqualTo(1); + assertThat(mUserSessions.get(sessionId2)).isNotNull(); + + assertThat(mService.getKnownIdsForTest()).containsExactly(sessionId2); + } + + @Test + public void testHandleIdleMaintenance_deleteStaleBlobs() throws Exception { + // Setup blobs + final long blobId1 = 3489; + final File blobFile1 = mock(File.class); + final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(), + "label1", System.currentTimeMillis() - 2000, "tag1"); + final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, true); + mUserBlobs.put(blobHandle1, blobMetadata1); + + final long blobId2 = 78974; + final File blobFile2 = mock(File.class); + final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(), + "label2", System.currentTimeMillis() + 30000, "tag2"); + final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, true); + mUserBlobs.put(blobHandle2, blobMetadata2); + + final long blobId3 = 97; + final File blobFile3 = mock(File.class); + final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(), + "label3", System.currentTimeMillis() + 4400000, "tag3"); + final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3, false); + mUserBlobs.put(blobHandle3, blobMetadata3); + + mService.addKnownIdsForTest(blobId1, blobId2, blobId3); + + // Invoke test method + mService.handleIdleMaintenanceLocked(); + + // Verify stale blobs are removed + verify(blobFile1).delete(); + verify(blobFile2, never()).delete(); + verify(blobFile3).delete(); + + assertThat(mUserBlobs.size()).isEqualTo(1); + assertThat(mUserBlobs.get(blobHandle2)).isNotNull(); + + assertThat(mService.getKnownIdsForTest()).containsExactly(blobId2); + } + + private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid, + long sessionId, File sessionFile) { + return createBlobStoreSessionMock(ownerPackageName, ownerUid, sessionId, sessionFile, + mock(BlobHandle.class)); + } + private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid, + long sessionId, File sessionFile, BlobHandle blobHandle) { + final BlobStoreSession session = mock(BlobStoreSession.class); + doReturn(ownerPackageName).when(session).getOwnerPackageName(); + doReturn(ownerUid).when(session).getOwnerUid(); + doReturn(sessionId).when(session).getSessionId(); + doReturn(sessionFile).when(session).getSessionFile(); + doReturn(blobHandle).when(session).getBlobHandle(); + return session; + } + + private BlobMetadata createBlobMetadataMock(long blobId, File blobFile, boolean hasLeases) { + final BlobMetadata blobMetadata = mock(BlobMetadata.class); + doReturn(blobId).when(blobMetadata).getBlobId(); + doReturn(blobFile).when(blobMetadata).getBlobFile(); + doReturn(hasLeases).when(blobMetadata).hasLeases(); + return blobMetadata; + } + + private class TestHandler extends Handler { + TestHandler(Looper looper) { + super(looper); + } + + @Override + public void dispatchMessage(Message msg) { + // Ignore all messages + } + } + + private class TestInjector extends Injector { + @Override + public Handler initializeMessageHandler() { + return mHandler; + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index 4a40b80a85c1..6d1530219372 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -20,6 +20,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -29,6 +31,7 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.view.Display; import android.view.DisplayAddress; import android.view.SurfaceControl; @@ -47,6 +50,7 @@ import org.mockito.Mock; import org.mockito.quality.Strictness; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedList; @@ -167,6 +171,7 @@ public class LocalDisplayAdapterTest { */ @Test public void testDpiValues() throws Exception { + // needs default one always setUpDisplay(new FakeDisplay(PORT_A)); setUpDisplay(new FakeDisplay(PORT_B)); updateAvailableDisplays(); @@ -182,6 +187,67 @@ public class LocalDisplayAdapterTest { 16000); } + @Test + public void testAfterDisplayChange_ModesAreUpdated() throws Exception { + SurfaceControl.DisplayConfig displayInfo = createFakeDisplayConfig(1920, 1080, 60f); + SurfaceControl.DisplayConfig[] configs = + new SurfaceControl.DisplayConfig[]{displayInfo}; + FakeDisplay display = new FakeDisplay(PORT_A, configs, 0); + setUpDisplay(display); + updateAvailableDisplays(); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + assertThat(mListener.changedDisplays).isEmpty(); + + DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get( + 0).getDisplayDeviceInfoLocked(); + + assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(configs.length); + assertModeIsSupported(displayDeviceInfo.supportedModes, displayInfo); + + Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); + assertThat(defaultMode.matches(displayInfo.width, displayInfo.height, + displayInfo.refreshRate)).isTrue(); + + Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId); + assertThat(activeMode.matches(displayInfo.width, displayInfo.height, + displayInfo.refreshRate)).isTrue(); + + // Change the display + SurfaceControl.DisplayConfig addedDisplayInfo = createFakeDisplayConfig(3840, 2160, + 60f); + configs = new SurfaceControl.DisplayConfig[]{displayInfo, addedDisplayInfo}; + display.configs = configs; + display.activeConfig = 1; + setUpDisplay(display); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + assertThat(SurfaceControl.getActiveConfig(display.token)).isEqualTo(1); + assertThat(SurfaceControl.getDisplayConfigs(display.token).length).isEqualTo(2); + + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + assertThat(mListener.changedDisplays.size()).isEqualTo(1); + + DisplayDevice displayDevice = mListener.changedDisplays.get(0); + displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); + displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked(); + + assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(configs.length); + assertModeIsSupported(displayDeviceInfo.supportedModes, displayInfo); + assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo); + + activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId); + assertThat(activeMode.matches(addedDisplayInfo.width, addedDisplayInfo.height, + addedDisplayInfo.refreshRate)).isTrue(); + + defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); + assertThat(defaultMode.matches(addedDisplayInfo.width, addedDisplayInfo.height, + addedDisplayInfo.refreshRate)).isTrue(); + } + private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort, float expectedXdpi, float expectedYDpi, @@ -194,16 +260,40 @@ public class LocalDisplayAdapterTest { assertEquals(expectedDensityDpi, info.densityDpi); } + private Display.Mode getModeById(DisplayDeviceInfo displayDeviceInfo, int modeId) { + return Arrays.stream(displayDeviceInfo.supportedModes) + .filter(mode -> mode.getModeId() == modeId) + .findFirst() + .get(); + } + + private void assertModeIsSupported(Display.Mode[] supportedModes, + SurfaceControl.DisplayConfig mode) { + assertThat(Arrays.stream(supportedModes).anyMatch( + x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue(); + } + private static class FakeDisplay { public final DisplayAddress.Physical address; public final IBinder token = new Binder(); public final SurfaceControl.DisplayInfo info; - public final SurfaceControl.DisplayConfig config; + public SurfaceControl.DisplayConfig[] configs; + public int activeConfig; private FakeDisplay(int port) { this.address = createDisplayAddress(port); this.info = createFakeDisplayInfo(); - this.config = createFakeDisplayConfig(); + this.configs = new SurfaceControl.DisplayConfig[]{ + createFakeDisplayConfig(800, 600, 60f) + }; + this.activeConfig = 0; + } + + private FakeDisplay(int port, SurfaceControl.DisplayConfig[] configs, int activeConfig) { + this.address = createDisplayAddress(port); + this.info = createFakeDisplayInfo(); + this.configs = configs; + this.activeConfig = activeConfig; } } @@ -212,9 +302,9 @@ public class LocalDisplayAdapterTest { doReturn(display.token).when(() -> SurfaceControl.getPhysicalDisplayToken(display.address.getPhysicalDisplayId())); doReturn(display.info).when(() -> SurfaceControl.getDisplayInfo(display.token)); - doReturn(new SurfaceControl.DisplayConfig[] { display.config }).when( + doReturn(display.configs).when( () -> SurfaceControl.getDisplayConfigs(display.token)); - doReturn(0).when(() -> SurfaceControl.getActiveConfig(display.token)); + doReturn(display.activeConfig).when(() -> SurfaceControl.getActiveConfig(display.token)); doReturn(0).when(() -> SurfaceControl.getActiveColorMode(display.token)); doReturn(new int[] { 0 }).when( () -> SurfaceControl.getDisplayColorModes(display.token)); @@ -242,10 +332,12 @@ public class LocalDisplayAdapterTest { return info; } - private static SurfaceControl.DisplayConfig createFakeDisplayConfig() { + private static SurfaceControl.DisplayConfig createFakeDisplayConfig(int width, int height, + float refreshRate) { final SurfaceControl.DisplayConfig config = new SurfaceControl.DisplayConfig(); - config.width = 800; - config.height = 600; + config.width = width; + config.height = height; + config.refreshRate = refreshRate; config.xDpi = 100; config.yDpi = 100; return config; @@ -266,17 +358,19 @@ public class LocalDisplayAdapterTest { private class TestListener implements DisplayAdapter.Listener { public ArrayList<DisplayDevice> addedDisplays = new ArrayList<>(); + public ArrayList<DisplayDevice> changedDisplays = new ArrayList<>(); @Override public void onDisplayDeviceEvent(DisplayDevice device, int event) { if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED) { addedDisplays.add(device); + } else if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED) { + changedDisplays.add(device); } } @Override public void onTraversalRequested() { - } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java index 8e9d7ee71df3..3566aee2eba3 100644 --- a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java +++ b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java @@ -21,6 +21,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; +import org.junit.AssumptionViolatedException; import org.junit.rules.TestRule; import org.junit.rules.TestWatcher; import org.junit.runner.Description; @@ -100,6 +101,11 @@ public class StaticMockFixtureRule implements TestRule { } @Override + protected void skipped(AssumptionViolatedException e, Description description) { + tearDown(e); + } + + @Override protected void failed(Throwable e, Description description) { tearDown(e); } diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java index b7e71ded30ab..8e0ccf01d7a7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java @@ -16,8 +16,10 @@ package com.android.server.testables; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -30,6 +32,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; import org.junit.After; +import org.junit.AssumptionViolatedException; import org.junit.Before; import org.junit.Test; import org.junit.runner.Description; @@ -57,6 +60,8 @@ public class StaticMockFixtureRuleTest { @Mock private Supplier<StaticMockFixture> mSupplyA; @Mock private Supplier<StaticMockFixture> mSupplyB; @Mock private Statement mStatement; + @Mock private Statement mSkipStatement; + @Mock private Statement mThrowStatement; @Mock private Description mDescription; @Before @@ -91,17 +96,22 @@ public class StaticMockFixtureRuleTest { when(mB1.setUpMockedClasses(any())).thenAnswer(invocation -> invocation.getArgument(0)); doNothing().when(mB1).setUpMockBehaviors(); doNothing().when(mStatement).evaluate(); + doThrow(new AssumptionViolatedException("bad assumption, test should be skipped")) + .when(mSkipStatement).evaluate(); + doThrow(new IllegalArgumentException("bad argument, test should be failed")) + .when(mThrowStatement).evaluate(); doNothing().when(mA1).tearDown(); doNothing().when(mB1).tearDown(); } private InOrder mocksInOrder() { - return inOrder(mSessionBuilder, mSession, mSupplyA, mSupplyB, - mA1, mA2, mB1, mB2, mStatement, mDescription); + return inOrder(mSessionBuilder, mSession, mSupplyA, mSupplyB, mA1, mA2, mB1, mB2, + mStatement, mSkipStatement, mThrowStatement, mDescription); } private void verifyNoMoreImportantMockInteractions() { - verifyNoMoreInteractions(mSupplyA, mSupplyB, mA1, mA2, mB1, mB2, mStatement); + verifyNoMoreInteractions(mSupplyA, mSupplyB, mA1, mA2, mB1, mB2, mStatement, + mSkipStatement, mThrowStatement); } @Test @@ -183,4 +193,66 @@ public class StaticMockFixtureRuleTest { verifyNoMoreImportantMockInteractions(); } + + @Test + public void testTearDownOnSkippedTests() throws Throwable { + InOrder inOrder = mocksInOrder(); + + StaticMockFixtureRule rule = new StaticMockFixtureRule(mA1, mB1) { + @Override public StaticMockitoSessionBuilder getSessionBuilder() { + return mSessionBuilder; + } + }; + Statement skipStatement = rule.apply(mSkipStatement, mDescription); + + inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); + inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); + inOrder.verify(mA1).setUpMockBehaviors(); + inOrder.verify(mB1).setUpMockBehaviors(); + + try { + skipStatement.evaluate(); + fail("AssumptionViolatedException should have been thrown"); + } catch (AssumptionViolatedException e) { + // expected + } + + inOrder.verify(mSkipStatement).evaluate(); + // note: tearDown in reverse order + inOrder.verify(mB1).tearDown(); + inOrder.verify(mA1).tearDown(); + + verifyNoMoreImportantMockInteractions(); + } + + @Test + public void testTearDownOnFailedTests() throws Throwable { + InOrder inOrder = mocksInOrder(); + + StaticMockFixtureRule rule = new StaticMockFixtureRule(mA1, mB1) { + @Override public StaticMockitoSessionBuilder getSessionBuilder() { + return mSessionBuilder; + } + }; + Statement failStatement = rule.apply(mThrowStatement, mDescription); + + inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); + inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); + inOrder.verify(mA1).setUpMockBehaviors(); + inOrder.verify(mB1).setUpMockBehaviors(); + + try { + failStatement.evaluate(); + fail("IllegalArgumentException should have been thrown"); + } catch (IllegalArgumentException e) { + // expected + } + + inOrder.verify(mThrowStatement).evaluate(); + // note: tearDown in reverse order + inOrder.verify(mB1).tearDown(); + inOrder.verify(mA1).tearDown(); + + verifyNoMoreImportantMockInteractions(); + } } diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 8381205fa48e..bf2b9bec33c9 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -46,7 +46,6 @@ android_test { "service-appsearch", "service-jobscheduler", "service-permission", - "service-blobstore", // TODO: remove once Android migrates to JUnit 4.12, // which provides assertThrows "testng", @@ -75,7 +74,6 @@ android_test { "libbacktrace", "libbase", "libbinder", - "libbinderthreadstate", "libc++", "libcutils", "liblog", diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index d2ddff3627b9..1212f2082404 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -65,6 +65,8 @@ <uses-permission android:name="android.permission.WATCH_APPOPS" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.SUSPEND_APPS"/> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" /> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> <uses-permission android:name="android.permission.CONTROL_KEYGUARD"/> <uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"/> <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> 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/blob/BlobStoreManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java deleted file mode 100644 index ff728e7a4017..000000000000 --- a/services/tests/servicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * 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. - */ -package com.android.server.blob; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.blob.BlobHandle; -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.UserHandle; -import android.platform.test.annotations.Presubmit; -import android.util.ArrayMap; -import android.util.LongSparseArray; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.server.blob.BlobStoreManagerService.Injector; -import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; - -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class BlobStoreManagerServiceTest { - private Context mContext; - private Handler mHandler; - private BlobStoreManagerService mService; - - private LongSparseArray<BlobStoreSession> mUserSessions; - private ArrayMap<BlobHandle, BlobMetadata> mUserBlobs; - - private SessionStateChangeListener mStateChangeListener; - - private static final String TEST_PKG1 = "com.example1"; - private static final String TEST_PKG2 = "com.example2"; - private static final String TEST_PKG3 = "com.example3"; - - private static final int TEST_UID1 = 10001; - private static final int TEST_UID2 = 10002; - private static final int TEST_UID3 = 10003; - - @Before - public void setUp() { - // Share classloader to allow package private access. - System.setProperty("dexmaker.share_classloader", "true"); - - mContext = InstrumentationRegistry.getTargetContext(); - mHandler = new TestHandler(Looper.getMainLooper()); - mService = new BlobStoreManagerService(mContext, new TestInjector()); - mUserSessions = new LongSparseArray<>(); - mUserBlobs = new ArrayMap<>(); - - mService.addUserSessionsForTest(mUserSessions, UserHandle.myUserId()); - mService.addUserBlobsForTest(mUserBlobs, UserHandle.myUserId()); - - mStateChangeListener = mService.new SessionStateChangeListener(); - } - - @Test - public void testHandlePackageRemoved() throws Exception { - // Setup sessions - final File sessionFile1 = mock(File.class); - final long sessionId1 = 11; - final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, - sessionId1, sessionFile1); - mUserSessions.append(sessionId1, session1); - - final File sessionFile2 = mock(File.class); - final long sessionId2 = 25; - final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2, - sessionId2, sessionFile2); - mUserSessions.append(sessionId2, session2); - - final File sessionFile3 = mock(File.class); - final long sessionId3 = 37; - final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3, - sessionId3, sessionFile3); - mUserSessions.append(sessionId3, session3); - - final File sessionFile4 = mock(File.class); - final long sessionId4 = 48; - final BlobStoreSession session4 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, - sessionId4, sessionFile4); - mUserSessions.append(sessionId4, session4); - - // Setup blobs - final File blobFile1 = mock(File.class); - final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(), - "label1", System.currentTimeMillis(), "tag1"); - final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobFile1, true); - mUserBlobs.put(blobHandle1, blobMetadata1); - - final File blobFile2 = mock(File.class); - final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(), - "label2", System.currentTimeMillis(), "tag2"); - final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobFile2, false); - mUserBlobs.put(blobHandle2, blobMetadata2); - - // Invoke test method - mService.handlePackageRemoved(TEST_PKG1, TEST_UID1); - - // Verify sessions are removed - verify(sessionFile1).delete(); - verify(sessionFile2, never()).delete(); - verify(sessionFile3, never()).delete(); - verify(sessionFile4).delete(); - - assertThat(mUserSessions.size()).isEqualTo(2); - assertThat(mUserSessions.get(sessionId1)).isNull(); - assertThat(mUserSessions.get(sessionId2)).isNotNull(); - assertThat(mUserSessions.get(sessionId3)).isNotNull(); - assertThat(mUserSessions.get(sessionId4)).isNull(); - - // Verify blobs are removed - verify(blobMetadata1).removeCommitter(TEST_PKG1, TEST_UID1); - verify(blobMetadata1).removeLeasee(TEST_PKG1, TEST_UID1); - verify(blobMetadata2).removeCommitter(TEST_PKG1, TEST_UID1); - verify(blobMetadata2).removeLeasee(TEST_PKG1, TEST_UID1); - - verify(blobFile1, never()).delete(); - verify(blobFile2).delete(); - - assertThat(mUserBlobs.size()).isEqualTo(1); - assertThat(mUserBlobs.get(blobHandle1)).isNotNull(); - assertThat(mUserBlobs.get(blobHandle2)).isNull(); - } - - private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid, - long sessionId, File sessionFile) { - final BlobStoreSession session = mock(BlobStoreSession.class); - when(session.getOwnerPackageName()).thenReturn(ownerPackageName); - when(session.getOwnerUid()).thenReturn(ownerUid); - when(session.getSessionId()).thenReturn(sessionId); - when(session.getSessionFile()).thenReturn(sessionFile); - return session; - } - - private BlobMetadata createBlobMetadataMock(File blobFile, boolean hasLeases) { - final BlobMetadata blobMetadata = mock(BlobMetadata.class); - when(blobMetadata.getBlobFile()).thenReturn(blobFile); - when(blobMetadata.hasLeases()).thenReturn(hasLeases); - return blobMetadata; - } - - private class TestHandler extends Handler { - TestHandler(Looper looper) { - super(looper); - } - - @Override - public void dispatchMessage(Message msg) { - // Ignore all messages - } - } - - private class TestInjector extends Injector { - @Override - public Handler initializeMessageHandler() { - return mHandler; - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java index ce5d6d9be770..4a686ee6a081 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -19,6 +19,7 @@ package com.android.server.compat; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -28,10 +29,12 @@ import static org.testng.Assert.assertThrows; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import androidx.test.runner.AndroidJUnit4; import com.android.internal.compat.AndroidBuildClassifier; +import com.android.server.LocalServices; import org.junit.Before; import org.junit.Test; @@ -48,6 +51,8 @@ public class PlatformCompatTest { @Mock private PackageManager mPackageManager; @Mock + private PackageManagerInternal mPackageManagerInternal; + @Mock CompatChange.ChangeListener mListener1, mListener2; PlatformCompat mPlatformCompat; CompatConfig mCompatConfig; @@ -60,11 +65,15 @@ public class PlatformCompatTest { when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow( new PackageManager.NameNotFoundException()); + when(mPackageManagerInternal.getPackageUid(eq(PACKAGE_NAME), eq(0), anyInt())) + .thenReturn(-1); mCompatConfig = new CompatConfig(mBuildClassifier, mContext); mPlatformCompat = new PlatformCompat(mContext, mCompatConfig); // Assume userdebug/eng non-final build when(mBuildClassifier.isDebuggableBuild()).thenReturn(true); when(mBuildClassifier.isFinalBuild()).thenReturn(false); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal); } @Test diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index e0c6d6a36d0c..39a749f9a6dc 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -96,6 +96,7 @@ import android.test.MoreAsserts; import android.util.ArraySet; import android.util.Pair; +import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import com.android.internal.R; @@ -5446,26 +5447,28 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertTrue(dpms.isAdminActive(admin1, UserHandle.USER_SYSTEM)); } - public void testRevertDeviceOwnership_adminAndDeviceMigrated() throws Exception { - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), - getDeviceOwnerPoliciesFile()); - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_migrated), - getDeviceOwnerFile()); - assertDeviceOwnershipRevertedWithFakeTransferMetadata(); - } - - public void testRevertDeviceOwnership_deviceNotMigrated() - throws Exception { - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), - getDeviceOwnerPoliciesFile()); - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_not_migrated), - getDeviceOwnerFile()); - assertDeviceOwnershipRevertedWithFakeTransferMetadata(); - } + // @FlakyTest(bugId = 148934649) + // public void testRevertDeviceOwnership_adminAndDeviceMigrated() throws Exception { + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), + // getDeviceOwnerPoliciesFile()); + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_migrated), + // getDeviceOwnerFile()); + // assertDeviceOwnershipRevertedWithFakeTransferMetadata(); + // } + + // @FlakyTest(bugId = 148934649) + // public void testRevertDeviceOwnership_deviceNotMigrated() + // throws Exception { + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), + // getDeviceOwnerPoliciesFile()); + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_not_migrated), + // getDeviceOwnerFile()); + // assertDeviceOwnershipRevertedWithFakeTransferMetadata(); + // } public void testRevertDeviceOwnership_adminAndDeviceNotMigrated() throws Exception { @@ -5487,29 +5490,31 @@ public class DevicePolicyManagerTest extends DpmTestBase { UserHandle userHandle = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE); } - public void testRevertProfileOwnership_adminAndProfileMigrated() throws Exception { - getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0, - UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM); - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), - getProfileOwnerPoliciesFile()); - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_migrated), - getProfileOwnerFile()); - assertProfileOwnershipRevertedWithFakeTransferMetadata(); - } - - public void testRevertProfileOwnership_profileNotMigrated() throws Exception { - getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0, - UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM); - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), - getProfileOwnerPoliciesFile()); - DpmTestUtils.writeInputStreamToFile( - getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_not_migrated), - getProfileOwnerFile()); - assertProfileOwnershipRevertedWithFakeTransferMetadata(); - } + // @FlakyTest(bugId = 148934649) + // public void testRevertProfileOwnership_adminAndProfileMigrated() throws Exception { + // getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0, + // UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM); + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), + // getProfileOwnerPoliciesFile()); + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_migrated), + // getProfileOwnerFile()); + // assertProfileOwnershipRevertedWithFakeTransferMetadata(); + // } + + // @FlakyTest(bugId = 148934649) + // public void testRevertProfileOwnership_profileNotMigrated() throws Exception { + // getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0, + // UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM); + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), + // getProfileOwnerPoliciesFile()); + // DpmTestUtils.writeInputStreamToFile( + // getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_not_migrated), + // getProfileOwnerFile()); + // assertProfileOwnershipRevertedWithFakeTransferMetadata(); + // } public void testRevertProfileOwnership_adminAndProfileNotMigrated() throws Exception { getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0, 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..d40130a62fd9 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -281,9 +281,9 @@ public class AppIntegrityManagerServiceImplTest { AppInstallMetadata appInstallMetadata = metadataCaptor.getValue(); allowedInstallers = allowedInstallersCaptor.getValue(); assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName()); - assertEquals(APP_CERT, appInstallMetadata.getAppCertificate()); + assertThat(appInstallMetadata.getAppCertificates()).containsExactly(APP_CERT); assertEquals(INSTALLER_SHA256, appInstallMetadata.getInstallerName()); - assertEquals(INSTALLER_CERT, appInstallMetadata.getInstallerCertificate()); + assertThat(appInstallMetadata.getInstallerCertificates()).containsExactly(INSTALLER_CERT); assertEquals(VERSION_CODE, appInstallMetadata.getVersionCode()); assertFalse(appInstallMetadata.isPreInstalled()); // These are hardcoded in the test apk android manifest @@ -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/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java index 86daf69fb2d9..41be54ab5b7a 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java @@ -141,10 +141,10 @@ public class IntegrityFileManagerTest { AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() .setPackageName(packageName) - .setAppCertificate(packageCert) + .setAppCertificates(Collections.singletonList(packageCert)) .setVersionCode(version) .setInstallerName("abc") - .setInstallerCertificate("abc") + .setInstallerCertificates(Collections.singletonList("abc")) .setIsPreInstalled(true) .build(); List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata); @@ -182,10 +182,10 @@ public class IntegrityFileManagerTest { AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() .setPackageName(installedPackageName) - .setAppCertificate(installedAppCertificate) + .setAppCertificates(Collections.singletonList(installedAppCertificate)) .setVersionCode(250) .setInstallerName("abc") - .setInstallerCertificate("abc") + .setInstallerCertificates(Collections.singletonList("abc")) .setIsPreInstalled(true) .build(); List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata); diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java index 26b20965fbf5..b0b9596829f1 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java @@ -70,17 +70,17 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata1 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_1) - .setInstallerCertificate(INSTALLER_1_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) .build(); AppInstallMetadata appInstallMetadata2 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_2) - .setInstallerCertificate(INSTALLER_2_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT)) .build(); AppInstallMetadata appInstallMetadata3 = getAppInstallMetadataBuilder() .setInstallerName(RANDOM_INSTALLER) - .setInstallerCertificate(RANDOM_INSTALLER_CERT) + .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect()) @@ -99,7 +99,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata1 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_1) - .setInstallerCertificate(INSTALLER_1_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.ALLOW); @@ -107,7 +107,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata2 = getAppInstallMetadataBuilder() .setInstallerName(RANDOM_INSTALLER) - .setInstallerCertificate(INSTALLER_1_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.DENY); @@ -115,7 +115,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata3 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_1) - .setInstallerCertificate(RANDOM_INSTALLER_CERT) + .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.DENY); @@ -123,7 +123,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata4 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_1) - .setInstallerCertificate(RANDOM_INSTALLER_CERT) + .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.DENY); @@ -138,7 +138,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata1 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_1) - .setInstallerCertificate(INSTALLER_1_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.ALLOW); @@ -146,7 +146,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata2 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_2) - .setInstallerCertificate(INSTALLER_2_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.ALLOW); @@ -154,7 +154,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata3 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_1) - .setInstallerCertificate(INSTALLER_2_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.DENY); @@ -162,7 +162,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata4 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_2) - .setInstallerCertificate(INSTALLER_1_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.DENY); @@ -178,7 +178,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata1 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_1) - .setInstallerCertificate(INSTALLER_1_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.ALLOW); @@ -186,7 +186,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata2 = getAppInstallMetadataBuilder() .setInstallerName(RANDOM_INSTALLER) - .setInstallerCertificate(INSTALLER_1_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.DENY); @@ -196,8 +196,8 @@ public class RuleEvaluationEngineTest { private AppInstallMetadata.Builder getAppInstallMetadataBuilder() { return new AppInstallMetadata.Builder() .setPackageName("abc") - .setAppCertificate("abc") - .setInstallerCertificate("abc") + .setAppCertificates(Collections.singletonList("abc")) + .setInstallerCertificates(Collections.singletonList("abc")) .setInstallerName("abc") .setVersionCode(-1) .setIsPreInstalled(true); diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java index 7b53e5e7c762..b271a7766d63 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java @@ -48,7 +48,7 @@ public class RuleEvaluatorTest { private static final AppInstallMetadata APP_INSTALL_METADATA = new AppInstallMetadata.Builder() .setPackageName(PACKAGE_NAME_1) - .setAppCertificate(APP_CERTIFICATE) + .setAppCertificates(Collections.singletonList(APP_CERTIFICATE)) .setVersionCode(2) .build(); diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java index 742952e056bc..0784b7a7d519 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java @@ -35,6 +35,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collections; import java.util.List; @RunWith(JUnit4.class) @@ -49,7 +51,7 @@ public class RuleIndexingControllerTest { AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() .setPackageName("ddd") - .setAppCertificate("777") + .setAppCertificates(Collections.singletonList("777")) .build(); List<RuleIndexRange> resultingIndexes = @@ -63,6 +65,29 @@ public class RuleIndexingControllerTest { } @Test + public void verifyIndexRangeSearchIsCorrect_multipleAppCertificates() throws IOException { + InputStream inputStream = obtainDefaultIndexingMapForTest(); + + RuleIndexingController indexingController = new RuleIndexingController(inputStream); + + AppInstallMetadata appInstallMetadata = + new AppInstallMetadata.Builder() + .setPackageName("ddd") + .setAppCertificates(Arrays.asList("777", "999")) + .build(); + + List<RuleIndexRange> resultingIndexes = + indexingController.identifyRulesToEvaluate(appInstallMetadata); + + assertThat(resultingIndexes) + .containsExactly( + new RuleIndexRange(200, 300), + new RuleIndexRange(700, 800), + new RuleIndexRange(800, 900), + new RuleIndexRange(900, 945)); + } + + @Test public void verifyIndexRangeSearchIsCorrect_keysInFirstAndLastBlock() throws IOException { InputStream inputStream = obtainDefaultIndexingMapForTest(); @@ -71,7 +96,7 @@ public class RuleIndexingControllerTest { AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() .setPackageName("bbb") - .setAppCertificate("999") + .setAppCertificates(Collections.singletonList("999")) .build(); List<RuleIndexRange> resultingIndexes = @@ -93,7 +118,7 @@ public class RuleIndexingControllerTest { AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() .setPackageName("ccc") - .setAppCertificate("444") + .setAppCertificates(Collections.singletonList("444")) .build(); List<RuleIndexRange> resultingIndexes = @@ -125,7 +150,7 @@ public class RuleIndexingControllerTest { AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() .setPackageName("ccc") - .setAppCertificate("444") + .setAppCertificates(Collections.singletonList("444")) .build(); List<RuleIndexRange> resultingIndexes = 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 62ea425a54a7..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(); @@ -232,7 +241,7 @@ public final class DataManagerTest { mDataManager.getShortcut(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID); verify(mShortcutServiceInternal).getShortcuts(anyInt(), anyString(), anyLong(), eq(TEST_PKG_NAME), eq(Collections.singletonList(TEST_SHORTCUT_ID)), - eq(null), anyInt(), eq(USER_ID_PRIMARY), anyInt(), anyInt()); + eq(null), eq(null), anyInt(), eq(USER_ID_PRIMARY), anyInt(), anyInt()); } @Test @@ -263,6 +272,7 @@ public final class DataManagerTest { @Test public void testContactsChanged() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onUserUnlocked(USER_ID_SECONDARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); @@ -287,7 +297,7 @@ public final class DataManagerTest { } @Test - public void testNotificationListener() { + public void testNotificationOpened() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, @@ -310,37 +320,86 @@ 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 public void testCallLogContentObserver() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onUserUnlocked(USER_ID_SECONDARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); @@ -368,6 +427,7 @@ public final class DataManagerTest { @Test public void testMmsSmsContentObserver() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onUserUnlocked(USER_ID_SECONDARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); 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/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java index 3e3f40d31d0e..e28d13a1a20c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -35,7 +35,6 @@ import android.content.pm.parsing.ComponentParseUtils.ParsedActivity; import android.content.pm.parsing.ComponentParseUtils.ParsedActivityIntentInfo; import android.content.pm.parsing.PackageImpl; import android.content.pm.parsing.ParsingPackage; -import android.net.Uri; import android.os.Build; import android.os.Process; import android.util.ArrayMap; @@ -87,6 +86,17 @@ public class AppsFilterTest { return pkg; } + private static ParsingPackage pkgQueriesProvider(String packageName, + String... queriesAuthorities) { + ParsingPackage pkg = pkg(packageName); + if (queriesAuthorities != null) { + for (String authority : queriesAuthorities) { + pkg.addQueriesProvider(authority); + } + } + return pkg; + } + private static ParsingPackage pkg(String packageName, String... queriesPackages) { ParsingPackage pkg = pkg(packageName); if (queriesPackages != null) { @@ -172,8 +182,7 @@ public class AppsFilterTest { PackageSetting target = simulateAddPackage(appsFilter, pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", - new Intent().setData(Uri.parse("content://com.some.authority"))), + pkgQueriesProvider("com.some.other.package", "com.some.authority"), DUMMY_CALLING_UID); assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); @@ -188,8 +197,7 @@ public class AppsFilterTest { PackageSetting target = simulateAddPackage(appsFilter, pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", - new Intent().setData(Uri.parse("content://com.some.other.authority"))), + pkgQueriesProvider("com.some.other.package", "com.some.other.authority"), DUMMY_CALLING_UID); assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); @@ -205,8 +213,7 @@ public class AppsFilterTest { pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", - new Intent().setData(Uri.parse("content://com.some.authority"))), + pkgQueriesProvider("com.some.other.package", "com.some.authority"), DUMMY_CALLING_UID); assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); @@ -266,7 +273,7 @@ public class AppsFilterTest { appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID); + pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), DUMMY_CALLING_UID); diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 41416f1352a8..77f842aa503f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -52,6 +52,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ILauncherApps; @@ -555,6 +556,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { void injectRestoreCallingIdentity(long token) { mInjectedCallingUid = (int) token; } + + @Override + boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) { + return true; + } } protected class LauncherAppsTestable extends LauncherApps { @@ -1600,6 +1606,38 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } /** + * Make a shortcut with an ID and a locus ID. + */ + protected ShortcutInfo makeShortcutWithLocusId(String id, LocusId locusId) { + final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, id) + .setActivity(new ComponentName(mClientContext.getPackageName(), "main")) + .setShortLabel("title-" + id) + .setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class)) + .setLocusId(locusId); + final ShortcutInfo s = b.build(); + + s.setTimestamp(mInjectedCurrentTimeMillis); // HACK + + return s; + } + + /** + * Make a long lived shortcut with an ID. + */ + protected ShortcutInfo makeLongLivedShortcut(String id) { + final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, id) + .setActivity(new ComponentName(mClientContext.getPackageName(), "main")) + .setShortLabel("title-" + id) + .setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class)) + .setLongLived(true); + final ShortcutInfo s = b.build(); + + s.setTimestamp(mInjectedCurrentTimeMillis); // HACK + + return s; + } + + /** * Make an intent. */ protected Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) { @@ -1618,6 +1656,13 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } /** + * Make a LocusId. + */ + protected LocusId makeLocusId(String id) { + return new LocusId(id); + } + + /** * Make an component name, with the client context. */ @NonNull @@ -1955,16 +2000,17 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected static ShortcutQuery buildQuery(long changedSince, String packageName, ComponentName componentName, /* @ShortcutQuery.QueryFlags */ int flags) { - return buildQuery(changedSince, packageName, null, componentName, flags); + return buildQuery(changedSince, packageName, null, null, componentName, flags); } protected static ShortcutQuery buildQuery(long changedSince, - String packageName, List<String> shortcutIds, ComponentName componentName, - /* @ShortcutQuery.QueryFlags */ int flags) { + String packageName, List<String> shortcutIds, List<LocusId> locusIds, + ComponentName componentName, /* @ShortcutQuery.QueryFlags */ int flags) { final ShortcutQuery q = new ShortcutQuery(); q.setChangedSince(changedSince); q.setPackage(packageName); q.setShortcutIds(shortcutIds); + q.setLocusIds(locusIds); q.setActivity(componentName); q.setQueryFlags(flags); return q; diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java index bfe0c15ef6e8..d4edab44bae3 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -24,6 +24,7 @@ import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; import android.content.pm.PackageInstaller; +import android.platform.test.annotations.Presubmit; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; @@ -57,6 +58,7 @@ import java.util.Arrays; import java.util.List; @RunWith(AndroidJUnit4.class) +@Presubmit public class PackageInstallerSessionTest { @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 798420ee0137..f03670843f3a 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -1240,7 +1240,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { assertTrue(mManager.setDynamicShortcuts(list( - makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); + makeLongLivedShortcut("s1"), makeLongLivedShortcut("s2"), makeShortcut("s3")))); }); // Pin 2 and 3 @@ -1250,9 +1250,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); // Cache 1 and 2 + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"), + HANDLE_USER_0); + }); + setCaller(CALLING_PACKAGE_1); - getCallerShortcut("s1").setCached(); - getCallerShortcut("s2").setCached(); // Get manifest shortcuts assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_MANIFEST), @@ -1315,8 +1318,9 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { public void testCachedShortcuts() { runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { - assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeShortcut("s2"), - makeShortcut("s3"), makeShortcut("s4")))); + assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), + makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"), + makeLongLivedShortcut("s4")))); }); // Pin s2 @@ -1325,11 +1329,13 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { HANDLE_USER_0); }); - // Cache 2, 3 and 4 + // Cache some, but non long lived shortcuts will be ignored. + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s4"), + HANDLE_USER_0); + }); + setCaller(CALLING_PACKAGE_1); - getCallerShortcut("s2").setCached(); - getCallerShortcut("s3").setCached(); - getCallerShortcut("s4").setCached(); // Get dynamic shortcuts assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), @@ -1339,27 +1345,37 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { "s2"); // Get cached shortcuts assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), - "s2", "s3", "s4"); + "s2", "s4"); // Remove a dynamic cached shortcut - mManager.removeDynamicShortcuts(list("s3")); + mManager.removeDynamicShortcuts(list("s4")); assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), - "s1", "s2", "s4"); + "s1", "s2", "s3"); assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), - "s2", "s3", "s4"); + "s2", "s4"); - // Remove dynamic cached long lived shortcuts - mManager.removeLongLivedShortcuts(list("s3", "s4")); - assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), - "s1", "s2"); + // uncache a non-dynamic shortcut. Should be removed. + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s4"), + HANDLE_USER_0); + }); assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s2"); + // Cache another shortcut + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s3"), + HANDLE_USER_0); + }); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), + "s2", "s3"); + // Remove a dynamic cached pinned long lived shortcut mManager.removeLongLivedShortcuts(list("s2")); assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), - "s1"); - assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED)); + "s1", "s3"); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), + "s3"); assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED), "s2"); } @@ -1371,8 +1387,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // Set up shortcuts. setCaller(CALLING_PACKAGE_1); - final ShortcutInfo s1_1 = makeShortcut("s1"); - final ShortcutInfo s1_2 = makeShortcut("s2"); + final ShortcutInfo s1_1 = makeLongLivedShortcut("s1"); + final ShortcutInfo s1_2 = makeShortcutWithLocusId("s2", makeLocusId("l1")); assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2))); @@ -1394,7 +1410,9 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { getCallerShortcut("s4").setTimestamp(500); setCaller(CALLING_PACKAGE_3); - final ShortcutInfo s3_2 = makeShortcut("s3"); + final ShortcutInfo s3_2 = makeShortcutWithLocusId("s3", makeLocusId("l2")); + s3_2.setLongLived(); + assertTrue(mManager.setDynamicShortcuts(list(s3_2))); getCallerShortcut("s3").setTimestamp(START_TIME + 5000); @@ -1446,7 +1464,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // With ID. assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( - /* time =*/ 1000, CALLING_PACKAGE_2, list("s3"), + /* time =*/ 1000, CALLING_PACKAGE_2, list("s3"), /* locusIds =*/ null, /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), getCallingUser())), @@ -1454,20 +1472,51 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( /* time =*/ 1000, CALLING_PACKAGE_2, list("s3", "s2", "ss"), - /* activity =*/ null, + /* locusIds =*/ null, /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), getCallingUser())), "s2", "s3")))); assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( /* time =*/ 1000, CALLING_PACKAGE_2, list("s3x", "s2x"), + /* locusIds =*/ null, /* activity =*/ null, + ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), + getCallingUser())) + /* empty */)))); + assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( + assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( + /* time =*/ 1000, CALLING_PACKAGE_2, list(), /* locusIds =*/ null, + /* activity =*/ null, + ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), + getCallingUser())) + /* empty */)))); + + // With locus ID. + assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( + assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( + /* time =*/ 1000, CALLING_PACKAGE_3, /* shortcutIds =*/ null, + list(makeLocusId("l2")), /* activity =*/ null, + ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), + getCallingUser())), + "s3")))); + assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( + assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( + /* time =*/ 1000, CALLING_PACKAGE_1, /* shortcutIds =*/ null, + list(makeLocusId("l1"), makeLocusId("l2"), makeLocusId("l3")), /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), + getCallingUser())), + "s2")))); + assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( + assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( + /* time =*/ 1000, CALLING_PACKAGE_1, /* shortcutIds =*/ null, + list(makeLocusId("lx1"), makeLocusId("lx2")), /* activity =*/ null, + ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), getCallingUser())) /* empty */)))); assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery( - /* time =*/ 1000, CALLING_PACKAGE_2, list(), + /* time =*/ 1000, CALLING_PACKAGE_3, /* shortcutIds =*/ null, list(), /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY), getCallingUser())) @@ -1498,32 +1547,26 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertExpectException( IllegalArgumentException.class, "package name must also be set", () -> { mLauncherApps.getShortcuts(buildQuery( - /* time =*/ 0, /* package= */ null, list("id"), + /* time =*/ 0, /* package= */ null, list("id"), /* locusIds =*/ null, /* activity =*/ null, /* flags */ 0), getCallingUser()); }); // TODO More tests: pinned but dynamic. - // Cache some shortcuts - setCaller(CALLING_PACKAGE_1); - getCallerShortcut("s1").setCached(); - - setCaller(CALLING_PACKAGE_2); - getCallerShortcut("s4").setCached(); - - setCaller(CALLING_PACKAGE_3); - getCallerShortcut("s3").setCached(); - setCaller(LAUNCHER_1); + // Cache some shortcuts. Only long lived shortcuts can get cached. + mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1"), getCallingUser()); + mLauncherApps.cacheShortcuts(CALLING_PACKAGE_3, list("s3"), getCallingUser()); + // Cached ones only assertShortcutIds(assertAllNotKeyFieldsOnly( mLauncherApps.getShortcuts(buildQuery( - /* time =*/ 0, CALLING_PACKAGE_2, + /* time =*/ 0, CALLING_PACKAGE_3, /* activity =*/ null, ShortcutQuery.FLAG_MATCH_CACHED), getCallingUser())), - "s4"); + "s3"); // All packages. assertShortcutIds(assertAllNotKeyFieldsOnly( @@ -1532,12 +1575,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /* activity =*/ null, ShortcutQuery.FLAG_MATCH_CACHED), getCallingUser())), - "s1", "s4", "s3"); + "s1", "s3"); assertExpectException( IllegalArgumentException.class, "package name must also be set", () -> { mLauncherApps.getShortcuts(buildQuery( - /* time =*/ 0, /* package= */ null, list("id"), + /* time =*/ 0, /* package= */ null, list("id"), /* locusIds= */ null, /* activity =*/ null, /* flags */ 0), getCallingUser()); }); @@ -1550,7 +1593,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /* activity =*/ null, ShortcutQuery.FLAG_MATCH_CACHED), getCallingUser())), - "s1", "s4", "s3"); + "s1", "s3"); } public void testGetShortcuts_shortcutKinds() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java index 804c1b9e09fd..1a6c6b4011cd 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java @@ -224,21 +224,43 @@ public class RollbackUnitTest { } @Test - public void snapshotThenDelete() { + public void snapshotThenDeleteNoApex() { + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); + PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); + PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, false); + rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2)); + + int[] userIds = {111, 222}; + rollback.snapshotUserData(PKG_2, userIds, mMockDataHelper); + + verify(mMockDataHelper).snapshotAppData(eq(123), pkgRollbackInfoFor(PKG_2), eq(userIds)); + + rollback.delete(mMockDataHelper); + + verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(111)); + verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(222)); + verify(mMockDataHelper, never()).destroyApexDeSnapshots(anyInt()); + + assertThat(rollback.isDeleted()).isTrue(); + } + + @Test + public void snapshotThenDeleteWithApex() { Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true); rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2)); - int[] userIds = {12, 18}; + int[] userIds = {111, 222}; rollback.snapshotUserData(PKG_2, userIds, mMockDataHelper); verify(mMockDataHelper).snapshotAppData(eq(123), pkgRollbackInfoFor(PKG_2), eq(userIds)); rollback.delete(mMockDataHelper); - verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(12)); - verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(18)); + verify(mMockDataHelper, never()) + .destroyAppDataSnapshot(anyInt(), pkgRollbackInfoFor(PKG_2), anyInt()); + verify(mMockDataHelper).destroyApexDeSnapshots(123); assertThat(rollback.isDeleted()).isTrue(); } 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/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java index fde0ddffa365..4d0481be20b3 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -144,6 +144,40 @@ public class SystemConfigTest { assertEquals("Incorrect blacklist", expectedBlack, actualBlack); } + @Test + public void testComponentOverride() throws Exception { + final String contents = + "<permissions>" + + " <component-override package=\"com.android.package1\">\n" + + " <component class=\"com.android.package1.Full\" enabled=\"true\"/>" + + " <component class=\".Relative\" enabled=\"false\" />\n" + + " </component-override>" + + " <component-override package=\"com.android.package2\" >\n" + + " <component class=\"com.android.package3.Relative2\" enabled=\"yes\" />\n" + + " </component-override>\n" + + "</permissions>"; + + final File folder = createTempSubfolder("folder"); + createTempFile(folder, "component-override.xml", contents); + + mSysConfig.readPermissions(folder, /* No permission needed anyway */ 0); + + final ArrayMap<String, Boolean> packageOneExpected = new ArrayMap<>(); + packageOneExpected.put("com.android.package1.Full", true); + packageOneExpected.put("com.android.package1.Relative", false); + + final ArrayMap<String, Boolean> packageTwoExpected = new ArrayMap<>(); + packageTwoExpected.put("com.android.package3.Relative2", true); + + final Map<String, Boolean> packageOne = mSysConfig.getComponentsEnabledStates( + "com.android.package1"); + assertEquals(packageOneExpected, packageOne); + + final Map<String, Boolean> packageTwo = mSysConfig.getComponentsEnabledStates( + "com.android.package2"); + assertEquals(packageTwoExpected, packageTwo); + } + /** * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents. * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index ae5369204428..2eeeb3ebaa2a 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -30,17 +30,16 @@ import static org.mockito.Mockito.when; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; -import android.app.timedetector.PhoneTimeSuggestion; +import android.app.timedetector.TelephonyTimeSuggestion; import android.content.Context; import android.content.pm.PackageManager; -import android.os.Handler; import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; import android.os.TimestampedValue; import androidx.test.runner.AndroidJUnit4; +import com.android.server.timezonedetector.TestHandler; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -81,35 +80,35 @@ public class TimeDetectorServiceTest { } @Test(expected = SecurityException.class) - public void testSuggestPhoneTime_withoutPermission() { + public void testSuggestTelephonyTime_withoutPermission() { doThrow(new SecurityException("Mock")) .when(mMockContext).enforceCallingPermission(anyString(), any()); - PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion(); + TelephonyTimeSuggestion timeSuggestion = createTelephonyTimeSuggestion(); try { - mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion); + mTimeDetectorService.suggestTelephonyTime(timeSuggestion); fail(); } finally { verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE), + eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString()); } } @Test - public void testSuggestPhoneTime() throws Exception { + public void testSuggestTelephonyTime() throws Exception { doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); - PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion(); - mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion); + TelephonyTimeSuggestion timeSuggestion = createTelephonyTimeSuggestion(); + mTimeDetectorService.suggestTelephonyTime(timeSuggestion); mTestHandler.assertTotalMessagesEnqueued(1); verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE), + eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString()); - mTestHandler.waitForEmptyQueue(); - mStubbedTimeDetectorStrategy.verifySuggestPhoneTimeCalled(phoneTimeSuggestion); + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeDetectorStrategy.verifySuggestTelephonyTimeCalled(timeSuggestion); } @Test(expected = SecurityException.class) @@ -140,7 +139,7 @@ public class TimeDetectorServiceTest { eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), anyString()); - mTestHandler.waitForEmptyQueue(); + mTestHandler.waitForMessagesToBeProcessed(); mStubbedTimeDetectorStrategy.verifySuggestManualTimeCalled(manualTimeSuggestion); } @@ -170,7 +169,7 @@ public class TimeDetectorServiceTest { verify(mMockContext).enforceCallingOrSelfPermission( eq(android.Manifest.permission.SET_TIME), anyString()); - mTestHandler.waitForEmptyQueue(); + mTestHandler.waitForMessagesToBeProcessed(); mStubbedTimeDetectorStrategy.verifySuggestNetworkTimeCalled(NetworkTimeSuggestion); } @@ -187,21 +186,23 @@ public class TimeDetectorServiceTest { @Test public void testAutoTimeDetectionToggle() throws Exception { - mTimeDetectorService.handleAutoTimeDetectionToggle(); + mTimeDetectorService.handleAutoTimeDetectionChanged(); mTestHandler.assertTotalMessagesEnqueued(1); - mTestHandler.waitForEmptyQueue(); - mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(); + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled(); + + mStubbedTimeDetectorStrategy.resetCallTracking(); - mTimeDetectorService.handleAutoTimeDetectionToggle(); + mTimeDetectorService.handleAutoTimeDetectionChanged(); mTestHandler.assertTotalMessagesEnqueued(2); - mTestHandler.waitForEmptyQueue(); - mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(); + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled(); } - private static PhoneTimeSuggestion createPhoneTimeSuggestion() { - int phoneId = 1234; + private static TelephonyTimeSuggestion createTelephonyTimeSuggestion() { + int slotIndex = 1234; TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L); - return new PhoneTimeSuggestion.Builder(phoneId) + return new TelephonyTimeSuggestion.Builder(slotIndex) .setUtcTime(timeValue) .build(); } @@ -219,10 +220,10 @@ public class TimeDetectorServiceTest { private static class StubbedTimeDetectorStrategy implements TimeDetectorStrategy { // Call tracking. - private PhoneTimeSuggestion mLastPhoneSuggestion; + private TelephonyTimeSuggestion mLastTelephonySuggestion; private ManualTimeSuggestion mLastManualSuggestion; private NetworkTimeSuggestion mLastNetworkSuggestion; - private boolean mLastAutoTimeDetectionToggleCalled; + private boolean mHandleAutoTimeDetectionChangedCalled; private boolean mDumpCalled; @Override @@ -230,45 +231,40 @@ public class TimeDetectorServiceTest { } @Override - public void suggestPhoneTime(PhoneTimeSuggestion timeSuggestion) { - resetCallTracking(); - mLastPhoneSuggestion = timeSuggestion; + public void suggestTelephonyTime(TelephonyTimeSuggestion timeSuggestion) { + mLastTelephonySuggestion = timeSuggestion; } @Override public void suggestManualTime(ManualTimeSuggestion timeSuggestion) { - resetCallTracking(); mLastManualSuggestion = timeSuggestion; } @Override public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) { - resetCallTracking(); mLastNetworkSuggestion = timeSuggestion; } @Override public void handleAutoTimeDetectionChanged() { - resetCallTracking(); - mLastAutoTimeDetectionToggleCalled = true; + mHandleAutoTimeDetectionChangedCalled = true; } @Override public void dump(PrintWriter pw, String[] args) { - resetCallTracking(); mDumpCalled = true; } void resetCallTracking() { - mLastPhoneSuggestion = null; + mLastTelephonySuggestion = null; mLastManualSuggestion = null; mLastNetworkSuggestion = null; - mLastAutoTimeDetectionToggleCalled = false; + mHandleAutoTimeDetectionChangedCalled = false; mDumpCalled = false; } - void verifySuggestPhoneTimeCalled(PhoneTimeSuggestion expectedSuggestion) { - assertEquals(expectedSuggestion, mLastPhoneSuggestion); + void verifySuggestTelephonyTimeCalled(TelephonyTimeSuggestion expectedSuggestion) { + assertEquals(expectedSuggestion, mLastTelephonySuggestion); } public void verifySuggestManualTimeCalled(ManualTimeSuggestion expectedSuggestion) { @@ -279,45 +275,12 @@ public class TimeDetectorServiceTest { assertEquals(expectedSuggestion, mLastNetworkSuggestion); } - void verifyHandleAutoTimeDetectionToggleCalled() { - assertTrue(mLastAutoTimeDetectionToggleCalled); + void verifyHandleAutoTimeDetectionChangedCalled() { + assertTrue(mHandleAutoTimeDetectionChangedCalled); } void verifyDumpCalled() { assertTrue(mDumpCalled); } } - - /** - * A Handler that can track posts/sends and wait for work to be completed. - */ - private static class TestHandler extends Handler { - - private int mMessagesSent; - - TestHandler(Looper looper) { - super(looper); - } - - @Override - public boolean sendMessageAtTime(Message msg, long uptimeMillis) { - mMessagesSent++; - return super.sendMessageAtTime(msg, uptimeMillis); - } - - /** Asserts the number of messages posted or sent is as expected. */ - void assertTotalMessagesEnqueued(int expected) { - assertEquals(expected, mMessagesSent); - } - - /** - * Waits for all currently enqueued work due to be processed to be completed before - * returning. - */ - void waitForEmptyQueue() throws InterruptedException { - while (!getLooper().getQueue().isIdle()) { - Thread.sleep(100); - } - } - } } diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java index d940a6a320f2..803b245889e7 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -24,7 +24,7 @@ import static org.junit.Assert.fail; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; -import android.app.timedetector.PhoneTimeSuggestion; +import android.app.timedetector.TelephonyTimeSuggestion; import android.icu.util.Calendar; import android.icu.util.GregorianCalendar; import android.icu.util.TimeZone; @@ -52,7 +52,7 @@ public class TimeDetectorStrategyImplTest { */ private static final long ARBITRARY_TEST_TIME_MILLIS = createUtcTime(2018, 1, 1, 12, 0, 0); - private static final int ARBITRARY_PHONE_ID = 123456; + private static final int ARBITRARY_SLOT_INDEX = 123456; private Script mScript; @@ -62,51 +62,51 @@ public class TimeDetectorStrategyImplTest { } @Test - public void testSuggestPhoneTime_autoTimeEnabled() { + public void testSuggestTelephonyTime_autoTimeEnabled() { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(true); - int phoneId = ARBITRARY_PHONE_ID; + int slotIndex = ARBITRARY_SLOT_INDEX; long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; - PhoneTimeSuggestion timeSuggestion = - mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + TelephonyTimeSuggestion timeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis); mScript.simulateTimePassing() - .simulatePhoneTimeSuggestion(timeSuggestion); + .simulateTelephonyTimeSuggestion(timeSuggestion); long expectedSystemClockMillis = mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime()); mScript.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) - .assertLatestPhoneSuggestion(phoneId, timeSuggestion); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion); } @Test - public void testSuggestPhoneTime_emptySuggestionIgnored() { + public void testSuggestTelephonyTime_emptySuggestionIgnored() { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(true); - int phoneId = ARBITRARY_PHONE_ID; - PhoneTimeSuggestion timeSuggestion = - mScript.generatePhoneTimeSuggestion(phoneId, null); - mScript.simulatePhoneTimeSuggestion(timeSuggestion) + int slotIndex = ARBITRARY_SLOT_INDEX; + TelephonyTimeSuggestion timeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex, null); + mScript.simulateTelephonyTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, null); + .assertLatestTelephonySuggestion(slotIndex, null); } @Test - public void testSuggestPhoneTime_systemClockThreshold() { + public void testSuggestTelephonyTime_systemClockThreshold() { final int systemClockUpdateThresholdMillis = 1000; final int clockIncrementMillis = 100; mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeThresholds(systemClockUpdateThresholdMillis) .pokeAutoTimeDetectionEnabled(true); - int phoneId = ARBITRARY_PHONE_ID; + int slotIndex = ARBITRARY_SLOT_INDEX; // Send the first time signal. It should be used. { - PhoneTimeSuggestion timeSuggestion1 = - mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS); + TelephonyTimeSuggestion timeSuggestion1 = + mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME_MILLIS); // Increment the the device clocks to simulate the passage of time. mScript.simulateTimePassing(clockIncrementMillis); @@ -114,151 +114,151 @@ public class TimeDetectorStrategyImplTest { long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(timeSuggestion1.getUtcTime()); - mScript.simulatePhoneTimeSuggestion(timeSuggestion1) + mScript.simulateTelephonyTimeSuggestion(timeSuggestion1) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) - .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); } // Now send another time signal, but one that is too similar to the last one and should be // stored, but not used to set the system clock. { int underThresholdMillis = systemClockUpdateThresholdMillis - 1; - PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion( - phoneId, mScript.peekSystemClockMillis() + underThresholdMillis); + TelephonyTimeSuggestion timeSuggestion2 = mScript.generateTelephonyTimeSuggestion( + slotIndex, mScript.peekSystemClockMillis() + underThresholdMillis); mScript.simulateTimePassing(clockIncrementMillis) - .simulatePhoneTimeSuggestion(timeSuggestion2) + .simulateTelephonyTimeSuggestion(timeSuggestion2) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, timeSuggestion2); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion2); } // Now send another time signal, but one that is on the threshold and so should be used. { - PhoneTimeSuggestion timeSuggestion3 = mScript.generatePhoneTimeSuggestion( - phoneId, + TelephonyTimeSuggestion timeSuggestion3 = mScript.generateTelephonyTimeSuggestion( + slotIndex, mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis); mScript.simulateTimePassing(clockIncrementMillis); long expectedSystemClockMillis3 = mScript.calculateTimeInMillisForNow(timeSuggestion3.getUtcTime()); - mScript.simulatePhoneTimeSuggestion(timeSuggestion3) + mScript.simulateTelephonyTimeSuggestion(timeSuggestion3) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis3) - .assertLatestPhoneSuggestion(phoneId, timeSuggestion3); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion3); } } @Test - public void testSuggestPhoneTime_multiplePhoneIdsAndBucketing() { + public void testSuggestTelephonyTime_multipleSlotIndexsAndBucketing() { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(true); - // There are 2 phones in this test. Phone 2 has a different idea of the current time. - // phone1Id < phone2Id (which is important because the strategy uses the lowest ID when - // multiple phone suggestions are available. - int phone1Id = ARBITRARY_PHONE_ID; - int phone2Id = ARBITRARY_PHONE_ID + 1; - long phone1TimeMillis = ARBITRARY_TEST_TIME_MILLIS; - long phone2TimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(1).toMillis(); + // There are 2 slotIndexes in this test. slotIndex1 and slotIndex2 have different opinions + // about the current time. slotIndex1 < slotIndex2 (which is important because the strategy + // uses the lowest slotIndex when multiple telephony suggestions are available. + int slotIndex1 = ARBITRARY_SLOT_INDEX; + int slotIndex2 = ARBITRARY_SLOT_INDEX + 1; + long slotIndex1TimeMillis = ARBITRARY_TEST_TIME_MILLIS; + long slotIndex2TimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(1).toMillis(); - // Make a suggestion with phone2Id. + // Make a suggestion with slotIndex2. { - PhoneTimeSuggestion phone2TimeSuggestion = - mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); + TelephonyTimeSuggestion slotIndex2TimeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis); mScript.simulateTimePassing(); long expectedSystemClockMillis = - mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime()); + mScript.calculateTimeInMillisForNow(slotIndex2TimeSuggestion.getUtcTime()); - mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) + mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) - .assertLatestPhoneSuggestion(phone1Id, null) - .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex1, null) + .assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion); } mScript.simulateTimePassing(); - // Now make a different suggestion with phone1Id. + // Now make a different suggestion with slotIndex1. { - PhoneTimeSuggestion phone1TimeSuggestion = - mScript.generatePhoneTimeSuggestion(phone1Id, phone1TimeMillis); + TelephonyTimeSuggestion slotIndex1TimeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex1, slotIndex1TimeMillis); mScript.simulateTimePassing(); long expectedSystemClockMillis = - mScript.calculateTimeInMillisForNow(phone1TimeSuggestion.getUtcTime()); + mScript.calculateTimeInMillisForNow(slotIndex1TimeSuggestion.getUtcTime()); - mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion) + mScript.simulateTelephonyTimeSuggestion(slotIndex1TimeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) - .assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex1, slotIndex1TimeSuggestion); } mScript.simulateTimePassing(); - // Make another suggestion with phone2Id. It should be stored but not used because the - // phone1Id suggestion will still "win". + // Make another suggestion with slotIndex2. It should be stored but not used because the + // slotIndex1 suggestion will still "win". { - PhoneTimeSuggestion phone2TimeSuggestion = - mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); + TelephonyTimeSuggestion slotIndex2TimeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis); mScript.simulateTimePassing(); - mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) + mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion); } - // Let enough time pass that phone1Id's suggestion should now be too old. - mScript.simulateTimePassing(TimeDetectorStrategyImpl.PHONE_BUCKET_SIZE_MILLIS); + // Let enough time pass that slotIndex1's suggestion should now be too old. + mScript.simulateTimePassing(TimeDetectorStrategyImpl.TELEPHONY_BUCKET_SIZE_MILLIS); - // Make another suggestion with phone2Id. It should be used because the phoneId1 + // Make another suggestion with slotIndex2. It should be used because the slotIndex1 // is in an older "bucket". { - PhoneTimeSuggestion phone2TimeSuggestion = - mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); + TelephonyTimeSuggestion slotIndex2TimeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis); mScript.simulateTimePassing(); long expectedSystemClockMillis = - mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime()); + mScript.calculateTimeInMillisForNow(slotIndex2TimeSuggestion.getUtcTime()); - mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) + mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis) - .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex2, slotIndex2TimeSuggestion); } } @Test - public void testSuggestPhoneTime_autoTimeDisabled() { + public void testSuggestTelephonyTime_autoTimeDisabled() { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(false); - int phoneId = ARBITRARY_PHONE_ID; - PhoneTimeSuggestion timeSuggestion = - mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS); + int slotIndex = ARBITRARY_SLOT_INDEX; + TelephonyTimeSuggestion timeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME_MILLIS); mScript.simulateTimePassing() - .simulatePhoneTimeSuggestion(timeSuggestion) + .simulateTelephonyTimeSuggestion(timeSuggestion) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, timeSuggestion); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion); } @Test - public void testSuggestPhoneTime_invalidNitzReferenceTimesIgnored() { + public void testSuggestTelephonyTime_invalidNitzReferenceTimesIgnored() { final int systemClockUpdateThreshold = 2000; mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeThresholds(systemClockUpdateThreshold) .pokeAutoTimeDetectionEnabled(true); long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; - int phoneId = ARBITRARY_PHONE_ID; + int slotIndex = ARBITRARY_SLOT_INDEX; - PhoneTimeSuggestion timeSuggestion1 = - mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + TelephonyTimeSuggestion timeSuggestion1 = + mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis); TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime(); - // Initialize the strategy / device with a time set from a phone suggestion. + // Initialize the strategy / device with a time set from a telephony suggestion. mScript.simulateTimePassing(); long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1); - mScript.simulatePhoneTimeSuggestion(timeSuggestion1) + mScript.simulateTelephonyTimeSuggestion(timeSuggestion1) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) - .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // The UTC time increment should be larger than the system clock update threshold so we // know it shouldn't be ignored for other reasons. @@ -269,11 +269,11 @@ public class TimeDetectorStrategyImplTest { long referenceTimeBeforeLastSignalMillis = utcTime1.getReferenceTimeMillis() - 1; TimestampedValue<Long> utcTime2 = new TimestampedValue<>( referenceTimeBeforeLastSignalMillis, validUtcTimeMillis); - PhoneTimeSuggestion timeSuggestion2 = - createPhoneTimeSuggestion(phoneId, utcTime2); - mScript.simulatePhoneTimeSuggestion(timeSuggestion2) + TelephonyTimeSuggestion timeSuggestion2 = + createTelephonyTimeSuggestion(slotIndex, utcTime2); + mScript.simulateTelephonyTimeSuggestion(timeSuggestion2) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // Now supply a new signal that has an obviously bogus reference time : substantially in the // future. @@ -281,36 +281,36 @@ public class TimeDetectorStrategyImplTest { utcTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1; TimestampedValue<Long> utcTime3 = new TimestampedValue<>( referenceTimeInFutureMillis, validUtcTimeMillis); - PhoneTimeSuggestion timeSuggestion3 = - createPhoneTimeSuggestion(phoneId, utcTime3); - mScript.simulatePhoneTimeSuggestion(timeSuggestion3) + TelephonyTimeSuggestion timeSuggestion3 = + createTelephonyTimeSuggestion(slotIndex, utcTime3); + mScript.simulateTelephonyTimeSuggestion(timeSuggestion3) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // Just to prove validUtcTimeMillis is valid. long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100; TimestampedValue<Long> utcTime4 = new TimestampedValue<>( validReferenceTimeMillis, validUtcTimeMillis); long expectedSystemClockMillis4 = mScript.calculateTimeInMillisForNow(utcTime4); - PhoneTimeSuggestion timeSuggestion4 = - createPhoneTimeSuggestion(phoneId, utcTime4); - mScript.simulatePhoneTimeSuggestion(timeSuggestion4) + TelephonyTimeSuggestion timeSuggestion4 = + createTelephonyTimeSuggestion(slotIndex, utcTime4); + mScript.simulateTelephonyTimeSuggestion(timeSuggestion4) .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4) - .assertLatestPhoneSuggestion(phoneId, timeSuggestion4); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion4); } @Test - public void testSuggestPhoneTime_timeDetectionToggled() { + public void testSuggestTelephonyTime_timeDetectionToggled() { final int clockIncrementMillis = 100; final int systemClockUpdateThreshold = 2000; mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeThresholds(systemClockUpdateThreshold) .pokeAutoTimeDetectionEnabled(false); - int phoneId = ARBITRARY_PHONE_ID; + int slotIndex = ARBITRARY_SLOT_INDEX; long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; - PhoneTimeSuggestion timeSuggestion1 = - mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + TelephonyTimeSuggestion timeSuggestion1 = + mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis); TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime(); // Simulate time passing. @@ -318,9 +318,9 @@ public class TimeDetectorStrategyImplTest { // Simulate the time signal being received. It should not be used because auto time // detection is off but it should be recorded. - mScript.simulatePhoneTimeSuggestion(timeSuggestion1) + mScript.simulateTelephonyTimeSuggestion(timeSuggestion1) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // Simulate more time passing. mScript.simulateTimePassing(clockIncrementMillis); @@ -330,17 +330,17 @@ public class TimeDetectorStrategyImplTest { // Turn on auto time detection. mScript.simulateAutoTimeDetectionToggle() .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1) - .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // Turn off auto time detection. mScript.simulateAutoTimeDetectionToggle() .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion1); // Receive another valid time signal. // It should be on the threshold and accounting for the clock increments. - PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion( - phoneId, mScript.peekSystemClockMillis() + systemClockUpdateThreshold); + TelephonyTimeSuggestion timeSuggestion2 = mScript.generateTelephonyTimeSuggestion( + slotIndex, mScript.peekSystemClockMillis() + systemClockUpdateThreshold); // Simulate more time passing. mScript.simulateTimePassing(clockIncrementMillis); @@ -350,45 +350,45 @@ public class TimeDetectorStrategyImplTest { // The new time, though valid, should not be set in the system clock because auto time is // disabled. - mScript.simulatePhoneTimeSuggestion(timeSuggestion2) + mScript.simulateTelephonyTimeSuggestion(timeSuggestion2) .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, timeSuggestion2); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion2); // Turn on auto time detection. mScript.simulateAutoTimeDetectionToggle() .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2) - .assertLatestPhoneSuggestion(phoneId, timeSuggestion2); + .assertLatestTelephonySuggestion(slotIndex, timeSuggestion2); } @Test - public void testSuggestPhoneTime_maxSuggestionAge() { + public void testSuggestTelephonyTime_maxSuggestionAge() { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(true); - int phoneId = ARBITRARY_PHONE_ID; + int slotIndex = ARBITRARY_SLOT_INDEX; long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; - PhoneTimeSuggestion phoneSuggestion = - mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + TelephonyTimeSuggestion telephonySuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis); mScript.simulateTimePassing(); long expectedSystemClockMillis = - mScript.calculateTimeInMillisForNow(phoneSuggestion.getUtcTime()); - mScript.simulatePhoneTimeSuggestion(phoneSuggestion) + mScript.calculateTimeInMillisForNow(telephonySuggestion.getUtcTime()); + mScript.simulateTelephonyTimeSuggestion(telephonySuggestion) .verifySystemClockWasSetAndResetCallTracking( expectedSystemClockMillis /* expectedNetworkBroadcast */) - .assertLatestPhoneSuggestion(phoneId, phoneSuggestion); + .assertLatestTelephonySuggestion(slotIndex, telephonySuggestion); - // Look inside and check what the strategy considers the current best phone suggestion. - assertEquals(phoneSuggestion, mScript.peekBestPhoneSuggestion()); + // Look inside and check what the strategy considers the current best telephony suggestion. + assertEquals(telephonySuggestion, mScript.peekBestTelephonySuggestion()); - // Simulate time passing, long enough that phoneSuggestion is now too old. + // Simulate time passing, long enough that telephonySuggestion is now too old. mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS); - // Look inside and check what the strategy considers the current best phone suggestion. It - // should still be the, it's just no longer used. - assertNull(mScript.peekBestPhoneSuggestion()); - mScript.assertLatestPhoneSuggestion(phoneId, phoneSuggestion); + // Look inside and check what the strategy considers the current best telephony suggestion. + // It should still be the, it's just no longer used. + assertNull(mScript.peekBestTelephonySuggestion()); + mScript.assertLatestTelephonySuggestion(slotIndex, telephonySuggestion); } @Test @@ -413,21 +413,21 @@ public class TimeDetectorStrategyImplTest { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(true); - int phoneId = ARBITRARY_PHONE_ID; + int slotIndex = ARBITRARY_SLOT_INDEX; - // Simulate a phone suggestion. + // Simulate a telephony suggestion. long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; - PhoneTimeSuggestion phoneTimeSuggestion = - mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + TelephonyTimeSuggestion telephonyTimeSuggestion = + mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis); // Simulate the passage of time. mScript.simulateTimePassing(); long expectedAutoClockMillis = - mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()); - mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion) + mScript.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUtcTime()); + mScript.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis) - .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); // Simulate the passage of time. mScript.simulateTimePassing(); @@ -435,7 +435,7 @@ public class TimeDetectorStrategyImplTest { // Switch to manual. mScript.simulateAutoTimeDetectionToggle() .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); // Simulate the passage of time. mScript.simulateTimePassing(); @@ -450,7 +450,7 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUtcTime()); mScript.simulateManualTimeSuggestion(manualTimeSuggestion) .verifySystemClockWasSetAndResetCallTracking(expectedManualClockMillis) - .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); // Simulate the passage of time. mScript.simulateTimePassing(); @@ -459,14 +459,14 @@ public class TimeDetectorStrategyImplTest { mScript.simulateAutoTimeDetectionToggle(); expectedAutoClockMillis = - mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()); + mScript.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUtcTime()); mScript.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis) - .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); // Switch back to manual - nothing should happen to the clock. mScript.simulateAutoTimeDetectionToggle() .verifySystemClockWasNotSetAndResetCallTracking() - .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion); } /** @@ -515,19 +515,19 @@ public class TimeDetectorStrategyImplTest { } @Test - public void testSuggestNetworkTime_phoneSuggestionsBeatNetworkSuggestions() { + public void testSuggestNetworkTime_telephonySuggestionsBeatNetworkSuggestions() { mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) .pokeAutoTimeDetectionEnabled(true); // Three obviously different times that could not be mistaken for each other. long networkTimeMillis1 = ARBITRARY_TEST_TIME_MILLIS; long networkTimeMillis2 = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(30).toMillis(); - long phoneTimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(60).toMillis(); + long telephonyTimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(60).toMillis(); // A small increment used to simulate the passage of time, but not enough to interfere with // macro-level time changes associated with suggestion age. final long smallTimeIncrementMillis = 101; - // A network suggestion is made. It should be used because there is no phone suggestion. + // A network suggestion is made. It should be used because there is no telephony suggestion. NetworkTimeSuggestion networkTimeSuggestion1 = mScript.generateNetworkTimeSuggestion(networkTimeMillis1); mScript.simulateTimePassing(smallTimeIncrementMillis) @@ -536,37 +536,37 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime())); // Check internal state. - mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, null) + mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, null) .assertLatestNetworkSuggestion(networkTimeSuggestion1); assertEquals(networkTimeSuggestion1, mScript.peekLatestValidNetworkSuggestion()); - assertNull(mScript.peekBestPhoneSuggestion()); + assertNull(mScript.peekBestTelephonySuggestion()); // Simulate a little time passing. mScript.simulateTimePassing(smallTimeIncrementMillis) .verifySystemClockWasNotSetAndResetCallTracking(); - // Now a phone suggestion is made. Phone suggestions are prioritized over network + // Now a telephony suggestion is made. Telephony suggestions are prioritized over network // suggestions so it should "win". - PhoneTimeSuggestion phoneTimeSuggestion = - mScript.generatePhoneTimeSuggestion(ARBITRARY_PHONE_ID, phoneTimeMillis); + TelephonyTimeSuggestion telephonyTimeSuggestion = + mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeMillis); mScript.simulateTimePassing(smallTimeIncrementMillis) - .simulatePhoneTimeSuggestion(phoneTimeSuggestion) + .simulateTelephonyTimeSuggestion(telephonyTimeSuggestion) .verifySystemClockWasSetAndResetCallTracking( - mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime())); + mScript.calculateTimeInMillisForNow(telephonyTimeSuggestion.getUtcTime())); // Check internal state. - mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) + mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) .assertLatestNetworkSuggestion(networkTimeSuggestion1); assertEquals(networkTimeSuggestion1, mScript.peekLatestValidNetworkSuggestion()); - assertEquals(phoneTimeSuggestion, mScript.peekBestPhoneSuggestion()); + assertEquals(telephonyTimeSuggestion, mScript.peekBestTelephonySuggestion()); // Simulate some significant time passing: half the time allowed before a time signal // becomes "too old to use". mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS / 2) .verifySystemClockWasNotSetAndResetCallTracking(); - // Now another network suggestion is made. Phone suggestions are prioritized over network - // suggestions so the latest phone suggestion should still "win". + // Now another network suggestion is made. Telephony suggestions are prioritized over + // network suggestions so the latest telephony suggestion should still "win". NetworkTimeSuggestion networkTimeSuggestion2 = mScript.generateNetworkTimeSuggestion(networkTimeMillis2); mScript.simulateTimePassing(smallTimeIncrementMillis) @@ -574,14 +574,14 @@ public class TimeDetectorStrategyImplTest { .verifySystemClockWasNotSetAndResetCallTracking(); // Check internal state. - mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) + mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) .assertLatestNetworkSuggestion(networkTimeSuggestion2); assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion()); - assertEquals(phoneTimeSuggestion, mScript.peekBestPhoneSuggestion()); + assertEquals(telephonyTimeSuggestion, mScript.peekBestTelephonySuggestion()); // Simulate some significant time passing: half the time allowed before a time signal - // becomes "too old to use". This should mean that phoneTimeSuggestion is now too old to be - // used but networkTimeSuggestion2 is not. + // becomes "too old to use". This should mean that telephonyTimeSuggestion is now too old to + // be used but networkTimeSuggestion2 is not. mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS / 2); // NOTE: The TimeDetectorStrategyImpl doesn't set an alarm for the point when the last @@ -591,10 +591,10 @@ public class TimeDetectorStrategyImplTest { mScript.verifySystemClockWasNotSetAndResetCallTracking(); // Check internal state. - mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) + mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) .assertLatestNetworkSuggestion(networkTimeSuggestion2); assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion()); - assertNull(mScript.peekBestPhoneSuggestion()); + assertNull(mScript.peekBestTelephonySuggestion()); // Toggle auto-time off and on to force the detection logic to run. mScript.simulateAutoTimeDetectionToggle() @@ -606,10 +606,10 @@ public class TimeDetectorStrategyImplTest { mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime())); // Check internal state. - mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion) + mScript.assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion) .assertLatestNetworkSuggestion(networkTimeSuggestion2); assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion()); - assertNull(mScript.peekBestPhoneSuggestion()); + assertNull(mScript.peekBestTelephonySuggestion()); } /** @@ -760,8 +760,8 @@ public class TimeDetectorStrategyImplTest { return mFakeCallback.peekSystemClockMillis(); } - Script simulatePhoneTimeSuggestion(PhoneTimeSuggestion timeSuggestion) { - mTimeDetectorStrategy.suggestPhoneTime(timeSuggestion); + Script simulateTelephonyTimeSuggestion(TelephonyTimeSuggestion timeSuggestion) { + mTimeDetectorStrategy.suggestTelephonyTime(timeSuggestion); return this; } @@ -806,10 +806,10 @@ public class TimeDetectorStrategyImplTest { } /** - * White box test info: Asserts the latest suggestion for the phone ID is as expected. + * White box test info: Asserts the latest suggestion for the slotIndex is as expected. */ - Script assertLatestPhoneSuggestion(int phoneId, PhoneTimeSuggestion expected) { - assertEquals(expected, mTimeDetectorStrategy.getLatestPhoneSuggestion(phoneId)); + Script assertLatestTelephonySuggestion(int slotIndex, TelephonyTimeSuggestion expected) { + assertEquals(expected, mTimeDetectorStrategy.getLatestTelephonySuggestion(slotIndex)); return this; } @@ -822,11 +822,11 @@ public class TimeDetectorStrategyImplTest { } /** - * White box test info: Returns the phone suggestion that would be used, if any, given the - * current elapsed real time clock and regardless of origin prioritization. + * White box test info: Returns the telephony suggestion that would be used, if any, given + * the current elapsed real time clock and regardless of origin prioritization. */ - PhoneTimeSuggestion peekBestPhoneSuggestion() { - return mTimeDetectorStrategy.findBestPhoneSuggestionForTests(); + TelephonyTimeSuggestion peekBestTelephonySuggestion() { + return mTimeDetectorStrategy.findBestTelephonySuggestionForTests(); } /** @@ -848,15 +848,15 @@ public class TimeDetectorStrategyImplTest { } /** - * Generates a PhoneTimeSuggestion using the current elapsed realtime clock for the - * reference time. + * Generates a {@link TelephonyTimeSuggestion} using the current elapsed realtime clock for + * the reference time. */ - PhoneTimeSuggestion generatePhoneTimeSuggestion(int phoneId, Long timeMillis) { + TelephonyTimeSuggestion generateTelephonyTimeSuggestion(int slotIndex, Long timeMillis) { TimestampedValue<Long> time = null; if (timeMillis != null) { time = new TimestampedValue<>(peekElapsedRealtimeMillis(), timeMillis); } - return createPhoneTimeSuggestion(phoneId, time); + return createTelephonyTimeSuggestion(slotIndex, time); } /** @@ -878,9 +878,9 @@ public class TimeDetectorStrategyImplTest { } } - private static PhoneTimeSuggestion createPhoneTimeSuggestion(int phoneId, + private static TelephonyTimeSuggestion createTelephonyTimeSuggestion(int slotIndex, TimestampedValue<Long> utcTime) { - return new PhoneTimeSuggestion.Builder(phoneId) + return new TelephonyTimeSuggestion.Builder(slotIndex) .setUtcTime(utcTime) .build(); } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java new file mode 100644 index 000000000000..21c9685b05d2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java @@ -0,0 +1,76 @@ +/* + * 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.timezonedetector; + +import static org.junit.Assert.assertEquals; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +/** + * A Handler that can track posts/sends and wait for them to be completed. + */ +public class TestHandler extends Handler { + + private final Object mMonitor = new Object(); + private int mMessagesProcessed = 0; + private int mMessagesSent = 0; + + public TestHandler(Looper looper) { + super(looper); + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + synchronized (mMonitor) { + mMessagesSent++; + } + + Runnable callback = msg.getCallback(); + // Have the callback increment the mMessagesProcessed when it is done. It will notify + // any threads waiting for all messages to be processed if appropriate. + Runnable newCallback = () -> { + callback.run(); + synchronized (mMonitor) { + mMessagesProcessed++; + if (mMessagesSent == mMessagesProcessed) { + mMonitor.notifyAll(); + } + } + }; + msg.setCallback(newCallback); + return super.sendMessageAtTime(msg, uptimeMillis); + } + + /** Asserts the number of messages posted or sent is as expected. */ + public void assertTotalMessagesEnqueued(int expected) { + synchronized (mMonitor) { + assertEquals(expected, mMessagesSent); + } + } + + /** + * Waits for all enqueued work to be completed before returning. + */ + public void waitForMessagesToBeProcessed() throws InterruptedException { + synchronized (mMonitor) { + if (mMessagesSent != mMessagesProcessed) { + mMonitor.wait(); + } + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java new file mode 100644 index 000000000000..039c2b4933e9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java @@ -0,0 +1,233 @@ +/* + * 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.timezonedetector; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.timezonedetector.ManualTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.HandlerThread; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.PrintWriter; + +@RunWith(AndroidJUnit4.class) +public class TimeZoneDetectorServiceTest { + + private Context mMockContext; + private StubbedTimeZoneDetectorStrategy mStubbedTimeZoneDetectorStrategy; + + private TimeZoneDetectorService mTimeZoneDetectorService; + private HandlerThread mHandlerThread; + private TestHandler mTestHandler; + + + @Before + public void setUp() { + mMockContext = mock(Context.class); + + // Create a thread + handler for processing the work that the service posts. + mHandlerThread = new HandlerThread("TimeZoneDetectorServiceTest"); + mHandlerThread.start(); + mTestHandler = new TestHandler(mHandlerThread.getLooper()); + + mStubbedTimeZoneDetectorStrategy = new StubbedTimeZoneDetectorStrategy(); + + mTimeZoneDetectorService = new TimeZoneDetectorService( + mMockContext, mTestHandler, mStubbedTimeZoneDetectorStrategy); + } + + @After + public void tearDown() throws Exception { + mHandlerThread.quit(); + mHandlerThread.join(); + } + + @Test(expected = SecurityException.class) + public void testSuggestTelephonyTime_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingPermission(anyString(), any()); + TelephonyTimeZoneSuggestion timeZoneSuggestion = createTelephonyTimeZoneSuggestion(); + + try { + mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion); + fail(); + } finally { + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), + anyString()); + } + } + + @Test + public void testSuggestTelephonyTimeZone() throws Exception { + doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); + + TelephonyTimeZoneSuggestion timeZoneSuggestion = createTelephonyTimeZoneSuggestion(); + mTimeZoneDetectorService.suggestTelephonyTimeZone(timeZoneSuggestion); + mTestHandler.assertTotalMessagesEnqueued(1); + + verify(mMockContext).enforceCallingPermission( + eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), + anyString()); + + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeZoneDetectorStrategy.verifySuggestTelephonyTimeZoneCalled(timeZoneSuggestion); + } + + @Test(expected = SecurityException.class) + public void testSuggestManualTime_withoutPermission() { + doThrow(new SecurityException("Mock")) + .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any()); + ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion(); + + try { + mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion); + fail(); + } finally { + verify(mMockContext).enforceCallingOrSelfPermission( + eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), + anyString()); + } + } + + @Test + public void testSuggestManualTimeZone() throws Exception { + doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any()); + + ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion(); + mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion); + mTestHandler.assertTotalMessagesEnqueued(1); + + verify(mMockContext).enforceCallingOrSelfPermission( + eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), + anyString()); + + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(timeZoneSuggestion); + } + + @Test + public void testDump() { + when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + + mTimeZoneDetectorService.dump(null, null, null); + + verify(mMockContext).checkCallingOrSelfPermission(eq(android.Manifest.permission.DUMP)); + mStubbedTimeZoneDetectorStrategy.verifyDumpCalled(); + } + + @Test + public void testAutoTimeZoneDetectionChanged() throws Exception { + mTimeZoneDetectorService.handleAutoTimeZoneDetectionChanged(); + mTestHandler.assertTotalMessagesEnqueued(1); + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneDetectionChangedCalled(); + + mStubbedTimeZoneDetectorStrategy.resetCallTracking(); + + mTimeZoneDetectorService.handleAutoTimeZoneDetectionChanged(); + mTestHandler.assertTotalMessagesEnqueued(2); + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneDetectionChangedCalled(); + } + + private static TelephonyTimeZoneSuggestion createTelephonyTimeZoneSuggestion() { + int slotIndex = 1234; + return new TelephonyTimeZoneSuggestion.Builder(slotIndex) + .setZoneId("TestZoneId") + .setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET) + .setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE) + .build(); + } + + private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() { + return new ManualTimeZoneSuggestion("TestZoneId"); + } + + private static class StubbedTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { + + // Call tracking. + private TelephonyTimeZoneSuggestion mLastTelephonySuggestion; + private ManualTimeZoneSuggestion mLastManualSuggestion; + private boolean mHandleAutoTimeZoneDetectionChangedCalled; + private boolean mDumpCalled; + + @Override + public void suggestTelephonyTimeZone(TelephonyTimeZoneSuggestion timeZoneSuggestion) { + mLastTelephonySuggestion = timeZoneSuggestion; + } + + @Override + public void suggestManualTimeZone(ManualTimeZoneSuggestion timeZoneSuggestion) { + mLastManualSuggestion = timeZoneSuggestion; + } + + @Override + public void handleAutoTimeZoneDetectionChanged() { + mHandleAutoTimeZoneDetectionChangedCalled = true; + } + + @Override + public void dump(PrintWriter pw, String[] args) { + mDumpCalled = true; + } + + void resetCallTracking() { + mLastTelephonySuggestion = null; + mLastManualSuggestion = null; + mHandleAutoTimeZoneDetectionChangedCalled = false; + mDumpCalled = false; + } + + void verifySuggestTelephonyTimeZoneCalled(TelephonyTimeZoneSuggestion expectedSuggestion) { + assertEquals(expectedSuggestion, mLastTelephonySuggestion); + } + + public void verifySuggestManualTimeZoneCalled(ManualTimeZoneSuggestion expectedSuggestion) { + assertEquals(expectedSuggestion, mLastManualSuggestion); + } + + void verifyHandleAutoTimeZoneDetectionChangedCalled() { + assertTrue(mHandleAutoTimeZoneDetectionChangedCalled); + } + + void verifyDumpCalled() { + assertTrue(mDumpCalled); + } + } + +} diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java index 2429cfc1bcd0..ba309679e47a 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java @@ -16,20 +16,20 @@ package com.android.server.timezonedetector; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET; -import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE; - -import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_HIGH; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_HIGHEST; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_LOW; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_MEDIUM; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_NONE; -import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_USAGE_THRESHOLD; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET; +import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE; + +import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGH; +import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGHEST; +import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_LOW; +import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_MEDIUM; +import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_NONE; +import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_USAGE_THRESHOLD; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -37,11 +37,11 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.app.timezonedetector.ManualTimeZoneSuggestion; -import android.app.timezonedetector.PhoneTimeZoneSuggestion; -import android.app.timezonedetector.PhoneTimeZoneSuggestion.MatchType; -import android.app.timezonedetector.PhoneTimeZoneSuggestion.Quality; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType; +import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality; -import com.android.server.timezonedetector.TimeZoneDetectorStrategy.QualifiedPhoneTimeZoneSuggestion; +import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion; import org.junit.Before; import org.junit.Test; @@ -52,113 +52,119 @@ import java.util.Collections; import java.util.LinkedList; /** - * White-box unit tests for {@link TimeZoneDetectorStrategy}. + * White-box unit tests for {@link TimeZoneDetectorStrategyImpl}. */ -public class TimeZoneDetectorStrategyTest { +public class TimeZoneDetectorStrategyImplTest { /** A time zone used for initialization that does not occur elsewhere in tests. */ private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC"; - private static final int PHONE1_ID = 10000; - private static final int PHONE2_ID = 20000; + private static final int SLOT_INDEX1 = 10000; + private static final int SLOT_INDEX2 = 20000; // Suggestion test cases are ordered so that each successive one is of the same or higher score // than the previous. private static final SuggestionTestCase[] TEST_CASES = new SuggestionTestCase[] { newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, - QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, PHONE_SCORE_LOW), + QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, TELEPHONY_SCORE_LOW), newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, - PHONE_SCORE_MEDIUM), + TELEPHONY_SCORE_MEDIUM), newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, - QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, PHONE_SCORE_MEDIUM), - newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_SINGLE_ZONE, PHONE_SCORE_HIGH), + QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, TELEPHONY_SCORE_MEDIUM), + newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGH), newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, - PHONE_SCORE_HIGH), + TELEPHONY_SCORE_HIGH), newTestCase(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY, - QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, PHONE_SCORE_HIGHEST), - newTestCase(MATCH_TYPE_EMULATOR_ZONE_ID, QUALITY_SINGLE_ZONE, PHONE_SCORE_HIGHEST), + QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, TELEPHONY_SCORE_HIGHEST), + newTestCase(MATCH_TYPE_EMULATOR_ZONE_ID, QUALITY_SINGLE_ZONE, TELEPHONY_SCORE_HIGHEST), }; - private TimeZoneDetectorStrategy mTimeZoneDetectorStrategy; + private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy; private FakeTimeZoneDetectorStrategyCallback mFakeTimeZoneDetectorStrategyCallback; @Before public void setUp() { mFakeTimeZoneDetectorStrategyCallback = new FakeTimeZoneDetectorStrategyCallback(); mTimeZoneDetectorStrategy = - new TimeZoneDetectorStrategy(mFakeTimeZoneDetectorStrategyCallback); + new TimeZoneDetectorStrategyImpl(mFakeTimeZoneDetectorStrategyCallback); } @Test - public void testEmptyPhoneSuggestions() { - PhoneTimeZoneSuggestion phone1TimeZoneSuggestion = createEmptyPhone1Suggestion(); - PhoneTimeZoneSuggestion phone2TimeZoneSuggestion = createEmptyPhone2Suggestion(); + public void testEmptyTelephonySuggestions() { + TelephonyTimeZoneSuggestion slotIndex1TimeZoneSuggestion = + createEmptySlotIndex1Suggestion(); + TelephonyTimeZoneSuggestion slotIndex2TimeZoneSuggestion = + createEmptySlotIndex2Suggestion(); Script script = new Script() .initializeAutoTimeZoneDetection(true) .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID); - script.suggestPhoneTimeZone(phone1TimeZoneSuggestion) + script.suggestTelephonyTimeZone(slotIndex1TimeZoneSuggestion) .verifyTimeZoneNotSet(); // Assert internal service state. - QualifiedPhoneTimeZoneSuggestion expectedPhone1ScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(phone1TimeZoneSuggestion, PHONE_SCORE_NONE); - assertEquals(expectedPhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); - assertNull(mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID)); - assertEquals(expectedPhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); - - script.suggestPhoneTimeZone(phone2TimeZoneSuggestion) + QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex1ScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion, + TELEPHONY_SCORE_NONE); + assertEquals(expectedSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + assertNull(mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + assertEquals(expectedSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); + + script.suggestTelephonyTimeZone(slotIndex2TimeZoneSuggestion) .verifyTimeZoneNotSet(); // Assert internal service state. - QualifiedPhoneTimeZoneSuggestion expectedPhone2ScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(phone2TimeZoneSuggestion, PHONE_SCORE_NONE); - assertEquals(expectedPhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); - assertEquals(expectedPhone2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID)); - // Phone 1 should always beat phone 2, all other things being equal. - assertEquals(expectedPhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex2ScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(slotIndex2TimeZoneSuggestion, + TELEPHONY_SCORE_NONE); + assertEquals(expectedSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + assertEquals(expectedSlotIndex2ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + // SlotIndex1 should always beat slotIndex2, all other things being equal. + assertEquals(expectedSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); } @Test - public void testFirstPlausiblePhoneSuggestionAcceptedWhenTimeZoneUninitialized() { + public void testFirstPlausibleTelephonySuggestionAcceptedWhenTimeZoneUninitialized() { SuggestionTestCase testCase = newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, - QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, PHONE_SCORE_LOW); - PhoneTimeZoneSuggestion lowQualitySuggestion = - testCase.createSuggestion(PHONE1_ID, "America/New_York"); + QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, TELEPHONY_SCORE_LOW); + TelephonyTimeZoneSuggestion lowQualitySuggestion = + testCase.createSuggestion(SLOT_INDEX1, "America/New_York"); // The device time zone setting is left uninitialized. Script script = new Script() .initializeAutoTimeZoneDetection(true); // The very first suggestion will be taken. - script.suggestPhoneTimeZone(lowQualitySuggestion) + script.suggestTelephonyTimeZone(lowQualitySuggestion) .verifyTimeZoneSetAndReset(lowQualitySuggestion); // Assert internal service state. - QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(lowQualitySuggestion, testCase.expectedScore); + QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion( + lowQualitySuggestion, testCase.expectedScore); assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); // Another low quality suggestion will be ignored now that the setting is initialized. - PhoneTimeZoneSuggestion lowQualitySuggestion2 = - testCase.createSuggestion(PHONE1_ID, "America/Los_Angeles"); - script.suggestPhoneTimeZone(lowQualitySuggestion2) + TelephonyTimeZoneSuggestion lowQualitySuggestion2 = + testCase.createSuggestion(SLOT_INDEX1, "America/Los_Angeles"); + script.suggestTelephonyTimeZone(lowQualitySuggestion2) .verifyTimeZoneNotSet(); // Assert internal service state. - QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion2 = - new QualifiedPhoneTimeZoneSuggestion(lowQualitySuggestion2, testCase.expectedScore); + QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion2 = + new QualifiedTelephonyTimeZoneSuggestion( + lowQualitySuggestion2, testCase.expectedScore); assertEquals(expectedScoredSuggestion2, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); assertEquals(expectedScoredSuggestion2, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); } /** @@ -174,28 +180,28 @@ public class TimeZoneDetectorStrategyTest { script.initializeAutoTimeZoneDetection(false) .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID); - PhoneTimeZoneSuggestion suggestion = - testCase.createSuggestion(PHONE1_ID, "Europe/London"); - script.suggestPhoneTimeZone(suggestion); + TelephonyTimeZoneSuggestion suggestion = + testCase.createSuggestion(SLOT_INDEX1, "Europe/London"); + script.suggestTelephonyTimeZone(suggestion); // When time zone detection is not enabled, the time zone suggestion will not be set // regardless of the score. script.verifyTimeZoneNotSet(); // Assert internal service state. - QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(suggestion, testCase.expectedScore); + QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(suggestion, testCase.expectedScore); assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); // Toggling the time zone setting on should cause the device setting to be set. script.autoTimeZoneDetectionEnabled(true); // When time zone detection is already enabled the suggestion (if it scores highly // enough) should be set immediately. - if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) { + if (testCase.expectedScore >= TELEPHONY_SCORE_USAGE_THRESHOLD) { script.verifyTimeZoneSetAndReset(suggestion); } else { script.verifyTimeZoneNotSet(); @@ -203,9 +209,9 @@ public class TimeZoneDetectorStrategyTest { // Assert internal service state. assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); // Toggling the time zone setting should off should do nothing. script.autoTimeZoneDetectionEnabled(false) @@ -213,20 +219,20 @@ public class TimeZoneDetectorStrategyTest { // Assert internal service state. assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); assertEquals(expectedScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); } } @Test - public void testPhoneSuggestionsSinglePhone() { + public void testTelephonySuggestionsSingleSlotId() { Script script = new Script() .initializeAutoTimeZoneDetection(true) .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID); for (SuggestionTestCase testCase : TEST_CASES) { - makePhone1SuggestionAndCheckState(script, testCase); + makeSlotIndex1SuggestionAndCheckState(script, testCase); } /* @@ -241,130 +247,133 @@ public class TimeZoneDetectorStrategyTest { Collections.reverse(descendingCasesByScore); for (SuggestionTestCase testCase : descendingCasesByScore) { - makePhone1SuggestionAndCheckState(script, testCase); + makeSlotIndex1SuggestionAndCheckState(script, testCase); } } - private void makePhone1SuggestionAndCheckState(Script script, SuggestionTestCase testCase) { + private void makeSlotIndex1SuggestionAndCheckState(Script script, SuggestionTestCase testCase) { // Give the next suggestion a different zone from the currently set device time zone; String currentZoneId = mFakeTimeZoneDetectorStrategyCallback.getDeviceTimeZone(); String suggestionZoneId = "Europe/London".equals(currentZoneId) ? "Europe/Paris" : "Europe/London"; - PhoneTimeZoneSuggestion zonePhone1Suggestion = - testCase.createSuggestion(PHONE1_ID, suggestionZoneId); - QualifiedPhoneTimeZoneSuggestion expectedZonePhone1ScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(zonePhone1Suggestion, testCase.expectedScore); - - script.suggestPhoneTimeZone(zonePhone1Suggestion); - if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) { - script.verifyTimeZoneSetAndReset(zonePhone1Suggestion); + TelephonyTimeZoneSuggestion zoneSlotIndex1Suggestion = + testCase.createSuggestion(SLOT_INDEX1, suggestionZoneId); + QualifiedTelephonyTimeZoneSuggestion expectedZoneSlotIndex1ScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion( + zoneSlotIndex1Suggestion, testCase.expectedScore); + + script.suggestTelephonyTimeZone(zoneSlotIndex1Suggestion); + if (testCase.expectedScore >= TELEPHONY_SCORE_USAGE_THRESHOLD) { + script.verifyTimeZoneSetAndReset(zoneSlotIndex1Suggestion); } else { script.verifyTimeZoneNotSet(); } // Assert internal service state. - assertEquals(expectedZonePhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); - assertEquals(expectedZonePhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + assertEquals(expectedZoneSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + assertEquals(expectedZoneSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); } /** - * Tries a set of test cases to see if the phone with the lowest ID is given preference. This - * test also confirms that the time zone setting would only be set if a suggestion is of - * sufficient quality. + * Tries a set of test cases to see if the slotIndex with the lowest numeric value is given + * preference. This test also confirms that the time zone setting would only be set if a + * suggestion is of sufficient quality. */ @Test - public void testMultiplePhoneSuggestionScoringAndPhoneIdBias() { + public void testMultipleSlotIndexSuggestionScoringAndSlotIndexBias() { String[] zoneIds = { "Europe/London", "Europe/Paris" }; - PhoneTimeZoneSuggestion emptyPhone1Suggestion = createEmptyPhone1Suggestion(); - PhoneTimeZoneSuggestion emptyPhone2Suggestion = createEmptyPhone2Suggestion(); - QualifiedPhoneTimeZoneSuggestion expectedEmptyPhone1ScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(emptyPhone1Suggestion, PHONE_SCORE_NONE); - QualifiedPhoneTimeZoneSuggestion expectedEmptyPhone2ScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(emptyPhone2Suggestion, PHONE_SCORE_NONE); + TelephonyTimeZoneSuggestion emptySlotIndex1Suggestion = createEmptySlotIndex1Suggestion(); + TelephonyTimeZoneSuggestion emptySlotIndex2Suggestion = createEmptySlotIndex2Suggestion(); + QualifiedTelephonyTimeZoneSuggestion expectedEmptySlotIndex1ScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(emptySlotIndex1Suggestion, + TELEPHONY_SCORE_NONE); + QualifiedTelephonyTimeZoneSuggestion expectedEmptySlotIndex2ScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(emptySlotIndex2Suggestion, + TELEPHONY_SCORE_NONE); Script script = new Script() .initializeAutoTimeZoneDetection(true) .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) // Initialize the latest suggestions as empty so we don't need to worry about nulls // below for the first loop. - .suggestPhoneTimeZone(emptyPhone1Suggestion) - .suggestPhoneTimeZone(emptyPhone2Suggestion) + .suggestTelephonyTimeZone(emptySlotIndex1Suggestion) + .suggestTelephonyTimeZone(emptySlotIndex2Suggestion) .resetState(); for (SuggestionTestCase testCase : TEST_CASES) { - PhoneTimeZoneSuggestion zonePhone1Suggestion = - testCase.createSuggestion(PHONE1_ID, zoneIds[0]); - PhoneTimeZoneSuggestion zonePhone2Suggestion = - testCase.createSuggestion(PHONE2_ID, zoneIds[1]); - QualifiedPhoneTimeZoneSuggestion expectedZonePhone1ScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(zonePhone1Suggestion, + TelephonyTimeZoneSuggestion zoneSlotIndex1Suggestion = + testCase.createSuggestion(SLOT_INDEX1, zoneIds[0]); + TelephonyTimeZoneSuggestion zoneSlotIndex2Suggestion = + testCase.createSuggestion(SLOT_INDEX2, zoneIds[1]); + QualifiedTelephonyTimeZoneSuggestion expectedZoneSlotIndex1ScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(zoneSlotIndex1Suggestion, testCase.expectedScore); - QualifiedPhoneTimeZoneSuggestion expectedZonePhone2ScoredSuggestion = - new QualifiedPhoneTimeZoneSuggestion(zonePhone2Suggestion, + QualifiedTelephonyTimeZoneSuggestion expectedZoneSlotIndex2ScoredSuggestion = + new QualifiedTelephonyTimeZoneSuggestion(zoneSlotIndex2Suggestion, testCase.expectedScore); - // Start the test by making a suggestion for phone 1. - script.suggestPhoneTimeZone(zonePhone1Suggestion); - if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) { - script.verifyTimeZoneSetAndReset(zonePhone1Suggestion); + // Start the test by making a suggestion for slotIndex1. + script.suggestTelephonyTimeZone(zoneSlotIndex1Suggestion); + if (testCase.expectedScore >= TELEPHONY_SCORE_USAGE_THRESHOLD) { + script.verifyTimeZoneSetAndReset(zoneSlotIndex1Suggestion); } else { script.verifyTimeZoneNotSet(); } // Assert internal service state. - assertEquals(expectedZonePhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); - assertEquals(expectedEmptyPhone2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID)); - assertEquals(expectedZonePhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); - - // Phone 2 then makes an alternative suggestion with an identical score. Phone 1's + assertEquals(expectedZoneSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + assertEquals(expectedEmptySlotIndex2ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + assertEquals(expectedZoneSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); + + // SlotIndex2 then makes an alternative suggestion with an identical score. SlotIndex1's // suggestion should still "win" if it is above the required threshold. - script.suggestPhoneTimeZone(zonePhone2Suggestion); + script.suggestTelephonyTimeZone(zoneSlotIndex2Suggestion); script.verifyTimeZoneNotSet(); // Assert internal service state. - assertEquals(expectedZonePhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); - assertEquals(expectedZonePhone2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID)); - // Phone 1 should always beat phone 2, all other things being equal. - assertEquals(expectedZonePhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); - - // Withdrawing phone 1's suggestion should leave phone 2 as the new winner. Since the - // zoneId is different, the time zone setting should be updated if the score is high + assertEquals(expectedZoneSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + assertEquals(expectedZoneSlotIndex2ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + // SlotIndex1 should always beat slotIndex2, all other things being equal. + assertEquals(expectedZoneSlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); + + // Withdrawing slotIndex1's suggestion should leave slotIndex2 as the new winner. Since + // the zoneId is different, the time zone setting should be updated if the score is high // enough. - script.suggestPhoneTimeZone(emptyPhone1Suggestion); - if (testCase.expectedScore >= PHONE_SCORE_USAGE_THRESHOLD) { - script.verifyTimeZoneSetAndReset(zonePhone2Suggestion); + script.suggestTelephonyTimeZone(emptySlotIndex1Suggestion); + if (testCase.expectedScore >= TELEPHONY_SCORE_USAGE_THRESHOLD) { + script.verifyTimeZoneSetAndReset(zoneSlotIndex2Suggestion); } else { script.verifyTimeZoneNotSet(); } // Assert internal service state. - assertEquals(expectedEmptyPhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); - assertEquals(expectedZonePhone2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID)); - assertEquals(expectedZonePhone2ScoredSuggestion, - mTimeZoneDetectorStrategy.findBestPhoneSuggestionForTests()); + assertEquals(expectedEmptySlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + assertEquals(expectedZoneSlotIndex2ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); + assertEquals(expectedZoneSlotIndex2ScoredSuggestion, + mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests()); // Reset the state for the next loop. - script.suggestPhoneTimeZone(emptyPhone2Suggestion) + script.suggestTelephonyTimeZone(emptySlotIndex2Suggestion) .verifyTimeZoneNotSet(); - assertEquals(expectedEmptyPhone1ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID)); - assertEquals(expectedEmptyPhone2ScoredSuggestion, - mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID)); + assertEquals(expectedEmptySlotIndex1ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1)); + assertEquals(expectedEmptySlotIndex2ScoredSuggestion, + mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2)); } } /** - * The {@link TimeZoneDetectorStrategy.Callback} is left to detect whether changing the time + * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing the time * zone is actually necessary. This test proves that the service doesn't assume it knows the * current setting. */ @@ -375,21 +384,21 @@ public class TimeZoneDetectorStrategyTest { SuggestionTestCase testCase = newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, - PHONE_SCORE_HIGH); - PhoneTimeZoneSuggestion losAngelesSuggestion = - testCase.createSuggestion(PHONE1_ID, "America/Los_Angeles"); - PhoneTimeZoneSuggestion newYorkSuggestion = - testCase.createSuggestion(PHONE1_ID, "America/New_York"); + TELEPHONY_SCORE_HIGH); + TelephonyTimeZoneSuggestion losAngelesSuggestion = + testCase.createSuggestion(SLOT_INDEX1, "America/Los_Angeles"); + TelephonyTimeZoneSuggestion newYorkSuggestion = + testCase.createSuggestion(SLOT_INDEX1, "America/New_York"); // Initialization. - script.suggestPhoneTimeZone(losAngelesSuggestion) + script.suggestTelephonyTimeZone(losAngelesSuggestion) .verifyTimeZoneSetAndReset(losAngelesSuggestion); // Suggest it again - it should not be set because it is already set. - script.suggestPhoneTimeZone(losAngelesSuggestion) + script.suggestTelephonyTimeZone(losAngelesSuggestion) .verifyTimeZoneNotSet(); // Toggling time zone detection should set the device time zone only if the current setting - // value is different from the most recent phone suggestion. + // value is different from the most recent telephony suggestion. script.autoTimeZoneDetectionEnabled(false) .verifyTimeZoneNotSet() .autoTimeZoneDetectionEnabled(true) @@ -398,7 +407,7 @@ public class TimeZoneDetectorStrategyTest { // Simulate a user turning auto detection off, a new suggestion being made while auto // detection is off, and the user turning it on again. script.autoTimeZoneDetectionEnabled(false) - .suggestPhoneTimeZone(newYorkSuggestion) + .suggestTelephonyTimeZone(newYorkSuggestion) .verifyTimeZoneNotSet(); // Latest suggestion should be used. script.autoTimeZoneDetectionEnabled(true) @@ -433,15 +442,16 @@ public class TimeZoneDetectorStrategyTest { return new ManualTimeZoneSuggestion(zoneId); } - private static PhoneTimeZoneSuggestion createEmptyPhone1Suggestion() { - return new PhoneTimeZoneSuggestion.Builder(PHONE1_ID).build(); + private static TelephonyTimeZoneSuggestion createEmptySlotIndex1Suggestion() { + return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX1).build(); } - private static PhoneTimeZoneSuggestion createEmptyPhone2Suggestion() { - return new PhoneTimeZoneSuggestion.Builder(PHONE2_ID).build(); + private static TelephonyTimeZoneSuggestion createEmptySlotIndex2Suggestion() { + return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX2).build(); } - static class FakeTimeZoneDetectorStrategyCallback implements TimeZoneDetectorStrategy.Callback { + static class FakeTimeZoneDetectorStrategyCallback + implements TimeZoneDetectorStrategyImpl.Callback { private boolean mAutoTimeZoneDetectionEnabled; private TestState<String> mTimeZoneId = new TestState<>(); @@ -560,13 +570,15 @@ public class TimeZoneDetectorStrategyTest { Script autoTimeZoneDetectionEnabled(boolean enabled) { mFakeTimeZoneDetectorStrategyCallback.setAutoTimeZoneDetectionEnabled(enabled); - mTimeZoneDetectorStrategy.handleAutoTimeZoneDetectionChange(); + mTimeZoneDetectorStrategy.handleAutoTimeZoneDetectionChanged(); return this; } - /** Simulates the time zone detection strategy receiving a phone-originated suggestion. */ - Script suggestPhoneTimeZone(PhoneTimeZoneSuggestion phoneTimeZoneSuggestion) { - mTimeZoneDetectorStrategy.suggestPhoneTimeZone(phoneTimeZoneSuggestion); + /** + * Simulates the time zone detection strategy receiving a telephony-originated suggestion. + */ + Script suggestTelephonyTimeZone(TelephonyTimeZoneSuggestion timeZoneSuggestion) { + mTimeZoneDetectorStrategy.suggestTelephonyTimeZone(timeZoneSuggestion); return this; } @@ -581,7 +593,7 @@ public class TimeZoneDetectorStrategyTest { return this; } - Script verifyTimeZoneSetAndReset(PhoneTimeZoneSuggestion suggestion) { + Script verifyTimeZoneSetAndReset(TelephonyTimeZoneSuggestion suggestion) { mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(suggestion.getZoneId()); mFakeTimeZoneDetectorStrategyCallback.commitAllChanges(); return this; @@ -610,8 +622,8 @@ public class TimeZoneDetectorStrategyTest { this.expectedScore = expectedScore; } - private PhoneTimeZoneSuggestion createSuggestion(int phoneId, String zoneId) { - return new PhoneTimeZoneSuggestion.Builder(phoneId) + private TelephonyTimeZoneSuggestion createSuggestion(int slotIndex, String zoneId) { + return new TelephonyTimeZoneSuggestion.Builder(slotIndex) .setZoneId(zoneId) .setMatchType(matchType) .setQuality(quality) diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp index f608babd062c..3f0cda3b8e5a 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -47,7 +47,6 @@ android_test { "libbacktrace", "libbase", "libbinder", - "libbinderthreadstate", "libc++", "libcutils", "liblog", diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml index 180deb5c4dcc..dab0a5f0e279 100644 --- a/services/tests/uiservicestests/AndroidManifest.xml +++ b/services/tests/uiservicestests/AndroidManifest.xml @@ -28,6 +28,8 @@ <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" /> <uses-permission android:name="android.permission.DEVICE_POWER" /> <uses-permission android:name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY" /> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" /> <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/> diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java index 5b5ad877081c..cbb760a7a871 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java @@ -44,6 +44,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.internal.matchers.Not; import java.io.File; import java.util.ArrayList; @@ -239,6 +240,52 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { verify(af2, never()).openRead(); } + @Test + public void testRemoveNotificationRunnable() throws Exception { + NotificationHistory nh = mock(NotificationHistory.class); + NotificationHistoryDatabase.RemoveNotificationRunnable rnr = + mDataBase.new RemoveNotificationRunnable("pkg", 123); + rnr.setNotificationHistory(nh); + + AtomicFile af = mock(AtomicFile.class); + when(af.getBaseFile()).thenReturn(new File(mRootDir, "af")); + mDataBase.mHistoryFiles.addLast(af); + + when(nh.removeNotificationFromWrite("pkg", 123)).thenReturn(true); + + mDataBase.mBuffer = mock(NotificationHistory.class); + + rnr.run(); + + verify(mDataBase.mBuffer).removeNotificationFromWrite("pkg", 123); + verify(af).openRead(); + verify(nh).removeNotificationFromWrite("pkg", 123); + verify(af).startWrite(); + } + + @Test + public void testRemoveNotificationRunnable_noChanges() throws Exception { + NotificationHistory nh = mock(NotificationHistory.class); + NotificationHistoryDatabase.RemoveNotificationRunnable rnr = + mDataBase.new RemoveNotificationRunnable("pkg", 123); + rnr.setNotificationHistory(nh); + + AtomicFile af = mock(AtomicFile.class); + when(af.getBaseFile()).thenReturn(new File(mRootDir, "af")); + mDataBase.mHistoryFiles.addLast(af); + + when(nh.removeNotificationFromWrite("pkg", 123)).thenReturn(false); + + mDataBase.mBuffer = mock(NotificationHistory.class); + + rnr.run(); + + verify(mDataBase.mBuffer).removeNotificationFromWrite("pkg", 123); + verify(af).openRead(); + verify(nh).removeNotificationFromWrite("pkg", 123); + verify(af, never()).startWrite(); + } + private class TestFileAttrProvider implements NotificationHistoryDatabase.FileAttrProvider { public Map<File, Long> creationDates = new HashMap<>(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java index b5eb1bf31848..2c548be185c9 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java @@ -289,6 +289,20 @@ public class NotificationHistoryManagerTest extends UiServiceTestCase { } @Test + public void testDeleteNotificationHistoryItem_userUnlocked() { + String pkg = "pkg"; + long time = 235; + NotificationHistoryDatabase userHistory = mock(NotificationHistoryDatabase.class); + + mHistoryManager.onUserUnlocked(USER_SYSTEM); + mHistoryManager.replaceNotificationHistoryDatabase(USER_SYSTEM, userHistory); + + mHistoryManager.deleteNotificationHistoryItem(pkg, 1, time); + + verify(userHistory, times(1)).deleteNotificationHistoryItem(pkg, time); + } + + @Test public void testTriggerWriteToDisk() { NotificationHistoryDatabase userHistorySystem = mock(NotificationHistoryDatabase.class); NotificationHistoryDatabase userHistoryAll = mock(NotificationHistoryDatabase.class); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index ad5be43e11e6..57428dc75813 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -32,7 +32,6 @@ import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS; -import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; @@ -42,6 +41,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; +import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; +import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -50,6 +51,8 @@ import static android.os.Build.VERSION_CODES.P; import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; +import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; @@ -70,6 +73,7 @@ import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -127,6 +131,7 @@ import android.provider.DeviceConfig; import android.provider.MediaStore; import android.provider.Settings; import android.service.notification.Adjustment; +import android.service.notification.ConversationChannelWrapper; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; @@ -1143,7 +1148,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); assertEquals(1, mNotificationRecordLogger.getCalls().size()); NotificationRecordLoggerFake.CallRecord call = mNotificationRecordLogger.get(0); - assertTrue(call.shouldLog()); + assertTrue(call.shouldLog); + assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, + call.event); assertNotNull(call.r); assertNull(call.old); assertEquals(0, call.position); @@ -1152,7 +1159,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(0, call.r.getSbn().getId()); assertEquals(tag, call.r.getSbn().getTag()); assertNotNull(call.r.getSbn().getInstanceId()); - assertEquals(0, call.r.getSbn().getInstanceId().getId()); + assertEquals(0, call.getInstanceId()); // Fake instance IDs are assigned in order } @Test @@ -1170,18 +1177,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); assertEquals(2, mNotificationRecordLogger.getCalls().size()); - assertTrue(mNotificationRecordLogger.get(0).shouldLog()); + assertTrue(mNotificationRecordLogger.get(0).shouldLog); assertEquals( - NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED, - mNotificationRecordLogger.get(0).getUiEvent()); - assertEquals(0, mNotificationRecordLogger.get(0).r.getSbn().getInstanceId().getId()); + NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, + mNotificationRecordLogger.get(0).event); + assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId()); - assertTrue(mNotificationRecordLogger.get(1).shouldLog()); + assertTrue(mNotificationRecordLogger.get(1).shouldLog); assertEquals( - NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_UPDATED, - mNotificationRecordLogger.get(1).getUiEvent()); + NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED, + mNotificationRecordLogger.get(1).event); // Instance ID doesn't change on update of an active notification - assertEquals(0, mNotificationRecordLogger.get(1).r.getSbn().getInstanceId().getId()); + assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId()); } @Test @@ -1193,8 +1200,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { generateNotificationRecord(null).getNotification(), 0); waitForIdle(); assertEquals(2, mNotificationRecordLogger.getCalls().size()); - assertTrue(mNotificationRecordLogger.get(0).shouldLog()); - assertFalse(mNotificationRecordLogger.get(1).shouldLog()); + assertTrue(mNotificationRecordLogger.get(0).shouldLog); + assertFalse(mNotificationRecordLogger.get(1).shouldLog); } @Test @@ -1209,20 +1216,37 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, 0); waitForIdle(); - assertEquals(2, mNotificationRecordLogger.getCalls().size()); + assertEquals(3, mNotificationRecordLogger.getCalls().size()); + + assertEquals( + NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, + mNotificationRecordLogger.get(0).event); + assertTrue(mNotificationRecordLogger.get(0).shouldLog); + assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId()); - assertTrue(mNotificationRecordLogger.get(0).shouldLog()); + assertEquals(REASON_APP_CANCEL, mNotificationRecordLogger.get(1).reason); assertEquals( - NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED, - mNotificationRecordLogger.get(0).getUiEvent()); - assertEquals(0, mNotificationRecordLogger.get(0).r.getSbn().getInstanceId().getId()); + NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_APP_CANCEL, + mNotificationRecordLogger.get(1).event); + assertTrue(mNotificationRecordLogger.get(1).shouldLog); + assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId()); - assertTrue(mNotificationRecordLogger.get(1).shouldLog()); assertEquals( - NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED, - mNotificationRecordLogger.get(1).getUiEvent()); + NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, + mNotificationRecordLogger.get(2).event); + assertTrue(mNotificationRecordLogger.get(2).shouldLog); // New instance ID because notification was canceled before re-post - assertEquals(1, mNotificationRecordLogger.get(1).r.getSbn().getInstanceId().getId()); + assertEquals(1, mNotificationRecordLogger.get(2).getInstanceId()); + } + + @Test + public void testCancelNonexistentNotification() throws Exception { + mBinderService.cancelNotificationWithTag(PKG, PKG, + "testCancelNonexistentNotification", 0, 0); + waitForIdle(); + // The notification record logger doesn't even get called when a nonexistent notification + // is cancelled, because that happens very frequently and is not interesting. + assertEquals(0, mNotificationRecordLogger.getCalls().size()); } @Test @@ -3364,6 +3388,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); assertEquals(NotificationStats.DISMISSAL_AOD, r.getStats().getDismissalSurface()); + + // Using mService.addNotification() does not generate a NotificationRecordLogger log, + // so we only get the cancel notification. + assertEquals(1, mNotificationRecordLogger.getCalls().size()); + + assertEquals(REASON_CANCEL, mNotificationRecordLogger.get(0).reason); + assertEquals( + NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_AOD, + mNotificationRecordLogger.get(0).event); + assertTrue(mNotificationRecordLogger.get(0).shouldLog); + assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId()); } @Test @@ -3644,6 +3679,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void updateUriPermissions_posterDoesNotOwnUri() throws Exception { + NotificationChannel c = new NotificationChannel( + TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); + c.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); + Message message1 = new Message("", 0, ""); + message1.setData("", + ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1)); + + Notification.Builder nbA = new Notification.Builder(mContext, c.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.MessagingStyle("") + .addMessage(message1)); + NotificationRecord recordA = new NotificationRecord(mContext, new StatusBarNotification( + PKG, PKG, 0, "tag", mUid, 0, nbA.build(), new UserHandle(mUid), null, 0), c); + + doThrow(new SecurityException("no access")).when(mUgm) + .grantUriPermissionFromOwner( + any(), anyInt(), any(), any(), anyInt(), anyInt(), anyInt()); + + when(mUgmInternal.newUriPermissionOwner(any())).thenReturn(new Binder()); + mService.updateUriPermissions(recordA, null, mContext.getPackageName(), USER_SYSTEM); + + // yay, no crash + } + + @Test public void testVisitUris() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); final Uri backgroundImage = Uri.parse("content://com.example/background"); @@ -6245,4 +6307,60 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(PRIORITY_CATEGORY_CALLS, actual); } + + @Test + public void testGetConversationsForPackage_hasShortcut() throws Exception { + mService.setPreferencesHelper(mPreferencesHelper); + ArrayList<ConversationChannelWrapper> convos = new ArrayList<>(); + ConversationChannelWrapper convo1 = new ConversationChannelWrapper(); + NotificationChannel channel1 = new NotificationChannel("a", "a", 1); + channel1.setConversationId("parent1", "convo 1"); + convo1.setNotificationChannel(channel1); + convos.add(convo1); + + ConversationChannelWrapper convo2 = new ConversationChannelWrapper(); + NotificationChannel channel2 = new NotificationChannel("b", "b", 1); + channel2.setConversationId("parent1", "convo 2"); + convo2.setNotificationChannel(channel2); + convos.add(convo2); + when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos); + + // only one valid shortcut + LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery() + .setPackage(PKG_P) + .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED) + .setShortcutIds(Arrays.asList(channel1.getConversationId())); + ShortcutInfo si = mock(ShortcutInfo.class); + when(si.getShortLabel()).thenReturn("Hello"); + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(Arrays.asList(si)); + + List<ConversationChannelWrapper> conversations = + mBinderService.getConversationsForPackage(PKG_P, mUid).getList(); + assertEquals(si, conversations.get(0).getShortcutInfo()); + assertEquals(si, conversations.get(1).getShortcutInfo()); + + } + + @Test + public void testGetConversationsForPackage_doesNotHaveShortcut() throws Exception { + mService.setPreferencesHelper(mPreferencesHelper); + ArrayList<ConversationChannelWrapper> convos = new ArrayList<>(); + ConversationChannelWrapper convo1 = new ConversationChannelWrapper(); + NotificationChannel channel1 = new NotificationChannel("a", "a", 1); + channel1.setConversationId("parent1", "convo 1"); + convo1.setNotificationChannel(channel1); + convos.add(convo1); + + ConversationChannelWrapper convo2 = new ConversationChannelWrapper(); + NotificationChannel channel2 = new NotificationChannel("b", "b", 1); + channel2.setConversationId("parent1", "convo 2"); + convo2.setNotificationChannel(channel2); + convos.add(convo2); + when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos); + + List<ConversationChannelWrapper> conversations = + mBinderService.getConversationsForPackage(PKG_P, mUid).getList(); + assertNull(conversations.get(0).getShortcutInfo()); + assertNull(conversations.get(1).getShortcutInfo()); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java index 5c1487f0fdf6..b120dbee03c5 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java @@ -16,6 +16,8 @@ package com.android.server.notification; +import com.android.internal.logging.UiEventLogger; + import java.util.ArrayList; import java.util.List; @@ -24,18 +26,27 @@ import java.util.List; */ class NotificationRecordLoggerFake implements NotificationRecordLogger { static class CallRecord extends NotificationRecordPair { - public int position, buzzBeepBlink; + static final int INVALID = -1; + public int position = INVALID, buzzBeepBlink = INVALID, reason = INVALID; + public boolean shouldLog; + public UiEventLogger.UiEventEnum event; CallRecord(NotificationRecord r, NotificationRecord old, int position, int buzzBeepBlink) { super(r, old); + this.position = position; this.buzzBeepBlink = buzzBeepBlink; + shouldLog = shouldLog(buzzBeepBlink); + event = NotificationReportedEvent.fromRecordPair(this); } - boolean shouldLog() { - return shouldLog(buzzBeepBlink); + CallRecord(NotificationRecord r, int reason, int dismissalSurface) { + super(r, null); + this.reason = reason; + shouldLog = true; + event = NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface); } } - private List<CallRecord> mCalls = new ArrayList<CallRecord>(); + private List<CallRecord> mCalls = new ArrayList<>(); List<CallRecord> getCalls() { return mCalls; @@ -50,4 +61,10 @@ class NotificationRecordLoggerFake implements NotificationRecordLogger { int position, int buzzBeepBlink) { mCalls.add(new CallRecord(r, old, position, buzzBeepBlink)); } + + @Override + public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) { + mCalls.add(new CallRecord(r, reason, dismissalSurface)); + } + } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index bb84b04361ea..3f690b105d8b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -26,6 +26,8 @@ import static android.util.FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertNull; import static junit.framework.Assert.fail; @@ -64,6 +66,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.Secure; +import android.service.notification.ConversationChannelWrapper; import android.test.suitebuilder.annotation.SmallTest; import android.testing.TestableContentResolver; import android.util.ArrayMap; @@ -91,6 +94,7 @@ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -2939,4 +2943,97 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertNotNull(mHelper.getNotificationChannel(PKG_O, UID_O, "id", true)); } + + @Test + public void testGetConversations_invalidPkg() { + assertThat(mHelper.getConversations("bad", 1)).isEmpty(); + } + + @Test + public void testGetConversations_noConversations() { + NotificationChannel channel = + new NotificationChannel("not_convo", "not_convo", IMPORTANCE_DEFAULT); + mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false); + + assertThat(mHelper.getConversations(PKG_O, UID_O)).isEmpty(); + } + + @Test + public void testGetConversations_noDisabledGroups() { + NotificationChannelGroup group = new NotificationChannelGroup("a", "a"); + group.setBlocked(true); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + NotificationChannel parent = new NotificationChannel("parent", "p", 1); + mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false); + + NotificationChannel channel = + new NotificationChannel("convo", "convo", IMPORTANCE_DEFAULT); + channel.setConversationId("parent", "convo"); + channel.setGroup(group.getId()); + mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false); + + assertThat(mHelper.getConversations(PKG_O, UID_O)).isEmpty(); + } + + @Test + public void testGetConversations_noDeleted() { + NotificationChannel parent = new NotificationChannel("parent", "p", 1); + mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false); + NotificationChannel channel = + new NotificationChannel("convo", "convo", IMPORTANCE_DEFAULT); + channel.setConversationId("parent", "convo"); + mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false); + mHelper.deleteNotificationChannel(PKG_O, UID_O, channel.getId()); + + assertThat(mHelper.getConversations(PKG_O, UID_O)).isEmpty(); + } + + @Test + public void testGetConversations() { + NotificationChannelGroup group = new NotificationChannelGroup("acct", "account_name"); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + + NotificationChannel messages = + new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT); + messages.setGroup(group.getId()); + mHelper.createNotificationChannel(PKG_O, UID_O, messages, true, false); + NotificationChannel calls = + new NotificationChannel("calls", "Calls", IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_O, UID_O, calls, true, false); + + NotificationChannel channel = + new NotificationChannel("A person", "A lovely person", IMPORTANCE_DEFAULT); + channel.setGroup(group.getId()); + channel.setConversationId(messages.getId(), channel.getName().toString()); + mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false); + + NotificationChannel channel2 = + new NotificationChannel("B person", "B fabulous person", IMPORTANCE_DEFAULT); + channel2.setConversationId(calls.getId(), channel.getName().toString()); + mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false); + + Map<String, NotificationChannel> expected = new HashMap<>(); + expected.put(channel.getId(), channel); + expected.put(channel2.getId(), channel2); + + Map<String, CharSequence> expectedGroup = new HashMap<>(); + expectedGroup.put(channel.getId(), group.getName()); + expectedGroup.put(channel2.getId(), null); + + Map<String, CharSequence> expectedParentLabel= new HashMap<>(); + expectedParentLabel.put(channel.getId(), messages.getName()); + expectedParentLabel.put(channel2.getId(), calls.getName()); + + ArrayList<ConversationChannelWrapper> convos = mHelper.getConversations(PKG_O, UID_O); + assertThat(convos).hasSize(2); + + for (ConversationChannelWrapper convo : convos) { + assertThat(convo.getNotificationChannel()) + .isEqualTo(expected.get(convo.getNotificationChannel().getId())); + assertThat(convo.getParentChannelLabel()) + .isEqualTo(expectedParentLabel.get(convo.getNotificationChannel().getId())); + assertThat(convo.getGroupLabel()) + .isEqualTo(expectedGroup.get(convo.getNotificationChannel().getId())); + } + } } 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/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index ebe4ab99663a..a0ea7290ec01 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -29,6 +29,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_TASK_CLOSE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -140,6 +141,19 @@ public class ActivityRecordTests extends ActivityTestsBase { } @Test + public void testRemoveChildWithOverlayActivity() { + final ActivityRecord overlayActivity = + new ActivityBuilder(mService).setTask(mTask).build(); + overlayActivity.setTaskOverlay(true); + final ActivityRecord overlayActivity2 = + new ActivityBuilder(mService).setTask(mTask).build(); + overlayActivity2.setTaskOverlay(true); + + mTask.removeChild(overlayActivity2, "test"); + verify(mSupervisor, never()).removeTask(any(), anyBoolean(), anyBoolean(), any()); + } + + @Test public void testNoCleanupMovingActivityInSameStack() { final Task newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); mActivity.reparent(newTask, 0, null /*reason*/); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 9dbaa4c9ca82..e6291a4c34ac 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -95,8 +95,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { @Before public void setUp() throws Exception { - updateDisplayFrames(); - mWindow = spy(createWindow(null, TYPE_APPLICATION, "window")); // We only test window frames set by DisplayPolicy, so here prevents computeFrameLw from // changing those frames. @@ -106,6 +104,8 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { attrs.width = MATCH_PARENT; attrs.height = MATCH_PARENT; attrs.format = PixelFormat.TRANSLUCENT; + + updateDisplayFrames(); } @After @@ -126,6 +126,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { private void updateDisplayFrames() { mFrames = createDisplayFrames(); + mDisplayContent.mDisplayFrames = mFrames; } private DisplayFrames createDisplayFrames() { @@ -180,63 +181,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - public void layoutWindowLw_appDrawsBars() { - assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); - - mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - mWindow.mAttrs.setFitInsetsTypes(Type.systemBars()); - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0); - assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0); - assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - } - - @Test - public void layoutWindowLw_forceAppDrawBars() { - assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); - - mWindow.mAttrs.privateFlags = PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; - mWindow.mAttrs.setFitInsetsTypes(Type.systemBars()); - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0); - assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0); - assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - } - - @Test - public void layoutWindowLw_onlyDrawBottomBar() { - assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); - - mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - mWindow.mAttrs.setFitInsetsTypes(Type.systemBars()); - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0); - assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0); - assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - } - - @Test public void layoutWindowLw_fitAllSides() { assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); @@ -273,7 +217,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - public void layoutWindowLw_fitMax() { + public void layoutWindowLw_fitInsetsIgnoringVisibility() { assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); final InsetsState state = @@ -295,7 +239,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - public void layoutWindowLw_fitNonMax() { + public void layoutWindowLw_fitInsetsNotIgnoringVisibility() { assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); final InsetsState state = @@ -398,6 +342,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); addWindow(mWindow); mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); @@ -416,6 +361,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; addWindow(mWindow); @@ -435,6 +381,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; mWindow.mAttrs.setFitInsetsTypes( mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars()); @@ -456,6 +403,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow) .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false); @@ -477,6 +425,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow) .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false); @@ -501,6 +450,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); addWindow(mWindow); mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); @@ -521,6 +471,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); addWindow(mWindow); mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); @@ -541,6 +492,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; mWindow.mAttrs.setFitInsetsTypes( mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars()); @@ -581,6 +533,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; mWindow.mAttrs.setFitInsetsTypes( mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars()); @@ -603,6 +556,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE; addWindow(mWindow); @@ -625,6 +579,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { public void layoutWindowLw_withForwardInset_SoftInputAdjustNothing() { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING; addWindow(mWindow); @@ -849,7 +804,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { final String prefix = ""; final InsetsState simulatedInsetsState = new InsetsState(); final DisplayFrames simulatedDisplayFrames = createDisplayFrames(); - mDisplayContent.mDisplayFrames = mFrames; mDisplayPolicy.beginLayoutLw(mFrames, uiMode); mDisplayContent.getInsetsStateController().onPostLayout(); mDisplayPolicy.simulateLayoutDisplay(simulatedDisplayFrames, simulatedInsetsState, uiMode); @@ -867,6 +821,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { // Exclude comparing IME insets because currently the simulated layout only focuses on the // insets from status bar and navigation bar. realInsetsState.removeSource(InsetsState.ITYPE_IME); + realInsetsState.removeSource(InsetsState.ITYPE_CAPTION_BAR); realInsetsState.dump(prefix, new PrintWriter(realInsetsDump)); final StringWriter simulatedInsetsDump = new StringWriter(); simulatedInsetsState.dump(prefix, new PrintWriter(simulatedInsetsDump)); diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index 5acc0f273554..956c200022e9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -126,7 +126,8 @@ public class DragDropControllerTests extends WindowTestsBase { mTarget = new TestDragDropController(mWm, mWm.mH.getLooper()); mWindow = createDropTargetWindow("Drag test window", 0); doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0); - when(mWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true); + when(mWm.mInputManager.transferTouchFocus(any(InputChannel.class), + any(InputChannel.class))).thenReturn(true); mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); } @@ -176,7 +177,8 @@ public class DragDropControllerTests extends WindowTestsBase { .setFormat(PixelFormat.TRANSLUCENT) .build(); - assertTrue(mWm.mInputManager.transferTouchFocus(null, null)); + assertTrue(mWm.mInputManager.transferTouchFocus(new InputChannel(), + new InputChannel())); mToken = mTarget.performDrag( new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0, data); 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 078347e96a07..f7aa3cc9e52c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -26,8 +26,10 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; 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; @@ -36,14 +38,17 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import android.app.ActivityManager.RunningTaskInfo; 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; @@ -139,6 +144,52 @@ public class TaskOrganizerTests extends WindowTestsBase { } @Test + public void testUnregisterOrganizer() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ITaskOrganizer organizer = registerMockOrganizer(); + + stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + verify(organizer).taskAppeared(any()); + assertTrue(stack.isControlledByTaskOrganizer()); + + mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer); + verify(organizer).taskVanished(any()); + assertFalse(stack.isControlledByTaskOrganizer()); + } + + @Test + public void testUnregisterOrganizerReturnsRegistrationToPrevious() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent); + final Task task2 = createTaskInStack(stack2, 0 /* userId */); + final ActivityStack stack3 = createTaskStackOnDisplay(mDisplayContent); + final Task task3 = createTaskInStack(stack3, 0 /* userId */); + final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW); + + // First organizer is registered, verify a task appears when changing windowing mode + stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + verify(organizer, times(1)).taskAppeared(any()); + assertTrue(stack.isControlledByTaskOrganizer()); + + // Now we replace the registration and1 verify the new organizer receives tasks + // newly entering the windowing mode. + final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW); + stack2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + verify(organizer2).taskAppeared(any()); + assertTrue(stack2.isControlledByTaskOrganizer()); + + // Now we unregister the second one, the first one should automatically be reregistered + // so we verify that it's now seeing changes. + mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer2); + + stack3.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + verify(organizer, times(2)).taskAppeared(any()); + assertTrue(stack3.isControlledByTaskOrganizer()); + } + + @Test public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException { final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_PINNED); @@ -161,7 +212,7 @@ public class TaskOrganizerTests extends WindowTestsBase { WindowContainerTransaction t = new WindowContainerTransaction(); Rect newBounds = new Rect(10, 10, 100, 100); t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100)); - mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null); assertEquals(newBounds, task.getBounds()); } @@ -176,7 +227,7 @@ public class TaskOrganizerTests extends WindowTestsBase { assertEquals(stack.mRemoteToken, info.stackToken); Rect newBounds = new Rect(10, 10, 100, 100); t.setBounds(info.stackToken, new Rect(10, 10, 100, 100)); - mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null); assertEquals(newBounds, stack.getBounds()); } @@ -189,7 +240,7 @@ public class TaskOrganizerTests extends WindowTestsBase { WindowContainerTransaction t = new WindowContainerTransaction(); assertTrue(task.isFocusable()); t.setFocusable(stack.mRemoteToken, false); - mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null); assertFalse(task.isFocusable()); } @@ -308,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) { @@ -318,4 +441,46 @@ public class TaskOrganizerTests extends WindowTestsBase { } return out; } + + @Test + public void testTrivialBLASTCallback() throws RemoteException { + final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stackController1, 0 /* userId */); + final ITaskOrganizer organizer = registerMockOrganizer(); + + BLASTSyncEngine bse = new BLASTSyncEngine(); + + BLASTSyncEngine.TransactionReadyListener transactionListener = + mock(BLASTSyncEngine.TransactionReadyListener.class); + + int id = bse.startSyncSet(transactionListener); + bse.addToSyncSet(id, task); + bse.setReady(id); + // Since this task has no windows the sync is trivial and completes immediately. + verify(transactionListener) + .transactionReady(anyInt(), any()); + } + + @Test + public void testBLASTCallbackWithWindow() { + final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stackController1, 0 /* userId */); + final ITaskOrganizer organizer = registerMockOrganizer(); + final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window"); + + BLASTSyncEngine bse = new BLASTSyncEngine(); + + BLASTSyncEngine.TransactionReadyListener transactionListener = + mock(BLASTSyncEngine.TransactionReadyListener.class); + + int id = bse.startSyncSet(transactionListener); + bse.addToSyncSet(id, task); + bse.setReady(id); + // Since we have a window we have to wait for it to draw to finish sync. + verify(transactionListener, never()) + .transactionReady(anyInt(), any()); + w.finishDrawing(null); + verify(transactionListener) + .transactionReady(anyInt(), any()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java index 7aaf3fbccb5e..52b465ff4634 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java @@ -20,7 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.server.wm.TaskPositioner.MIN_ASPECT; +import static com.android.internal.policy.TaskResizingAlgorithm.MIN_ASPECT; import static com.android.server.wm.WindowManagerService.dipToPixel; import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP; 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 672352211089..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. @@ -11290,14 +11289,6 @@ public class TelephonyManager { */ public static final int INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF = 2; - /** @hide */ - @IntDef(prefix = { "INDICATION_UPDATE_MODE_" }, value = { - INDICATION_UPDATE_MODE_NORMAL, - INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF - }) - @Retention(RetentionPolicy.SOURCE) - public @interface IndicationUpdateMode{} - /** * The indication for signal strength update. * @hide @@ -11328,51 +11319,6 @@ public class TelephonyManager { */ public static final int INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG = 0x10; - /** @hide */ - @IntDef(flag = true, prefix = { "INDICATION_FILTER_" }, value = { - INDICATION_FILTER_SIGNAL_STRENGTH, - INDICATION_FILTER_FULL_NETWORK_STATE, - INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED, - INDICATION_FILTER_LINK_CAPACITY_ESTIMATE, - INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG - }) - @Retention(RetentionPolicy.SOURCE) - public @interface IndicationFilters{} - - /** - * Sets radio indication update mode. This can be used to control the behavior of indication - * update from modem to Android frameworks. For example, by default several indication updates - * are turned off when screen is off, but in some special cases (e.g. carkit is connected but - * screen is off) we want to turn on those indications even when the screen is off. - * - * <p>Requires Permission: - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} - * - * @param filters Indication filters. Should be a bitmask of INDICATION_FILTER_XXX. - * @see #INDICATION_FILTER_SIGNAL_STRENGTH - * @see #INDICATION_FILTER_FULL_NETWORK_STATE - * @see #INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED - * @param updateMode The voice activation state - * @see #INDICATION_UPDATE_MODE_NORMAL - * @see #INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF - * @hide - */ - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public void setRadioIndicationUpdateMode(@IndicationFilters int filters, - @IndicationUpdateMode int updateMode) { - try { - ITelephony telephony = getITelephony(); - if (telephony != null) { - telephony.setRadioIndicationUpdateMode(getSubId(), filters, updateMode); - } - } catch (RemoteException ex) { - // This could happen if binder process crashes. - if (!isSystemProcess()) { - ex.rethrowAsRuntimeException(); - } - } - } - /** * A test API to override carrier information including mccmnc, imsi, iccid, gid1, gid2, * plmn and spn. This would be handy for, eg, forcing a particular carrier id, carrier's config diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java index e16fffa69f8a..e1aec0a593b4 100644 --- a/telephony/java/android/telephony/euicc/EuiccCardManager.java +++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; +import android.os.Binder; import android.os.RemoteException; import android.service.euicc.EuiccProfileInfo; import android.telephony.TelephonyFrameworkInitializer; @@ -168,7 +169,12 @@ public class EuiccCardManager { new IGetAllProfilesCallback.Stub() { @Override public void onComplete(int resultCode, EuiccProfileInfo[] profiles) { - executor.execute(() -> callback.onComplete(resultCode, profiles)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, profiles)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -192,7 +198,12 @@ public class EuiccCardManager { new IGetProfileCallback.Stub() { @Override public void onComplete(int resultCode, EuiccProfileInfo profile) { - executor.execute(() -> callback.onComplete(resultCode, profile)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, profile)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -217,7 +228,12 @@ public class EuiccCardManager { refresh, new IDisableProfileCallback.Stub() { @Override public void onComplete(int resultCode) { - executor.execute(() -> callback.onComplete(resultCode, null)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, null)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -243,7 +259,12 @@ public class EuiccCardManager { refresh, new ISwitchToProfileCallback.Stub() { @Override public void onComplete(int resultCode, EuiccProfileInfo profile) { - executor.execute(() -> callback.onComplete(resultCode, profile)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, profile)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -268,7 +289,12 @@ public class EuiccCardManager { nickname, new ISetNicknameCallback.Stub() { @Override public void onComplete(int resultCode) { - executor.execute(() -> callback.onComplete(resultCode, null)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, null)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -292,7 +318,12 @@ public class EuiccCardManager { new IDeleteProfileCallback.Stub() { @Override public void onComplete(int resultCode) { - executor.execute(() -> callback.onComplete(resultCode, null)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, null)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -317,7 +348,12 @@ public class EuiccCardManager { new IResetMemoryCallback.Stub() { @Override public void onComplete(int resultCode) { - executor.execute(() -> callback.onComplete(resultCode, null)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, null)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -340,7 +376,12 @@ public class EuiccCardManager { new IGetDefaultSmdpAddressCallback.Stub() { @Override public void onComplete(int resultCode, String address) { - executor.execute(() -> callback.onComplete(resultCode, address)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, address)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -363,7 +404,12 @@ public class EuiccCardManager { new IGetSmdsAddressCallback.Stub() { @Override public void onComplete(int resultCode, String address) { - executor.execute(() -> callback.onComplete(resultCode, address)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, address)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -388,7 +434,12 @@ public class EuiccCardManager { new ISetDefaultSmdpAddressCallback.Stub() { @Override public void onComplete(int resultCode) { - executor.execute(() -> callback.onComplete(resultCode, null)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, null)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -411,7 +462,12 @@ public class EuiccCardManager { new IGetRulesAuthTableCallback.Stub() { @Override public void onComplete(int resultCode, EuiccRulesAuthTable rat) { - executor.execute(() -> callback.onComplete(resultCode, rat)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, rat)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -434,7 +490,12 @@ public class EuiccCardManager { new IGetEuiccChallengeCallback.Stub() { @Override public void onComplete(int resultCode, byte[] challenge) { - executor.execute(() -> callback.onComplete(resultCode, challenge)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, challenge)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -457,7 +518,12 @@ public class EuiccCardManager { new IGetEuiccInfo1Callback.Stub() { @Override public void onComplete(int resultCode, byte[] info) { - executor.execute(() -> callback.onComplete(resultCode, info)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, info)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -480,7 +546,12 @@ public class EuiccCardManager { new IGetEuiccInfo2Callback.Stub() { @Override public void onComplete(int resultCode, byte[] info) { - executor.execute(() -> callback.onComplete(resultCode, info)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, info)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -522,7 +593,12 @@ public class EuiccCardManager { new IAuthenticateServerCallback.Stub() { @Override public void onComplete(int resultCode, byte[] response) { - executor.execute(() -> callback.onComplete(resultCode, response)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, response)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -561,7 +637,12 @@ public class EuiccCardManager { new IPrepareDownloadCallback.Stub() { @Override public void onComplete(int resultCode, byte[] response) { - executor.execute(() -> callback.onComplete(resultCode, response)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, response)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -589,7 +670,12 @@ public class EuiccCardManager { new ILoadBoundProfilePackageCallback.Stub() { @Override public void onComplete(int resultCode, byte[] response) { - executor.execute(() -> callback.onComplete(resultCode, response)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, response)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -619,7 +705,12 @@ public class EuiccCardManager { new ICancelSessionCallback.Stub() { @Override public void onComplete(int resultCode, byte[] response) { - executor.execute(() -> callback.onComplete(resultCode, response)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, response)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -643,7 +734,13 @@ public class EuiccCardManager { new IListNotificationsCallback.Stub() { @Override public void onComplete(int resultCode, EuiccNotification[] notifications) { - executor.execute(() -> callback.onComplete(resultCode, notifications)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete( + resultCode, notifications)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -667,7 +764,13 @@ public class EuiccCardManager { events, new IRetrieveNotificationListCallback.Stub() { @Override public void onComplete(int resultCode, EuiccNotification[] notifications) { - executor.execute(() -> callback.onComplete(resultCode, notifications)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete( + resultCode, notifications)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -691,7 +794,13 @@ public class EuiccCardManager { seqNumber, new IRetrieveNotificationCallback.Stub() { @Override public void onComplete(int resultCode, EuiccNotification notification) { - executor.execute(() -> callback.onComplete(resultCode, notification)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete( + resultCode, notification)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -718,7 +827,12 @@ public class EuiccCardManager { new IRemoveNotificationFromListCallback.Stub() { @Override public void onComplete(int resultCode) { - executor.execute(() -> callback.onComplete(resultCode, null)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, null)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index beb3c8cac41e..168c8b64c194 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1827,14 +1827,6 @@ interface ITelephony { boolean switchSlots(in int[] physicalSlots); /** - * Sets radio indication update mode. This can be used to control the behavior of indication - * update from modem to Android frameworks. For example, by default several indication updates - * are turned off when screen is off, but in some special cases (e.g. carkit is connected but - * screen is off) we want to turn on those indications even when the screen is off. - */ - void setRadioIndicationUpdateMode(int subId, int filters, int mode); - - /** * Returns whether mobile data roaming is enabled on the subscription with id {@code subId}. * * @param subId the subscription id 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/tests/BlobStoreTestUtils/Android.bp b/tests/BlobStoreTestUtils/Android.bp new file mode 100644 index 000000000000..edd2b435f1da --- /dev/null +++ b/tests/BlobStoreTestUtils/Android.bp @@ -0,0 +1,20 @@ +// 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. + +java_library { + name: "BlobStoreTestUtils", + srcs: ["src/**/*.java"], + static_libs: ["truth-prebuilt"], + platform_apis: true +}
\ No newline at end of file diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java new file mode 100644 index 000000000000..f96766a1d3ad --- /dev/null +++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java @@ -0,0 +1,227 @@ +/* + * 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. + */ +package com.android.utils.blob; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.blob.BlobHandle; +import android.app.blob.BlobStoreManager; +import android.content.Context; +import android.os.FileUtils; +import android.os.ParcelFileDescriptor; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.security.MessageDigest; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +public class DummyBlobData { + private static final long DEFAULT_SIZE_BYTES = 10 * 1024L * 1024L; + private static final int BUFFER_SIZE_BYTES = 16 * 1024; + + private final Context mContext; + private final Random mRandom; + private final File mFile; + private final long mFileSize; + private final String mLabel; + + byte[] mFileDigest; + long mExpiryTimeMs; + + public DummyBlobData(Context context) { + this(context, new Random(0), "blob_" + System.nanoTime()); + } + + public DummyBlobData(Context context, long fileSize) { + this(context, fileSize, new Random(0), "blob_" + System.nanoTime(), "Test label"); + } + + public DummyBlobData(Context context, Random random, String fileName) { + this(context, DEFAULT_SIZE_BYTES, random, fileName, "Test label"); + } + + public DummyBlobData(Context context, Random random, String fileName, String label) { + this(context, DEFAULT_SIZE_BYTES, random, fileName, label); + } + + public DummyBlobData(Context context, long fileSize, Random random, String fileName, + String label) { + mContext = context; + mRandom = random; + mFile = new File(mContext.getFilesDir(), fileName); + mFileSize = fileSize; + mLabel = label; + } + + public void prepare() throws Exception { + try (RandomAccessFile file = new RandomAccessFile(mFile, "rw")) { + writeRandomData(file, mFileSize); + } + mFileDigest = FileUtils.digest(mFile, "SHA-256"); + mExpiryTimeMs = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); + } + + public BlobHandle getBlobHandle() throws Exception { + return BlobHandle.createWithSha256(createSha256Digest(mFile), mLabel, + mExpiryTimeMs, "test_tag"); + } + + public long getFileSize() throws Exception { + return mFileSize; + } + + public long getExpiryTimeMillis() { + return mExpiryTimeMs; + } + + public void delete() { + mFile.delete(); + } + + public void writeToSession(BlobStoreManager.Session session) throws Exception { + writeToSession(session, 0, mFileSize); + } + + public void writeToSession(BlobStoreManager.Session session, + long offsetBytes, long lengthBytes) throws Exception { + try (FileInputStream in = new FileInputStream(mFile)) { + in.getChannel().position(offsetBytes); + try (FileOutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream( + session.openWrite(offsetBytes, lengthBytes))) { + copy(in, out, lengthBytes); + } + } + } + + public void writeToFd(FileDescriptor fd, long offsetBytes, long lengthBytes) throws Exception { + try (FileInputStream in = new FileInputStream(mFile)) { + in.getChannel().position(offsetBytes); + try (FileOutputStream out = new FileOutputStream(fd)) { + copy(in, out, lengthBytes); + } + } + } + + private void copy(InputStream in, OutputStream out, long lengthBytes) throws Exception { + final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; + long bytesWrittern = 0; + while (bytesWrittern < lengthBytes) { + final int toWrite = (bytesWrittern + buffer.length <= lengthBytes) + ? buffer.length : (int) (lengthBytes - bytesWrittern); + in.read(buffer, 0, toWrite); + out.write(buffer, 0, toWrite); + bytesWrittern += toWrite; + } + } + + public void readFromSessionAndVerifyBytes(BlobStoreManager.Session session, + long offsetBytes, int lengthBytes) throws Exception { + final byte[] expectedBytes = new byte[lengthBytes]; + try (FileInputStream in = new FileInputStream(mFile)) { + read(in, expectedBytes, offsetBytes, lengthBytes); + } + + final byte[] actualBytes = new byte[lengthBytes]; + try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream( + session.openWrite(0L, 0L))) { + read(in, actualBytes, offsetBytes, lengthBytes); + } + + assertThat(actualBytes).isEqualTo(expectedBytes); + + } + + private void read(FileInputStream in, byte[] buffer, + long offsetBytes, int lengthBytes) throws Exception { + in.getChannel().position(offsetBytes); + in.read(buffer, 0, lengthBytes); + } + + public void readFromSessionAndVerifyDigest(BlobStoreManager.Session session) + throws Exception { + readFromSessionAndVerifyDigest(session, 0, mFile.length()); + } + + public void readFromSessionAndVerifyDigest(BlobStoreManager.Session session, + long offsetBytes, long lengthBytes) throws Exception { + final byte[] actualDigest; + try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream( + session.openWrite(0L, 0L))) { + actualDigest = createSha256Digest(in, offsetBytes, lengthBytes); + } + + assertThat(actualDigest).isEqualTo(mFileDigest); + } + + public void verifyBlob(ParcelFileDescriptor pfd) throws Exception { + final byte[] actualDigest; + try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { + actualDigest = FileUtils.digest(in, "SHA-256"); + } + assertThat(actualDigest).isEqualTo(mFileDigest); + } + + private byte[] createSha256Digest(FileInputStream in, long offsetBytes, long lengthBytes) + throws Exception { + final MessageDigest digest = MessageDigest.getInstance("SHA-256"); + in.getChannel().position(offsetBytes); + final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; + long bytesRead = 0; + while (bytesRead < lengthBytes) { + int toRead = (bytesRead + buffer.length <= lengthBytes) + ? buffer.length : (int) (lengthBytes - bytesRead); + toRead = in.read(buffer, 0, toRead); + digest.update(buffer, 0, toRead); + bytesRead += toRead; + } + return digest.digest(); + } + + private byte[] createSha256Digest(File file) throws Exception { + final MessageDigest digest = MessageDigest.getInstance("SHA-256"); + try (BufferedInputStream in = new BufferedInputStream( + Files.newInputStream(file.toPath()))) { + final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; + int bytesRead; + while ((bytesRead = in.read(buffer)) > 0) { + digest.update(buffer, 0, bytesRead); + } + } + return digest.digest(); + } + + private void writeRandomData(RandomAccessFile file, long fileSize) + throws Exception { + long bytesWritten = 0; + final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; + while (bytesWritten < fileSize) { + mRandom.nextBytes(buffer); + final int toWrite = (bytesWritten + buffer.length <= fileSize) + ? buffer.length : (int) (fileSize - bytesWritten); + file.seek(bytesWritten); + file.write(buffer, 0, toWrite); + bytesWritten += toWrite; + } + } +} diff --git a/tests/PlatformCompatGating/Android.bp b/tests/PlatformCompatGating/Android.bp index 5e9ef8efc402..74dfde848191 100644 --- a/tests/PlatformCompatGating/Android.bp +++ b/tests/PlatformCompatGating/Android.bp @@ -18,14 +18,11 @@ android_test { name: "PlatformCompatGating", // Only compile source java files in this apk. srcs: ["src/**/*.java"], - certificate: "platform", - libs: [ - "android.test.runner", - "android.test.base", - ], static_libs: [ "junit", - "android-support-test", + "androidx.test.runner", + "androidx.test.core", + "androidx.test.ext.junit", "mockito-target-minus-junit4", "truth-prebuilt", "platform-compat-test-rules" diff --git a/tests/PlatformCompatGating/AndroidManifest.xml b/tests/PlatformCompatGating/AndroidManifest.xml index 7f14b83fbc75..c24dc31b7bf3 100644 --- a/tests/PlatformCompatGating/AndroidManifest.xml +++ b/tests/PlatformCompatGating/AndroidManifest.xml @@ -6,6 +6,6 @@ <uses-library android:name="android.test.runner" /> </application> - <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.tests.gating"/> </manifest> diff --git a/tests/PlatformCompatGating/AndroidTest.xml b/tests/PlatformCompatGating/AndroidTest.xml index c62684837332..0c7485b27fb8 100644 --- a/tests/PlatformCompatGating/AndroidTest.xml +++ b/tests/PlatformCompatGating/AndroidTest.xml @@ -24,7 +24,6 @@ <test class="com.android.tradefed.testtype.AndroidJUnitTest"> <option name="package" value="com.android.tests.gating"/> - <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/> <option name="hidden-api-checks" value="false"/> </test> </configuration> diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java index dc317f1941c7..c1ce0e99640c 100644 --- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java +++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatGatingTest.java @@ -18,8 +18,9 @@ package com.android.tests.gating; import static com.google.common.truth.Truth.assertThat; import android.compat.testing.PlatformCompatChangeRule; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.compat.testing.DummyApi; @@ -81,14 +82,14 @@ public class PlatformCompatGatingTest { @Test @EnableCompatChanges({DummyApi.CHANGE_SYSTEM_SERVER}) public void testDummyGatingPositiveSystemServer() { - assertThat( - DummyApi.dummySystemServer(InstrumentationRegistry.getTargetContext())).isTrue(); + assertThat(DummyApi.dummySystemServer( + InstrumentationRegistry.getInstrumentation().getTargetContext())).isTrue(); } @Test @DisableCompatChanges({DummyApi.CHANGE_SYSTEM_SERVER}) public void testDummyGatingNegativeSystemServer() { - assertThat( - DummyApi.dummySystemServer(InstrumentationRegistry.getTargetContext())).isFalse(); + assertThat(DummyApi.dummySystemServer( + InstrumentationRegistry.getInstrumentation().getTargetContext())).isFalse(); } } diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java new file mode 100644 index 000000000000..9b9e5815a588 --- /dev/null +++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatPermissionsTest.java @@ -0,0 +1,319 @@ +/* + * 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.tests.gating; + +import static android.Manifest.permission.LOG_COMPAT_CHANGE; +import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG; +import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG; + +import android.app.Instrumentation; +import android.app.UiAutomation; +import android.compat.Compatibility.ChangeConfig; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Process; +import android.os.ServiceManager; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.compat.CompatibilityChangeConfig; +import com.android.internal.compat.IPlatformCompat; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.HashSet; +import java.util.Set; + +@RunWith(JUnit4.class) +public final class PlatformCompatPermissionsTest { + + // private Context mContext; + private IPlatformCompat mPlatformCompat; + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + private Context mContext; + private UiAutomation mUiAutomation; + private PackageManager mPackageManager; + + @Before + public void setUp() { + // mContext; + mPlatformCompat = IPlatformCompat.Stub + .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + mUiAutomation = instrumentation.getUiAutomation(); + mContext = instrumentation.getTargetContext(); + + mPackageManager = mContext.getPackageManager(); + } + + @After + public void tearDown() { + + mUiAutomation.dropShellPermissionIdentity(); + } + + @Test + public void reportChange_noLogCompatChangePermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.reportChange(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void reportChange_logCompatChangePermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.reportChange(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void reportChangeByPackageName_noLogCompatChangePermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.reportChangeByPackageName(1, packageName, 0); + } + + @Test + public void reportChangeByPackageName_logCompatChangePermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.reportChangeByPackageName(1, packageName, 0); + } + + @Test + public void reportChangeByUid_noLogCompatChangePermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.reportChangeByUid(1, Process.myUid()); + } + + @Test + public void reportChangeByUid_logCompatChangePermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(LOG_COMPAT_CHANGE); + + mPlatformCompat.reportChangeByUid(1, Process.myUid()); + } + + @Test + public void isChangeEnabled_noReadCompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void isChangeEnabled_noLogCompatChangeConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void isChangeEnabled_readAndLogCompatChangeConfigPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabled(1, mPackageManager.getApplicationInfo(packageName, 0)); + } + + @Test + public void isChangeEnabledByPackageName_noReadCompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0); + } + + @Test + public void isChangeEnabledByPackageName_noLogompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0); + } + + @Test + public void isChangeEnabledByPackageName_readAndLogCompatChangeConfigPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE); + final String packageName = mContext.getPackageName(); + + mPlatformCompat.isChangeEnabledByPackageName(1, packageName, 0); + } + + @Test + public void isChangeEnabledByUid_noReadCompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.isChangeEnabledByUid(1, Process.myUid()); + } + + @Test + public void isChangeEnabledByUid_noLogCompatChangePermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.isChangeEnabledByUid(1, Process.myUid()); + } + + @Test + public void isChangeEnabledByUid_readAndLogCompatChangeConfigPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE); + + mPlatformCompat.isChangeEnabledByUid(1, Process.myUid()); + } + + @Test + public void setOverrides_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + Set<Long> enabled = new HashSet<>(); + Set<Long> disabled = new HashSet<>(); + ChangeConfig changeConfig = new ChangeConfig(enabled, disabled); + CompatibilityChangeConfig compatibilityChangeConfig = + new CompatibilityChangeConfig(changeConfig); + + mPlatformCompat.setOverrides(compatibilityChangeConfig, "foo.bar"); + } + @Test + public void setOverrides_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + Set<Long> enabled = new HashSet<>(); + Set<Long> disabled = new HashSet<>(); + ChangeConfig changeConfig = new ChangeConfig(enabled, disabled); + CompatibilityChangeConfig compatibilityChangeConfig = + new CompatibilityChangeConfig(changeConfig); + + mPlatformCompat.setOverrides(compatibilityChangeConfig, "foo.bar"); + } + + @Test + public void setOverridesForTest_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + Set<Long> enabled = new HashSet<>(); + Set<Long> disabled = new HashSet<>(); + ChangeConfig changeConfig = new ChangeConfig(enabled, disabled); + CompatibilityChangeConfig compatibilityChangeConfig = + new CompatibilityChangeConfig(changeConfig); + + mPlatformCompat.setOverridesForTest(compatibilityChangeConfig, "foo.bar"); + } + @Test + public void setOverridesForTest_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + Set<Long> enabled = new HashSet<>(); + Set<Long> disabled = new HashSet<>(); + ChangeConfig changeConfig = new ChangeConfig(enabled, disabled); + CompatibilityChangeConfig compatibilityChangeConfig = + new CompatibilityChangeConfig(changeConfig); + + mPlatformCompat.setOverridesForTest(compatibilityChangeConfig, "foo.bar"); + } + + @Test + public void clearOverrides_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.clearOverrides("foo.bar"); + } + @Test + public void clearOverrides_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.clearOverrides("foo.bar"); + } + + @Test + public void clearOverridesForTest_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.clearOverridesForTest("foo.bar"); + } + @Test + public void clearOverridesForTest_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.clearOverridesForTest("foo.bar"); + } + + @Test + public void clearOverride_noOverridesPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.clearOverride(1, "foo.bar"); + } + @Test + public void clearOverride_overridesPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(OVERRIDE_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.clearOverride(1, "foo.bar"); + } + + @Test + public void listAllChanges_noReadCompatConfigPermission_throwsSecurityException() + throws Throwable { + thrown.expect(SecurityException.class); + + mPlatformCompat.listAllChanges(); + } + @Test + public void listAllChanges_readCompatConfigPermission_noThrow() + throws Throwable { + mUiAutomation.adoptShellPermissionIdentity(READ_COMPAT_CHANGE_CONFIG); + + mPlatformCompat.listAllChanges(); + } +} diff --git a/tests/PlatformCompatGating/test-rules/Android.bp b/tests/PlatformCompatGating/test-rules/Android.bp index 8211ef523ee7..10fa2dc0d7c6 100644 --- a/tests/PlatformCompatGating/test-rules/Android.bp +++ b/tests/PlatformCompatGating/test-rules/Android.bp @@ -19,7 +19,7 @@ java_library { srcs: ["src/**/*.java"], static_libs: [ "junit", - "android-support-test", + "androidx.test.core", "truth-prebuilt", "core-compat-test-rules" ], diff --git a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java index 932ec643d478..d6846faa5c00 100644 --- a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java +++ b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java @@ -16,13 +16,17 @@ package android.compat.testing; +import android.Manifest; import android.app.Instrumentation; +import android.app.UiAutomation; import android.compat.Compatibility; import android.compat.Compatibility.ChangeConfig; import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; -import android.support.test.InstrumentationRegistry; + +import androidx.test.platform.app.InstrumentationRegistry; + import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.IPlatformCompat; @@ -83,12 +87,17 @@ public class PlatformCompatChangeRule extends CoreCompatChangeRule { @Override public void evaluate() throws Throwable { Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + UiAutomation uiAutomation = instrumentation.getUiAutomation(); String packageName = instrumentation.getTargetContext().getPackageName(); IPlatformCompat platformCompat = IPlatformCompat.Stub .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); if (platformCompat == null) { throw new IllegalStateException("Could not get IPlatformCompat service!"); } + uiAutomation.adoptShellPermissionIdentity( + Manifest.permission.LOG_COMPAT_CHANGE, + Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG, + Manifest.permission.READ_COMPAT_CHANGE_CONFIG); Compatibility.setOverrides(mConfig); try { platformCompat.setOverridesForTest(new CompatibilityChangeConfig(mConfig), @@ -101,6 +110,7 @@ public class PlatformCompatChangeRule extends CoreCompatChangeRule { } catch (RemoteException e) { throw new RuntimeException("Could not call IPlatformCompat binder method!", e); } finally { + uiAutomation.dropShellPermissionIdentity(); Compatibility.clearOverrides(); } } diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp index 98e7b4e1b430..89005da27709 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -36,6 +36,15 @@ java_test_host { } java_test_host { + name: "NetworkStagedRollbackTest", + srcs: ["NetworkStagedRollbackTest/src/**/*.java"], + libs: ["tradefed"], + static_libs: ["testng"], + test_suites: ["general-tests"], + test_config: "NetworkStagedRollbackTest.xml", +} + +java_test_host { name: "MultiUserRollbackTest", srcs: ["MultiUserRollbackTest/src/**/*.java"], libs: ["tradefed"], diff --git a/tests/RollbackTest/NetworkStagedRollbackTest.xml b/tests/RollbackTest/NetworkStagedRollbackTest.xml new file mode 100644 index 000000000000..a465a4fe578b --- /dev/null +++ b/tests/RollbackTest/NetworkStagedRollbackTest.xml @@ -0,0 +1,25 @@ +<?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 the network staged rollback tests"> + <option name="test-suite-tag" value="NetworkStagedRollbackTest" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="RollbackTest.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.HostTest" > + <option name="class" value="com.android.tests.rollback.host.NetworkStagedRollbackTest" /> + </test> +</configuration> diff --git a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java new file mode 100644 index 000000000000..2c2e8282ff51 --- /dev/null +++ b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.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.tests.rollback.host; + +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Runs the network rollback tests. + */ +@RunWith(DeviceJUnit4ClassRunner.class) +public class NetworkStagedRollbackTest extends BaseHostJUnit4Test { + /** + * Tests failed network health check triggers watchdog staged rollbacks. + */ + @Test + public void testNetworkFailedRollback() throws Exception { + } + + /** + * Tests passed network health check does not trigger watchdog staged rollbacks. + */ + @Test + public void testNetworkPassedDoesNotRollback() throws Exception { + } +} diff --git a/tests/RollbackTest/RollbackTest.xml b/tests/RollbackTest/RollbackTest.xml index a14b01c57b1b..f2c0f86c3ce1 100644 --- a/tests/RollbackTest/RollbackTest.xml +++ b/tests/RollbackTest/RollbackTest.xml @@ -22,9 +22,9 @@ <option name="package" value="com.android.tests.rollback" /> <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> - <!-- Exclude the StagedRollbackTest and MultiUserRollbackTest tests, which need to be - specially driven from the StagedRollbackTest and MultiUserRollbackTest host test --> + <!-- Exclude the device tests which need to be specially driven from the host tests --> <option name="exclude-filter" value="com.android.tests.rollback.StagedRollbackTest" /> + <option name="exclude-filter" value="com.android.tests.rollback.NetworkStagedRollbackTest" /> <option name="exclude-filter" value="com.android.tests.rollback.MultiUserRollbackTest" /> </test> </configuration> diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java new file mode 100644 index 000000000000..04004d6abb5a --- /dev/null +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java @@ -0,0 +1,24 @@ +/* + * 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.tests.rollback; + +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class NetworkStagedRollbackTest { +} diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index 82a524be5c1e..032f18240a55 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -23,6 +23,8 @@ import static org.junit.Assume.assumeTrue; import static org.testng.Assert.assertThrows; import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; +import com.android.tradefed.device.LogcatReceiver; +import com.android.tradefed.result.InputStreamSource; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; @@ -31,11 +33,18 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.BufferedReader; import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; /** * Runs the staged rollback tests. + * + * TODO(gavincorkery): Support the verification of logging parents in Watchdog metrics. */ @RunWith(DeviceJUnit4ClassRunner.class) public class StagedRollbackTest extends BaseHostJUnit4Test { @@ -54,6 +63,7 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; + private static final String TESTAPP_A = "com.android.cts.install.lib.testapp.A"; private static final String TEST_SUBDIR = "/subdir/"; @@ -66,8 +76,19 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { private static final String TEST_FILENAME_4 = "one_more.test"; private static final String TEST_STRING_4 = "once more unto the test"; + private static final String REASON_APP_CRASH = "REASON_APP_CRASH"; + private static final String REASON_NATIVE_CRASH = "REASON_NATIVE_CRASH"; + private static final String REASON_EXPLICIT_HEALTH_CHECK = "REASON_EXPLICIT_HEALTH_CHECK"; + + private static final String ROLLBACK_INITIATE = "ROLLBACK_INITIATE"; + private static final String ROLLBACK_BOOT_TRIGGERED = "ROLLBACK_BOOT_TRIGGERED"; + + private LogcatReceiver mReceiver; + @Before public void setUp() throws Exception { + mReceiver = new LogcatReceiver(getDevice(), "logcat -s WatchdogRollbackLogger", + getDevice().getOptions().getMaxLogcatDataSize(), 0); if (!getDevice().isAdbRoot()) { getDevice().enableAdbRoot(); } @@ -77,10 +98,13 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex"); getDevice().reboot(); runPhase("testCleanUp"); + mReceiver.start(); } @After public void tearDown() throws Exception { + mReceiver.stop(); + mReceiver.clear(); runPhase("testCleanUp"); if (!getDevice().isAdbRoot()) { @@ -110,6 +134,16 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { getDevice().waitForDeviceAvailable(); runPhase("testBadApkOnly_Phase4"); + InputStreamSource logcatStream = mReceiver.getLogcatData(); + try { + List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_APP_CRASH, TESTAPP_A)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + } finally { + logcatStream.close(); + } } @Test @@ -137,6 +171,16 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { // verify rollback committed runPhase("testNativeWatchdogTriggersRollback_Phase3"); + InputStreamSource logcatStream = mReceiver.getLogcatData(); + try { + List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_NATIVE_CRASH, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + } finally { + logcatStream.close(); + } } @Test @@ -171,6 +215,16 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { // verify all available rollbacks have been committed runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4"); + InputStreamSource logcatStream = mReceiver.getLogcatData(); + try { + List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_NATIVE_CRASH, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + } finally { + logcatStream.close(); + } } /** @@ -194,6 +248,16 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { getDevice().waitForDeviceAvailable(); // Verify rollback was executed after health check deadline runPhase("testNetworkFailedRollback_Phase4"); + InputStreamSource logcatStream = mReceiver.getLogcatData(); + try { + List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_EXPLICIT_HEALTH_CHECK, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + } finally { + logcatStream.close(); + } } finally { // Reconnect internet again so we won't break tests which assume internet available getDevice().executeShellCommand("svc wifi enable"); @@ -223,6 +287,15 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { // Verify rollback was not executed after health check deadline runPhase("testNetworkPassedDoesNotRollback_Phase3"); + InputStreamSource logcatStream = mReceiver.getLogcatData(); + try { + List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); + assertEquals(watchdogEventOccurred(watchdogEvents, null, null, + REASON_EXPLICIT_HEALTH_CHECK, null), false); + } finally { + logcatStream.close(); + } + } /** @@ -288,6 +361,16 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { getDevice().waitForDeviceAvailable(); // Verify rollback occurred due to crash of apk-in-apex runPhase("testRollbackApexWithApkCrashing_Phase3"); + InputStreamSource logcatStream = mReceiver.getLogcatData(); + try { + List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_APP_CRASH, TESTAPP_A)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + } finally { + logcatStream.close(); + } } /** @@ -457,4 +540,57 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { return false; } } + + /** + * Returns a list of all Watchdog logging events which have occurred. + */ + private List<String> getWatchdogLoggingEvents(InputStreamSource inputStreamSource) + throws Exception { + List<String> watchdogEvents = new ArrayList<>(); + InputStream inputStream = inputStreamSource.createInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String line; + while ((line = reader.readLine()) != null) { + if (line.contains("Watchdog event occurred")) { + watchdogEvents.add(line); + } + } + return watchdogEvents; + } + + /** + * Returns whether a Watchdog event has occurred that matches the given criteria. + * + * Check the value of all non-null parameters against the list of Watchdog events that have + * occurred, and return {@code true} if an event exists which matches all criteria. + */ + private boolean watchdogEventOccurred(List<String> loggingEvents, + String type, String logPackage, + String rollbackReason, String failedPackageName) throws Exception { + List<String> eventCriteria = new ArrayList<>(); + if (type != null) { + eventCriteria.add("type: " + type); + } + if (logPackage != null) { + eventCriteria.add("logPackage: " + logPackage); + } + if (rollbackReason != null) { + eventCriteria.add("rollbackReason: " + rollbackReason); + } + if (failedPackageName != null) { + eventCriteria.add("failedPackageName: " + failedPackageName); + } + for (String loggingEvent: loggingEvents) { + boolean matchesCriteria = true; + for (String criterion: eventCriteria) { + if (!loggingEvent.contains(criterion)) { + matchesCriteria = false; + } + } + if (matchesCriteria) { + return true; + } + } + return false; + } } diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java index 8f3cb3442f5a..bdfaaa8f11ea 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java @@ -45,7 +45,7 @@ public class TaskOrganizerPipTest extends Service { final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.scheduleFinishEnterPip(ti.token, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT)); try { - ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct); + ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct, null); } catch (Exception e) { } } diff --git a/tests/net/Android.bp b/tests/net/Android.bp index b2f384ac8360..124b6609f687 100644 --- a/tests/net/Android.bp +++ b/tests/net/Android.bp @@ -8,7 +8,6 @@ java_defaults { "libbacktrace", "libbase", "libbinder", - "libbinderthreadstate", "libbpf", "libbpf_android", "libc++", diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java index 3e4f3d818840..efea91ab91f0 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -272,10 +272,24 @@ public class NetworkCapabilitiesTest { netCap.setOwnerUid(123); assertParcelingIsLossless(netCap); netCap.setSSID(TEST_SSID); - assertParcelSane(netCap, 13); + assertParcelSane(netCap, 15); } @Test + public void testParcelNetworkCapabilitiesWithRequestorUidAndPackageName() { + final NetworkCapabilities netCap = new NetworkCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .setRequestorUid(9304) + .setRequestorPackageName("com.android.test") + .addCapability(NET_CAPABILITY_EIMS) + .addCapability(NET_CAPABILITY_NOT_METERED); + assertParcelingIsLossless(netCap); + netCap.setSSID(TEST_SSID); + assertParcelSane(netCap, 15); + } + + + @Test public void testOemPaid() { NetworkCapabilities nc = new NetworkCapabilities(); // By default OEM_PAID is neither in the unwanted or required lists and the network is not diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java index 7ede14428a4f..d6bf334ee56a 100644 --- a/tests/net/java/android/net/ConnectivityManagerTest.java +++ b/tests/net/java/android/net/ConnectivityManagerTest.java @@ -212,7 +212,8 @@ public class ConnectivityManagerTest { ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class); // register callback - when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt())) + when(mService.requestNetwork( + any(), captor.capture(), anyInt(), any(), anyInt(), any())) .thenReturn(request); manager.requestNetwork(request, callback, handler); @@ -240,7 +241,8 @@ public class ConnectivityManagerTest { ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class); // register callback - when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt())) + when(mService.requestNetwork( + any(), captor.capture(), anyInt(), any(), anyInt(), any())) .thenReturn(req1); manager.requestNetwork(req1, callback, handler); @@ -258,7 +260,8 @@ public class ConnectivityManagerTest { verify(callback, timeout(100).times(0)).onLosing(any(), anyInt()); // callback can be registered again - when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt())) + when(mService.requestNetwork( + any(), captor.capture(), anyInt(), any(), anyInt(), any())) .thenReturn(req2); manager.requestNetwork(req2, callback, handler); @@ -282,7 +285,8 @@ public class ConnectivityManagerTest { info.targetSdkVersion = VERSION_CODES.N_MR1 + 1; when(mCtx.getApplicationInfo()).thenReturn(info); - when(mService.requestNetwork(any(), any(), anyInt(), any(), anyInt())).thenReturn(request); + when(mService.requestNetwork(any(), any(), anyInt(), any(), anyInt(), any())) + .thenReturn(request); Handler handler = new Handler(Looper.getMainLooper()); manager.requestNetwork(request, callback, handler); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 50c312cacc80..968f5523d818 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -24,6 +24,7 @@ import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport; import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL; @@ -106,6 +107,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -304,6 +306,7 @@ public class ConnectivityServiceTest { private static final String MOBILE_IFNAME = "test_rmnet_data0"; private static final String WIFI_IFNAME = "test_wlan0"; private static final String WIFI_WOL_IFNAME = "test_wlan_wol"; + private static final String TEST_PACKAGE_NAME = "com.android.test.package"; private static final String[] EMPTY_STRING_ARRAY = new String[0]; private MockContext mServiceContext; @@ -570,6 +573,9 @@ public class ConnectivityServiceTest { | NETWORK_VALIDATION_RESULT_PARTIAL; private static final int VALIDATION_RESULT_INVALID = 0; + private static final long DATA_STALL_TIMESTAMP = 10L; + private static final int DATA_STALL_DETECTION_METHOD = 1; + private INetworkMonitor mNetworkMonitor; private INetworkMonitorCallbacks mNmCallbacks; private int mNmValidationResult = VALIDATION_RESULT_BASE; @@ -577,6 +583,7 @@ public class ConnectivityServiceTest { private int mProbesSucceeded; private String mNmValidationRedirectUrl = null; private PersistableBundle mValidationExtras = PersistableBundle.EMPTY; + private PersistableBundle mDataStallExtras = PersistableBundle.EMPTY; private boolean mNmProvNotificationRequested = false; private final ConditionVariable mNetworkStatusReceived = new ConditionVariable(); @@ -649,7 +656,7 @@ public class ConnectivityServiceTest { if (mNmValidationRedirectUrl != null) { mNmCallbacks.showProvisioningNotification( - "test_provisioning_notif_action", "com.android.test.package"); + "test_provisioning_notif_action", TEST_PACKAGE_NAME); mNmProvNotificationRequested = true; } } @@ -804,6 +811,11 @@ public class ConnectivityServiceTest { public void expectPreventReconnectReceived() { expectPreventReconnectReceived(TIMEOUT_MS); } + + void notifyDataStallSuspected() throws Exception { + mNmCallbacks.notifyDataStallSuspected( + DATA_STALL_TIMESTAMP, DATA_STALL_DETECTION_METHOD, mDataStallExtras); + } } /** @@ -2962,7 +2974,7 @@ public class ConnectivityServiceTest { networkCapabilities.addTransportType(TRANSPORT_WIFI) .setNetworkSpecifier(new MatchAllNetworkSpecifier()); mService.requestNetwork(networkCapabilities, null, 0, null, - ConnectivityManager.TYPE_WIFI); + ConnectivityManager.TYPE_WIFI, TEST_PACKAGE_NAME); }); class NonParcelableSpecifier extends NetworkSpecifier { @@ -3001,31 +3013,12 @@ public class ConnectivityServiceTest { } @Test - public void testNetworkSpecifierUidSpoofSecurityException() throws Exception { - class UidAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable { - @Override - public boolean satisfiedBy(NetworkSpecifier other) { - return true; - } - - @Override - public void assertValidFromUid(int requestorUid) { - throw new SecurityException("failure"); - } - - @Override - public int describeContents() { return 0; } - @Override - public void writeToParcel(Parcel dest, int flags) {} - } - + public void testNetworkRequestUidSpoofSecurityException() throws Exception { mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - - UidAwareNetworkSpecifier networkSpecifier = new UidAwareNetworkSpecifier(); - NetworkRequest networkRequest = newWifiRequestBuilder().setNetworkSpecifier( - networkSpecifier).build(); + NetworkRequest networkRequest = newWifiRequestBuilder().build(); TestNetworkCallback networkCallback = new TestNetworkCallback(); + doThrow(new SecurityException()).when(mAppOpsManager).checkPackage(anyInt(), anyString()); assertThrows(SecurityException.class, () -> { mCm.requestNetwork(networkRequest, networkCallback); }); @@ -6436,14 +6429,16 @@ public class ConnectivityServiceTest { public void testRegisterUnregisterConnectivityDiagnosticsCallback() throws Exception { final NetworkRequest wifiRequest = new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(); - when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); mService.registerConnectivityDiagnosticsCallback( mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); - verify(mIBinder, timeout(TIMEOUT_MS)) - .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + verify(mConnectivityDiagnosticsCallback).asBinder(); assertTrue( mService.mConnectivityDiagnosticsCallbacks.containsKey( mConnectivityDiagnosticsCallback)); @@ -6466,8 +6461,10 @@ public class ConnectivityServiceTest { mService.registerConnectivityDiagnosticsCallback( mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); - verify(mIBinder, timeout(TIMEOUT_MS)) - .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); verify(mConnectivityDiagnosticsCallback).asBinder(); assertTrue( mService.mConnectivityDiagnosticsCallbacks.containsKey( @@ -6599,8 +6596,7 @@ public class ConnectivityServiceTest { mServiceContext.setPermission(perm, PERMISSION_GRANTED); } - @Test - public void testConnectivityDiagnosticsCallbackOnConnectivityReport() throws Exception { + private void setUpConnectivityDiagnosticsCallback() throws Exception { final NetworkRequest request = new NetworkRequest.Builder().build(); when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); @@ -6620,9 +6616,58 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(true); callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); callback.assertNoCallback(); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnConnectivityReport() throws Exception { + setUpConnectivityDiagnosticsCallback(); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); - // Wait for onConnectivityReport to fire - verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS)) + // Verify onConnectivityReport fired + verify(mConnectivityDiagnosticsCallback) .onConnectivityReport(any(ConnectivityReport.class)); } + + @Test + public void testConnectivityDiagnosticsCallbackOnDataStallSuspected() throws Exception { + setUpConnectivityDiagnosticsCallback(); + + // Trigger notifyDataStallSuspected() on the INetworkMonitorCallbacks instance in the + // cellular network agent + mCellNetworkAgent.notifyDataStallSuspected(); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Verify onDataStallSuspected fired + verify(mConnectivityDiagnosticsCallback).onDataStallSuspected(any(DataStallReport.class)); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnConnectivityReported() throws Exception { + setUpConnectivityDiagnosticsCallback(); + + final Network n = mCellNetworkAgent.getNetwork(); + final boolean hasConnectivity = true; + mService.reportNetworkConnectivity(n, hasConnectivity); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Verify onNetworkConnectivityReported fired + verify(mConnectivityDiagnosticsCallback) + .onNetworkConnectivityReported(eq(n), eq(hasConnectivity)); + + final boolean noConnectivity = false; + mService.reportNetworkConnectivity(n, noConnectivity); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Wait for onNetworkConnectivityReported to fire + verify(mConnectivityDiagnosticsCallback) + .onNetworkConnectivityReported(eq(n), eq(noConnectivity)); + } } diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 2af118c40138..5aa32f868104 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -391,6 +391,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action["uses-split"].Action(RequiredNameIsJavaPackage); manifest_action["queries"]["package"].Action(RequiredNameIsJavaPackage); manifest_action["queries"]["intent"] = intent_filter_action; + manifest_action["queries"]["provider"].Action(RequiredAndroidAttribute("authorities")); // TODO: more complicated component name tag manifest_action["key-sets"]["key-set"]["public-key"]; diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp index d3958a65c704..a251c053e004 100644 --- a/tools/stats_log_api_gen/Android.bp +++ b/tools/stats_log_api_gen/Android.bp @@ -30,7 +30,7 @@ cc_binary_host { "utils.cpp", ], cflags: [ - "-DSTATS_SCHEMA_LEGACY", + //"-DSTATS_SCHEMA_LEGACY", "-Wall", "-Werror", ], diff --git a/tools/stats_log_api_gen/java_writer_q.cpp b/tools/stats_log_api_gen/java_writer_q.cpp index a68c3a208869..12c050d8ef8d 100644 --- a/tools/stats_log_api_gen/java_writer_q.cpp +++ b/tools/stats_log_api_gen/java_writer_q.cpp @@ -175,9 +175,7 @@ int write_java_methods_q_schema( indent.c_str()); fprintf(out, "%s android.util.SparseArray<Float> floatMap = null;\n", indent.c_str()); - fprintf(out, - "%s int keyValuePairSize = LIST_TYPE_OVERHEAD * 5;\n", - indent.c_str()); + fprintf(out, "%s int keyValuePairSize = LIST_TYPE_OVERHEAD;\n", indent.c_str()); fprintf(out, "%s for (int i = 0; i < count; i++) {\n", indent.c_str()); fprintf(out, @@ -360,8 +358,9 @@ int write_java_methods_q_schema( requiredHelpers |= JAVA_MODULE_REQUIRES_FLOAT; requiredHelpers |= JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS; fprintf(out, - "%s writeKeyValuePairs(buff, pos, intMap, longMap, stringMap, " - "floatMap);\n", indent.c_str()); + "%s writeKeyValuePairs(buff, pos, (byte) count, intMap, longMap, " + "stringMap, floatMap);\n", + indent.c_str()); fprintf(out, "%s pos += keyValuePairSize;\n", indent.c_str()); break; default: @@ -472,7 +471,8 @@ void write_java_helpers_for_q_schema_methods( } if (requiredHelpers & JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS) { - fprintf(out, "%sprivate static void writeKeyValuePairs(byte[] buff, int pos,\n", + fprintf(out, + "%sprivate static void writeKeyValuePairs(byte[] buff, int pos, byte numPairs,\n", indent.c_str()); fprintf(out, "%s final android.util.SparseIntArray intMap,\n", indent.c_str()); fprintf(out, "%s final android.util.SparseLongArray longMap,\n", indent.c_str()); @@ -483,15 +483,12 @@ void write_java_helpers_for_q_schema_methods( // Start list of lists. fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); - fprintf(out, "%s buff[pos + 1] = (byte) 4;\n", indent.c_str()); + fprintf(out, "%s buff[pos + 1] = (byte) numPairs;\n", indent.c_str()); fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str()); // Write integers. fprintf(out, "%s final int intMapSize = null == intMap ? 0 : intMap.size();\n", indent.c_str()); - fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); - fprintf(out, "%s buff[pos + 1] = (byte) intMapSize;\n", indent.c_str()); - fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str()); fprintf(out, "%s for (int i = 0; i < intMapSize; i++) {\n", indent.c_str()); fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); fprintf(out, "%s buff[pos + 1] = (byte) 2;\n", indent.c_str()); @@ -509,9 +506,6 @@ void write_java_helpers_for_q_schema_methods( // Write longs. fprintf(out, "%s final int longMapSize = null == longMap ? 0 : longMap.size();\n", indent.c_str()); - fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); - fprintf(out, "%s buff[pos + 1] = (byte) longMapSize;\n", indent.c_str()); - fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str()); fprintf(out, "%s for (int i = 0; i < longMapSize; i++) {\n", indent.c_str()); fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); fprintf(out, "%s buff[pos + 1] = (byte) 2;\n", indent.c_str()); @@ -529,9 +523,6 @@ void write_java_helpers_for_q_schema_methods( // Write Strings. fprintf(out, "%s final int stringMapSize = null == stringMap ? 0 : stringMap.size();\n", indent.c_str()); - fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); - fprintf(out, "%s buff[pos + 1] = (byte) stringMapSize;\n", indent.c_str()); - fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str()); fprintf(out, "%s for (int i = 0; i < stringMapSize; i++) {\n", indent.c_str()); fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); fprintf(out, "%s buff[pos + 1] = (byte) 2;\n", indent.c_str()); @@ -556,9 +547,6 @@ void write_java_helpers_for_q_schema_methods( // Write floats. fprintf(out, "%s final int floatMapSize = null == floatMap ? 0 : floatMap.size();\n", indent.c_str()); - fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); - fprintf(out, "%s buff[pos + 1] = (byte) floatMapSize;\n", indent.c_str()); - fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str()); fprintf(out, "%s for (int i = 0; i < floatMapSize; i++) {\n", indent.c_str()); fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str()); fprintf(out, "%s buff[pos + 1] = (byte) 2;\n", indent.c_str()); diff --git a/tools/stats_log_api_gen/native_writer.cpp b/tools/stats_log_api_gen/native_writer.cpp index c7a34feff94b..285514df5ff3 100644 --- a/tools/stats_log_api_gen/native_writer.cpp +++ b/tools/stats_log_api_gen/native_writer.cpp @@ -22,15 +22,6 @@ namespace android { namespace stats_log_api_gen { #if !defined(STATS_SCHEMA_LEGACY) -static void write_native_key_value_pairs_for_type(FILE* out, const int argIndex, - const int typeIndex, const string& type, const string& valueFieldName) { - fprintf(out, " for (const auto& it : arg%d_%d) {\n", argIndex, typeIndex); - fprintf(out, " pairs.push_back(" - "{ .key = it.first, .valueType = %s, .%s = it.second });\n", - type.c_str(), valueFieldName.c_str()); - fprintf(out, " }\n"); - -} static int write_native_stats_write_methods(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl, const string& moduleName, const bool supportQ) { @@ -41,7 +32,10 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms, continue; } vector<java_type_t> signature = signature_to_modules_it->first; - + // Key value pairs not supported in native. + if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) { + continue; + } write_native_method_signature(out, "int stats_write", signature, attributionDecl, " {"); @@ -59,11 +53,6 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms, uidName, uidName, tagName); break; } - case JAVA_TYPE_KEY_VALUE_PAIR: - fprintf(out, " event.writeKeyValuePairs(" - "arg%d_1, arg%d_2, arg%d_3, arg%d_4);\n", - argIndex, argIndex, argIndex, argIndex); - break; case JAVA_TYPE_BYTE_ARRAY: fprintf(out, " event.writeByteArray(arg%d.arg, arg%d.arg_length);\n", argIndex, argIndex); @@ -85,7 +74,7 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms, fprintf(out, " event.writeString(arg%d);\n", argIndex); break; default: - // Unsupported types: OBJECT, DOUBLE. + // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIRS. fprintf(stderr, "Encountered unsupported type."); return 1; } @@ -93,8 +82,8 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms, } fprintf(out, " return event.writeToSocket();\n"); } else { - fprintf(out, " struct stats_event* event = stats_event_obtain();\n"); - fprintf(out, " stats_event_set_atom_id(event, code);\n"); + fprintf(out, " AStatsEvent* event = AStatsEvent_obtain();\n"); + fprintf(out, " AStatsEvent_setAtomId(event, code);\n"); for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end(); arg++) { switch (*arg) { @@ -102,57 +91,43 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms, const char* uidName = attributionDecl.fields.front().name.c_str(); const char* tagName = attributionDecl.fields.back().name.c_str(); fprintf(out, - " stats_event_write_attribution_chain(event, " + " AStatsEvent_writeAttributionChain(event, " "reinterpret_cast<const uint32_t*>(%s), %s.data(), " "static_cast<uint8_t>(%s_length));\n", uidName, tagName, uidName); break; } - case JAVA_TYPE_KEY_VALUE_PAIR: - fprintf(out, " std::vector<key_value_pair> pairs;\n"); - write_native_key_value_pairs_for_type( - out, argIndex, 1, "INT32_TYPE", "int32Value"); - write_native_key_value_pairs_for_type( - out, argIndex, 2, "INT64_TYPE", "int64Value"); - write_native_key_value_pairs_for_type( - out, argIndex, 3, "STRING_TYPE", "stringValue"); - write_native_key_value_pairs_for_type( - out, argIndex, 4, "FLOAT_TYPE", "floatValue"); - fprintf(out, - " stats_event_write_key_value_pairs(event, pairs.data(), " - "static_cast<uint8_t>(pairs.size()));\n"); - break; case JAVA_TYPE_BYTE_ARRAY: fprintf(out, - " stats_event_write_byte_array(event, " + " AStatsEvent_writeByteArray(event, " "reinterpret_cast<const uint8_t*>(arg%d.arg), arg%d.arg_length);\n", argIndex, argIndex); break; case JAVA_TYPE_BOOLEAN: - fprintf(out, " stats_event_write_bool(event, arg%d);\n", argIndex); + fprintf(out, " AStatsEvent_writeBool(event, arg%d);\n", argIndex); break; case JAVA_TYPE_INT: // Fall through. case JAVA_TYPE_ENUM: - fprintf(out, " stats_event_write_int32(event, arg%d);\n", argIndex); + fprintf(out, " AStatsEvent_writeInt32(event, arg%d);\n", argIndex); break; case JAVA_TYPE_FLOAT: - fprintf(out, " stats_event_write_float(event, arg%d);\n", argIndex); + fprintf(out, " AStatsEvent_writeFloat(event, arg%d);\n", argIndex); break; case JAVA_TYPE_LONG: - fprintf(out, " stats_event_write_int64(event, arg%d);\n", argIndex); + fprintf(out, " AStatsEvent_writeInt64(event, arg%d);\n", argIndex); break; case JAVA_TYPE_STRING: - fprintf(out, " stats_event_write_string8(event, arg%d);\n", argIndex); + fprintf(out, " AStatsEvent_writeString(event, arg%d);\n", argIndex); break; default: - // Unsupported types: OBJECT, DOUBLE. + // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIRS fprintf(stderr, "Encountered unsupported type."); return 1; } argIndex++; } - fprintf(out, " const int ret = stats_event_write(event);\n"); - fprintf(out, " stats_event_release(event);\n"); + fprintf(out, " const int ret = AStatsEvent_write(event);\n"); + fprintf(out, " AStatsEvent_release(event);\n"); fprintf(out, " return ret;\n"); } fprintf(out, "}\n\n"); @@ -169,6 +144,10 @@ static void write_native_stats_write_non_chained_methods(FILE* out, const Atoms& continue; } vector<java_type_t> signature = signature_it->first; + // Key value pairs not supported in native. + if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) { + continue; + } write_native_method_signature(out, "int stats_write_non_chained", signature, attributionDecl, " {"); @@ -210,8 +189,14 @@ static void write_native_method_header( if (!signature_needed_for_module(signature_to_modules_it->second, moduleName)) { continue; } - vector<java_type_t> signature = signature_to_modules_it->first; + +#if !defined(STATS_SCHEMA_LEGACY) + // Key value pairs not supported in native. + if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) { + continue; + } +#endif write_native_method_signature(out, methodName, signature, attributionDecl, ";"); } } diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp index d6b9d81137ac..fe9a438d81d7 100644 --- a/tools/streaming_proto/cpp/main.cpp +++ b/tools/streaming_proto/cpp/main.cpp @@ -33,13 +33,13 @@ write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& ind if (GENERATE_MAPPING) { string name = make_constant_name(enu.name()); string prefix = name + "_"; - text << indent << "const int _ENUM_" << name << "_COUNT = " << N << ";" << endl; - text << indent << "const char* _ENUM_" << name << "_NAMES[" << N << "] = {" << endl; + text << indent << "static const int _ENUM_" << name << "_COUNT = " << N << ";" << endl; + text << indent << "static const char* _ENUM_" << name << "_NAMES[" << N << "] = {" << endl; for (int i=0; i<N; i++) { text << indent << INDENT << "\"" << stripPrefix(enu.value(i).name(), prefix) << "\"," << endl; } text << indent << "};" << endl; - text << indent << "const int _ENUM_" << name << "_VALUES[" << N << "] = {" << endl; + text << indent << "static const int _ENUM_" << name << "_VALUES[" << N << "] = {" << endl; for (int i=0; i<N; i++) { text << indent << INDENT << make_constant_name(enu.value(i).name()) << "," << endl; } @@ -102,13 +102,13 @@ write_message(stringstream& text, const DescriptorProto& message, const string& if (GENERATE_MAPPING) { N = message.field_size(); - text << indented << "const int _FIELD_COUNT = " << N << ";" << endl; - text << indented << "const char* _FIELD_NAMES[" << N << "] = {" << endl; + text << indented << "static const int _FIELD_COUNT = " << N << ";" << endl; + text << indented << "static const char* _FIELD_NAMES[" << N << "] = {" << endl; for (int i=0; i<N; i++) { text << indented << INDENT << "\"" << message.field(i).name() << "\"," << endl; } text << indented << "};" << endl; - text << indented << "const uint64_t _FIELD_IDS[" << N << "] = {" << endl; + text << indented << "static const uint64_t _FIELD_IDS[" << N << "] = {" << endl; for (int i=0; i<N; i++) { text << indented << INDENT << make_constant_name(message.field(i).name()) << "," << endl; } @@ -152,7 +152,7 @@ write_header_file(CodeGeneratorResponse* response, const FileDescriptorProto& fi write_message(text, file_descriptor.message_type(i), ""); } - for (vector<string>::iterator it = namespaces.begin(); it != namespaces.end(); it++) { + for (vector<string>::reverse_iterator it = namespaces.rbegin(); it != namespaces.rend(); it++) { text << "} // " << *it << endl; } diff --git a/wifi/Android.bp b/wifi/Android.bp index dae04c6c3a25..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", ], @@ -63,7 +63,6 @@ test_access_hidden_api_whitelist = [ "//frameworks/base/wifi/tests", "//frameworks/opt/net/wifi/tests/wifitests:__subpackages__", - "//frameworks/opt/net/wifi/libs/WifiTrackerLib/tests", "//external/robolectric-shadows:__subpackages__", "//frameworks/base/packages/SettingsLib/tests/integ", "//external/sl4a:__subpackages__", diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 1f1c0c12cf21..d4e024dbe0c7 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -266,4 +266,10 @@ 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(); + + Map getAllMatchingPasspointProfilesForScanResults(in List<ScanResult> scanResult); } diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java index c8fd2434e64f..0db3313ae137 100644 --- a/wifi/java/android/net/wifi/SoftApConfiguration.java +++ b/wifi/java/android/net/wifi/SoftApConfiguration.java @@ -519,6 +519,9 @@ public final class SoftApConfiguration implements Parcelable { case BAND_5GHZ: wifiConfig.apBand = WifiConfiguration.AP_BAND_5GHZ; break; + case BAND_2GHZ | BAND_5GHZ: + wifiConfig.apBand = WifiConfiguration.AP_BAND_ANY; + break; case BAND_ANY: wifiConfig.apBand = WifiConfiguration.AP_BAND_ANY; break; diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 91b7df372e01..a720236689d3 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -568,14 +568,12 @@ public class WifiConfiguration implements Parcelable { * 2GHz band. * @hide */ - @SystemApi public static final int AP_BAND_2GHZ = 0; /** * 5GHz band. * @hide */ - @SystemApi public static final int AP_BAND_5GHZ = 1; /** @@ -583,7 +581,6 @@ public class WifiConfiguration implements Parcelable { * operating country code and current radio conditions. * @hide */ - @SystemApi public static final int AP_BAND_ANY = -1; /** @@ -593,7 +590,7 @@ public class WifiConfiguration implements Parcelable { * * @hide */ - @SystemApi + @UnsupportedAppUsage @ApBand public int apBand = AP_BAND_2GHZ; @@ -1304,10 +1301,34 @@ public class WifiConfiguration implements Parcelable { public static final int DISABLED_BY_WRONG_PASSWORD = 8; /** This network is disabled because service is not subscribed. */ public static final int DISABLED_AUTHENTICATION_NO_SUBSCRIPTION = 9; - /** All other disable reasons should be strictly less than this value. */ + /** + * All other disable reasons should be strictly less than this value. + * @hide + */ public static final int NETWORK_SELECTION_DISABLED_MAX = 10; /** + * Get an integer that is equal to the maximum integer value of all the + * DISABLED_* reasons + * e.g. {@link #DISABLED_NONE}, {@link #DISABLED_ASSOCIATION_REJECTION}, etc. + * + * All DISABLED_* constants will be contiguous in the range + * 0, 1, 2, 3, ..., getMaxNetworkSelectionDisableReasons() + * + * <br /> + * For example, this can be used to iterate through all the network selection + * disable reasons like so: + * <pre>{@code + * for (int reason = 0; reason <= getMaxNetworkSelectionDisableReasons(); reason++) { + * ... + * } + * }</pre> + */ + public static int getMaxNetworkSelectionDisableReason() { + return NETWORK_SELECTION_DISABLED_MAX - 1; + } + + /** * Contains info about disable reasons. * @hide */ @@ -1709,7 +1730,10 @@ public class WifiConfiguration implements Parcelable { return mStatus; } - /** True if the current network is enabled to join network selection, false otherwise. */ + /** + * True if the current network is enabled to join network selection, false otherwise. + * @hide + */ public boolean isNetworkEnabled() { return mStatus == NETWORK_SELECTION_ENABLED; } @@ -1722,7 +1746,10 @@ public class WifiConfiguration implements Parcelable { return mStatus == NETWORK_SELECTION_TEMPORARY_DISABLED; } - /** True if the current network is permanently disabled, false otherwise. */ + /** + * True if the current network is permanently disabled, false otherwise. + * @hide + */ public boolean isNetworkPermanentlyDisabled() { return mStatus == NETWORK_SELECTION_PERMANENTLY_DISABLED; } @@ -2085,7 +2112,8 @@ public class WifiConfiguration implements Parcelable { return !TextUtils.isEmpty(FQDN) && !TextUtils.isEmpty(providerFriendlyName) && enterpriseConfig != null - && enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE; + && enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE + && !TextUtils.isEmpty(mPasspointUniqueId); } /** @@ -2467,12 +2495,17 @@ public class WifiConfiguration implements Parcelable { */ @NonNull public String getKey() { - String key = providerFriendlyName == null - ? getSsidAndSecurityTypeString() - : FQDN + KeyMgmt.strings[KeyMgmt.WPA_EAP]; + // Passpoint ephemeral networks have their unique identifier set. Return it as is to be + // able to match internally. + if (mPasspointUniqueId != null) { + return mPasspointUniqueId; + } + + String key = getSsidAndSecurityTypeString(); if (!shared) { key += "-" + UserHandle.getUserHandleForUid(creatorUid).getIdentifier(); } + return key; } @@ -2727,6 +2760,7 @@ public class WifiConfiguration implements Parcelable { requirePMF = source.requirePMF; updateIdentifier = source.updateIdentifier; carrierId = source.carrierId; + mPasspointUniqueId = source.mPasspointUniqueId; } } @@ -2799,6 +2833,7 @@ public class WifiConfiguration implements Parcelable { dest.writeInt(osu ? 1 : 0); dest.writeLong(randomizedMacExpirationTimeMs); dest.writeInt(carrierId); + dest.writeString(mPasspointUniqueId); } /** Implement the Parcelable interface {@hide} */ @@ -2873,6 +2908,7 @@ public class WifiConfiguration implements Parcelable { config.osu = in.readInt() != 0; config.randomizedMacExpirationTimeMs = in.readLong(); config.carrierId = in.readInt(); + config.mPasspointUniqueId = in.readString(); return config; } @@ -2880,4 +2916,28 @@ public class WifiConfiguration implements Parcelable { return new WifiConfiguration[size]; } }; + + /** + * Passpoint Unique identifier + * @hide + */ + private String mPasspointUniqueId = null; + + /** + * Set the Passpoint unique identifier + * @param uniqueId Passpoint unique identifier to be set + * @hide + */ + public void setPasspointUniqueId(String uniqueId) { + mPasspointUniqueId = uniqueId; + } + + /** + * Set the Passpoint unique identifier + * @hide + */ + public String getPasspointUniqueId() { + return mPasspointUniqueId; + } + } diff --git a/wifi/java/android/net/wifi/WifiFrameworkInitializer.java b/wifi/java/android/net/wifi/WifiFrameworkInitializer.java index 002820b1bcc8..1507199b0264 100644 --- a/wifi/java/android/net/wifi/WifiFrameworkInitializer.java +++ b/wifi/java/android/net/wifi/WifiFrameworkInitializer.java @@ -102,15 +102,6 @@ public class WifiFrameworkInitializer { } ); SystemServiceRegistry.registerContextAwareService( - Context.WIFI_RTT_SERVICE, - RttManager.class, - (context, serviceBinder) -> { - IWifiRttManager service = IWifiRttManager.Stub.asInterface(serviceBinder); - WifiRttManager wifiRttManager = new WifiRttManager(context, service); - return new RttManager(context, wifiRttManager); - } - ); - SystemServiceRegistry.registerContextAwareService( Context.WIFI_RTT_RANGING_SERVICE, WifiRttManager.class, (context, serviceBinder) -> { @@ -118,5 +109,13 @@ public class WifiFrameworkInitializer { return new WifiRttManager(context, service); } ); + SystemServiceRegistry.registerContextAwareService( + Context.WIFI_RTT_SERVICE, + RttManager.class, + context -> { + WifiRttManager wifiRttManager = context.getSystemService(WifiRttManager.class); + return new RttManager(context, wifiRttManager); + } + ); } } diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 7c031eaaeaf4..0c306b4fb8cc 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -291,6 +291,11 @@ public class WifiInfo implements Parcelable { */ private boolean mMeteredHint; + /** + * Passpoint unique key + */ + private String mPasspointUniqueId; + /** @hide */ @UnsupportedAppUsage public WifiInfo() { @@ -322,6 +327,7 @@ public class WifiInfo implements Parcelable { setRequestingPackageName(null); setFQDN(null); setProviderFriendlyName(null); + setPasspointUniqueId(null); txBad = 0; txSuccess = 0; rxSuccess = 0; @@ -370,6 +376,7 @@ public class WifiInfo implements Parcelable { mWifiStandard = source.mWifiStandard; mMaxSupportedTxLinkSpeed = source.mMaxSupportedTxLinkSpeed; mMaxSupportedRxLinkSpeed = source.mMaxSupportedRxLinkSpeed; + mPasspointUniqueId = source.mPasspointUniqueId; } } @@ -449,9 +456,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 @@ -978,6 +984,7 @@ public class WifiInfo implements Parcelable { dest.writeInt(mWifiStandard); dest.writeInt(mMaxSupportedTxLinkSpeed); dest.writeInt(mMaxSupportedRxLinkSpeed); + dest.writeString(mPasspointUniqueId); } /** Implement the Parcelable interface {@hide} */ @@ -1022,6 +1029,7 @@ public class WifiInfo implements Parcelable { info.mWifiStandard = in.readInt(); info.mMaxSupportedTxLinkSpeed = in.readInt(); info.mMaxSupportedRxLinkSpeed = in.readInt(); + info.mPasspointUniqueId = in.readString(); return info; } @@ -1029,4 +1037,24 @@ public class WifiInfo implements Parcelable { return new WifiInfo[size]; } }; + + /** + * Set the Passpoint unique identifier for the current connection + * + * @param passpointUniqueId Unique identifier + * @hide + */ + public void setPasspointUniqueId(@Nullable String passpointUniqueId) { + mPasspointUniqueId = passpointUniqueId; + } + + /** + * Get the Passpoint unique identifier for the current connection + * + * @return Passpoint unique identifier + * @hide + */ + public @Nullable String getPasspointUniqueId() { + return mPasspointUniqueId; + } } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 1dc4a069fe2f..fb30910c17b1 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1400,8 +1400,7 @@ public class WifiManager { List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> configs = new ArrayList<>(); try { Map<String, Map<Integer, List<ScanResult>>> results = - mService.getAllMatchingFqdnsForScanResults( - scanResults); + mService.getAllMatchingPasspointProfilesForScanResults(scanResults); if (results.isEmpty()) { return configs; } @@ -1409,8 +1408,8 @@ public class WifiManager { mService.getWifiConfigsForPasspointProfiles( new ArrayList<>(results.keySet())); for (WifiConfiguration configuration : wifiConfigurations) { - Map<Integer, List<ScanResult>> scanResultsPerNetworkType = results.get( - configuration.FQDN); + Map<Integer, List<ScanResult>> scanResultsPerNetworkType = + results.get(configuration.getKey()); if (scanResultsPerNetworkType != null) { configs.add(Pair.create(configuration, scanResultsPerNetworkType)); } @@ -1962,9 +1961,11 @@ public class WifiManager { * for connecting to Passpoint networks that are operated by the Passpoint * service provider specified in the configuration. * - * Each configuration is uniquely identified by its FQDN (Fully Qualified Domain - * Name). In the case when there is an existing configuration with the same - * FQDN, the new configuration will replace the existing configuration. + * Each configuration is uniquely identified by a unique key which depends on the contents of + * the configuration. This allows the caller to install multiple profiles with the same FQDN + * (Fully qualified domain name). Therefore, in order to update an existing profile, it is + * first required to remove it using {@link WifiManager#removePasspointConfiguration(String)}. + * Otherwise, a new profile will be added with both configuration. * * @param config The Passpoint configuration to be added * @throws IllegalArgumentException if configuration is invalid or Passpoint is not enabled on @@ -2625,8 +2626,8 @@ public class WifiManager { public void getWifiActivityEnergyInfoAsync( @NonNull @CallbackExecutor Executor executor, @NonNull OnWifiActivityEnergyInfoListener listener) { - if (executor == null) throw new IllegalArgumentException("executor cannot be null"); - if (listener == null) throw new IllegalArgumentException("listener cannot be null"); + Objects.requireNonNull(executor, "executor cannot be null"); + Objects.requireNonNull(listener, "listener cannot be null"); try { mService.getWifiActivityEnergyInfoAsync( new OnWifiActivityEnergyInfoProxy(executor, listener)); @@ -2982,7 +2983,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 +3279,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 +3298,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 +3311,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 +3333,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 +3350,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 +3369,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 +6119,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/WifiNetworkAgentSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java index 04d2e1a8b5dd..6632c162fcf9 100644 --- a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java +++ b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java @@ -27,7 +27,6 @@ import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.os.Parcel; import android.os.Parcelable; -import android.text.TextUtils; import java.util.Objects; @@ -41,33 +40,10 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements */ private final WifiConfiguration mWifiConfiguration; - /** - * The UID of the app that requested a specific wifi network using {@link WifiNetworkSpecifier}. - * - * Will only be filled when the device connects to a wifi network as a result of a - * {@link NetworkRequest} with {@link WifiNetworkSpecifier}. Will be set to -1 if the device - * auto-connected to a wifi network. - */ - private final int mOriginalRequestorUid; - - /** - * The package name of the app that requested a specific wifi network using - * {@link WifiNetworkSpecifier}. - * - * Will only be filled when the device connects to a wifi network as a result of a - * {@link NetworkRequest} with {@link WifiNetworkSpecifier}. Will be set to null if the device - * auto-connected to a wifi network. - */ - private final String mOriginalRequestorPackageName; - - public WifiNetworkAgentSpecifier(@NonNull WifiConfiguration wifiConfiguration, - int originalRequestorUid, - @Nullable String originalRequestorPackageName) { + public WifiNetworkAgentSpecifier(@NonNull WifiConfiguration wifiConfiguration) { checkNotNull(wifiConfiguration); mWifiConfiguration = wifiConfiguration; - mOriginalRequestorUid = originalRequestorUid; - mOriginalRequestorPackageName = originalRequestorPackageName; } /** @@ -78,10 +54,7 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements @Override public WifiNetworkAgentSpecifier createFromParcel(@NonNull Parcel in) { WifiConfiguration wifiConfiguration = in.readParcelable(null); - int originalRequestorUid = in.readInt(); - String originalRequestorPackageName = in.readString(); - return new WifiNetworkAgentSpecifier( - wifiConfiguration, originalRequestorUid, originalRequestorPackageName); + return new WifiNetworkAgentSpecifier(wifiConfiguration); } @Override @@ -98,8 +71,6 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeParcelable(mWifiConfiguration, flags); - dest.writeInt(mOriginalRequestorUid); - dest.writeString(mOriginalRequestorPackageName); } @Override @@ -149,12 +120,6 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements this.mWifiConfiguration.allowedKeyManagement)) { return false; } - if (ns.requestorUid != this.mOriginalRequestorUid) { - return false; - } - if (!TextUtils.equals(ns.requestorPackageName, this.mOriginalRequestorPackageName)) { - return false; - } return true; } @@ -163,9 +128,7 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements return Objects.hash( mWifiConfiguration.SSID, mWifiConfiguration.BSSID, - mWifiConfiguration.allowedKeyManagement, - mOriginalRequestorUid, - mOriginalRequestorPackageName); + mWifiConfiguration.allowedKeyManagement); } @Override @@ -180,10 +143,7 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements return Objects.equals(this.mWifiConfiguration.SSID, lhs.mWifiConfiguration.SSID) && Objects.equals(this.mWifiConfiguration.BSSID, lhs.mWifiConfiguration.BSSID) && Objects.equals(this.mWifiConfiguration.allowedKeyManagement, - lhs.mWifiConfiguration.allowedKeyManagement) - && mOriginalRequestorUid == lhs.mOriginalRequestorUid - && TextUtils.equals(mOriginalRequestorPackageName, - lhs.mOriginalRequestorPackageName); + lhs.mWifiConfiguration.allowedKeyManagement); } @Override @@ -192,19 +152,11 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements sb.append("WifiConfiguration=") .append(", SSID=").append(mWifiConfiguration.SSID) .append(", BSSID=").append(mWifiConfiguration.BSSID) - .append(", mOriginalRequestorUid=").append(mOriginalRequestorUid) - .append(", mOriginalRequestorPackageName=").append(mOriginalRequestorPackageName) .append("]"); return sb.toString(); } @Override - public void assertValidFromUid(int requestorUid) { - throw new IllegalStateException("WifiNetworkAgentSpecifier should never be used " - + "for requests."); - } - - @Override public NetworkSpecifier redact() { return null; } diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java index 444e1ef041e8..3d946c9f887d 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java +++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java @@ -20,7 +20,6 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Application; import android.net.MacAddress; import android.net.MatchAllNetworkSpecifier; import android.net.NetworkRequest; @@ -28,13 +27,9 @@ import android.net.NetworkSpecifier; import android.os.Parcel; import android.os.Parcelable; import android.os.PatternMatcher; -import android.os.Process; import android.text.TextUtils; -import android.util.Log; import android.util.Pair; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; import java.util.Objects; @@ -438,24 +433,7 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc return new WifiNetworkSpecifier( mSsidPatternMatcher, mBssidPatternMatcher, - buildWifiConfiguration(), - Process.myUid(), - getCurrentApplicationReflectively().getApplicationContext().getOpPackageName()); - } - - // TODO(b/144102365): Remove once refactor is complete - private static Application getCurrentApplicationReflectively() { - try { - // reflection for static method android.app.ActivityThread#currentApplication() - Class<?> klass = Class.forName("android.app.ActivityThread"); - Method currentApplicationMethod = klass.getDeclaredMethod("currentApplication"); - Object result = currentApplicationMethod.invoke(null); - return (Application) result; - } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException - | InvocationTargetException e) { - Log.e(TAG, "Failed to call ActivityThread#currentApplication() reflectively!", e); - throw new RuntimeException(e); - } + buildWifiConfiguration()); } } @@ -483,20 +461,6 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc */ public final WifiConfiguration wifiConfiguration; - /** - * The UID of the process initializing this network specifier. Validated by receiver using - * checkUidIfNecessary() and is used by satisfiedBy() to determine whether the specifier - * matches the offered network. - * @hide - */ - public final int requestorUid; - - /** - * The package name of the app initializing this network specifier. - * @hide - */ - public final String requestorPackageName; - /** @hide */ public WifiNetworkSpecifier() throws IllegalAccessException { throw new IllegalAccessException("Use the builder to create an instance"); @@ -505,18 +469,14 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc /** @hide */ public WifiNetworkSpecifier(@NonNull PatternMatcher ssidPatternMatcher, @NonNull Pair<MacAddress, MacAddress> bssidPatternMatcher, - @NonNull WifiConfiguration wifiConfiguration, - int requestorUid, @NonNull String requestorPackageName) { + @NonNull WifiConfiguration wifiConfiguration) { checkNotNull(ssidPatternMatcher); checkNotNull(bssidPatternMatcher); checkNotNull(wifiConfiguration); - checkNotNull(requestorPackageName); this.ssidPatternMatcher = ssidPatternMatcher; this.bssidPatternMatcher = bssidPatternMatcher; this.wifiConfiguration = wifiConfiguration; - this.requestorUid = requestorUid; - this.requestorPackageName = requestorPackageName; } public static final @NonNull Creator<WifiNetworkSpecifier> CREATOR = @@ -529,10 +489,8 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc Pair<MacAddress, MacAddress> bssidPatternMatcher = Pair.create(baseAddress, mask); WifiConfiguration wifiConfiguration = in.readParcelable(null); - int requestorUid = in.readInt(); - String requestorPackageName = in.readString(); return new WifiNetworkSpecifier(ssidPatternMatcher, bssidPatternMatcher, - wifiConfiguration, requestorUid, requestorPackageName); + wifiConfiguration); } @Override @@ -552,18 +510,13 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc dest.writeParcelable(bssidPatternMatcher.first, flags); dest.writeParcelable(bssidPatternMatcher.second, flags); dest.writeParcelable(wifiConfiguration, flags); - dest.writeInt(requestorUid); - dest.writeString(requestorPackageName); } @Override public int hashCode() { return Objects.hash( - ssidPatternMatcher.getPath(), - ssidPatternMatcher.getType(), - bssidPatternMatcher, - wifiConfiguration.allowedKeyManagement, - requestorUid, requestorPackageName); + ssidPatternMatcher.getPath(), ssidPatternMatcher.getType(), bssidPatternMatcher, + wifiConfiguration.allowedKeyManagement); } @Override @@ -582,9 +535,7 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc && Objects.equals(this.bssidPatternMatcher, lhs.bssidPatternMatcher) && Objects.equals(this.wifiConfiguration.allowedKeyManagement, - lhs.wifiConfiguration.allowedKeyManagement) - && requestorUid == lhs.requestorUid - && TextUtils.equals(requestorPackageName, lhs.requestorPackageName); + lhs.wifiConfiguration.allowedKeyManagement); } @Override @@ -595,8 +546,6 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc .append(", BSSID Match pattern=").append(bssidPatternMatcher) .append(", SSID=").append(wifiConfiguration.SSID) .append(", BSSID=").append(wifiConfiguration.BSSID) - .append(", requestorUid=").append(requestorUid) - .append(", requestorPackageName=").append(requestorPackageName) .append("]") .toString(); } @@ -618,12 +567,4 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc // not make much sense! return equals(other); } - - /** @hide */ - @Override - public void assertValidFromUid(int requestorUid) { - if (this.requestorUid != requestorUid) { - throw new SecurityException("mismatched UIDs"); - } - } } diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java index 720149695abc..a854a4ba3ae5 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java +++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java @@ -569,6 +569,7 @@ public final class WifiNetworkSuggestion implements Parcelable { private WifiConfiguration buildWifiConfigurationForPasspoint() { WifiConfiguration wifiConfiguration = new WifiConfiguration(); wifiConfiguration.FQDN = mPasspointConfiguration.getHomeSp().getFqdn(); + wifiConfiguration.setPasspointUniqueId(mPasspointConfiguration.getUniqueId()); wifiConfiguration.priority = mPriority; wifiConfiguration.meteredOverride = mIsMetered ? WifiConfiguration.METERED_OVERRIDE_METERED @@ -804,7 +805,7 @@ public final class WifiNetworkSuggestion implements Parcelable { @Override public int hashCode() { return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID, - wifiConfiguration.allowedKeyManagement, wifiConfiguration.FQDN); + wifiConfiguration.allowedKeyManagement, wifiConfiguration.getKey()); } /** @@ -827,7 +828,8 @@ public final class WifiNetworkSuggestion implements Parcelable { && TextUtils.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID) && Objects.equals(this.wifiConfiguration.allowedKeyManagement, lhs.wifiConfiguration.allowedKeyManagement) - && TextUtils.equals(this.wifiConfiguration.FQDN, lhs.wifiConfiguration.FQDN); + && TextUtils.equals(this.wifiConfiguration.getKey(), + lhs.wifiConfiguration.getKey()); } @Override 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/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index a85f40b3c1b8..b4eb30b8cfe6 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -293,26 +293,34 @@ public class WifiScanner { public final List<HiddenNetwork> hiddenNetworks = new ArrayList<>(); /** * period of background scan; in millisecond, 0 => single shot scan - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This + * support may not be present on newer devices. Use {@link #startScan(ScanSettings, + * ScanListener)} instead for single scans. */ @Deprecated public int periodInMs; /** * must have a valid REPORT_EVENT value - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This + * support may not be present on newer devices. Use {@link #startScan(ScanSettings, + * ScanListener)} instead for single scans. */ @Deprecated public int reportEvents; /** * defines number of bssids to cache from each scan - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This + * support may not be present on newer devices. Use {@link #startScan(ScanSettings, + * ScanListener)} instead for single scans. */ @Deprecated public int numBssidsPerScan; /** * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL * to wake up at fixed interval - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This + * support may not be present on newer devices. Use {@link #startScan(ScanSettings, + * ScanListener)} instead for single scans. */ @Deprecated public int maxScansToCache; @@ -321,14 +329,18 @@ public class WifiScanner { * a truncated binary exponential backoff bucket and the scan period will grow * exponentially as per formula: actual_period(N) = period * (2 ^ (N/stepCount)) * to maxPeriodInMs - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This + * support may not be present on newer devices. Use {@link #startScan(ScanSettings, + * ScanListener)} instead for single scans. */ @Deprecated public int maxPeriodInMs; /** * for truncated binary exponential back off bucket, number of scans to perform * for a given period - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This + * support may not be present on newer devices. Use {@link #startScan(ScanSettings, + * ScanListener)} instead for single scans. */ @Deprecated public int stepCount; @@ -806,7 +818,9 @@ public class WifiScanner { /** * Framework co-ordinates scans across multiple apps; so it may not give exactly the * same period requested. If period of a scan is changed; it is reported by this event. - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This + * support may not be present on newer devices. Use {@link #startScan(ScanSettings, + * ScanListener)} instead for single scans. */ @Deprecated public void onPeriodChanged(int periodInMs); @@ -913,7 +927,9 @@ public class WifiScanner { * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This support + * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)} + * instead for single scans. */ @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) @@ -935,7 +951,9 @@ public class WifiScanner { * stop an ongoing wifi scan * @param listener specifies which scan to cancel; must be same object as passed in {@link * #startBackgroundScan} - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This support + * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)} + * instead for single scans. */ @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) @@ -953,7 +971,9 @@ public class WifiScanner { /** * reports currently available scan results on appropriate listeners * @return true if all scan results were reported correctly - * @deprecated Background scan support is removed. + * @deprecated Background scan support has always been hardware vendor dependent. This support + * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)} + * instead for single scans. */ @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) diff --git a/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java index c66733472d0e..a4b3e86398a8 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java @@ -143,12 +143,6 @@ public class WifiAwareAgentNetworkSpecifier extends NetworkSpecifier implements } @Override - public void assertValidFromUid(int requestorUid) { - throw new SecurityException( - "WifiAwareAgentNetworkSpecifier should not be used in network requests"); - } - - @Override public NetworkSpecifier redact() { return null; } diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java index 81bf81e40199..2ebaa1805b2b 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java @@ -34,7 +34,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.Process; import android.os.RemoteException; import android.util.Log; @@ -447,8 +446,7 @@ public class WifiAwareManager { pmk, passphrase, 0, // no port info for deprecated IB APIs - -1, // no transport info for deprecated IB APIs - Process.myUid()); + -1); // no transport info for deprecated IB APIs } /** @hide */ @@ -488,8 +486,7 @@ public class WifiAwareManager { pmk, passphrase, 0, // no port info for OOB APIs - -1, // no transport protocol info for OOB APIs - Process.myUid()); + -1); // no transport protocol info for OOB APIs } private static class WifiAwareEventCallbackProxy extends IWifiAwareEventCallback.Stub { diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java index 5a4ed3c2f5e3..65ac1ab26064 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java @@ -23,7 +23,6 @@ import android.annotation.NonNull; import android.net.NetworkSpecifier; import android.os.Parcel; import android.os.Parcelable; -import android.os.Process; import android.text.TextUtils; import java.util.Arrays; @@ -144,19 +143,9 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements */ public final int transportProtocol; - /** - * The UID of the process initializing this network specifier. Validated by receiver using - * checkUidIfNecessary() and is used by satisfiedBy() to determine whether matches the - * offered network. - * - * @hide - */ - public final int requestorUid; - /** @hide */ public WifiAwareNetworkSpecifier(int type, int role, int clientId, int sessionId, int peerId, - byte[] peerMac, byte[] pmk, String passphrase, int port, int transportProtocol, - int requestorUid) { + byte[] peerMac, byte[] pmk, String passphrase, int port, int transportProtocol) { this.type = type; this.role = role; this.clientId = clientId; @@ -167,7 +156,6 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements this.passphrase = passphrase; this.port = port; this.transportProtocol = transportProtocol; - this.requestorUid = requestorUid; } public static final @android.annotation.NonNull Creator<WifiAwareNetworkSpecifier> CREATOR = @@ -184,8 +172,7 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements in.createByteArray(), // pmk in.readString(), // passphrase in.readInt(), // port - in.readInt(), // transportProtocol - in.readInt()); // requestorUid + in.readInt()); // transportProtocol } @Override @@ -221,7 +208,6 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements dest.writeString(passphrase); dest.writeInt(port); dest.writeInt(transportProtocol); - dest.writeInt(requestorUid); } /** @hide */ @@ -238,7 +224,7 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements @Override public int hashCode() { return Objects.hash(type, role, clientId, sessionId, peerId, Arrays.hashCode(peerMac), - Arrays.hashCode(pmk), passphrase, port, transportProtocol, requestorUid); + Arrays.hashCode(pmk), passphrase, port, transportProtocol); } /** @hide */ @@ -263,8 +249,7 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements && Arrays.equals(pmk, lhs.pmk) && Objects.equals(passphrase, lhs.passphrase) && port == lhs.port - && transportProtocol == lhs.transportProtocol - && requestorUid == lhs.requestorUid; + && transportProtocol == lhs.transportProtocol; } /** @hide */ @@ -283,19 +268,11 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements // masking PII .append(", passphrase=").append((passphrase == null) ? "<null>" : "<non-null>") .append(", port=").append(port).append(", transportProtocol=") - .append(transportProtocol).append(", requestorUid=").append(requestorUid) + .append(transportProtocol) .append("]"); return sb.toString(); } - /** @hide */ - @Override - public void assertValidFromUid(int requestorUid) { - if (this.requestorUid != requestorUid) { - throw new SecurityException("mismatched UIDs"); - } - } - /** * A builder class for a Wi-Fi Aware network specifier to set up an Aware connection with a * peer. @@ -463,7 +440,7 @@ public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements return new WifiAwareNetworkSpecifier( WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB, role, mDiscoverySession.mClientId, mDiscoverySession.mSessionId, mPeerHandle.peerId, - null, mPmk, mPskPassphrase, mPort, mTransportProtocol, Process.myUid()); + null, mPmk, mPskPassphrase, mPort, mTransportProtocol); } } } diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java index 615331f65258..9f581849efca 100644 --- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java +++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java @@ -908,6 +908,9 @@ public final class PasspointConfiguration implements Parcelable { throw new IllegalStateException("Credential or HomeSP are not initialized"); } - return mHomeSp.getFqdn(); + StringBuilder sb = new StringBuilder(); + sb.append(String.format("%s_%x%x", mHomeSp.getFqdn(), mHomeSp.hashCode(), + mCredential.hashCode())); + return sb.toString(); } } diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java index 49a76c33d209..a5de3318f454 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java @@ -16,8 +16,8 @@ package android.net.wifi.hotspot2.pps; -import android.os.Parcelable; import android.os.Parcel; +import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; @@ -299,8 +299,10 @@ public final class HomeSp implements Parcelable { @Override public int hashCode() { - return Objects.hash(mFqdn, mFriendlyName, mIconUrl, mHomeNetworkIds, mMatchAllOis, - mMatchAnyOis, mOtherHomePartners, mRoamingConsortiumOis); + return Objects.hash(mFqdn, mFriendlyName, mIconUrl, + mHomeNetworkIds, Arrays.hashCode(mMatchAllOis), + Arrays.hashCode(mMatchAnyOis), Arrays.hashCode(mOtherHomePartners), + Arrays.hashCode(mRoamingConsortiumOis)); } @Override 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/android/net/wifi/wificond/NativeScanResult.java b/wifi/java/android/net/wifi/wificond/NativeScanResult.java index 85251e8b1d42..7cc617d61b00 100644 --- a/wifi/java/android/net/wifi/wificond/NativeScanResult.java +++ b/wifi/java/android/net/wifi/wificond/NativeScanResult.java @@ -24,7 +24,6 @@ import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; -import java.util.BitSet; import java.util.List; /** @@ -34,8 +33,6 @@ import java.util.List; */ @SystemApi public final class NativeScanResult implements Parcelable { - private static final int CAPABILITY_SIZE = 16; - /** @hide */ @VisibleForTesting public byte[] ssid; @@ -56,7 +53,7 @@ public final class NativeScanResult implements Parcelable { public long tsf; /** @hide */ @VisibleForTesting - public BitSet capability; + public int capability; /** @hide */ @VisibleForTesting public boolean associated; @@ -134,7 +131,7 @@ public final class NativeScanResult implements Parcelable { * Returns the capabilities of the AP repseresented by this scan result as advertised in the * received probe response or beacon. * - * This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 8.4.1.4: + * This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 9.4.1.4: * Bit 0 - ESS * Bit 1 - IBSS * Bit 2 - CF Pollable @@ -143,7 +140,7 @@ public final class NativeScanResult implements Parcelable { * Bit 5 - Short Preamble * Bit 6 - PBCC * Bit 7 - Channel Agility - * Bit 8 - Spectrum Mgmt + * Bit 8 - Spectrum Management * Bit 9 - QoS * Bit 10 - Short Slot Time * Bit 11 - APSD @@ -154,7 +151,7 @@ public final class NativeScanResult implements Parcelable { * * @return a bit mask of capabilities. */ - @NonNull public BitSet getCapabilities() { + @NonNull public int getCapabilities() { return capability; } @@ -188,13 +185,7 @@ public final class NativeScanResult implements Parcelable { out.writeInt(frequency); out.writeInt(signalMbm); out.writeLong(tsf); - int capabilityInt = 0; - for (int i = 0; i < CAPABILITY_SIZE; i++) { - if (capability.get(i)) { - capabilityInt |= 1 << i; - } - } - out.writeInt(capabilityInt); + out.writeInt(capability); out.writeInt(associated ? 1 : 0); out.writeTypedList(radioChainInfos); } @@ -220,13 +211,7 @@ public final class NativeScanResult implements Parcelable { result.frequency = in.readInt(); result.signalMbm = in.readInt(); result.tsf = in.readLong(); - int capabilityInt = in.readInt(); - result.capability = new BitSet(CAPABILITY_SIZE); - for (int i = 0; i < CAPABILITY_SIZE; i++) { - if ((capabilityInt & (1 << i)) != 0) { - result.capability.set(i); - } - } + result.capability = in.readInt(); result.associated = (in.readInt() != 0); result.radioChainInfos = new ArrayList<>(); in.readTypedList(result.radioChainInfos, RadioChainInfo.CREATOR); diff --git a/wifi/java/android/net/wifi/wificond/PnoSettings.java b/wifi/java/android/net/wifi/wificond/PnoSettings.java index 57c9ca5fd302..533d37d3a23a 100644 --- a/wifi/java/android/net/wifi/wificond/PnoSettings.java +++ b/wifi/java/android/net/wifi/wificond/PnoSettings.java @@ -16,6 +16,7 @@ package android.net.wifi.wificond; +import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; @@ -33,7 +34,7 @@ import java.util.Objects; */ @SystemApi public final class PnoSettings implements Parcelable { - private int mIntervalMs; + private long mIntervalMs; private int mMin2gRssi; private int mMin5gRssi; private int mMin6gRssi; @@ -47,17 +48,17 @@ public final class PnoSettings implements Parcelable { * * @return An interval in milliseconds. */ - public int getIntervalMillis() { + public @DurationMillisLong long getIntervalMillis() { return mIntervalMs; } /** * Set the requested PNO scan interval in milliseconds. * - * @param intervalMs An interval in milliseconds. + * @param intervalMillis An interval in milliseconds. */ - public void setIntervalMillis(int intervalMs) { - this.mIntervalMs = intervalMs; + public void setIntervalMillis(@DurationMillisLong long intervalMillis) { + this.mIntervalMs = intervalMillis; } /** @@ -176,7 +177,7 @@ public final class PnoSettings implements Parcelable { **/ @Override public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeInt(mIntervalMs); + out.writeLong(mIntervalMs); out.writeInt(mMin2gRssi); out.writeInt(mMin5gRssi); out.writeInt(mMin6gRssi); @@ -189,7 +190,7 @@ public final class PnoSettings implements Parcelable { @Override public PnoSettings createFromParcel(Parcel in) { PnoSettings result = new PnoSettings(); - result.mIntervalMs = in.readInt(); + result.mIntervalMs = in.readLong(); result.mMin2gRssi = in.readInt(); result.mMin5gRssi = in.readInt(); result.mMin6gRssi = in.readInt(); diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiCondManager.java index 43aa1b64efbc..7a31a5afab05 100644 --- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java +++ b/wifi/java/android/net/wifi/wificond/WifiCondManager.java @@ -496,22 +496,17 @@ public class WifiCondManager { } /** - * Initializes WifiCondManager & registers a death notification for the WifiCondManager which - * acts as a proxy for the wificond daemon (i.e. the death listener will be called when and if - * the wificond daemon dies). - * - * Note: This method clears any existing state in wificond daemon. + * Register a death notification for the WifiCondManager which acts as a proxy for the + * wificond daemon (i.e. the death listener will be called when and if the wificond daemon + * dies). * * @param deathEventHandler A {@link Runnable} to be called whenever the wificond daemon dies. - * @return Returns true on success. */ - public boolean initialize(@NonNull Runnable deathEventHandler) { + public void setOnServiceDeadCallback(@NonNull Runnable deathEventHandler) { if (mDeathEventHandler != null) { Log.e(TAG, "Death handler already present"); } mDeathEventHandler = deathEventHandler; - tearDownInterfaces(); - return true; } /** @@ -603,11 +598,12 @@ public class WifiCondManager { } /** - * Tear down a specific client (STA) interface, initially configured using + * Tear down a specific client (STA) interface configured using * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}. * * @param ifaceName Name of the interface to tear down. - * @return Returns true on success. + * @return Returns true on success, false on failure (e.g. when called before an interface was + * set up). */ public boolean tearDownClientInterface(@NonNull String ifaceName) { if (getClientInterface(ifaceName) == null) { @@ -681,11 +677,12 @@ public class WifiCondManager { } /** - * Tear down a Soft AP interface initially configured using + * Tear down a Soft AP interface configured using * {@link #setupInterfaceForSoftApMode(String)}. * * @param ifaceName Name of the interface to tear down. - * @return Returns true on success. + * @return Returns true on success, false on failure (e.g. when called before an interface was + * set up). */ public boolean tearDownSoftApInterface(@NonNull String ifaceName) { if (getApInterface(ifaceName) == null) { @@ -750,9 +747,13 @@ public class WifiCondManager { /** * Request signal polling. * - * @param ifaceName Name of the interface on which to poll. + * @param ifaceName Name of the interface on which to poll. The interface must have been + * already set up using + *{@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * * @return A {@link SignalPollResult} object containing interface statistics, or a null on - * error. + * error (e.g. the interface hasn't been set up yet). */ @Nullable public SignalPollResult signalPoll(@NonNull String ifaceName) { IClientInterface iface = getClientInterface(ifaceName); @@ -776,10 +777,14 @@ public class WifiCondManager { } /** - * Get current transmit (Tx) packet counters of the specified interface. + * Get current transmit (Tx) packet counters of the specified interface. The interface must + * have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. * * @param ifaceName Name of the interface. - * @return {@link TxPacketCounters} of the current interface or null on error. + * @return {@link TxPacketCounters} of the current interface or null on error (e.g. when + * called before the interface has been set up). */ @Nullable public TxPacketCounters getTxPacketCounters(@NonNull String ifaceName) { IClientInterface iface = getClientInterface(ifaceName); @@ -813,10 +818,15 @@ public class WifiCondManager { * be done using {@link #startScan(String, int, Set, List)} or * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}. * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * * @param ifaceName Name of the interface. * @param scanType The type of scan result to be returned, can be * {@link #SCAN_TYPE_SINGLE_SCAN} or {@link #SCAN_TYPE_PNO_SCAN}. - * @return Returns an array of {@link NativeScanResult} or an empty array on failure. + * @return Returns an array of {@link NativeScanResult} or an empty array on failure (e.g. when + * called before the interface has been set up). */ @NonNull public List<NativeScanResult> getScanResults(@NonNull String ifaceName, @ScanResultType int scanType) { @@ -869,13 +879,19 @@ public class WifiCondManager { * The latest scans can be obtained using {@link #getScanResults(String, int)} and using a * {@link #SCAN_TYPE_SINGLE_SCAN} for the {@code scanType}. * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * * @param ifaceName Name of the interface on which to initiate the scan. * @param scanType Type of scan to perform, can be any of * {@link WifiScanner#SCAN_TYPE_HIGH_ACCURACY}, {@link WifiScanner#SCAN_TYPE_LOW_POWER}, or * {@link WifiScanner#SCAN_TYPE_LOW_LATENCY}. * @param freqs list of frequencies to scan for, if null scan all supported channels. - * @param hiddenNetworkSSIDs List of hidden networks to be scanned for. - * @return Returns true on success. + * @param hiddenNetworkSSIDs List of hidden networks to be scanned for, a null indicates that + * no hidden frequencies will be scanned for. + * @return Returns true on success, false on failure (e.g. when called before the interface + * has been set up). */ public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType, @Nullable Set<Integer> freqs, @Nullable List<byte[]> hiddenNetworkSSIDs) { @@ -931,11 +947,16 @@ public class WifiCondManager { * The latest PNO scans can be obtained using {@link #getScanResults(String, int)} with the * {@code scanType} set to {@link #SCAN_TYPE_PNO_SCAN}. * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * * @param ifaceName Name of the interface on which to request a PNO. * @param pnoSettings PNO scan configuration. * @param executor The Executor on which to execute the callback. * @param callback Callback for the results of the offload request. - * @return true on success. + * @return true on success, false on failure (e.g. when called before the interface has been set + * up). */ public boolean startPnoScan(@NonNull String ifaceName, @NonNull PnoSettings pnoSettings, @NonNull @CallbackExecutor Executor executor, @@ -969,8 +990,13 @@ public class WifiCondManager { * Stop PNO scan configured with * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}. * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * * @param ifaceName Name of the interface on which the PNO scan was configured. - * @return true on success. + * @return true on success, false on failure (e.g. when called before the interface has been + * set up). */ public boolean stopPnoScan(@NonNull String ifaceName) { IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); @@ -987,7 +1013,13 @@ public class WifiCondManager { } /** - * Abort ongoing single scan started with {@link #startScan(String, int, Set, List)}. + * Abort ongoing single scan started with {@link #startScan(String, int, Set, List)}. No failure + * callback, e.g. {@link ScanEventCallback#onScanFailed()}, is triggered by this operation. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. If the interface has not been set up then + * this method has no impact. * * @param ifaceName Name of the interface on which the scan was started. */ @@ -1055,7 +1087,14 @@ public class WifiCondManager { } /** - * Get the device phy capabilities for a given interface + * Get the device phy capabilities for a given interface. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @return DeviceWiphyCapabilities or null on error (e.g. when called on an interface which has + * not been set up). */ @Nullable public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) { if (mWificond == null) { @@ -1071,13 +1110,19 @@ public class WifiCondManager { } /** - * Register the provided callback handler for SoftAp events. Note that the Soft AP itself is - * configured using {@link #setupInterfaceForSoftApMode(String)}. + * Register the provided callback handler for SoftAp events. The interface must first be created + * using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until + * the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration + * method is provided). + * <p> + * Note that only one callback can be registered at a time - any registration overrides previous + * registrations. * * @param ifaceName Name of the interface on which to register the callback. * @param executor The Executor on which to execute the callbacks. * @param callback Callback for AP events. - * @return true on success, false otherwise. + * @return true on success, false on failure (e.g. when called on an interface which has not + * been set up). */ public boolean registerApCallback(@NonNull String ifaceName, @NonNull @CallbackExecutor Executor executor, @@ -1113,6 +1158,10 @@ public class WifiCondManager { * Send a management frame on the specified interface at the specified rate. Useful for probing * the link with arbitrary frames. * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * * @param ifaceName The interface on which to send the frame. * @param frame The raw byte array of the management frame to tramit. * @param mcs The MCS (modulation and coding scheme), i.e. rate, at which to transmit the 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/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java index 2efdd97543a9..d9584885a045 100644 --- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java @@ -287,17 +287,43 @@ public class SoftApConfigurationTest { @Test public void testToWifiConfigurationWithSupportedParameter() { - SoftApConfiguration softApConfig = new SoftApConfiguration.Builder() + SoftApConfiguration softApConfig_2g = new SoftApConfiguration.Builder() + .setPassphrase("secretsecret", + SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) + .setChannel(11, SoftApConfiguration.BAND_2GHZ) + .setHiddenSsid(true) + .build(); + WifiConfiguration wifiConfig_2g = softApConfig_2g.toWifiConfiguration(); + assertThat(wifiConfig_2g.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK); + assertThat(wifiConfig_2g.preSharedKey).isEqualTo("secretsecret"); + assertThat(wifiConfig_2g.apBand).isEqualTo(WifiConfiguration.AP_BAND_2GHZ); + assertThat(wifiConfig_2g.apChannel).isEqualTo(11); + assertThat(wifiConfig_2g.hiddenSSID).isEqualTo(true); + + SoftApConfiguration softApConfig_5g = new SoftApConfiguration.Builder() .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) .setChannel(149, SoftApConfiguration.BAND_5GHZ) .setHiddenSsid(true) .build(); - WifiConfiguration wifiConfig = softApConfig.toWifiConfiguration(); - assertThat(wifiConfig.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK); - assertThat(wifiConfig.preSharedKey).isEqualTo("secretsecret"); - assertThat(wifiConfig.apBand).isEqualTo(WifiConfiguration.AP_BAND_5GHZ); - assertThat(wifiConfig.apChannel).isEqualTo(149); - assertThat(wifiConfig.hiddenSSID).isEqualTo(true); + WifiConfiguration wifiConfig_5g = softApConfig_5g.toWifiConfiguration(); + assertThat(wifiConfig_5g.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK); + assertThat(wifiConfig_5g.preSharedKey).isEqualTo("secretsecret"); + assertThat(wifiConfig_5g.apBand).isEqualTo(WifiConfiguration.AP_BAND_5GHZ); + assertThat(wifiConfig_5g.apChannel).isEqualTo(149); + assertThat(wifiConfig_5g.hiddenSSID).isEqualTo(true); + + SoftApConfiguration softApConfig_2g5g = new SoftApConfiguration.Builder() + .setPassphrase("secretsecret", + SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) + .setBand(SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ) + .setHiddenSsid(true) + .build(); + WifiConfiguration wifiConfig_2g5g = softApConfig_2g5g.toWifiConfiguration(); + assertThat(wifiConfig_2g5g.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK); + assertThat(wifiConfig_2g5g.preSharedKey).isEqualTo("secretsecret"); + assertThat(wifiConfig_2g5g.apBand).isEqualTo(WifiConfiguration.AP_BAND_ANY); + assertThat(wifiConfig_2g5g.apChannel).isEqualTo(0); + assertThat(wifiConfig_2g5g.hiddenSSID).isEqualTo(true); } } diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java index 8023160a811e..05a3dce44022 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -336,6 +336,20 @@ public class WifiConfigurationTest { } /** + * Ensure that {@link NetworkSelectionStatus#getMaxNetworkSelectionDisableReason()} returns + * the maximum disable reason. + */ + @Test + public void testNetworkSelectionGetMaxNetworkSelectionDisableReason() { + int maxReason = Integer.MIN_VALUE; + for (int i = 0; i < NetworkSelectionStatus.DISABLE_REASON_INFOS.size(); i++) { + int reason = NetworkSelectionStatus.DISABLE_REASON_INFOS.keyAt(i); + maxReason = Math.max(maxReason, reason); + } + assertEquals(maxReason, NetworkSelectionStatus.getMaxNetworkSelectionDisableReason()); + } + + /** * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the * {@link WifiConfiguration} object correctly for SAE security type. * @throws Exception diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index a189d507a32a..847040ca914a 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -1551,14 +1551,15 @@ public class WifiManagerTest { */ @Test public void testGetAllMatchingWifiConfigs() throws Exception { - Map<String, List<ScanResult>> fqdns = new HashMap<>(); - fqdns.put("www.test.com", new ArrayList<>()); - when(mWifiService.getAllMatchingFqdnsForScanResults(any(List.class))).thenReturn(fqdns); + Map<String, List<ScanResult>> passpointProfiles = new HashMap<>(); + passpointProfiles.put("www.test.com_987a69bca26", new ArrayList<>()); + when(mWifiService.getAllMatchingPasspointProfilesForScanResults( + any(List.class))).thenReturn(passpointProfiles); InOrder inOrder = inOrder(mWifiService); mWifiManager.getAllMatchingWifiConfigs(new ArrayList<>()); - inOrder.verify(mWifiService).getAllMatchingFqdnsForScanResults(any(List.class)); + inOrder.verify(mWifiService).getAllMatchingPasspointProfilesForScanResults(any(List.class)); inOrder.verify(mWifiService).getWifiConfigsForPasspointProfiles(any(List.class)); } @@ -1867,7 +1868,7 @@ public class WifiManagerTest { * Tests that passing a null Executor to {@link WifiManager#getWifiActivityEnergyInfoAsync} * throws an exception. */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testGetWifiActivityInfoNullExecutor() throws Exception { mWifiManager.getWifiActivityEnergyInfoAsync(null, mOnWifiActivityEnergyInfoListener); } @@ -1876,7 +1877,7 @@ public class WifiManagerTest { * Tests that passing a null listener to {@link WifiManager#getWifiActivityEnergyInfoAsync} * throws an exception. */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testGetWifiActivityInfoNullListener() throws Exception { mWifiManager.getWifiActivityEnergyInfoAsync(mExecutor, null); } @@ -2380,4 +2381,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/WifiNetworkAgentSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java index adc41f0df4b4..0233ee2e2785 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java @@ -22,7 +22,6 @@ import static org.junit.Assert.assertTrue; import android.net.MacAddress; import android.net.MatchAllNetworkSpecifier; -import android.net.NetworkRequest; import android.os.Parcel; import android.os.PatternMatcher; import android.util.Pair; @@ -36,10 +35,6 @@ import org.junit.Test; */ @SmallTest public class WifiNetworkAgentSpecifierTest { - private static final int TEST_UID = 5; - private static final int TEST_UID_1 = 8; - private static final String TEST_PACKAGE = "com.test"; - private static final String TEST_PACKAGE_1 = "com.test.1"; private static final String TEST_SSID = "Test123"; private static final String TEST_SSID_PATTERN = "Test"; private static final String TEST_SSID_1 = "456test"; @@ -71,16 +66,6 @@ public class WifiNetworkAgentSpecifierTest { } /** - * Validate that the NetworkAgentSpecifier cannot be used in a {@link NetworkRequest} by apps. - */ - @Test(expected = IllegalStateException.class) - public void testWifiNetworkAgentSpecifierNotUsedInNetworkRequest() { - WifiNetworkAgentSpecifier specifier = createDefaultNetworkAgentSpecifier(); - - specifier.assertValidFromUid(TEST_UID); - } - - /** * Validate NetworkAgentSpecifier equals with itself. * a) Create network agent specifier 1 for WPA_PSK network * b) Create network agent specifier 2 with the same params as specifier 1. @@ -105,15 +90,13 @@ public class WifiNetworkAgentSpecifierTest { WifiConfiguration wifiConfiguration1 = createDefaultWifiConfiguration(); WifiNetworkAgentSpecifier specifier1 = new WifiNetworkAgentSpecifier( - wifiConfiguration1, - TEST_UID, TEST_PACKAGE); + wifiConfiguration1); WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1); wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkAgentSpecifier specifier2 = new WifiNetworkAgentSpecifier( - wifiConfiguration2, - TEST_UID, TEST_PACKAGE); + wifiConfiguration2); assertFalse(specifier2.equals(specifier1)); } @@ -129,15 +112,13 @@ public class WifiNetworkAgentSpecifierTest { WifiConfiguration wifiConfiguration1 = createDefaultWifiConfiguration(); WifiNetworkAgentSpecifier specifier1 = new WifiNetworkAgentSpecifier( - wifiConfiguration1, - TEST_UID, TEST_PACKAGE); + wifiConfiguration1); WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1); wifiConfiguration2.SSID = TEST_SSID_1; WifiNetworkAgentSpecifier specifier2 = new WifiNetworkAgentSpecifier( - wifiConfiguration2, - TEST_UID, TEST_PACKAGE); + wifiConfiguration2); assertFalse(specifier2.equals(specifier1)); } @@ -153,15 +134,13 @@ public class WifiNetworkAgentSpecifierTest { WifiConfiguration wifiConfiguration1 = createDefaultWifiConfiguration(); WifiNetworkAgentSpecifier specifier1 = new WifiNetworkAgentSpecifier( - wifiConfiguration1, - TEST_UID, TEST_PACKAGE); + wifiConfiguration1); WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1); wifiConfiguration2.BSSID = TEST_BSSID_1; WifiNetworkAgentSpecifier specifier2 = new WifiNetworkAgentSpecifier( - wifiConfiguration2, - TEST_UID, TEST_PACKAGE); + wifiConfiguration2); assertFalse(specifier2.equals(specifier1)); } @@ -215,8 +194,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( ssidPattern, bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID, TEST_PACKAGE); + wificonfigurationNetworkSpecifier); assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -244,8 +222,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( ssidPattern, bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID, TEST_PACKAGE); + wificonfigurationNetworkSpecifier); assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -273,8 +250,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( ssidPattern, bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID, TEST_PACKAGE); + wificonfigurationNetworkSpecifier); assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -293,8 +269,7 @@ public class WifiNetworkAgentSpecifierTest { wifiConfigurationNetworkAgent.SSID = "\"" + TEST_SSID_1 + "\""; WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier = new WifiNetworkAgentSpecifier( - wifiConfigurationNetworkAgent, - TEST_UID, TEST_PACKAGE); + wifiConfigurationNetworkAgent); PatternMatcher ssidPattern = new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX); @@ -306,8 +281,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( ssidPattern, bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID, TEST_PACKAGE); + wificonfigurationNetworkSpecifier); assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -326,8 +300,7 @@ public class WifiNetworkAgentSpecifierTest { wifiConfigurationNetworkAgent.BSSID = TEST_BSSID_1; WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier = new WifiNetworkAgentSpecifier( - wifiConfigurationNetworkAgent, - TEST_UID, TEST_PACKAGE); + wifiConfigurationNetworkAgent); PatternMatcher ssidPattern = new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB); @@ -340,8 +313,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( ssidPattern, bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID, TEST_PACKAGE); + wificonfigurationNetworkSpecifier); assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -360,8 +332,7 @@ public class WifiNetworkAgentSpecifierTest { wifiConfigurationNetworkAgent.BSSID = TEST_BSSID_1; WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier = new WifiNetworkAgentSpecifier( - wifiConfigurationNetworkAgent, - TEST_UID, TEST_PACKAGE); + wifiConfigurationNetworkAgent); PatternMatcher ssidPattern = new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX); @@ -374,8 +345,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( ssidPattern, bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID, TEST_PACKAGE); + wificonfigurationNetworkSpecifier); assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -402,41 +372,12 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( ssidPattern, bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID, TEST_PACKAGE); + wificonfigurationNetworkSpecifier); assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); } - /** - * Validate {@link WifiNetworkAgentSpecifier} with {@link WifiNetworkSpecifier} matching. - * a) Create network agent specifier for WPA_PSK network - * b) Create network specifier with matching SSID and BSSID pattern, but different UID. - * c) Ensure that the agent specifier is not satisfied by specifier. - */ - @Test - public void - testWifiNetworkAgentSpecifierDoesNotSatisfyNetworkSpecifierWithDifferentUid() { - WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier = createDefaultNetworkAgentSpecifier(); - - PatternMatcher ssidPattern = - new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX); - Pair<MacAddress, MacAddress> bssidPattern = - Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), - MacAddress.fromString(TEST_BSSID_OUI_MASK)); - WifiConfiguration wificonfigurationNetworkSpecifier = new WifiConfiguration(); - wificonfigurationNetworkSpecifier.allowedKeyManagement - .set(WifiConfiguration.KeyMgmt.WPA_PSK); - WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier( - ssidPattern, - bssidPattern, - wificonfigurationNetworkSpecifier, - TEST_UID_1, TEST_PACKAGE_1); - - assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); - assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); - } private WifiConfiguration createDefaultWifiConfiguration() { WifiConfiguration wifiConfiguration = new WifiConfiguration(); @@ -448,8 +389,7 @@ public class WifiNetworkAgentSpecifierTest { } private WifiNetworkAgentSpecifier createDefaultNetworkAgentSpecifier() { - return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration(), TEST_UID, - TEST_PACKAGE); + return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration()); } } diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java index 16197443b9d9..3b6723613c50 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java @@ -29,7 +29,6 @@ import android.net.MatchAllNetworkSpecifier; import android.net.NetworkSpecifier; import android.os.Parcel; import android.os.PatternMatcher; -import android.os.Process; import android.util.Pair; import androidx.test.filters.SmallTest; @@ -41,8 +40,6 @@ import org.junit.Test; */ @SmallTest public class WifiNetworkSpecifierTest { - private static final int TEST_UID = 5; - private static final String TEST_PACKAGE_NAME = "com.test"; private static final String TEST_SSID = "Test123"; private static final String TEST_BSSID_OUI_BASE_ADDRESS = "12:12:12:00:00:00"; private static final String TEST_BSSID_OUI_MASK = "ff:ff:ff:00:00:00"; @@ -62,7 +59,6 @@ public class WifiNetworkSpecifierTest { assertTrue(specifier instanceof WifiNetworkSpecifier); WifiNetworkSpecifier wifiNetworkSpecifier = (WifiNetworkSpecifier) specifier; - assertEquals(Process.myUid(), wifiNetworkSpecifier.requestorUid); assertEquals(TEST_SSID, wifiNetworkSpecifier.ssidPatternMatcher.getPath()); assertEquals(PATTERN_PREFIX, wifiNetworkSpecifier.ssidPatternMatcher.getType()); assertEquals(WifiManager.ALL_ZEROS_MAC_ADDRESS, @@ -367,8 +363,7 @@ public class WifiNetworkSpecifierTest { new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration); Parcel parcelW = Parcel.obtain(); specifier.writeToParcel(parcelW, 0); @@ -399,8 +394,7 @@ public class WifiNetworkSpecifierTest { new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration); assertTrue(specifier.satisfiedBy(null)); assertTrue(specifier.satisfiedBy(new MatchAllNetworkSpecifier())); @@ -422,15 +416,13 @@ public class WifiNetworkSpecifierTest { new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration); WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration); assertTrue(specifier2.satisfiedBy(specifier1)); } @@ -451,8 +443,7 @@ public class WifiNetworkSpecifierTest { new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration1, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration1); WifiConfiguration wifiConfiguration2 = new WifiConfiguration(); wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); @@ -460,8 +451,7 @@ public class WifiNetworkSpecifierTest { new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration2, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration2); assertFalse(specifier2.satisfiedBy(specifier1)); } @@ -482,15 +472,13 @@ public class WifiNetworkSpecifierTest { new WifiNetworkSpecifier(new PatternMatcher("", PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration); WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration); assertFalse(specifier2.satisfiedBy(specifier1)); } @@ -511,44 +499,13 @@ public class WifiNetworkSpecifierTest { new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); + wifiConfiguration); WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); - - assertFalse(specifier2.satisfiedBy(specifier1)); - } - - /** - * Validate NetworkSpecifier matching. - * a) Create network specifier 1 for WPA_PSK network - * b) Create network specifier 2 with different package name . - * c) Ensure that the specifier 2 is not satisfied by specifier 1. - */ - @Test - public void testWifiNetworkSpecifierDoesNotSatisfyWhenPackageNameDifferent() { - WifiConfiguration wifiConfiguration = new WifiConfiguration(); - wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); - wifiConfiguration.preSharedKey = TEST_PRESHARED_KEY; - - WifiNetworkSpecifier specifier1 = - new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), - Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), - MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME); - - WifiNetworkSpecifier specifier2 = - new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), - Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), - MacAddress.fromString(TEST_BSSID_OUI_MASK)), - wifiConfiguration, - TEST_UID, TEST_PACKAGE_NAME + "blah"); + wifiConfiguration); assertFalse(specifier2.satisfiedBy(specifier1)); } diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java index c3b62854f12c..81b02fa5f801 100644 --- a/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java +++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java @@ -162,17 +162,6 @@ public class WifiAwareAgentNetworkSpecifierTest { collector.checkThat("Match unexpected", oldNs.satisfiedBy(newNs), equalTo(false)); } - /** - * Validate that agent network specifier cannot be used as in network requests - i.e. that - * throws an exception when queried for UID validity. - */ - @Test(expected = SecurityException.class) - public void testNoUsageInRequest() { - WifiAwareAgentNetworkSpecifier dut = new WifiAwareAgentNetworkSpecifier(); - - dut.assertValidFromUid(0); - } - // utilities /** @@ -182,6 +171,6 @@ public class WifiAwareAgentNetworkSpecifierTest { WifiAwareNetworkSpecifier getDummyNetworkSpecifier(int clientId) { return new WifiAwareNetworkSpecifier(WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB, WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, clientId, 0, 0, new byte[6], - null, null, 10, 5, 0); + null, null, 10, 5); } } diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java index 65fbf5b099d4..c5f98045082b 100644 --- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java +++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java @@ -1564,7 +1564,7 @@ public class WifiAwareManagerTest { WifiAwareNetworkSpecifier ns = new WifiAwareNetworkSpecifier(NETWORK_SPECIFIER_TYPE_IB, WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, 5, 568, 334, HexEncoding.decode("000102030405".toCharArray(), false), - "01234567890123456789012345678901".getBytes(), "blah blah", 666, 4, 10001); + "01234567890123456789012345678901".getBytes(), "blah blah", 666, 4); Parcel parcelW = Parcel.obtain(); ns.writeToParcel(parcelW, 0); diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java index ce542c2bc4e9..8f6beb19091b 100644 --- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java @@ -20,8 +20,11 @@ import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import android.net.wifi.hotspot2.pps.Credential; +import android.net.wifi.hotspot2.pps.HomeSp; import android.os.Parcel; import androidx.test.filters.SmallTest; @@ -367,16 +370,55 @@ public class PasspointConfigurationTest { } /** - * Verify that the unique identifier generated is correct. + * Verify that the unique identifier generated is identical for two instances * * @throws Exception */ @Test public void validateUniqueId() throws Exception { - PasspointConfiguration config = PasspointTestUtils.createConfig(); - String uniqueId; - uniqueId = config.getUniqueId(); - assertEquals(uniqueId, config.getHomeSp().getFqdn()); + PasspointConfiguration config1 = PasspointTestUtils.createConfig(); + PasspointConfiguration config2 = PasspointTestUtils.createConfig(); + + assertEquals(config1.getUniqueId(), config2.getUniqueId()); + } + + /** + * Verify that the unique identifier generated is different for two instances with different + * HomeSp node + * + * @throws Exception + */ + @Test + public void validateUniqueIdDifferentHomeSp() throws Exception { + PasspointConfiguration config1 = PasspointTestUtils.createConfig(); + + // Modify config2's RCOIs to a different set of values + PasspointConfiguration config2 = PasspointTestUtils.createConfig(); + HomeSp homeSp = config2.getHomeSp(); + homeSp.setRoamingConsortiumOis(new long[] {0xaa, 0xbb}); + config2.setHomeSp(homeSp); + + assertNotEquals(config1.getUniqueId(), config2.getUniqueId()); + } + + /** + * Verify that the unique identifier generated is different for two instances with different + * Credential node + * + * @throws Exception + */ + @Test + public void validateUniqueIdDifferentCredential() throws Exception { + PasspointConfiguration config1 = PasspointTestUtils.createConfig(); + + // Modify config2's RCOIs to a different set of values + PasspointConfiguration config2 = PasspointTestUtils.createConfig(); + Credential credential = config2.getCredential(); + credential.setRealm("realm2.example.com"); + credential.getSimCredential().setImsi("350460*"); + config2.setCredential(credential); + + assertNotEquals(config1.getUniqueId(), config2.getUniqueId()); } /** 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()); diff --git a/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java b/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java index 06f12f7f37ea..0df170f8786c 100644 --- a/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java +++ b/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java @@ -28,7 +28,6 @@ import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; -import java.util.BitSet; /** * Unit tests for {@link android.net.wifi.wificond.NativeScanResult}. @@ -46,7 +45,7 @@ public class NativeScanResultTest { private static final int TEST_FREQUENCY = 2456; private static final int TEST_SIGNAL_MBM = -45; private static final long TEST_TSF = 34455441; - private static final BitSet TEST_CAPABILITY = new BitSet(16) {{ set(2); set(5); }}; + private static final int TEST_CAPABILITY = (0x1 << 2) | (0x1 << 5); private static final boolean TEST_ASSOCIATED = true; private static final int[] RADIO_CHAIN_IDS = { 0, 1 }; private static final int[] RADIO_CHAIN_LEVELS = { -56, -65 }; diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java index 5ba02a779663..32105be6ae4c 100644 --- a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java +++ b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java @@ -720,8 +720,7 @@ public class WifiCondManagerTest { @Test public void testRegisterDeathHandler() throws Exception { Runnable deathHandler = mock(Runnable.class); - assertTrue(mWificondControl.initialize(deathHandler)); - verify(mWificond).tearDownInterfaces(); + mWificondControl.setOnServiceDeadCallback(deathHandler); mWificondControl.binderDied(); mLooper.dispatchAll(); verify(deathHandler).run(); @@ -734,7 +733,7 @@ public class WifiCondManagerTest { @Test public void testDeathHandling() throws Exception { Runnable deathHandler = mock(Runnable.class); - assertTrue(mWificondControl.initialize(deathHandler)); + mWificondControl.setOnServiceDeadCallback(deathHandler); testSetupInterfaceForClientMode(); |